From e9ddcefd4acf5bc6f315c9a362a50dcdd5b014ac Mon Sep 17 00:00:00 2001 From: Stefan Schake Date: Thu, 29 Nov 2018 10:18:51 +0100 Subject: [PATCH] Include attribute values in target search text filter (#736) * Add specification for like attribute value v2: ensure returned targets are unique v3: remove redundant or Signed-off-by: Stefan Schake * Use like attribute value specification with target search text Target attributes are the primary way to relay back metadata, particularly in a plug&play application. They should therefore be included in the target search. Attribute keys are excluded since they are expected to be consistent between targets. v3: remove whitespace changes Signed-off-by: Stefan Schake * Add test for target search includes attribute values v2: test targets returned are unique Signed-off-by: Stefan Schake --- .../repository/jpa/JpaTargetManagement.java | 2 +- .../specifications/TargetSpecifications.java | 32 +++++++++++++++++++ .../jpa/TargetManagementSearchTest.java | 26 +++++++++++++++ 3 files changed, 59 insertions(+), 1 deletion(-) diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetManagement.java index 55374324b..4144258df 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetManagement.java @@ -465,7 +465,7 @@ public class JpaTargetManagement implements TargetManagement { .hasInstalledOrAssignedDistributionSet(filterParams.getFilterByDistributionId())); } if (!StringUtils.isEmpty(filterParams.getFilterBySearchText())) { - specList.add(TargetSpecifications.likeIdOrNameOrDescription(filterParams.getFilterBySearchText())); + specList.add(TargetSpecifications.likeIdOrNameOrDescriptionOrAttributeValue(filterParams.getFilterBySearchText())); } if (isHasTagsFilterActive(filterParams)) { specList.add(TargetSpecifications.hasTags(filterParams.getFilterByTagNames(), diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/TargetSpecifications.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/TargetSpecifications.java index 518e324db..52fd2b798 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/TargetSpecifications.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/TargetSpecifications.java @@ -18,6 +18,7 @@ import javax.persistence.criteria.Path; import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; import javax.persistence.criteria.SetJoin; +import javax.persistence.criteria.MapJoin; import javax.validation.constraints.NotNull; import org.eclipse.hawkbit.repository.jpa.model.JpaAction; @@ -37,6 +38,7 @@ import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.TargetTag; import org.eclipse.hawkbit.repository.model.TargetUpdateStatus; import org.springframework.data.jpa.domain.Specification; +import org.springframework.data.jpa.domain.Specifications; /** * Specifications class for {@link Target}s. The class provides Spring Data JPQL @@ -148,6 +150,36 @@ public final class TargetSpecifications { }; } + /** + * {@link Specification} for retrieving {@link Target}s by "like attribute + * value". + * + * @param searchText + * to be filtered on + * @return the {@link Target} {@link Specification} + */ + public static Specification likeAttributeValue(final String searchText) { + return (targetRoot, query, cb) -> { + final String searchTextToLower = searchText.toLowerCase(); + final MapJoin attributeMap = targetRoot.join(JpaTarget_.controllerAttributes, + JoinType.LEFT); + query.distinct(true); + return cb.like(cb.lower(attributeMap.value()), searchTextToLower); + }; + } + + /** + * {@link Specification} for retrieving {@link Target}s by "like + * controllerId or like name or like description or like attribute value". + * + * @param searchText + * to be filtered on + * @return the {@link Target} {@link Specification} + */ + public static Specification likeIdOrNameOrDescriptionOrAttributeValue(final String searchText) { + return Specifications.where(likeIdOrNameOrDescription(searchText)).or(likeAttributeValue(searchText)); + } + /** * {@link Specification} for retrieving {@link Target}s by "like * controllerId". diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetManagementSearchTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetManagementSearchTest.java index 1dc8765b8..df2c30242 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetManagementSearchTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetManagementSearchTest.java @@ -15,10 +15,13 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; import org.eclipse.hawkbit.repository.FilterParams; +import org.eclipse.hawkbit.repository.UpdateMode; import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action.Status; import org.eclipse.hawkbit.repository.model.DistributionSet; @@ -95,6 +98,19 @@ public class TargetManagementSearchTest extends AbstractJpaIntegrationTest { final String installedC = targCs.iterator().next().getControllerId(); final Long actionId = assignDistributionSet(installedSet.getId(), assignedC).getActions().get(0); + // add attributes to match against only attribute value or attribute + // value and name + final Map attributes = new HashMap<>(); + attributes.put("key", "targ-C-attribute-value"); + final Target targAttribute = controllerManagement.updateControllerAttributes(targCs.get(0).getControllerId(), + attributes, UpdateMode.REPLACE); + // prepare one target with an attribute value equal to controller id + Target targAttributeId = targCs.get(15); + final Map idAttributes = new HashMap<>(); + idAttributes.put("key", targAttributeId.getControllerId()); + targAttributeId = controllerManagement.updateControllerAttributes(targAttributeId.getControllerId(), + idAttributes, UpdateMode.REPLACE); + // set one installed DS also controllerManagement.addUpdateActionStatus( entityFactory.actionStatus().create(actionId).status(Status.FINISHED).message("message")); @@ -114,6 +130,8 @@ public class TargetManagementSearchTest extends AbstractJpaIntegrationTest { // try to find several targets with different filter settings verifyThat1TargetHasNameAndId("targ-A-special", targSpecialName.getControllerId()); + verifyThat1TargetHasAttributeValue("%c-attribute%", targAttribute.getControllerId()); + verifyThat1TargetHasAttributeValue("%" + targAttributeId.getControllerId() + "%", targAttributeId.getControllerId()); verifyThatRepositoryContains400Targets(); verifyThat200TargetsHaveTagD(targTagW, concat(targBs, targCs)); verifyThat100TargetsContainsGivenTextAndHaveTagAssigned(targTagY, targTagW, targBs); @@ -492,6 +510,14 @@ public class TargetManagementSearchTest extends AbstractJpaIntegrationTest { targetManagement.countByFilters(null, null, controllerId, null, Boolean.FALSE))); } + @Step + private void verifyThat1TargetHasAttributeValue(final String value, final String controllerId) { + assertThat(targetManagement.findByFilters(PAGE, new FilterParams(null, null, value, null, Boolean.FALSE)) + .getContent()).as("has number of elements").hasSize(1).as("that number is also returned by count query") + .hasSize(Ints.saturatedCast( + targetManagement.countByFilters(null, null, value, null, Boolean.FALSE))); + } + @Step private void verifyThat100TargetsContainsGivenTextAndHaveTagAssigned(final TargetTag targTagY, final TargetTag targTagW, final List expected) {