From d2263bc9eb8ab2c01b976073154daf2b92b543db Mon Sep 17 00:00:00 2001 From: antoinebhs Date: Wed, 4 Oct 2023 10:35:12 +0200 Subject: [PATCH 01/12] Add expert filter feature --- .../server/ExpertFilterRepositoryProxy.java | 168 ++++++++++++++++++ .../filter/server/FilterService.java | 10 +- .../filter/server/dto/AbstractFilter.java | 3 +- .../filter/server/dto/ExpertFilter.java | 43 +++++ .../dto/expertrule/AbstractExpertRule.java | 58 ++++++ .../dto/expertrule/BooleanExpertRule.java | 55 ++++++ .../dto/expertrule/CombinatorExpertRule.java | 38 ++++ .../server/dto/expertrule/EnumExpertRule.java | 49 +++++ .../dto/expertrule/NumberExpertRule.java | 58 ++++++ .../dto/expertrule/StringExpertRule.java | 51 ++++++ .../server/entities/ExpertFilterEntity.java | 36 ++++ .../server/entities/ExpertRuleEntity.java | 58 ++++++ .../repositories/ExpertFilterRepository.java | 18 ++ .../filter/server/utils/CombinatorType.java | 15 ++ .../filter/server/utils/DataType.java | 18 ++ .../server/utils/ExpertFilterUtils.java | 89 ++++++++++ .../filter/server/utils/FieldType.java | 18 ++ .../filter/server/utils/FilterType.java | 1 + .../filter/server/utils/OperatorType.java | 26 +++ .../changesets/changelog_20231013T170503Z.xml | 26 +++ .../db/changelog/db.changelog-master.yaml | 3 + .../filter/server/ExpertFilterUtilsTest.java | 145 +++++++++++++++ .../server/FilterEntityControllerTest.java | 126 +++++++++++-- 23 files changed, 1094 insertions(+), 18 deletions(-) create mode 100644 src/main/java/org/gridsuite/filter/server/ExpertFilterRepositoryProxy.java create mode 100644 src/main/java/org/gridsuite/filter/server/dto/ExpertFilter.java create mode 100644 src/main/java/org/gridsuite/filter/server/dto/expertrule/AbstractExpertRule.java create mode 100644 src/main/java/org/gridsuite/filter/server/dto/expertrule/BooleanExpertRule.java create mode 100644 src/main/java/org/gridsuite/filter/server/dto/expertrule/CombinatorExpertRule.java create mode 100644 src/main/java/org/gridsuite/filter/server/dto/expertrule/EnumExpertRule.java create mode 100644 src/main/java/org/gridsuite/filter/server/dto/expertrule/NumberExpertRule.java create mode 100644 src/main/java/org/gridsuite/filter/server/dto/expertrule/StringExpertRule.java create mode 100644 src/main/java/org/gridsuite/filter/server/entities/ExpertFilterEntity.java create mode 100644 src/main/java/org/gridsuite/filter/server/entities/ExpertRuleEntity.java create mode 100644 src/main/java/org/gridsuite/filter/server/repositories/ExpertFilterRepository.java create mode 100644 src/main/java/org/gridsuite/filter/server/utils/CombinatorType.java create mode 100644 src/main/java/org/gridsuite/filter/server/utils/DataType.java create mode 100644 src/main/java/org/gridsuite/filter/server/utils/ExpertFilterUtils.java create mode 100644 src/main/java/org/gridsuite/filter/server/utils/FieldType.java create mode 100644 src/main/java/org/gridsuite/filter/server/utils/OperatorType.java create mode 100644 src/main/resources/db/changelog/changesets/changelog_20231013T170503Z.xml create mode 100644 src/test/java/org/gridsuite/filter/server/ExpertFilterUtilsTest.java diff --git a/src/main/java/org/gridsuite/filter/server/ExpertFilterRepositoryProxy.java b/src/main/java/org/gridsuite/filter/server/ExpertFilterRepositoryProxy.java new file mode 100644 index 00000000..86566bc4 --- /dev/null +++ b/src/main/java/org/gridsuite/filter/server/ExpertFilterRepositoryProxy.java @@ -0,0 +1,168 @@ +/** + * Copyright (c) 2022, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package org.gridsuite.filter.server; + +import com.powsybl.commons.PowsyblException; +import org.gridsuite.filter.server.dto.*; +import org.gridsuite.filter.server.dto.expertrule.*; +import org.gridsuite.filter.server.entities.*; +import org.gridsuite.filter.server.repositories.ExpertFilterRepository; +import org.gridsuite.filter.server.utils.EquipmentType; +import org.gridsuite.filter.server.utils.FilterType; + +import java.util.Collections; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +/** + * @author Antoine Bouhours + */ +public class ExpertFilterRepositoryProxy extends AbstractFilterRepositoryProxy { + private final ExpertFilterRepository expertFilterRepository; + + public ExpertFilterRepositoryProxy(ExpertFilterRepository expertFilterRepository) { + this.expertFilterRepository = expertFilterRepository; + } + + @Override + ExpertFilterRepository getRepository() { + return expertFilterRepository; + } + + @Override + AbstractFilter toDto(ExpertFilterEntity filterEntity) { + return ExpertFilter.builder() + .id(filterEntity.getId()) + .modificationDate(filterEntity.getModificationDate()) + .equipmentType(filterEntity.getEquipmentType()) + .rules(mapEntityToRule(filterEntity.getRules())) + .build(); + } + + public static AbstractExpertRule mapEntityToRule(ExpertRuleEntity filterEntity) { + switch (filterEntity.getDataType()) { + case COMBINATOR -> { + return CombinatorExpertRule.builder() + .combinator(filterEntity.getCombinator()) + .rules(mapEntitiesToRules(filterEntity.getRules())) + .build(); + } + case BOOLEAN -> { + return BooleanExpertRule.builder() + .field(filterEntity.getField()) + .operator(filterEntity.getOperator()) + .value(Boolean.parseBoolean(filterEntity.getValue())) + .build(); + } + case NUMBER -> { + return NumberExpertRule.builder() + .field(filterEntity.getField()) + .operator(filterEntity.getOperator()) + .value(Double.valueOf(filterEntity.getValue())) + .build(); + } + case STRING -> { + return StringExpertRule.builder() + .field(filterEntity.getField()) + .operator(filterEntity.getOperator()) + .value(filterEntity.getValue()) + .build(); + } + case ENUM -> { + return EnumExpertRule.builder() + .field(filterEntity.getField()) + .operator(filterEntity.getOperator()) + .value(filterEntity.getValue()) + .build(); + } + default -> throw new PowsyblException(WRONG_FILTER_TYPE); + } + } + + private static List mapEntitiesToRules(List entities) { + if (entities == null) { + return Collections.emptyList(); + } + + return entities.stream() + .map(ExpertFilterRepositoryProxy::mapEntityToRule) + .collect(Collectors.toList()); + } + + @Override + ExpertFilterEntity fromDto(AbstractFilter dto) { + if (dto instanceof ExpertFilter filter) { + var expertFilterEntityBuilder = ExpertFilterEntity.builder() + .modificationDate(filter.getModificationDate()) + .equipmentType(filter.getEquipmentType()) + .rules(mapRuleToEntity(filter.getRules())); + buildAbstractFilter(expertFilterEntityBuilder, filter); + return expertFilterEntityBuilder.build(); + } + throw new PowsyblException(WRONG_FILTER_TYPE); + } + + public static ExpertRuleEntity mapRuleToEntity(AbstractExpertRule filter) { + var expertFilterEntityBuilder = ExpertRuleEntity.builder() + .id(UUID.randomUUID()) + .combinator(filter.getCombinator()) + .operator(filter.getOperator()) + .dataType(filter.getDataType()) + .field(filter.getField()) + .value(filter.getStringValue()); + expertFilterEntityBuilder.rules(mapRulesToEntities(filter.getRules(), expertFilterEntityBuilder.build())); + return expertFilterEntityBuilder.build(); + } + + private static List mapRulesToEntities(List ruleFromDto, ExpertRuleEntity parentRuleEntity) { + if (ruleFromDto == null) { + return Collections.emptyList(); + } + + return ruleFromDto.stream() + .map(rule -> { + var expertRuleEntityBuilder = ExpertRuleEntity.builder() + .id(UUID.randomUUID()) + .combinator(rule.getCombinator()) + .operator(rule.getOperator()) + .dataType(rule.getDataType()) + .field(rule.getField()) + .value(rule.getStringValue()) + .parentRule(parentRuleEntity); + + List rules = mapRulesToEntities(rule.getRules(), expertRuleEntityBuilder.build()); + expertRuleEntityBuilder.rules(rules); + + return expertRuleEntityBuilder.build(); + }) + .collect(Collectors.toList()); + } + + @Override + FilterType getFilterType() { + return FilterType.EXPERT; + } + + @Override + public EquipmentType getEquipmentType() { + throw new UnsupportedOperationException("A filter id must be provided to get equipment type !!"); + } + + @Override + public EquipmentType getEquipmentType(UUID id) { + return expertFilterRepository.findById(id) + .map(ExpertFilterEntity::getEquipmentType) + .orElseThrow(() -> new PowsyblException("Identifier list filter " + id + " not found")); + } + + @Override + public AbstractEquipmentFilterForm buildEquipmentFormFilter(AbstractFilterEntity entity) { + return null; + } +} diff --git a/src/main/java/org/gridsuite/filter/server/FilterService.java b/src/main/java/org/gridsuite/filter/server/FilterService.java index 833aa509..940421ec 100644 --- a/src/main/java/org/gridsuite/filter/server/FilterService.java +++ b/src/main/java/org/gridsuite/filter/server/FilterService.java @@ -13,6 +13,7 @@ import org.gridsuite.filter.server.entities.AbstractFilterEntity; import org.gridsuite.filter.server.repositories.*; import org.gridsuite.filter.server.utils.EquipmentType; +import org.gridsuite.filter.server.utils.ExpertFilterUtils; import org.gridsuite.filter.server.utils.FilterType; import org.gridsuite.filter.server.utils.FiltersUtils; import org.springframework.context.annotation.ComponentScan; @@ -71,6 +72,7 @@ public FilterService(FiltersToGroovyScript filtersToScript, final VoltageLevelFilterRepository voltageLevelFilterRepository, final SubstationFilterRepository substationFilterRepository, final IdentifierListFilterRepository identifierListFilterRepository, + final ExpertFilterRepository expertFilterRepository, NetworkStoreService networkStoreService, NotificationService notificationService) { this.filtersToScript = filtersToScript; @@ -95,6 +97,7 @@ public FilterService(FiltersToGroovyScript filtersToScript, filterRepositories.put(FilterType.IDENTIFIER_LIST.name(), new IdentifierListFilterRepositoryProxy(identifierListFilterRepository)); + filterRepositories.put(FilterType.EXPERT.name(), new ExpertFilterRepositoryProxy(expertFilterRepository)); this.networkStoreService = networkStoreService; this.notificationService = notificationService; } @@ -297,6 +300,9 @@ private > Stream> getInjectionList(Stream equipmentIds = getIdentifierListFilterEquipmentIds((IdentifierListFilter) filter); return stream.filter(injection -> equipmentIds.contains(injection.getId())); + } else if (filter instanceof ExpertFilter) { + var rule = ((ExpertFilter) filter).getRules(); + return stream.filter(injection -> ExpertFilterUtils.evaluateExpertFilter(rule, injection)); } else { return Stream.empty(); } @@ -309,7 +315,7 @@ private List> getGeneratorList(Network network, AbstractFilter f return getInjectionList(network.getGeneratorStream().map(injection -> injection), filter) .filter(injection -> filterByEnergySource((Generator) injection, generatorFilter.getEnergySource())) .collect(Collectors.toList()); - } else if (filter instanceof IdentifierListFilter) { + } else if (filter instanceof IdentifierListFilter || filter instanceof ExpertFilter) { return getInjectionList(network.getGeneratorStream().map(generator -> generator), filter).collect(Collectors.toList()); } else { return List.of(); @@ -634,7 +640,7 @@ private List> getIdentifiables(AbstractFilter filter, Network ne } private List> toIdentifiableFilter(AbstractFilter filter, UUID networkUuid, String variantId) { - if (filter.getType() == FilterType.CRITERIA || filter.getType() == FilterType.IDENTIFIER_LIST) { + if (filter.getType() == FilterType.CRITERIA || filter.getType() == FilterType.IDENTIFIER_LIST || filter.getType() == FilterType.EXPERT) { Network network = networkStoreService.getNetwork(networkUuid); if (network == null) { diff --git a/src/main/java/org/gridsuite/filter/server/dto/AbstractFilter.java b/src/main/java/org/gridsuite/filter/server/dto/AbstractFilter.java index b094d68a..35ad8b15 100644 --- a/src/main/java/org/gridsuite/filter/server/dto/AbstractFilter.java +++ b/src/main/java/org/gridsuite/filter/server/dto/AbstractFilter.java @@ -29,7 +29,8 @@ @JsonSubTypes({//Below, we define the names and the binding classes. @JsonSubTypes.Type(value = ScriptFilter.class, name = "SCRIPT"), @JsonSubTypes.Type(value = CriteriaFilter.class, name = "CRITERIA"), - @JsonSubTypes.Type(value = IdentifierListFilter.class, name = "IDENTIFIER_LIST") + @JsonSubTypes.Type(value = IdentifierListFilter.class, name = "IDENTIFIER_LIST"), + @JsonSubTypes.Type(value = ExpertFilter.class, name = "EXPERT") }) @Getter @Setter diff --git a/src/main/java/org/gridsuite/filter/server/dto/ExpertFilter.java b/src/main/java/org/gridsuite/filter/server/dto/ExpertFilter.java new file mode 100644 index 00000000..816c0c09 --- /dev/null +++ b/src/main/java/org/gridsuite/filter/server/dto/ExpertFilter.java @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2021, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.gridsuite.filter.server.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; +import org.gridsuite.filter.server.dto.expertrule.AbstractExpertRule; +import org.gridsuite.filter.server.utils.EquipmentType; +import org.gridsuite.filter.server.utils.FilterType; + +import java.util.Date; +import java.util.UUID; + +/** + * @author Antoine Bouhours + */ +@Getter +@Schema(description = "Expert Filters", allOf = AbstractFilter.class) +@SuperBuilder +@NoArgsConstructor +public class ExpertFilter extends AbstractFilter { + + @Schema(description = "Rules") + private AbstractExpertRule rules; + + public ExpertFilter(UUID id, Date modificationDate, EquipmentType equipmentType, AbstractExpertRule rules) { + super(id, modificationDate, equipmentType); + this.rules = rules; + } + + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + @Override + public FilterType getType() { + return FilterType.EXPERT; + } +} diff --git a/src/main/java/org/gridsuite/filter/server/dto/expertrule/AbstractExpertRule.java b/src/main/java/org/gridsuite/filter/server/dto/expertrule/AbstractExpertRule.java new file mode 100644 index 00000000..4a1939b0 --- /dev/null +++ b/src/main/java/org/gridsuite/filter/server/dto/expertrule/AbstractExpertRule.java @@ -0,0 +1,58 @@ +/** + * Copyright (c) 2021, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.gridsuite.filter.server.dto.expertrule; + +import com.fasterxml.jackson.annotation.*; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; +import org.gridsuite.filter.server.utils.*; + +import java.util.List; + +/** + * @author Antoine Bouhours + */ +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + property = "dataType", + include = JsonTypeInfo.As.EXISTING_PROPERTY) +@JsonSubTypes({ + @JsonSubTypes.Type(value = StringExpertRule.class, name = "STRING"), + @JsonSubTypes.Type(value = BooleanExpertRule.class, name = "BOOLEAN"), + @JsonSubTypes.Type(value = EnumExpertRule.class, name = "ENUM"), + @JsonSubTypes.Type(value = NumberExpertRule.class, name = "NUMBER"), + @JsonSubTypes.Type(value = CombinatorExpertRule.class, name = "COMBINATOR") +}) +@JsonInclude(JsonInclude.Include.NON_NULL) +@NoArgsConstructor +@AllArgsConstructor +@Getter +@SuperBuilder +public abstract class AbstractExpertRule { + + @Schema(description = "Combinator") + private CombinatorType combinator; + + @Schema(description = "Field") + private FieldType field; + + @Schema(description = "Operator") + private OperatorType operator; + + @Schema(description = "Rules") + private List rules; + + public abstract boolean evaluateRule(String identifiableValue); + + public abstract DataType getDataType(); + + @JsonIgnore + public abstract String getStringValue(); +} diff --git a/src/main/java/org/gridsuite/filter/server/dto/expertrule/BooleanExpertRule.java b/src/main/java/org/gridsuite/filter/server/dto/expertrule/BooleanExpertRule.java new file mode 100644 index 00000000..0a891564 --- /dev/null +++ b/src/main/java/org/gridsuite/filter/server/dto/expertrule/BooleanExpertRule.java @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2021, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.gridsuite.filter.server.dto.expertrule; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.powsybl.commons.PowsyblException; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; +import org.gridsuite.filter.server.utils.DataType; + +/** + * @author Antoine Bouhours + */ +@AllArgsConstructor +@NoArgsConstructor +@Getter +@SuperBuilder +public class BooleanExpertRule extends AbstractExpertRule { + + @Schema(description = "Value") + private boolean value; + + private static boolean getBooleanValue(String value) { + return Boolean.parseBoolean(value); + } + + @Override + public String getStringValue() { + return String.valueOf(isValue()); + } + + @Override + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + public DataType getDataType() { + return DataType.BOOLEAN; + } + + @Override + public boolean evaluateRule(String identifiableValue) { + boolean equipmentValue = getBooleanValue(identifiableValue); + boolean filterValue = isValue(); + return switch (this.getOperator()) { + case EQUALS -> equipmentValue == filterValue; + case NOT_EQUALS -> equipmentValue != filterValue; + default -> throw new PowsyblException("Operator not allowed"); + }; + } +} diff --git a/src/main/java/org/gridsuite/filter/server/dto/expertrule/CombinatorExpertRule.java b/src/main/java/org/gridsuite/filter/server/dto/expertrule/CombinatorExpertRule.java new file mode 100644 index 00000000..50b802a2 --- /dev/null +++ b/src/main/java/org/gridsuite/filter/server/dto/expertrule/CombinatorExpertRule.java @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2021, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.gridsuite.filter.server.dto.expertrule; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.experimental.SuperBuilder; +import org.gridsuite.filter.server.utils.DataType; + +/** + * @author Antoine Bouhours + */ +@AllArgsConstructor +@Getter +@SuperBuilder +public class CombinatorExpertRule extends AbstractExpertRule { + + @Override + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + public DataType getDataType() { + return DataType.COMBINATOR; + } + + @Override + public boolean evaluateRule(String identifiableValue) { + return false; + } + + @Override + public String getStringValue() { + return null; + } +} diff --git a/src/main/java/org/gridsuite/filter/server/dto/expertrule/EnumExpertRule.java b/src/main/java/org/gridsuite/filter/server/dto/expertrule/EnumExpertRule.java new file mode 100644 index 00000000..f3a1a96f --- /dev/null +++ b/src/main/java/org/gridsuite/filter/server/dto/expertrule/EnumExpertRule.java @@ -0,0 +1,49 @@ +/** + * Copyright (c) 2021, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.gridsuite.filter.server.dto.expertrule; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.powsybl.commons.PowsyblException; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; +import org.gridsuite.filter.server.utils.DataType; + +/** + * @author Antoine Bouhours + */ +@AllArgsConstructor +@NoArgsConstructor +@Getter +@SuperBuilder +public class EnumExpertRule extends AbstractExpertRule { + + @Schema(description = "Value") + private String value; + + @Override + public String getStringValue() { + return getValue(); + } + + @Override + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + public DataType getDataType() { + return DataType.ENUM; + } + + @Override + public boolean evaluateRule(String identifiableValue) { + return switch (this.getOperator()) { + case EQUALS -> identifiableValue.equals(this.getValue()); + case NOT_EQUALS -> !identifiableValue.equals(this.getValue()); + default -> throw new PowsyblException("Operator not allowed"); + }; + } +} diff --git a/src/main/java/org/gridsuite/filter/server/dto/expertrule/NumberExpertRule.java b/src/main/java/org/gridsuite/filter/server/dto/expertrule/NumberExpertRule.java new file mode 100644 index 00000000..e69dc61e --- /dev/null +++ b/src/main/java/org/gridsuite/filter/server/dto/expertrule/NumberExpertRule.java @@ -0,0 +1,58 @@ +/** + * Copyright (c) 2021, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.gridsuite.filter.server.dto.expertrule; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.powsybl.commons.PowsyblException; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; +import org.gridsuite.filter.server.utils.DataType; + +/** + * @author Antoine Bouhours + */ +@AllArgsConstructor +@NoArgsConstructor +@Getter +@SuperBuilder +public class NumberExpertRule extends AbstractExpertRule { + + @Schema(description = "Value") + private Double value; + + public static Double getNumberValue(String value) { + return Double.parseDouble(value); + } + + @Override + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + public DataType getDataType() { + return DataType.NUMBER; + } + + @Override + public boolean evaluateRule(String identifiableValue) { + Double equipmentValue = getNumberValue(identifiableValue); + Double filterValue = getValue(); + return switch (this.getOperator()) { + case EQUALS -> equipmentValue.equals(filterValue); + case GREATER_OR_EQUALS -> equipmentValue.compareTo(filterValue) >= 0; + case GREATER -> equipmentValue.compareTo(filterValue) > 0; + case LOWER_OR_EQUALS -> equipmentValue.compareTo(filterValue) <= 0; + case LOWER -> equipmentValue.compareTo(filterValue) < 0; + default -> throw new PowsyblException("Operator not allowed"); + }; + } + + @Override + public String getStringValue() { + return String.valueOf(getValue()); + } +} diff --git a/src/main/java/org/gridsuite/filter/server/dto/expertrule/StringExpertRule.java b/src/main/java/org/gridsuite/filter/server/dto/expertrule/StringExpertRule.java new file mode 100644 index 00000000..6a306c10 --- /dev/null +++ b/src/main/java/org/gridsuite/filter/server/dto/expertrule/StringExpertRule.java @@ -0,0 +1,51 @@ +/** + * Copyright (c) 2021, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.gridsuite.filter.server.dto.expertrule; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.powsybl.commons.PowsyblException; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; +import org.gridsuite.filter.server.utils.DataType; + +/** + * @author Antoine Bouhours + */ +@AllArgsConstructor +@NoArgsConstructor +@Getter +@SuperBuilder +public class StringExpertRule extends AbstractExpertRule { + + @Schema(description = "Value") + private String value; + + @Override + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + public DataType getDataType() { + return DataType.STRING; + } + + @Override + public boolean evaluateRule(String identifiableValue) { + return switch (this.getOperator()) { + case IS -> identifiableValue.equals(this.getValue()); + case CONTAINS -> identifiableValue.contains(this.getValue()); + case BEGINS_WITH -> identifiableValue.startsWith(this.getValue()); + case ENDS_WITH -> identifiableValue.endsWith(this.getValue()); + default -> throw new PowsyblException("Operator not allowed"); + }; + } + + @Override + public String getStringValue() { + return getValue(); + } +} diff --git a/src/main/java/org/gridsuite/filter/server/entities/ExpertFilterEntity.java b/src/main/java/org/gridsuite/filter/server/entities/ExpertFilterEntity.java new file mode 100644 index 00000000..6c3c01fb --- /dev/null +++ b/src/main/java/org/gridsuite/filter/server/entities/ExpertFilterEntity.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2021, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package org.gridsuite.filter.server.entities; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; +import org.gridsuite.filter.server.utils.EquipmentType; + +/** + * @author Antoine Bouhours + */ +@Getter +@NoArgsConstructor +@AllArgsConstructor +@SuperBuilder +@Entity +@Table(name = "expert_filter") +public class ExpertFilterEntity extends AbstractFilterEntity { + + @Column(name = "equipmentType") + private EquipmentType equipmentType; + + @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true) + @JoinColumn(name = "expertFilterEntity_rule_id", + referencedColumnName = "id", + foreignKey = @ForeignKey) + private ExpertRuleEntity rules; +} diff --git a/src/main/java/org/gridsuite/filter/server/entities/ExpertRuleEntity.java b/src/main/java/org/gridsuite/filter/server/entities/ExpertRuleEntity.java new file mode 100644 index 00000000..0729551c --- /dev/null +++ b/src/main/java/org/gridsuite/filter/server/entities/ExpertRuleEntity.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2021, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package org.gridsuite.filter.server.entities; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; +import org.gridsuite.filter.server.utils.CombinatorType; +import org.gridsuite.filter.server.utils.DataType; +import org.gridsuite.filter.server.utils.FieldType; +import org.gridsuite.filter.server.utils.OperatorType; + +import java.util.List; +import java.util.UUID; + +/** + * @author Antoine Bouhours + */ +@Getter +@NoArgsConstructor +@SuperBuilder +@AllArgsConstructor +@Entity +@Table(name = "expert_rule") +public class ExpertRuleEntity { + @Id + @Column(name = "id") + private UUID id; + + @Column(name = "combinator") + private CombinatorType combinator; + + @Column(name = "field") + private FieldType field; + + @Column(name = "value_") + private String value; + + @Column(name = "operator") + private OperatorType operator; + + @Column(name = "dataType") + private DataType dataType; + + @OneToMany(cascade = CascadeType.ALL, mappedBy = "parentRule") + private List rules; + + @ManyToOne + @JoinColumn(name = "parent_rule_id") + private ExpertRuleEntity parentRule; +} diff --git a/src/main/java/org/gridsuite/filter/server/repositories/ExpertFilterRepository.java b/src/main/java/org/gridsuite/filter/server/repositories/ExpertFilterRepository.java new file mode 100644 index 00000000..ac801087 --- /dev/null +++ b/src/main/java/org/gridsuite/filter/server/repositories/ExpertFilterRepository.java @@ -0,0 +1,18 @@ +/** + * Copyright (c) 2021, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.gridsuite.filter.server.repositories; + +import org.gridsuite.filter.server.entities.ExpertFilterEntity; +import org.springframework.stereotype.Repository; + + +/** + * @author Antoine Bouhours + */ +@Repository +public interface ExpertFilterRepository extends FilterRepository { +} diff --git a/src/main/java/org/gridsuite/filter/server/utils/CombinatorType.java b/src/main/java/org/gridsuite/filter/server/utils/CombinatorType.java new file mode 100644 index 00000000..266b5208 --- /dev/null +++ b/src/main/java/org/gridsuite/filter/server/utils/CombinatorType.java @@ -0,0 +1,15 @@ +/** + * Copyright (c) 2021, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.gridsuite.filter.server.utils; + +/** + * @author Antoine Bouhours + */ +public enum CombinatorType { + AND, + OR, +} diff --git a/src/main/java/org/gridsuite/filter/server/utils/DataType.java b/src/main/java/org/gridsuite/filter/server/utils/DataType.java new file mode 100644 index 00000000..6b7612e6 --- /dev/null +++ b/src/main/java/org/gridsuite/filter/server/utils/DataType.java @@ -0,0 +1,18 @@ +/** + * Copyright (c) 2021, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.gridsuite.filter.server.utils; + +/** + * @author Antoine Bouhours + */ +public enum DataType { + STRING, + NUMBER, + ENUM, + BOOLEAN, + COMBINATOR, +} diff --git a/src/main/java/org/gridsuite/filter/server/utils/ExpertFilterUtils.java b/src/main/java/org/gridsuite/filter/server/utils/ExpertFilterUtils.java new file mode 100644 index 00000000..4a6e7e0d --- /dev/null +++ b/src/main/java/org/gridsuite/filter/server/utils/ExpertFilterUtils.java @@ -0,0 +1,89 @@ +/** + * Copyright (c) 2021, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package org.gridsuite.filter.server.utils; + +import com.powsybl.commons.PowsyblException; +import com.powsybl.iidm.network.*; +import org.gridsuite.filter.server.dto.expertrule.AbstractExpertRule; + +import java.util.Optional; + +/** + * @author Antoine Bouhours + */ +public final class ExpertFilterUtils { + private ExpertFilterUtils() { } + + public static > boolean evaluateExpertFilter(AbstractExpertRule filter, Injection injection) { + // As long as there are rules, we go down the tree + if (CombinatorType.AND == (filter.getCombinator())) { + return evaluateAndCombination(filter, injection); + } else if (CombinatorType.OR == filter.getCombinator()) { + return evaluateOrCombination(filter, injection); + } else { + // Evaluate individual filters + return filter.evaluateRule(getFieldValue(filter.getField(), injection)); + } + } + + private static > boolean evaluateOrCombination(AbstractExpertRule filter, Injection injection) { + for (AbstractExpertRule rule : filter.getRules()) { + // Recursively evaluate the rule + if (evaluateExpertFilter(rule, injection)) { + // If any rule is true, the whole combination is true + return true; + } + } + return false; + } + + private static > boolean evaluateAndCombination(AbstractExpertRule filter, Injection injection) { + for (AbstractExpertRule rule : filter.getRules()) { + // Recursively evaluate the rule + if (!evaluateExpertFilter(rule, injection)) { + // If any rule is false, the whole combination is false + return false; + } + } + return true; + } + + private static > String getFieldValue(FieldType field, Injection injection) { + return switch (injection.getType()) { + case GENERATOR -> getGeneratorFieldValue(field, (Generator) injection); + case LOAD -> getLoadFieldValue(field, (Load) injection); + default -> throw new PowsyblException("Not implemented with expert filter"); + }; + } + + private static String getLoadFieldValue(FieldType field, Load load) { + return switch (field) { + case ID -> load.getId(); + default -> throw new PowsyblException("Not implemented with expert filter"); + }; + } + + private static String getGeneratorFieldValue(FieldType field, Generator generator) { + return switch (field) { + case ID -> generator.getId(); + case NAME -> generator.getNameOrId(); + case NOMINAL_VOLTAGE -> String.valueOf(generator.getTerminal().getVoltageLevel().getNominalV()); + case COUNTRY -> { + Optional country = generator.getTerminal().getVoltageLevel().getSubstation().flatMap(Substation::getCountry); + yield country.isPresent() ? String.valueOf(country.get()) : ""; + } + case ENERGY_SOURCE -> String.valueOf(generator.getEnergySource()); + case MIN_P -> String.valueOf(generator.getMinP()); + case MAX_P -> String.valueOf(generator.getMaxP()); + case TARGET_V -> String.valueOf(generator.getTargetV()); + case TARGET_P -> String.valueOf(generator.getTargetP()); + case TARGET_Q -> String.valueOf(generator.getTargetQ()); + case VOLTAGE_REGULATOR_ON -> String.valueOf(generator.isVoltageRegulatorOn()); + }; + } +} diff --git a/src/main/java/org/gridsuite/filter/server/utils/FieldType.java b/src/main/java/org/gridsuite/filter/server/utils/FieldType.java new file mode 100644 index 00000000..69c5a2bf --- /dev/null +++ b/src/main/java/org/gridsuite/filter/server/utils/FieldType.java @@ -0,0 +1,18 @@ +package org.gridsuite.filter.server.utils; + +/** + * @author Antoine Bouhours + */ +public enum FieldType { + ID, + NAME, + NOMINAL_VOLTAGE, + MIN_P, + MAX_P, + TARGET_P, + TARGET_V, + TARGET_Q, + ENERGY_SOURCE, + COUNTRY, + VOLTAGE_REGULATOR_ON +} diff --git a/src/main/java/org/gridsuite/filter/server/utils/FilterType.java b/src/main/java/org/gridsuite/filter/server/utils/FilterType.java index fb2680b7..138a6d46 100644 --- a/src/main/java/org/gridsuite/filter/server/utils/FilterType.java +++ b/src/main/java/org/gridsuite/filter/server/utils/FilterType.java @@ -14,4 +14,5 @@ public enum FilterType { SCRIPT, CRITERIA, IDENTIFIER_LIST, + EXPERT, } diff --git a/src/main/java/org/gridsuite/filter/server/utils/OperatorType.java b/src/main/java/org/gridsuite/filter/server/utils/OperatorType.java new file mode 100644 index 00000000..22f98d87 --- /dev/null +++ b/src/main/java/org/gridsuite/filter/server/utils/OperatorType.java @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2021, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.gridsuite.filter.server.utils; + +/** + * @author Antoine Bouhours + */ +public enum OperatorType { + // Common + EQUALS, + NOT_EQUALS, + // Number + LOWER, + LOWER_OR_EQUALS, + GREATER, + GREATER_OR_EQUALS, + // String + IS, + CONTAINS, + BEGINS_WITH, + ENDS_WITH, +} diff --git a/src/main/resources/db/changelog/changesets/changelog_20231013T170503Z.xml b/src/main/resources/db/changelog/changesets/changelog_20231013T170503Z.xml new file mode 100644 index 00000000..ef1880e4 --- /dev/null +++ b/src/main/resources/db/changelog/changesets/changelog_20231013T170503Z.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/db/changelog/db.changelog-master.yaml b/src/main/resources/db/changelog/db.changelog-master.yaml index 28d19dcb..e714ab10 100644 --- a/src/main/resources/db/changelog/db.changelog-master.yaml +++ b/src/main/resources/db/changelog/db.changelog-master.yaml @@ -31,3 +31,6 @@ databaseChangeLog: - include: file: changesets/changelog_20230810T095237Z.xml relativeToChangelogFile: true + - include: + file: changesets/changelog_20231013T170503Z.xml + relativeToChangelogFile: true diff --git a/src/test/java/org/gridsuite/filter/server/ExpertFilterUtilsTest.java b/src/test/java/org/gridsuite/filter/server/ExpertFilterUtilsTest.java new file mode 100644 index 00000000..b1f541ad --- /dev/null +++ b/src/test/java/org/gridsuite/filter/server/ExpertFilterUtilsTest.java @@ -0,0 +1,145 @@ +package org.gridsuite.filter.server; + +import com.powsybl.iidm.network.EnergySource; +import com.powsybl.iidm.network.Generator; +import com.powsybl.iidm.network.IdentifiableType; +import org.gridsuite.filter.server.dto.expertrule.AbstractExpertRule; +import org.gridsuite.filter.server.dto.expertrule.CombinatorExpertRule; +import org.gridsuite.filter.server.dto.expertrule.EnumExpertRule; +import org.gridsuite.filter.server.dto.expertrule.NumberExpertRule; +import org.gridsuite.filter.server.utils.CombinatorType; +import org.gridsuite.filter.server.utils.ExpertFilterUtils; +import org.gridsuite.filter.server.utils.FieldType; +import org.gridsuite.filter.server.utils.OperatorType; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class ExpertFilterUtilsTest { + + private Generator gen; + + @Before + public void setUp() { + gen = Mockito.mock(Generator.class); + Mockito.when(gen.getType()).thenReturn(IdentifiableType.GENERATOR); + Mockito.when(gen.getMinP()).thenReturn(-500.0); + Mockito.when(gen.getMaxP()).thenReturn(100.0); + Mockito.when(gen.getTargetV()).thenReturn(20.0); + Mockito.when(gen.getEnergySource()).thenReturn(EnergySource.HYDRO); + } + + @Test + public void testEvaluateExpertFilterWithANDCombination() { + List andRules1 = new ArrayList<>(); + NumberExpertRule numRule1 = NumberExpertRule.builder().value(0.0) + .field(FieldType.MIN_P).operator(OperatorType.LOWER).build(); + andRules1.add(numRule1); + NumberExpertRule numRule2 = NumberExpertRule.builder().value(100.0) + .field(FieldType.MAX_P).operator(OperatorType.GREATER_OR_EQUALS).build(); // false + andRules1.add(numRule2); + CombinatorExpertRule andCombination = CombinatorExpertRule.builder().combinator(CombinatorType.AND).rules(andRules1).build(); + + List andRules2 = new ArrayList<>(); + andRules2.add(andCombination); + NumberExpertRule numRule3 = NumberExpertRule.builder().value(20.0) + .field(FieldType.TARGET_V).operator(OperatorType.EQUALS).build(); + andRules2.add(numRule3); + EnumExpertRule enumRule4 = EnumExpertRule.builder().value("HYDRO") + .field(FieldType.ENERGY_SOURCE).operator(OperatorType.EQUALS).build(); + andRules2.add(enumRule4); + + CombinatorExpertRule andFilter = CombinatorExpertRule.builder().combinator(CombinatorType.AND).rules(andRules2).build(); + + boolean result = ExpertFilterUtils.evaluateExpertFilter(andFilter, gen); + + assertTrue(result); + } + + @Test + public void testEvaluateExpertFilterWithFalseANDCombination() { + List andRules1 = new ArrayList<>(); + NumberExpertRule numRule1 = NumberExpertRule.builder().value(0.0) + .field(FieldType.MIN_P).operator(OperatorType.LOWER).build(); + andRules1.add(numRule1); + NumberExpertRule numRule2 = NumberExpertRule.builder().value(105.0) + .field(FieldType.MAX_P).operator(OperatorType.GREATER_OR_EQUALS).build(); // false + andRules1.add(numRule2); + CombinatorExpertRule andCombination = CombinatorExpertRule.builder().combinator(CombinatorType.AND).rules(andRules1).build(); + + List andRules2 = new ArrayList<>(); + andRules2.add(andCombination); + NumberExpertRule numRule3 = NumberExpertRule.builder().value(20.0) + .field(FieldType.TARGET_V).operator(OperatorType.EQUALS).build(); + andRules2.add(numRule3); + EnumExpertRule enumRule4 = EnumExpertRule.builder().value("HYDRO") + .field(FieldType.ENERGY_SOURCE).operator(OperatorType.EQUALS).build(); + andRules2.add(enumRule4); + + CombinatorExpertRule andFilter = CombinatorExpertRule.builder().combinator(CombinatorType.AND).rules(andRules2).build(); + + boolean result = ExpertFilterUtils.evaluateExpertFilter(andFilter, gen); + + assertFalse(result); + } + + @Test + public void testEvaluateExpertFilterWithORCombination() { + List orRules1 = new ArrayList<>(); + NumberExpertRule numRule1 = NumberExpertRule.builder().value(0.0) + .field(FieldType.MIN_P).operator(OperatorType.LOWER).build(); + orRules1.add(numRule1); + NumberExpertRule numRule2 = NumberExpertRule.builder().value(100.0) + .field(FieldType.MAX_P).operator(OperatorType.GREATER_OR_EQUALS).build(); // false + orRules1.add(numRule2); + CombinatorExpertRule orCombination = CombinatorExpertRule.builder().combinator(CombinatorType.OR).rules(orRules1).build(); + + List orRules2 = new ArrayList<>(); + orRules2.add(orCombination); + NumberExpertRule numRule3 = NumberExpertRule.builder().value(20.0) + .field(FieldType.TARGET_V).operator(OperatorType.EQUALS).build(); + orRules2.add(numRule3); + EnumExpertRule enumRule4 = EnumExpertRule.builder().value("OTHER") + .field(FieldType.ENERGY_SOURCE).operator(OperatorType.EQUALS).build(); //false + orRules2.add(enumRule4); + + CombinatorExpertRule orFilter = CombinatorExpertRule.builder().combinator(CombinatorType.OR).rules(orRules2).build(); + + boolean result = ExpertFilterUtils.evaluateExpertFilter(orFilter, gen); + + assertTrue(result); + } + + @Test + public void testEvaluateExpertFilterWithFalseORCombination() { + List orRules1 = new ArrayList<>(); + NumberExpertRule numRule1 = NumberExpertRule.builder().value(0.0) + .field(FieldType.MIN_P).operator(OperatorType.GREATER).build(); // false + orRules1.add(numRule1); + NumberExpertRule numRule2 = NumberExpertRule.builder().value(105.0) + .field(FieldType.MAX_P).operator(OperatorType.GREATER_OR_EQUALS).build(); // false + orRules1.add(numRule2); + CombinatorExpertRule orCombination = CombinatorExpertRule.builder().combinator(CombinatorType.OR).rules(orRules1).build(); + + List orRules2 = new ArrayList<>(); + orRules2.add(orCombination); + NumberExpertRule numRule3 = NumberExpertRule.builder().value(25.0) // False + .field(FieldType.TARGET_V).operator(OperatorType.EQUALS).build(); + orRules2.add(numRule3); + EnumExpertRule enumRule4 = EnumExpertRule.builder().value("OTHER") + .field(FieldType.ENERGY_SOURCE).operator(OperatorType.EQUALS).build(); //false + orRules2.add(enumRule4); + + CombinatorExpertRule orFilter = CombinatorExpertRule.builder().combinator(CombinatorType.OR).rules(orRules2).build(); + + boolean result = ExpertFilterUtils.evaluateExpertFilter(orFilter, gen); + + assertFalse(result); + } +} diff --git a/src/test/java/org/gridsuite/filter/server/FilterEntityControllerTest.java b/src/test/java/org/gridsuite/filter/server/FilterEntityControllerTest.java index 3508d7db..3d11704c 100644 --- a/src/test/java/org/gridsuite/filter/server/FilterEntityControllerTest.java +++ b/src/test/java/org/gridsuite/filter/server/FilterEntityControllerTest.java @@ -21,17 +21,13 @@ import com.powsybl.iidm.network.test.*; import com.powsybl.network.store.client.NetworkStoreService; import com.powsybl.network.store.iidm.impl.NetworkFactoryImpl; - import jakarta.servlet.ServletException; import org.apache.commons.collections4.OrderedMap; import org.apache.commons.collections4.map.LinkedMap; -import org.gridsuite.filter.server.dto.*; import org.gridsuite.filter.server.dto.DanglingLineFilter; -import org.gridsuite.filter.server.utils.EquipmentType; -import org.gridsuite.filter.server.utils.FieldsMatcher; -import org.gridsuite.filter.server.utils.FilterType; -import org.gridsuite.filter.server.utils.MatcherJson; -import org.gridsuite.filter.server.utils.RangeType; +import org.gridsuite.filter.server.dto.*; +import org.gridsuite.filter.server.dto.expertrule.*; +import org.gridsuite.filter.server.utils.*; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -57,16 +53,10 @@ import static org.apache.commons.lang3.StringUtils.join; import static org.gridsuite.filter.server.AbstractFilterRepositoryProxy.WRONG_FILTER_TYPE; import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertThrows; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; import static org.mockito.BDDMockito.given; import static org.springframework.http.MediaType.APPLICATION_JSON; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -1023,6 +1013,29 @@ private void checkIdentifierListFilterExportAndMetadata(UUID filterId, String ex assertEquals(equipmentType, filterAttributes.get(0).getEquipmentType()); } + private void checkExpertFilterExportAndMetadata(UUID filterId, String expectedJson, EquipmentType equipmentType) throws Exception { + mvc.perform(get(URL_TEMPLATE + "/" + filterId + "/export?networkUuid=" + NETWORK_UUID) + .contentType(APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().contentTypeCompatibleWith(APPLICATION_JSON)) + .andExpect(content().json(expectedJson)); + + List filterAttributes = objectMapper.readValue( + mvc.perform(get("/" + FilterApi.API_VERSION + "/filters/metadata?ids={id}", filterId) + .contentType(APPLICATION_JSON)) + .andExpect(status().isOk()) + .andReturn().getResponse().getContentAsString(), + new TypeReference<>() { + }); + + assertEquals(1, filterAttributes.size()); + assertEquals(filterId, filterAttributes.get(0).getId()); + assertEquals(FilterType.EXPERT, filterAttributes.get(0).getType()); + assertEquals(equipmentType, filterAttributes.get(0).getEquipmentType()); + + mvc.perform(delete(URL_TEMPLATE + "/" + filterId)).andExpect(status().isOk()); + } + private AbstractFilter insertFilter(UUID filterId, AbstractFilter filter) throws Exception { String response = mvc.perform(post(URL_TEMPLATE).param("id", filterId.toString()) .content(objectMapper.writeValueAsString(filter)) @@ -1401,6 +1414,12 @@ private void checkIdentifierListFilter(UUID filterId, IdentifierListFilter ident matchIdentifierListFilterInfos(foundFilter, identifierListFilter); } + private void checkExpertFilter(UUID filterId, ExpertFilter expertFilter) throws Exception { + String foundFilterAsString = mvc.perform(get(URL_TEMPLATE + "/" + filterId)).andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); + ExpertFilter foundFilter = objectMapper.readValue(foundFilterAsString, ExpertFilter.class); + matchExpertFilterInfos(foundFilter, expertFilter); + } + private void matchFilterInfos(IFilterAttributes filter1, IFilterAttributes filter2) { assertEquals(filter1.getId(), filter2.getId()); assertEquals(filter1.getType(), filter2.getType()); @@ -1434,9 +1453,86 @@ private void matchIdentifierListFilterInfos(IdentifierListFilter identifierListF assertTrue(new MatcherJson<>(objectMapper, identifierListFilter2.getFilterEquipmentsAttributes()).matchesSafely(identifierListFilter1.getFilterEquipmentsAttributes())); } + private void matchExpertFilterInfos(ExpertFilter expertFilter1, ExpertFilter expertFilter2) { + matchFilterInfos(expertFilter1, expertFilter2); + assertTrue(new MatcherJson<>(objectMapper, expertFilter2.getRules()).matchesSafely(expertFilter1.getRules())); + } + private void checkElementUpdatedMessageSent(UUID elementUuid, String userId) { Message message = output.receive(TIMEOUT, elementUpdateDestination); assertEquals(elementUuid, message.getHeaders().get(NotificationService.HEADER_ELEMENT_UUID)); assertEquals(userId, message.getHeaders().get(NotificationService.HEADER_MODIFIED_BY)); } + + @Test + public void testExpertGeneratorFilter() throws Exception { + UUID filterId = UUID.fromString("77614d91-c168-4f89-8fb9-77a23729e88e"); + Date modificationDate = new Date(); + + // Create OR rules for generators + List orRules = new ArrayList<>(); + NumberExpertRule numRule1 = NumberExpertRule.builder().value(20.0) + .field(FieldType.NOMINAL_VOLTAGE).operator(OperatorType.GREATER).build(); + orRules.add(numRule1); + NumberExpertRule numRule2 = NumberExpertRule.builder().value(-9000.0) + .field(FieldType.MIN_P).operator(OperatorType.EQUALS).build(); // false + orRules.add(numRule2); + CombinatorExpertRule orCombination = CombinatorExpertRule.builder().combinator(CombinatorType.OR).rules(orRules).build(); + // Create AND rules for generators + List andRules = new ArrayList<>(); + andRules.add(orCombination); + NumberExpertRule numRule3 = NumberExpertRule.builder().value(9999.99) + .field(FieldType.MAX_P).operator(OperatorType.GREATER_OR_EQUALS).build(); + andRules.add(numRule3); + NumberExpertRule numRule4 = NumberExpertRule.builder().value(24.5) + .field(FieldType.TARGET_V).operator(OperatorType.EQUALS).build(); + andRules.add(numRule4); + NumberExpertRule numRule5 = NumberExpertRule.builder().value(400.0) + .field(FieldType.TARGET_Q).operator(OperatorType.LOWER_OR_EQUALS).build(); + andRules.add(numRule5); + NumberExpertRule numRule6 = NumberExpertRule.builder().value(500.0) + .field(FieldType.TARGET_P).operator(OperatorType.GREATER).build(); + andRules.add(numRule6); + EnumExpertRule enumRule1 = EnumExpertRule.builder().value("OTHER") + .field(FieldType.ENERGY_SOURCE).operator(OperatorType.EQUALS).build(); + andRules.add(enumRule1); + EnumExpertRule enumRule2 = EnumExpertRule.builder().value("ES") + .field(FieldType.COUNTRY).operator(OperatorType.NOT_EQUALS).build(); + andRules.add(enumRule2); + StringExpertRule stringRule1 = StringExpertRule.builder().value("N") + .field(FieldType.ID).operator(OperatorType.ENDS_WITH).build(); + andRules.add(stringRule1); + StringExpertRule stringRule2 = StringExpertRule.builder().value("E") + .field(FieldType.NAME).operator(OperatorType.CONTAINS).build(); + andRules.add(stringRule2); + BooleanExpertRule booleanRule1 = BooleanExpertRule.builder().value(false) + .field(FieldType.VOLTAGE_REGULATOR_ON).operator(OperatorType.NOT_EQUALS).build(); + andRules.add(booleanRule1); + + CombinatorExpertRule andCombination = CombinatorExpertRule.builder().combinator(CombinatorType.AND).rules(andRules).build(); + + ExpertFilter expertFilter = new ExpertFilter(filterId, modificationDate, EquipmentType.GENERATOR, andCombination); + insertFilter(filterId, expertFilter); + checkExpertFilter(filterId, expertFilter); + checkExpertFilterExportAndMetadata(filterId, "[{\"id\":\"GEN\",\"type\":\"GENERATOR\"}]", EquipmentType.GENERATOR); + } + + @Test + public void testExpertLoadFilter() throws Exception { + UUID filterId = UUID.fromString("77614d91-c168-4f89-8fb9-77a23729e88e"); + Date modificationDate = new Date(); + + // Create rules for loads + List rules = new ArrayList<>(); + StringExpertRule stringRule1 = StringExpertRule.builder().value("LOAD") + .field(FieldType.ID).operator(OperatorType.IS).build(); + rules.add(stringRule1); + + CombinatorExpertRule gen1 = CombinatorExpertRule.builder().combinator(CombinatorType.AND).rules(rules).build(); + + ExpertFilter expertFilter = new ExpertFilter(filterId, modificationDate, EquipmentType.LOAD, gen1); + insertFilter(filterId, expertFilter); + checkExpertFilter(filterId, expertFilter); + checkExpertFilterExportAndMetadata(filterId, "[{\"id\":\"LOAD\",\"type\":\"LOAD\"}]", EquipmentType.LOAD); + } } From e8504d29fb6e4e790fe89f9e5d49500513566851 Mon Sep 17 00:00:00 2001 From: antoinebhs Date: Mon, 16 Oct 2023 09:58:53 +0200 Subject: [PATCH 02/12] Improve Test coverage and fix code smell. --- .../filter/server/FilterService.java | 4 +-- .../filter/server/ExpertFilterUtilsTest.java | 31 ++++++++++++++++--- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/gridsuite/filter/server/FilterService.java b/src/main/java/org/gridsuite/filter/server/FilterService.java index 940421ec..c743efe9 100644 --- a/src/main/java/org/gridsuite/filter/server/FilterService.java +++ b/src/main/java/org/gridsuite/filter/server/FilterService.java @@ -300,8 +300,8 @@ private > Stream> getInjectionList(Stream equipmentIds = getIdentifierListFilterEquipmentIds((IdentifierListFilter) filter); return stream.filter(injection -> equipmentIds.contains(injection.getId())); - } else if (filter instanceof ExpertFilter) { - var rule = ((ExpertFilter) filter).getRules(); + } else if (filter instanceof ExpertFilter expertFilter) { + var rule = expertFilter.getRules(); return stream.filter(injection -> ExpertFilterUtils.evaluateExpertFilter(rule, injection)); } else { return Stream.empty(); diff --git a/src/test/java/org/gridsuite/filter/server/ExpertFilterUtilsTest.java b/src/test/java/org/gridsuite/filter/server/ExpertFilterUtilsTest.java index b1f541ad..3fa9dc1d 100644 --- a/src/test/java/org/gridsuite/filter/server/ExpertFilterUtilsTest.java +++ b/src/test/java/org/gridsuite/filter/server/ExpertFilterUtilsTest.java @@ -3,10 +3,7 @@ import com.powsybl.iidm.network.EnergySource; import com.powsybl.iidm.network.Generator; import com.powsybl.iidm.network.IdentifiableType; -import org.gridsuite.filter.server.dto.expertrule.AbstractExpertRule; -import org.gridsuite.filter.server.dto.expertrule.CombinatorExpertRule; -import org.gridsuite.filter.server.dto.expertrule.EnumExpertRule; -import org.gridsuite.filter.server.dto.expertrule.NumberExpertRule; +import org.gridsuite.filter.server.dto.expertrule.*; import org.gridsuite.filter.server.utils.CombinatorType; import org.gridsuite.filter.server.utils.ExpertFilterUtils; import org.gridsuite.filter.server.utils.FieldType; @@ -32,7 +29,9 @@ public void setUp() { Mockito.when(gen.getMinP()).thenReturn(-500.0); Mockito.when(gen.getMaxP()).thenReturn(100.0); Mockito.when(gen.getTargetV()).thenReturn(20.0); + Mockito.when(gen.getId()).thenReturn("ID_1"); Mockito.when(gen.getEnergySource()).thenReturn(EnergySource.HYDRO); + Mockito.when(gen.isVoltageRegulatorOn()).thenReturn(true); } @Test @@ -54,6 +53,18 @@ public void testEvaluateExpertFilterWithANDCombination() { EnumExpertRule enumRule4 = EnumExpertRule.builder().value("HYDRO") .field(FieldType.ENERGY_SOURCE).operator(OperatorType.EQUALS).build(); andRules2.add(enumRule4); + EnumExpertRule enumRule5 = EnumExpertRule.builder().value("OTHER") + .field(FieldType.ENERGY_SOURCE).operator(OperatorType.NOT_EQUALS).build(); + andRules2.add(enumRule5); + StringExpertRule stringRule5 = StringExpertRule.builder().value("ID") + .field(FieldType.ID).operator(OperatorType.BEGINS_WITH).build(); + andRules2.add(stringRule5); + BooleanExpertRule booleanRule6 = BooleanExpertRule.builder().value(true) + .field(FieldType.VOLTAGE_REGULATOR_ON).operator(OperatorType.EQUALS).build(); + andRules2.add(booleanRule6); + BooleanExpertRule booleanRule7 = BooleanExpertRule.builder().value(false) + .field(FieldType.VOLTAGE_REGULATOR_ON).operator(OperatorType.NOT_EQUALS).build(); + andRules2.add(booleanRule7); CombinatorExpertRule andFilter = CombinatorExpertRule.builder().combinator(CombinatorType.AND).rules(andRules2).build(); @@ -135,6 +146,18 @@ public void testEvaluateExpertFilterWithFalseORCombination() { EnumExpertRule enumRule4 = EnumExpertRule.builder().value("OTHER") .field(FieldType.ENERGY_SOURCE).operator(OperatorType.EQUALS).build(); //false orRules2.add(enumRule4); + EnumExpertRule enumRule5 = EnumExpertRule.builder().value("HYDRO") + .field(FieldType.ENERGY_SOURCE).operator(OperatorType.NOT_EQUALS).build(); + orRules2.add(enumRule5); + StringExpertRule stringRule5 = StringExpertRule.builder().value("TEST") + .field(FieldType.ID).operator(OperatorType.BEGINS_WITH).build(); + orRules2.add(stringRule5); + BooleanExpertRule booleanRule6 = BooleanExpertRule.builder().value(false) + .field(FieldType.VOLTAGE_REGULATOR_ON).operator(OperatorType.EQUALS).build(); + orRules2.add(booleanRule6); + BooleanExpertRule booleanRule7 = BooleanExpertRule.builder().value(true) + .field(FieldType.VOLTAGE_REGULATOR_ON).operator(OperatorType.NOT_EQUALS).build(); + orRules2.add(booleanRule7); CombinatorExpertRule orFilter = CombinatorExpertRule.builder().combinator(CombinatorType.OR).rules(orRules2).build(); From b3d7043ca0a4e778691e07b5a761c6cf487ba5d4 Mon Sep 17 00:00:00 2001 From: antoinebhs Date: Mon, 16 Oct 2023 10:14:11 +0200 Subject: [PATCH 03/12] Improve Test coverage and fix code smell. --- .../gridsuite/filter/server/ExpertFilterUtilsTest.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/test/java/org/gridsuite/filter/server/ExpertFilterUtilsTest.java b/src/test/java/org/gridsuite/filter/server/ExpertFilterUtilsTest.java index 3fa9dc1d..d84ff7c7 100644 --- a/src/test/java/org/gridsuite/filter/server/ExpertFilterUtilsTest.java +++ b/src/test/java/org/gridsuite/filter/server/ExpertFilterUtilsTest.java @@ -147,16 +147,16 @@ public void testEvaluateExpertFilterWithFalseORCombination() { .field(FieldType.ENERGY_SOURCE).operator(OperatorType.EQUALS).build(); //false orRules2.add(enumRule4); EnumExpertRule enumRule5 = EnumExpertRule.builder().value("HYDRO") - .field(FieldType.ENERGY_SOURCE).operator(OperatorType.NOT_EQUALS).build(); + .field(FieldType.ENERGY_SOURCE).operator(OperatorType.NOT_EQUALS).build(); //false orRules2.add(enumRule5); StringExpertRule stringRule5 = StringExpertRule.builder().value("TEST") - .field(FieldType.ID).operator(OperatorType.BEGINS_WITH).build(); + .field(FieldType.ID).operator(OperatorType.BEGINS_WITH).build(); //false orRules2.add(stringRule5); BooleanExpertRule booleanRule6 = BooleanExpertRule.builder().value(false) - .field(FieldType.VOLTAGE_REGULATOR_ON).operator(OperatorType.EQUALS).build(); + .field(FieldType.VOLTAGE_REGULATOR_ON).operator(OperatorType.EQUALS).build(); //false orRules2.add(booleanRule6); BooleanExpertRule booleanRule7 = BooleanExpertRule.builder().value(true) - .field(FieldType.VOLTAGE_REGULATOR_ON).operator(OperatorType.NOT_EQUALS).build(); + .field(FieldType.VOLTAGE_REGULATOR_ON).operator(OperatorType.NOT_EQUALS).build(); //false orRules2.add(booleanRule7); CombinatorExpertRule orFilter = CombinatorExpertRule.builder().combinator(CombinatorType.OR).rules(orRules2).build(); From 21c6421013e5fd647813636a89ec3f89357ecfe9 Mon Sep 17 00:00:00 2001 From: antoinebhs Date: Tue, 17 Oct 2023 16:01:28 +0200 Subject: [PATCH 04/12] Fix copyrights --- .../filter/server/ExpertFilterRepositoryProxy.java | 2 +- .../java/org/gridsuite/filter/server/dto/ExpertFilter.java | 2 +- .../filter/server/dto/expertrule/AbstractExpertRule.java | 2 +- .../filter/server/dto/expertrule/BooleanExpertRule.java | 2 +- .../filter/server/dto/expertrule/CombinatorExpertRule.java | 2 +- .../filter/server/dto/expertrule/EnumExpertRule.java | 2 +- .../filter/server/dto/expertrule/NumberExpertRule.java | 2 +- .../filter/server/dto/expertrule/StringExpertRule.java | 2 +- .../filter/server/entities/ExpertFilterEntity.java | 2 +- .../gridsuite/filter/server/entities/ExpertRuleEntity.java | 2 +- .../filter/server/repositories/ExpertFilterRepository.java | 2 +- .../org/gridsuite/filter/server/utils/CombinatorType.java | 2 +- .../java/org/gridsuite/filter/server/utils/DataType.java | 2 +- .../gridsuite/filter/server/utils/ExpertFilterUtils.java | 3 +-- .../java/org/gridsuite/filter/server/utils/FieldType.java | 6 ++++++ .../org/gridsuite/filter/server/utils/OperatorType.java | 2 +- .../org/gridsuite/filter/server/ExpertFilterUtilsTest.java | 6 ++++++ 17 files changed, 27 insertions(+), 16 deletions(-) diff --git a/src/main/java/org/gridsuite/filter/server/ExpertFilterRepositoryProxy.java b/src/main/java/org/gridsuite/filter/server/ExpertFilterRepositoryProxy.java index 86566bc4..b79e982b 100644 --- a/src/main/java/org/gridsuite/filter/server/ExpertFilterRepositoryProxy.java +++ b/src/main/java/org/gridsuite/filter/server/ExpertFilterRepositoryProxy.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2022, RTE (http://www.rte-france.com) + * Copyright (c) 2023, RTE (http://www.rte-france.com) * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. diff --git a/src/main/java/org/gridsuite/filter/server/dto/ExpertFilter.java b/src/main/java/org/gridsuite/filter/server/dto/ExpertFilter.java index 816c0c09..1867e4d2 100644 --- a/src/main/java/org/gridsuite/filter/server/dto/ExpertFilter.java +++ b/src/main/java/org/gridsuite/filter/server/dto/ExpertFilter.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021, RTE (http://www.rte-france.com) + * Copyright (c) 2023, RTE (http://www.rte-france.com) * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. diff --git a/src/main/java/org/gridsuite/filter/server/dto/expertrule/AbstractExpertRule.java b/src/main/java/org/gridsuite/filter/server/dto/expertrule/AbstractExpertRule.java index 4a1939b0..bcdb5b25 100644 --- a/src/main/java/org/gridsuite/filter/server/dto/expertrule/AbstractExpertRule.java +++ b/src/main/java/org/gridsuite/filter/server/dto/expertrule/AbstractExpertRule.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021, RTE (http://www.rte-france.com) + * Copyright (c) 2023, RTE (http://www.rte-france.com) * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. diff --git a/src/main/java/org/gridsuite/filter/server/dto/expertrule/BooleanExpertRule.java b/src/main/java/org/gridsuite/filter/server/dto/expertrule/BooleanExpertRule.java index 0a891564..36c0e8f8 100644 --- a/src/main/java/org/gridsuite/filter/server/dto/expertrule/BooleanExpertRule.java +++ b/src/main/java/org/gridsuite/filter/server/dto/expertrule/BooleanExpertRule.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021, RTE (http://www.rte-france.com) + * Copyright (c) 2023, RTE (http://www.rte-france.com) * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. diff --git a/src/main/java/org/gridsuite/filter/server/dto/expertrule/CombinatorExpertRule.java b/src/main/java/org/gridsuite/filter/server/dto/expertrule/CombinatorExpertRule.java index 50b802a2..d5ba5a5c 100644 --- a/src/main/java/org/gridsuite/filter/server/dto/expertrule/CombinatorExpertRule.java +++ b/src/main/java/org/gridsuite/filter/server/dto/expertrule/CombinatorExpertRule.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021, RTE (http://www.rte-france.com) + * Copyright (c) 2023, RTE (http://www.rte-france.com) * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. diff --git a/src/main/java/org/gridsuite/filter/server/dto/expertrule/EnumExpertRule.java b/src/main/java/org/gridsuite/filter/server/dto/expertrule/EnumExpertRule.java index f3a1a96f..72e198ef 100644 --- a/src/main/java/org/gridsuite/filter/server/dto/expertrule/EnumExpertRule.java +++ b/src/main/java/org/gridsuite/filter/server/dto/expertrule/EnumExpertRule.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021, RTE (http://www.rte-france.com) + * Copyright (c) 2023, RTE (http://www.rte-france.com) * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. diff --git a/src/main/java/org/gridsuite/filter/server/dto/expertrule/NumberExpertRule.java b/src/main/java/org/gridsuite/filter/server/dto/expertrule/NumberExpertRule.java index e69dc61e..d4bca2d8 100644 --- a/src/main/java/org/gridsuite/filter/server/dto/expertrule/NumberExpertRule.java +++ b/src/main/java/org/gridsuite/filter/server/dto/expertrule/NumberExpertRule.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021, RTE (http://www.rte-france.com) + * Copyright (c) 2023, RTE (http://www.rte-france.com) * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. diff --git a/src/main/java/org/gridsuite/filter/server/dto/expertrule/StringExpertRule.java b/src/main/java/org/gridsuite/filter/server/dto/expertrule/StringExpertRule.java index 6a306c10..5016ddd2 100644 --- a/src/main/java/org/gridsuite/filter/server/dto/expertrule/StringExpertRule.java +++ b/src/main/java/org/gridsuite/filter/server/dto/expertrule/StringExpertRule.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021, RTE (http://www.rte-france.com) + * Copyright (c) 2023, RTE (http://www.rte-france.com) * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. diff --git a/src/main/java/org/gridsuite/filter/server/entities/ExpertFilterEntity.java b/src/main/java/org/gridsuite/filter/server/entities/ExpertFilterEntity.java index 6c3c01fb..ce8c591e 100644 --- a/src/main/java/org/gridsuite/filter/server/entities/ExpertFilterEntity.java +++ b/src/main/java/org/gridsuite/filter/server/entities/ExpertFilterEntity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, RTE (http://www.rte-france.com) + * Copyright (c) 2023, RTE (http://www.rte-france.com) * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. diff --git a/src/main/java/org/gridsuite/filter/server/entities/ExpertRuleEntity.java b/src/main/java/org/gridsuite/filter/server/entities/ExpertRuleEntity.java index 0729551c..0f68dd55 100644 --- a/src/main/java/org/gridsuite/filter/server/entities/ExpertRuleEntity.java +++ b/src/main/java/org/gridsuite/filter/server/entities/ExpertRuleEntity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, RTE (http://www.rte-france.com) + * Copyright (c) 2023, RTE (http://www.rte-france.com) * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. diff --git a/src/main/java/org/gridsuite/filter/server/repositories/ExpertFilterRepository.java b/src/main/java/org/gridsuite/filter/server/repositories/ExpertFilterRepository.java index ac801087..1cb0eae9 100644 --- a/src/main/java/org/gridsuite/filter/server/repositories/ExpertFilterRepository.java +++ b/src/main/java/org/gridsuite/filter/server/repositories/ExpertFilterRepository.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021, RTE (http://www.rte-france.com) + * Copyright (c) 2023, RTE (http://www.rte-france.com) * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. diff --git a/src/main/java/org/gridsuite/filter/server/utils/CombinatorType.java b/src/main/java/org/gridsuite/filter/server/utils/CombinatorType.java index 266b5208..21f4ef31 100644 --- a/src/main/java/org/gridsuite/filter/server/utils/CombinatorType.java +++ b/src/main/java/org/gridsuite/filter/server/utils/CombinatorType.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021, RTE (http://www.rte-france.com) + * Copyright (c) 2023, RTE (http://www.rte-france.com) * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. diff --git a/src/main/java/org/gridsuite/filter/server/utils/DataType.java b/src/main/java/org/gridsuite/filter/server/utils/DataType.java index 6b7612e6..ca0e7e81 100644 --- a/src/main/java/org/gridsuite/filter/server/utils/DataType.java +++ b/src/main/java/org/gridsuite/filter/server/utils/DataType.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021, RTE (http://www.rte-france.com) + * Copyright (c) 2023, RTE (http://www.rte-france.com) * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. diff --git a/src/main/java/org/gridsuite/filter/server/utils/ExpertFilterUtils.java b/src/main/java/org/gridsuite/filter/server/utils/ExpertFilterUtils.java index 4a6e7e0d..26002ac0 100644 --- a/src/main/java/org/gridsuite/filter/server/utils/ExpertFilterUtils.java +++ b/src/main/java/org/gridsuite/filter/server/utils/ExpertFilterUtils.java @@ -1,10 +1,9 @@ /** - * Copyright (c) 2021, RTE (http://www.rte-france.com) + * Copyright (c) 2023, RTE (http://www.rte-france.com) * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - package org.gridsuite.filter.server.utils; import com.powsybl.commons.PowsyblException; diff --git a/src/main/java/org/gridsuite/filter/server/utils/FieldType.java b/src/main/java/org/gridsuite/filter/server/utils/FieldType.java index 69c5a2bf..ce44d1fc 100644 --- a/src/main/java/org/gridsuite/filter/server/utils/FieldType.java +++ b/src/main/java/org/gridsuite/filter/server/utils/FieldType.java @@ -1,3 +1,9 @@ +/** + * Copyright (c) 2023, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ package org.gridsuite.filter.server.utils; /** diff --git a/src/main/java/org/gridsuite/filter/server/utils/OperatorType.java b/src/main/java/org/gridsuite/filter/server/utils/OperatorType.java index 22f98d87..2eeabde7 100644 --- a/src/main/java/org/gridsuite/filter/server/utils/OperatorType.java +++ b/src/main/java/org/gridsuite/filter/server/utils/OperatorType.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021, RTE (http://www.rte-france.com) + * Copyright (c) 2023, RTE (http://www.rte-france.com) * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. diff --git a/src/test/java/org/gridsuite/filter/server/ExpertFilterUtilsTest.java b/src/test/java/org/gridsuite/filter/server/ExpertFilterUtilsTest.java index d84ff7c7..a0205ee0 100644 --- a/src/test/java/org/gridsuite/filter/server/ExpertFilterUtilsTest.java +++ b/src/test/java/org/gridsuite/filter/server/ExpertFilterUtilsTest.java @@ -1,3 +1,9 @@ +/** + * Copyright (c) 2023, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ package org.gridsuite.filter.server; import com.powsybl.iidm.network.EnergySource; From ae88e31348a769393961f83fd1c145535850d0cb Mon Sep 17 00:00:00 2001 From: antoinebhs Date: Tue, 17 Oct 2023 17:14:35 +0200 Subject: [PATCH 05/12] Improve exception messages. Improve test coverage. --- .../server/ExpertFilterRepositoryProxy.java | 2 +- .../dto/expertrule/BooleanExpertRule.java | 2 +- .../server/dto/expertrule/EnumExpertRule.java | 2 +- .../dto/expertrule/NumberExpertRule.java | 2 +- .../dto/expertrule/StringExpertRule.java | 2 +- .../server/utils/ExpertFilterUtils.java | 4 ++-- .../filter/server/ExpertFilterUtilsTest.java | 21 +++++++++++++------ 7 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/gridsuite/filter/server/ExpertFilterRepositoryProxy.java b/src/main/java/org/gridsuite/filter/server/ExpertFilterRepositoryProxy.java index b79e982b..5726ff3b 100644 --- a/src/main/java/org/gridsuite/filter/server/ExpertFilterRepositoryProxy.java +++ b/src/main/java/org/gridsuite/filter/server/ExpertFilterRepositoryProxy.java @@ -81,7 +81,7 @@ public static AbstractExpertRule mapEntityToRule(ExpertRuleEntity filterEntity) .value(filterEntity.getValue()) .build(); } - default -> throw new PowsyblException(WRONG_FILTER_TYPE); + default -> throw new PowsyblException("Unknown rule data type: " + filterEntity.getDataType() + ", supported data types are: COMBINATOR, BOOLEAN, NUMBER, STRING, ENUM"); } } diff --git a/src/main/java/org/gridsuite/filter/server/dto/expertrule/BooleanExpertRule.java b/src/main/java/org/gridsuite/filter/server/dto/expertrule/BooleanExpertRule.java index 36c0e8f8..236336d8 100644 --- a/src/main/java/org/gridsuite/filter/server/dto/expertrule/BooleanExpertRule.java +++ b/src/main/java/org/gridsuite/filter/server/dto/expertrule/BooleanExpertRule.java @@ -49,7 +49,7 @@ public boolean evaluateRule(String identifiableValue) { return switch (this.getOperator()) { case EQUALS -> equipmentValue == filterValue; case NOT_EQUALS -> equipmentValue != filterValue; - default -> throw new PowsyblException("Operator not allowed"); + default -> throw new PowsyblException(this.getOperator() + " operator not supported with " + this.getDataType() + " rule data type"); }; } } diff --git a/src/main/java/org/gridsuite/filter/server/dto/expertrule/EnumExpertRule.java b/src/main/java/org/gridsuite/filter/server/dto/expertrule/EnumExpertRule.java index 72e198ef..29b22634 100644 --- a/src/main/java/org/gridsuite/filter/server/dto/expertrule/EnumExpertRule.java +++ b/src/main/java/org/gridsuite/filter/server/dto/expertrule/EnumExpertRule.java @@ -43,7 +43,7 @@ public boolean evaluateRule(String identifiableValue) { return switch (this.getOperator()) { case EQUALS -> identifiableValue.equals(this.getValue()); case NOT_EQUALS -> !identifiableValue.equals(this.getValue()); - default -> throw new PowsyblException("Operator not allowed"); + default -> throw new PowsyblException(this.getOperator() + " operator not supported with " + this.getDataType() + " rule data type"); }; } } diff --git a/src/main/java/org/gridsuite/filter/server/dto/expertrule/NumberExpertRule.java b/src/main/java/org/gridsuite/filter/server/dto/expertrule/NumberExpertRule.java index d4bca2d8..2a796cd0 100644 --- a/src/main/java/org/gridsuite/filter/server/dto/expertrule/NumberExpertRule.java +++ b/src/main/java/org/gridsuite/filter/server/dto/expertrule/NumberExpertRule.java @@ -47,7 +47,7 @@ public boolean evaluateRule(String identifiableValue) { case GREATER -> equipmentValue.compareTo(filterValue) > 0; case LOWER_OR_EQUALS -> equipmentValue.compareTo(filterValue) <= 0; case LOWER -> equipmentValue.compareTo(filterValue) < 0; - default -> throw new PowsyblException("Operator not allowed"); + default -> throw new PowsyblException(this.getOperator() + " operator not supported with " + this.getDataType() + " rule data type"); }; } diff --git a/src/main/java/org/gridsuite/filter/server/dto/expertrule/StringExpertRule.java b/src/main/java/org/gridsuite/filter/server/dto/expertrule/StringExpertRule.java index 5016ddd2..82b56d7b 100644 --- a/src/main/java/org/gridsuite/filter/server/dto/expertrule/StringExpertRule.java +++ b/src/main/java/org/gridsuite/filter/server/dto/expertrule/StringExpertRule.java @@ -40,7 +40,7 @@ public boolean evaluateRule(String identifiableValue) { case CONTAINS -> identifiableValue.contains(this.getValue()); case BEGINS_WITH -> identifiableValue.startsWith(this.getValue()); case ENDS_WITH -> identifiableValue.endsWith(this.getValue()); - default -> throw new PowsyblException("Operator not allowed"); + default -> throw new PowsyblException(this.getOperator() + " operator not supported with " + this.getDataType() + " rule data type"); }; } diff --git a/src/main/java/org/gridsuite/filter/server/utils/ExpertFilterUtils.java b/src/main/java/org/gridsuite/filter/server/utils/ExpertFilterUtils.java index 26002ac0..3f1b2978 100644 --- a/src/main/java/org/gridsuite/filter/server/utils/ExpertFilterUtils.java +++ b/src/main/java/org/gridsuite/filter/server/utils/ExpertFilterUtils.java @@ -56,14 +56,14 @@ private static > String getFieldValue(FieldType field, In return switch (injection.getType()) { case GENERATOR -> getGeneratorFieldValue(field, (Generator) injection); case LOAD -> getLoadFieldValue(field, (Load) injection); - default -> throw new PowsyblException("Not implemented with expert filter"); + default -> throw new PowsyblException(injection.getType() + " injection type is not implemented with expert filter"); }; } private static String getLoadFieldValue(FieldType field, Load load) { return switch (field) { case ID -> load.getId(); - default -> throw new PowsyblException("Not implemented with expert filter"); + default -> throw new PowsyblException("Field " + field + " with " + load.getType() + " injection type is not implemented with expert filter"); }; } diff --git a/src/test/java/org/gridsuite/filter/server/ExpertFilterUtilsTest.java b/src/test/java/org/gridsuite/filter/server/ExpertFilterUtilsTest.java index a0205ee0..bec7aa0a 100644 --- a/src/test/java/org/gridsuite/filter/server/ExpertFilterUtilsTest.java +++ b/src/test/java/org/gridsuite/filter/server/ExpertFilterUtilsTest.java @@ -46,16 +46,19 @@ public void testEvaluateExpertFilterWithANDCombination() { NumberExpertRule numRule1 = NumberExpertRule.builder().value(0.0) .field(FieldType.MIN_P).operator(OperatorType.LOWER).build(); andRules1.add(numRule1); - NumberExpertRule numRule2 = NumberExpertRule.builder().value(100.0) - .field(FieldType.MAX_P).operator(OperatorType.GREATER_OR_EQUALS).build(); // false + NumberExpertRule numRule2 = NumberExpertRule.builder().value(-500.0) + .field(FieldType.MIN_P).operator(OperatorType.LOWER_OR_EQUALS).build(); andRules1.add(numRule2); + NumberExpertRule numRule3 = NumberExpertRule.builder().value(100.0) + .field(FieldType.MAX_P).operator(OperatorType.GREATER_OR_EQUALS).build(); + andRules1.add(numRule3); CombinatorExpertRule andCombination = CombinatorExpertRule.builder().combinator(CombinatorType.AND).rules(andRules1).build(); List andRules2 = new ArrayList<>(); andRules2.add(andCombination); - NumberExpertRule numRule3 = NumberExpertRule.builder().value(20.0) + NumberExpertRule numRule4 = NumberExpertRule.builder().value(20.0) .field(FieldType.TARGET_V).operator(OperatorType.EQUALS).build(); - andRules2.add(numRule3); + andRules2.add(numRule4); EnumExpertRule enumRule4 = EnumExpertRule.builder().value("HYDRO") .field(FieldType.ENERGY_SOURCE).operator(OperatorType.EQUALS).build(); andRules2.add(enumRule4); @@ -142,13 +145,19 @@ public void testEvaluateExpertFilterWithFalseORCombination() { NumberExpertRule numRule2 = NumberExpertRule.builder().value(105.0) .field(FieldType.MAX_P).operator(OperatorType.GREATER_OR_EQUALS).build(); // false orRules1.add(numRule2); + NumberExpertRule numRule3 = NumberExpertRule.builder().value(-500.0) + .field(FieldType.MIN_P).operator(OperatorType.LOWER).build(); // false + orRules1.add(numRule3); + NumberExpertRule numRule4 = NumberExpertRule.builder().value(-500.5) + .field(FieldType.MAX_P).operator(OperatorType.LOWER_OR_EQUALS).build(); // false + orRules1.add(numRule4); CombinatorExpertRule orCombination = CombinatorExpertRule.builder().combinator(CombinatorType.OR).rules(orRules1).build(); List orRules2 = new ArrayList<>(); orRules2.add(orCombination); - NumberExpertRule numRule3 = NumberExpertRule.builder().value(25.0) // False + NumberExpertRule numRule5 = NumberExpertRule.builder().value(25.0) // False .field(FieldType.TARGET_V).operator(OperatorType.EQUALS).build(); - orRules2.add(numRule3); + orRules2.add(numRule5); EnumExpertRule enumRule4 = EnumExpertRule.builder().value("OTHER") .field(FieldType.ENERGY_SOURCE).operator(OperatorType.EQUALS).build(); //false orRules2.add(enumRule4); From 8e3fa9a4380934b65525424b4ef0c25df4483c67 Mon Sep 17 00:00:00 2001 From: antoinebhs Date: Wed, 18 Oct 2023 09:55:15 +0200 Subject: [PATCH 06/12] Add name to foreign key. Use EnumType.STRING. --- .../server/entities/ExpertFilterEntity.java | 7 ++-- .../server/entities/ExpertRuleEntity.java | 11 ++++-- .../changesets/changelog_20231013T170503Z.xml | 26 -------------- .../changesets/changelog_20231017T161802Z.xml | 35 +++++++++++++++++++ .../db/changelog/db.changelog-master.yaml | 4 +-- 5 files changed, 51 insertions(+), 32 deletions(-) delete mode 100644 src/main/resources/db/changelog/changesets/changelog_20231013T170503Z.xml create mode 100644 src/main/resources/db/changelog/changesets/changelog_20231017T161802Z.xml diff --git a/src/main/java/org/gridsuite/filter/server/entities/ExpertFilterEntity.java b/src/main/java/org/gridsuite/filter/server/entities/ExpertFilterEntity.java index ce8c591e..bf6a9010 100644 --- a/src/main/java/org/gridsuite/filter/server/entities/ExpertFilterEntity.java +++ b/src/main/java/org/gridsuite/filter/server/entities/ExpertFilterEntity.java @@ -25,12 +25,15 @@ @Table(name = "expert_filter") public class ExpertFilterEntity extends AbstractFilterEntity { + @Enumerated(EnumType.STRING) @Column(name = "equipmentType") private EquipmentType equipmentType; @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true) - @JoinColumn(name = "expertFilterEntity_rule_id", + @JoinColumn(name = "expertFilterEntity_rules_id", referencedColumnName = "id", - foreignKey = @ForeignKey) + foreignKey = @ForeignKey( + name = "expertFilterEntity_rules_fk" + )) private ExpertRuleEntity rules; } diff --git a/src/main/java/org/gridsuite/filter/server/entities/ExpertRuleEntity.java b/src/main/java/org/gridsuite/filter/server/entities/ExpertRuleEntity.java index 0f68dd55..2d65d8a9 100644 --- a/src/main/java/org/gridsuite/filter/server/entities/ExpertRuleEntity.java +++ b/src/main/java/org/gridsuite/filter/server/entities/ExpertRuleEntity.java @@ -34,18 +34,22 @@ public class ExpertRuleEntity { @Column(name = "id") private UUID id; + @Enumerated(EnumType.STRING) @Column(name = "combinator") private CombinatorType combinator; + @Enumerated(EnumType.STRING) @Column(name = "field") private FieldType field; - @Column(name = "value_") + @Column(name = "value_") // "value" is not supported in UT with H2 private String value; + @Enumerated(EnumType.STRING) @Column(name = "operator") private OperatorType operator; + @Enumerated(EnumType.STRING) @Column(name = "dataType") private DataType dataType; @@ -53,6 +57,9 @@ public class ExpertRuleEntity { private List rules; @ManyToOne - @JoinColumn(name = "parent_rule_id") + @JoinColumn(name = "expertRuleEntity_parentRule_id", referencedColumnName = "id", + foreignKey = @ForeignKey( + name = "expertRuleEntity_parentRule_fk" + ), nullable = true) private ExpertRuleEntity parentRule; } diff --git a/src/main/resources/db/changelog/changesets/changelog_20231013T170503Z.xml b/src/main/resources/db/changelog/changesets/changelog_20231013T170503Z.xml deleted file mode 100644 index ef1880e4..00000000 --- a/src/main/resources/db/changelog/changesets/changelog_20231013T170503Z.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/main/resources/db/changelog/changesets/changelog_20231017T161802Z.xml b/src/main/resources/db/changelog/changesets/changelog_20231017T161802Z.xml new file mode 100644 index 00000000..73e7f02c --- /dev/null +++ b/src/main/resources/db/changelog/changesets/changelog_20231017T161802Z.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/db/changelog/db.changelog-master.yaml b/src/main/resources/db/changelog/db.changelog-master.yaml index e714ab10..72a0c10a 100644 --- a/src/main/resources/db/changelog/db.changelog-master.yaml +++ b/src/main/resources/db/changelog/db.changelog-master.yaml @@ -32,5 +32,5 @@ databaseChangeLog: file: changesets/changelog_20230810T095237Z.xml relativeToChangelogFile: true - include: - file: changesets/changelog_20231013T170503Z.xml - relativeToChangelogFile: true + file: changesets/changelog_20231017T161802Z.xml + relativeToChangelogFile: true \ No newline at end of file From 1dd4108fb47ef95b8f68b4c331dc6bebafab1acd Mon Sep 17 00:00:00 2001 From: antoinebhs Date: Mon, 23 Oct 2023 17:33:08 +0200 Subject: [PATCH 07/12] Ignore case for string filter. --- .../dto/expertrule/StringExpertRule.java | 9 ++--- .../filter/server/ExpertFilterUtilsTest.java | 35 +++++++++++++++++++ 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/gridsuite/filter/server/dto/expertrule/StringExpertRule.java b/src/main/java/org/gridsuite/filter/server/dto/expertrule/StringExpertRule.java index 82b56d7b..99fde7f3 100644 --- a/src/main/java/org/gridsuite/filter/server/dto/expertrule/StringExpertRule.java +++ b/src/main/java/org/gridsuite/filter/server/dto/expertrule/StringExpertRule.java @@ -13,6 +13,7 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; +import org.apache.commons.lang3.StringUtils; import org.gridsuite.filter.server.utils.DataType; /** @@ -36,10 +37,10 @@ public DataType getDataType() { @Override public boolean evaluateRule(String identifiableValue) { return switch (this.getOperator()) { - case IS -> identifiableValue.equals(this.getValue()); - case CONTAINS -> identifiableValue.contains(this.getValue()); - case BEGINS_WITH -> identifiableValue.startsWith(this.getValue()); - case ENDS_WITH -> identifiableValue.endsWith(this.getValue()); + case IS -> identifiableValue.equalsIgnoreCase(this.getValue()); + case CONTAINS -> StringUtils.containsIgnoreCase(identifiableValue, this.getValue()); + case BEGINS_WITH -> StringUtils.startsWithIgnoreCase(identifiableValue, this.getValue()); + case ENDS_WITH -> StringUtils.endsWithIgnoreCase(identifiableValue, this.getValue()); default -> throw new PowsyblException(this.getOperator() + " operator not supported with " + this.getDataType() + " rule data type"); }; } diff --git a/src/test/java/org/gridsuite/filter/server/ExpertFilterUtilsTest.java b/src/test/java/org/gridsuite/filter/server/ExpertFilterUtilsTest.java index bec7aa0a..7b6ed7b9 100644 --- a/src/test/java/org/gridsuite/filter/server/ExpertFilterUtilsTest.java +++ b/src/test/java/org/gridsuite/filter/server/ExpertFilterUtilsTest.java @@ -36,6 +36,7 @@ public void setUp() { Mockito.when(gen.getMaxP()).thenReturn(100.0); Mockito.when(gen.getTargetV()).thenReturn(20.0); Mockito.when(gen.getId()).thenReturn("ID_1"); + Mockito.when(gen.getNameOrId()).thenReturn("NAME"); Mockito.when(gen.getEnergySource()).thenReturn(EnergySource.HYDRO); Mockito.when(gen.isVoltageRegulatorOn()).thenReturn(true); } @@ -180,4 +181,38 @@ public void testEvaluateExpertFilterWithFalseORCombination() { assertFalse(result); } + + @Test + public void testEvaluateExpertFilterIgnoreCase() { + List andRules1 = new ArrayList<>(); + StringExpertRule rule1 = StringExpertRule.builder().value("id") + .field(FieldType.ID).operator(OperatorType.CONTAINS).build(); + andRules1.add(rule1); + StringExpertRule rule2 = StringExpertRule.builder().value("ID") + .field(FieldType.ID).operator(OperatorType.CONTAINS).build(); + andRules1.add(rule2); + StringExpertRule rule3 = StringExpertRule.builder().value("id_1") + .field(FieldType.ID).operator(OperatorType.IS).build(); + andRules1.add(rule3); + StringExpertRule rule4 = StringExpertRule.builder().value("ID_1") + .field(FieldType.ID).operator(OperatorType.IS).build(); + andRules1.add(rule4); + StringExpertRule rule5 = StringExpertRule.builder().value("id") + .field(FieldType.ID).operator(OperatorType.BEGINS_WITH).build(); + andRules1.add(rule5); + StringExpertRule rule6 = StringExpertRule.builder().value("ID") + .field(FieldType.ID).operator(OperatorType.BEGINS_WITH).build(); + andRules1.add(rule6); + StringExpertRule rule7 = StringExpertRule.builder().value("me") + .field(FieldType.NAME).operator(OperatorType.ENDS_WITH).build(); + andRules1.add(rule7); + StringExpertRule rule8 = StringExpertRule.builder().value("ME") + .field(FieldType.NAME).operator(OperatorType.ENDS_WITH).build(); + andRules1.add(rule8); + CombinatorExpertRule andCombination = CombinatorExpertRule.builder().combinator(CombinatorType.AND).rules(andRules1).build(); + + boolean result = ExpertFilterUtils.evaluateExpertFilter(andCombination, gen); + + assertTrue(result); + } } From 21a1c3917e804bb224d5135b8ce5497419aac932 Mon Sep 17 00:00:00 2001 From: BOUHOURS Antoine Date: Thu, 2 Nov 2023 12:45:07 +0100 Subject: [PATCH 08/12] Code improvement Signed-off-by: BOUHOURS Antoine --- .../server/ExpertFilterRepositoryProxy.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/gridsuite/filter/server/ExpertFilterRepositoryProxy.java b/src/main/java/org/gridsuite/filter/server/ExpertFilterRepositoryProxy.java index 5726ff3b..bfafa799 100644 --- a/src/main/java/org/gridsuite/filter/server/ExpertFilterRepositoryProxy.java +++ b/src/main/java/org/gridsuite/filter/server/ExpertFilterRepositoryProxy.java @@ -41,16 +41,16 @@ AbstractFilter toDto(ExpertFilterEntity filterEntity) { .id(filterEntity.getId()) .modificationDate(filterEntity.getModificationDate()) .equipmentType(filterEntity.getEquipmentType()) - .rules(mapEntityToRule(filterEntity.getRules())) + .rules(entityToDto(filterEntity.getRules())) .build(); } - public static AbstractExpertRule mapEntityToRule(ExpertRuleEntity filterEntity) { + public static AbstractExpertRule entityToDto(ExpertRuleEntity filterEntity) { switch (filterEntity.getDataType()) { case COMBINATOR -> { return CombinatorExpertRule.builder() .combinator(filterEntity.getCombinator()) - .rules(mapEntitiesToRules(filterEntity.getRules())) + .rules(entitiesToDto(filterEntity.getRules())) .build(); } case BOOLEAN -> { @@ -85,13 +85,13 @@ public static AbstractExpertRule mapEntityToRule(ExpertRuleEntity filterEntity) } } - private static List mapEntitiesToRules(List entities) { + private static List entitiesToDto(List entities) { if (entities == null) { return Collections.emptyList(); } return entities.stream() - .map(ExpertFilterRepositoryProxy::mapEntityToRule) + .map(ExpertFilterRepositoryProxy::entityToDto) .collect(Collectors.toList()); } @@ -101,14 +101,14 @@ ExpertFilterEntity fromDto(AbstractFilter dto) { var expertFilterEntityBuilder = ExpertFilterEntity.builder() .modificationDate(filter.getModificationDate()) .equipmentType(filter.getEquipmentType()) - .rules(mapRuleToEntity(filter.getRules())); + .rules(dtoToEntity(filter.getRules())); buildAbstractFilter(expertFilterEntityBuilder, filter); return expertFilterEntityBuilder.build(); } throw new PowsyblException(WRONG_FILTER_TYPE); } - public static ExpertRuleEntity mapRuleToEntity(AbstractExpertRule filter) { + public static ExpertRuleEntity dtoToEntity(AbstractExpertRule filter) { var expertFilterEntityBuilder = ExpertRuleEntity.builder() .id(UUID.randomUUID()) .combinator(filter.getCombinator()) @@ -116,11 +116,11 @@ public static ExpertRuleEntity mapRuleToEntity(AbstractExpertRule filter) { .dataType(filter.getDataType()) .field(filter.getField()) .value(filter.getStringValue()); - expertFilterEntityBuilder.rules(mapRulesToEntities(filter.getRules(), expertFilterEntityBuilder.build())); + expertFilterEntityBuilder.rules(dtoToEntities(filter.getRules(), expertFilterEntityBuilder.build())); return expertFilterEntityBuilder.build(); } - private static List mapRulesToEntities(List ruleFromDto, ExpertRuleEntity parentRuleEntity) { + private static List dtoToEntities(List ruleFromDto, ExpertRuleEntity parentRuleEntity) { if (ruleFromDto == null) { return Collections.emptyList(); } @@ -136,7 +136,7 @@ private static List mapRulesToEntities(List rules = mapRulesToEntities(rule.getRules(), expertRuleEntityBuilder.build()); + List rules = dtoToEntities(rule.getRules(), expertRuleEntityBuilder.build()); expertRuleEntityBuilder.rules(rules); return expertRuleEntityBuilder.build(); From 32f312bfe61367f3347110c498bdb11d6f2a59d9 Mon Sep 17 00:00:00 2001 From: BOUHOURS Antoine Date: Thu, 2 Nov 2023 12:52:32 +0100 Subject: [PATCH 09/12] Code improvement Signed-off-by: BOUHOURS Antoine --- .../filter/server/FilterEntityControllerTest.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/test/java/org/gridsuite/filter/server/FilterEntityControllerTest.java b/src/test/java/org/gridsuite/filter/server/FilterEntityControllerTest.java index 3d11704c..50b423f9 100644 --- a/src/test/java/org/gridsuite/filter/server/FilterEntityControllerTest.java +++ b/src/test/java/org/gridsuite/filter/server/FilterEntityControllerTest.java @@ -1514,7 +1514,9 @@ public void testExpertGeneratorFilter() throws Exception { ExpertFilter expertFilter = new ExpertFilter(filterId, modificationDate, EquipmentType.GENERATOR, andCombination); insertFilter(filterId, expertFilter); checkExpertFilter(filterId, expertFilter); - checkExpertFilterExportAndMetadata(filterId, "[{\"id\":\"GEN\",\"type\":\"GENERATOR\"}]", EquipmentType.GENERATOR); + checkExpertFilterExportAndMetadata(filterId, """ + [{"id":"GEN","type":"GENERATOR"}] + """, EquipmentType.GENERATOR); } @Test @@ -1533,6 +1535,8 @@ public void testExpertLoadFilter() throws Exception { ExpertFilter expertFilter = new ExpertFilter(filterId, modificationDate, EquipmentType.LOAD, gen1); insertFilter(filterId, expertFilter); checkExpertFilter(filterId, expertFilter); - checkExpertFilterExportAndMetadata(filterId, "[{\"id\":\"LOAD\",\"type\":\"LOAD\"}]", EquipmentType.LOAD); + checkExpertFilterExportAndMetadata(filterId, """ + [{"id":"LOAD","type":"LOAD"}] + """, EquipmentType.LOAD); } } From 3388550614cb77c3991cfb12b97763c86d83f2e6 Mon Sep 17 00:00:00 2001 From: BOUHOURS Antoine Date: Thu, 2 Nov 2023 13:47:48 +0100 Subject: [PATCH 10/12] Code improvement Signed-off-by: BOUHOURS Antoine --- .../server/entities/ExpertFilterEntity.java | 4 ++-- .../server/entities/ExpertRuleEntity.java | 4 ++-- ...02Z.xml => changelog_20231102T124442Z.xml} | 20 +++++++++---------- .../db/changelog/db.changelog-master.yaml | 2 +- 4 files changed, 15 insertions(+), 15 deletions(-) rename src/main/resources/db/changelog/changesets/{changelog_20231017T161802Z.xml => changelog_20231102T124442Z.xml} (56%) diff --git a/src/main/java/org/gridsuite/filter/server/entities/ExpertFilterEntity.java b/src/main/java/org/gridsuite/filter/server/entities/ExpertFilterEntity.java index bf6a9010..d595e08e 100644 --- a/src/main/java/org/gridsuite/filter/server/entities/ExpertFilterEntity.java +++ b/src/main/java/org/gridsuite/filter/server/entities/ExpertFilterEntity.java @@ -30,10 +30,10 @@ public class ExpertFilterEntity extends AbstractFilterEntity { private EquipmentType equipmentType; @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true) - @JoinColumn(name = "expertFilterEntity_rules_id", + @JoinColumn(name = "rules_id", referencedColumnName = "id", foreignKey = @ForeignKey( - name = "expertFilterEntity_rules_fk" + name = "expertRule_rules_fk" )) private ExpertRuleEntity rules; } diff --git a/src/main/java/org/gridsuite/filter/server/entities/ExpertRuleEntity.java b/src/main/java/org/gridsuite/filter/server/entities/ExpertRuleEntity.java index 2d65d8a9..8d270ad3 100644 --- a/src/main/java/org/gridsuite/filter/server/entities/ExpertRuleEntity.java +++ b/src/main/java/org/gridsuite/filter/server/entities/ExpertRuleEntity.java @@ -57,9 +57,9 @@ public class ExpertRuleEntity { private List rules; @ManyToOne - @JoinColumn(name = "expertRuleEntity_parentRule_id", referencedColumnName = "id", + @JoinColumn(name = "parentRule_id", referencedColumnName = "id", foreignKey = @ForeignKey( - name = "expertRuleEntity_parentRule_fk" + name = "expertRule_parentRule_fk" ), nullable = true) private ExpertRuleEntity parentRule; } diff --git a/src/main/resources/db/changelog/changesets/changelog_20231017T161802Z.xml b/src/main/resources/db/changelog/changesets/changelog_20231102T124442Z.xml similarity index 56% rename from src/main/resources/db/changelog/changesets/changelog_20231017T161802Z.xml rename to src/main/resources/db/changelog/changesets/changelog_20231102T124442Z.xml index 73e7f02c..e1895844 100644 --- a/src/main/resources/db/changelog/changesets/changelog_20231017T161802Z.xml +++ b/src/main/resources/db/changelog/changesets/changelog_20231102T124442Z.xml @@ -1,16 +1,16 @@ - + - + - + @@ -20,16 +20,16 @@ - + - - + + - - + + - - + + diff --git a/src/main/resources/db/changelog/db.changelog-master.yaml b/src/main/resources/db/changelog/db.changelog-master.yaml index 72a0c10a..b063f6dd 100644 --- a/src/main/resources/db/changelog/db.changelog-master.yaml +++ b/src/main/resources/db/changelog/db.changelog-master.yaml @@ -32,5 +32,5 @@ databaseChangeLog: file: changesets/changelog_20230810T095237Z.xml relativeToChangelogFile: true - include: - file: changesets/changelog_20231017T161802Z.xml + file: changesets/changelog_20231102T124442Z.xml relativeToChangelogFile: true \ No newline at end of file From 1108a782066501b30b27f0a3fea814ccddd7b645 Mon Sep 17 00:00:00 2001 From: Thang PHAM Date: Thu, 2 Nov 2023 15:27:33 +0100 Subject: [PATCH 11/12] Add expert filter feature (visitor version) --- .../filter/server/FilterService.java | 3 +-- .../dto/expertrule/AbstractExpertRule.java | 3 ++- .../dto/expertrule/BooleanExpertRule.java | 6 ++++- .../dto/expertrule/CombinatorExpertRule.java | 27 +++++++++++++++++-- .../server/dto/expertrule/EnumExpertRule.java | 6 ++++- .../dto/expertrule/NumberExpertRule.java | 6 ++++- .../dto/expertrule/StringExpertRule.java | 6 ++++- .../server/utils/ExpertFilterUtils.java | 4 +-- 8 files changed, 50 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/gridsuite/filter/server/FilterService.java b/src/main/java/org/gridsuite/filter/server/FilterService.java index c743efe9..e301705f 100644 --- a/src/main/java/org/gridsuite/filter/server/FilterService.java +++ b/src/main/java/org/gridsuite/filter/server/FilterService.java @@ -13,7 +13,6 @@ import org.gridsuite.filter.server.entities.AbstractFilterEntity; import org.gridsuite.filter.server.repositories.*; import org.gridsuite.filter.server.utils.EquipmentType; -import org.gridsuite.filter.server.utils.ExpertFilterUtils; import org.gridsuite.filter.server.utils.FilterType; import org.gridsuite.filter.server.utils.FiltersUtils; import org.springframework.context.annotation.ComponentScan; @@ -302,7 +301,7 @@ private > Stream> getInjectionList(Stream equipmentIds.contains(injection.getId())); } else if (filter instanceof ExpertFilter expertFilter) { var rule = expertFilter.getRules(); - return stream.filter(injection -> ExpertFilterUtils.evaluateExpertFilter(rule, injection)); + return stream.filter(injection -> rule.evaluateRule(injection)); } else { return Stream.empty(); } diff --git a/src/main/java/org/gridsuite/filter/server/dto/expertrule/AbstractExpertRule.java b/src/main/java/org/gridsuite/filter/server/dto/expertrule/AbstractExpertRule.java index bcdb5b25..d1d4144e 100644 --- a/src/main/java/org/gridsuite/filter/server/dto/expertrule/AbstractExpertRule.java +++ b/src/main/java/org/gridsuite/filter/server/dto/expertrule/AbstractExpertRule.java @@ -7,6 +7,7 @@ package org.gridsuite.filter.server.dto.expertrule; import com.fasterxml.jackson.annotation.*; +import com.powsybl.iidm.network.Injection; import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Getter; @@ -49,7 +50,7 @@ public abstract class AbstractExpertRule { @Schema(description = "Rules") private List rules; - public abstract boolean evaluateRule(String identifiableValue); + public abstract boolean evaluateRule(Injection injection); public abstract DataType getDataType(); diff --git a/src/main/java/org/gridsuite/filter/server/dto/expertrule/BooleanExpertRule.java b/src/main/java/org/gridsuite/filter/server/dto/expertrule/BooleanExpertRule.java index 236336d8..b83cd52b 100644 --- a/src/main/java/org/gridsuite/filter/server/dto/expertrule/BooleanExpertRule.java +++ b/src/main/java/org/gridsuite/filter/server/dto/expertrule/BooleanExpertRule.java @@ -8,6 +8,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.powsybl.commons.PowsyblException; +import com.powsybl.iidm.network.Injection; import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Getter; @@ -15,6 +16,8 @@ import lombok.experimental.SuperBuilder; import org.gridsuite.filter.server.utils.DataType; +import static org.gridsuite.filter.server.utils.ExpertFilterUtils.getFieldValue; + /** * @author Antoine Bouhours */ @@ -43,7 +46,8 @@ public DataType getDataType() { } @Override - public boolean evaluateRule(String identifiableValue) { + public boolean evaluateRule(Injection injection) { + String identifiableValue = getFieldValue(this.getField(), injection); boolean equipmentValue = getBooleanValue(identifiableValue); boolean filterValue = isValue(); return switch (this.getOperator()) { diff --git a/src/main/java/org/gridsuite/filter/server/dto/expertrule/CombinatorExpertRule.java b/src/main/java/org/gridsuite/filter/server/dto/expertrule/CombinatorExpertRule.java index d5ba5a5c..0c001ae6 100644 --- a/src/main/java/org/gridsuite/filter/server/dto/expertrule/CombinatorExpertRule.java +++ b/src/main/java/org/gridsuite/filter/server/dto/expertrule/CombinatorExpertRule.java @@ -7,9 +7,12 @@ package org.gridsuite.filter.server.dto.expertrule; import com.fasterxml.jackson.annotation.JsonProperty; +import com.powsybl.commons.PowsyblException; +import com.powsybl.iidm.network.Injection; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.experimental.SuperBuilder; +import org.gridsuite.filter.server.utils.CombinatorType; import org.gridsuite.filter.server.utils.DataType; /** @@ -27,8 +30,28 @@ public DataType getDataType() { } @Override - public boolean evaluateRule(String identifiableValue) { - return false; + public boolean evaluateRule(Injection injection) { + if (CombinatorType.OR == this.getCombinator()) { + for (AbstractExpertRule rule : this.getRules()) { + // Recursively evaluate the rule + if (rule.evaluateRule(injection)) { + // If any rule is true, the whole combination is true + return true; + } + } + return false; + } else if (CombinatorType.AND == (this.getCombinator())) { + for (AbstractExpertRule rule : this.getRules()) { + // Recursively evaluate the rule + if (!rule.evaluateRule(injection)) { + // If any rule is false, the whole combination is false + return false; + } + } + return true; + } else { + throw new PowsyblException("Not support combinator: " + this.getCombinator()) ; + } } @Override diff --git a/src/main/java/org/gridsuite/filter/server/dto/expertrule/EnumExpertRule.java b/src/main/java/org/gridsuite/filter/server/dto/expertrule/EnumExpertRule.java index 29b22634..ae7f8f3d 100644 --- a/src/main/java/org/gridsuite/filter/server/dto/expertrule/EnumExpertRule.java +++ b/src/main/java/org/gridsuite/filter/server/dto/expertrule/EnumExpertRule.java @@ -8,6 +8,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.powsybl.commons.PowsyblException; +import com.powsybl.iidm.network.Injection; import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Getter; @@ -15,6 +16,8 @@ import lombok.experimental.SuperBuilder; import org.gridsuite.filter.server.utils.DataType; +import static org.gridsuite.filter.server.utils.ExpertFilterUtils.getFieldValue; + /** * @author Antoine Bouhours */ @@ -39,7 +42,8 @@ public DataType getDataType() { } @Override - public boolean evaluateRule(String identifiableValue) { + public boolean evaluateRule(Injection injection) { + String identifiableValue = getFieldValue(this.getField(), injection); return switch (this.getOperator()) { case EQUALS -> identifiableValue.equals(this.getValue()); case NOT_EQUALS -> !identifiableValue.equals(this.getValue()); diff --git a/src/main/java/org/gridsuite/filter/server/dto/expertrule/NumberExpertRule.java b/src/main/java/org/gridsuite/filter/server/dto/expertrule/NumberExpertRule.java index 2a796cd0..f5529ed5 100644 --- a/src/main/java/org/gridsuite/filter/server/dto/expertrule/NumberExpertRule.java +++ b/src/main/java/org/gridsuite/filter/server/dto/expertrule/NumberExpertRule.java @@ -8,6 +8,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.powsybl.commons.PowsyblException; +import com.powsybl.iidm.network.Injection; import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Getter; @@ -15,6 +16,8 @@ import lombok.experimental.SuperBuilder; import org.gridsuite.filter.server.utils.DataType; +import static org.gridsuite.filter.server.utils.ExpertFilterUtils.getFieldValue; + /** * @author Antoine Bouhours */ @@ -38,7 +41,8 @@ public DataType getDataType() { } @Override - public boolean evaluateRule(String identifiableValue) { + public boolean evaluateRule(Injection injection) { + String identifiableValue = getFieldValue(this.getField(), injection); Double equipmentValue = getNumberValue(identifiableValue); Double filterValue = getValue(); return switch (this.getOperator()) { diff --git a/src/main/java/org/gridsuite/filter/server/dto/expertrule/StringExpertRule.java b/src/main/java/org/gridsuite/filter/server/dto/expertrule/StringExpertRule.java index 99fde7f3..07222c94 100644 --- a/src/main/java/org/gridsuite/filter/server/dto/expertrule/StringExpertRule.java +++ b/src/main/java/org/gridsuite/filter/server/dto/expertrule/StringExpertRule.java @@ -8,6 +8,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.powsybl.commons.PowsyblException; +import com.powsybl.iidm.network.Injection; import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Getter; @@ -16,6 +17,8 @@ import org.apache.commons.lang3.StringUtils; import org.gridsuite.filter.server.utils.DataType; +import static org.gridsuite.filter.server.utils.ExpertFilterUtils.getFieldValue; + /** * @author Antoine Bouhours */ @@ -35,7 +38,8 @@ public DataType getDataType() { } @Override - public boolean evaluateRule(String identifiableValue) { + public boolean evaluateRule(Injection injection) { + String identifiableValue = getFieldValue(this.getField(), injection); return switch (this.getOperator()) { case IS -> identifiableValue.equalsIgnoreCase(this.getValue()); case CONTAINS -> StringUtils.containsIgnoreCase(identifiableValue, this.getValue()); diff --git a/src/main/java/org/gridsuite/filter/server/utils/ExpertFilterUtils.java b/src/main/java/org/gridsuite/filter/server/utils/ExpertFilterUtils.java index 3f1b2978..43f3c06e 100644 --- a/src/main/java/org/gridsuite/filter/server/utils/ExpertFilterUtils.java +++ b/src/main/java/org/gridsuite/filter/server/utils/ExpertFilterUtils.java @@ -26,7 +26,7 @@ public static > boolean evaluateExpertFilter(AbstractExpe return evaluateOrCombination(filter, injection); } else { // Evaluate individual filters - return filter.evaluateRule(getFieldValue(filter.getField(), injection)); + return filter.evaluateRule(injection); } } @@ -52,7 +52,7 @@ private static > boolean evaluateAndCombination(AbstractE return true; } - private static > String getFieldValue(FieldType field, Injection injection) { + public static > String getFieldValue(FieldType field, Injection injection) { return switch (injection.getType()) { case GENERATOR -> getGeneratorFieldValue(field, (Generator) injection); case LOAD -> getLoadFieldValue(field, (Load) injection); From 34f82a3d41d2015f59fec28281a8e4b0f54550f2 Mon Sep 17 00:00:00 2001 From: Thang PHAM Date: Thu, 2 Nov 2023 15:59:41 +0100 Subject: [PATCH 12/12] style check --- .../filter/server/dto/expertrule/CombinatorExpertRule.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/gridsuite/filter/server/dto/expertrule/CombinatorExpertRule.java b/src/main/java/org/gridsuite/filter/server/dto/expertrule/CombinatorExpertRule.java index 0c001ae6..f9613ec0 100644 --- a/src/main/java/org/gridsuite/filter/server/dto/expertrule/CombinatorExpertRule.java +++ b/src/main/java/org/gridsuite/filter/server/dto/expertrule/CombinatorExpertRule.java @@ -50,7 +50,7 @@ public boolean evaluateRule(Injection injection) { } return true; } else { - throw new PowsyblException("Not support combinator: " + this.getCombinator()) ; + throw new PowsyblException("Not support combinator: " + this.getCombinator()); } }