From 4b3c3cc870483fd7c948ff490f0ef1d2661cf9e6 Mon Sep 17 00:00:00 2001 From: Avgustin Marinov Date: Thu, 16 Oct 2025 16:01:29 +0300 Subject: [PATCH] Remove org.eclipse.hawkbit.repository.jpa.specifications.SpecificationsBuilder and fix deprecated Specification.where (#2760) Signed-off-by: Avgustin Marinov --- .../repository/jpa/JpaManagementHelper.java | 34 ++---- .../jpa/management/JpaTargetManagement.java | 3 +- .../OfflineDsAssignmentStrategy.java | 12 +- .../jpa/repository/TargetTypeRepository.java | 3 +- .../specifications/SpecificationsBuilder.java | 41 ------- .../management/DeploymentManagementTest.java | 14 +-- .../SpecificationsBuilderTest.java | 111 ------------------ 7 files changed, 25 insertions(+), 193 deletions(-) delete mode 100644 hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/SpecificationsBuilder.java delete mode 100644 hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/specifications/SpecificationsBuilderTest.java diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaManagementHelper.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaManagementHelper.java index ebd044b30..8922f88aa 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaManagementHelper.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaManagementHelper.java @@ -11,7 +11,6 @@ package org.eclipse.hawkbit.repository.jpa; import java.util.Collections; import java.util.List; -import java.util.Optional; import jakarta.persistence.EntityManager; @@ -19,7 +18,6 @@ import lombok.AccessLevel; import lombok.NoArgsConstructor; import org.eclipse.hawkbit.repository.jpa.model.AbstractJpaBaseEntity; import org.eclipse.hawkbit.repository.jpa.repository.NoCountSliceRepository; -import org.eclipse.hawkbit.repository.jpa.specifications.SpecificationsBuilder; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; @@ -36,15 +34,10 @@ import org.springframework.util.ObjectUtils; @NoArgsConstructor(access = AccessLevel.PRIVATE) public final class JpaManagementHelper { - public static Optional findOneBySpec( - final JpaSpecificationExecutor repository, final List> specList) { - return repository.findOne(combineWithAnd(specList)); - } - public static Page findAllWithCountBySpec( final JpaSpecificationExecutor repository, final List> specList, final Pageable pageable) { if (CollectionUtils.isEmpty(specList)) { - return convertPage(repository.findAll(Specification.where(null), pageable), pageable); + return convertPage(repository.findAll(Specification.unrestricted(), pageable), pageable); } return convertPage(repository.findAll(combineWithAnd(specList), pageable), pageable); @@ -56,9 +49,16 @@ public final class JpaManagementHelper { public static Specification combineWithAnd(final List> specList) { if (ObjectUtils.isEmpty(specList)) { - return Specification.where(null); + return Specification.unrestricted(); + } else if (specList.size() == 1) { + return specList.get(0); + } else { + Specification specs = specList.get(0); + for (final Specification specification : specList.subList(1, specList.size())) { + specs = specs.and(specification); + } + return specs; } - return specList.size() == 1 ? specList.get(0) : SpecificationsBuilder.combineWithAnd(specList); } public static Slice findAllWithoutCountBySpec( @@ -76,7 +76,7 @@ public final class JpaManagementHelper { public static long countBySpec(final JpaSpecificationExecutor repository, final List> specList) { if (CollectionUtils.isEmpty(specList)) { - return repository.count(Specification.where(null)); + return repository.count(Specification.unrestricted()); } return repository.count(combineWithAnd(specList)); @@ -92,16 +92,4 @@ public final class JpaManagementHelper { return repository.save(result); } - - // the format of filter string is 'name:version'. 'name' and 'version' - // fields follow the starts_with semantic, that changes to equal for 'name' - // field when the semicolon is present - public static String[] getFilterNameAndVersionEntries(final String filterString) { - final int semicolonIndex = filterString.indexOf(':'); - - final String filterName = semicolonIndex != -1 ? filterString.substring(0, semicolonIndex) : (filterString + "%"); - final String filterVersion = semicolonIndex != -1 ? (filterString.substring(semicolonIndex + 1) + "%") : "%"; - - return new String[] { ObjectUtils.isEmpty(filterName) ? "%" : filterName, filterVersion }; - } } \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaTargetManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaTargetManagement.java index 2ccac70e7..9ca4d0a38 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaTargetManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaTargetManagement.java @@ -51,7 +51,6 @@ import org.eclipse.hawkbit.repository.jpa.repository.TargetRepository; import org.eclipse.hawkbit.repository.jpa.repository.TargetTagRepository; import org.eclipse.hawkbit.repository.jpa.repository.TargetTypeRepository; import org.eclipse.hawkbit.repository.jpa.ql.QLSupport; -import org.eclipse.hawkbit.repository.jpa.specifications.SpecificationsBuilder; import org.eclipse.hawkbit.repository.jpa.specifications.TargetSpecifications; import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper; import org.eclipse.hawkbit.repository.model.DistributionSet; @@ -121,7 +120,7 @@ public class JpaTargetManagement TargetSpecifications.hasControllerId(controllerId)); final Specification combinedSpecification = Objects - .requireNonNull(SpecificationsBuilder.combineWithAnd(specList)); + .requireNonNull(JpaManagementHelper.combineWithAnd(specList)); return jpaRepository.exists(AccessController.Operation.UPDATE, combinedSpecification); } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/OfflineDsAssignmentStrategy.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/OfflineDsAssignmentStrategy.java index 30f560ead..be031ae6d 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/OfflineDsAssignmentStrategy.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/OfflineDsAssignmentStrategy.java @@ -9,7 +9,6 @@ */ package org.eclipse.hawkbit.repository.jpa.management; -import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Set; @@ -21,6 +20,7 @@ import org.eclipse.hawkbit.repository.QuotaManagement; import org.eclipse.hawkbit.repository.RepositoryConstants; import org.eclipse.hawkbit.repository.RepositoryProperties; import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException; +import org.eclipse.hawkbit.repository.jpa.JpaManagementHelper; import org.eclipse.hawkbit.repository.jpa.acm.AccessController; import org.eclipse.hawkbit.repository.jpa.configuration.Constants; import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor; @@ -31,7 +31,6 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaTarget; import org.eclipse.hawkbit.repository.jpa.repository.ActionRepository; import org.eclipse.hawkbit.repository.jpa.repository.ActionStatusRepository; import org.eclipse.hawkbit.repository.jpa.repository.TargetRepository; -import org.eclipse.hawkbit.repository.jpa.specifications.SpecificationsBuilder; import org.eclipse.hawkbit.repository.jpa.specifications.TargetSpecifications; import org.eclipse.hawkbit.repository.model.Action.Status; import org.eclipse.hawkbit.repository.model.DistributionSet; @@ -79,12 +78,11 @@ class OfflineDsAssignmentStrategy extends AbstractDsAssignmentStrategy { if (isMultiAssignmentsEnabled()) { mapper = ids -> targetRepository.findAll(TargetSpecifications.hasControllerIdIn(ids)); } else { - mapper = ids -> targetRepository.findAll(SpecificationsBuilder.combineWithAnd( - Arrays.asList(TargetSpecifications.hasControllerIdAndAssignedDistributionSetIdNot(ids, setId), - TargetSpecifications.notEqualToTargetUpdateStatus(TargetUpdateStatus.PENDING)))); + mapper = ids -> targetRepository.findAll(JpaManagementHelper.combineWithAnd(List.of( + TargetSpecifications.hasControllerIdAndAssignedDistributionSetIdNot(ids, setId), + TargetSpecifications.notEqualToTargetUpdateStatus(TargetUpdateStatus.PENDING)))); } - return ListUtils.partition(controllerIDs, Constants.MAX_ENTRIES_IN_STATEMENT).stream().map(mapper) - .flatMap(List::stream).toList(); + return ListUtils.partition(controllerIDs, Constants.MAX_ENTRIES_IN_STATEMENT).stream().map(mapper).flatMap(List::stream).toList(); } @Override diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/TargetTypeRepository.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/TargetTypeRepository.java index 503f27a53..8fc6225cc 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/TargetTypeRepository.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/TargetTypeRepository.java @@ -13,7 +13,6 @@ import java.util.List; import org.eclipse.hawkbit.repository.jpa.model.JpaTargetType; import org.eclipse.hawkbit.repository.jpa.specifications.TargetTypeSpecification; -import org.springframework.data.jpa.domain.Specification; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.PagingAndSortingRepository; @@ -27,7 +26,7 @@ import org.springframework.transaction.annotation.Transactional; public interface TargetTypeRepository extends BaseEntityRepository { default List findByDsType(@Param("id") final Long dsTypeId) { - return findAll(Specification.where(TargetTypeSpecification.hasDsSetType(dsTypeId))); + return findAll(TargetTypeSpecification.hasDsSetType(dsTypeId)); } @Modifying diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/SpecificationsBuilder.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/SpecificationsBuilder.java deleted file mode 100644 index 41200b028..000000000 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/SpecificationsBuilder.java +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Copyright (c) 2015 Bosch Software Innovations GmbH and others - * - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.eclipse.hawkbit.repository.jpa.specifications; - -import java.util.List; - -import lombok.AccessLevel; -import lombok.NoArgsConstructor; -import org.springframework.data.jpa.domain.Specification; - -/** - * Helper class to easily combine {@link Specification} instances. - */ -@NoArgsConstructor(access = AccessLevel.PRIVATE) -public final class SpecificationsBuilder { - - /** - * Combine all given specification with and. The first specification is the - * where clause. - * - * @param specList all specification which will combine - * @return if the given specification list is empty - */ - public static Specification combineWithAnd(final List> specList) { - if (specList.isEmpty()) { - return null; - } - Specification specs = specList.get(0); - for (final Specification specification : specList.subList(1, specList.size())) { - specs = specs.and(specification); - } - return specs; - } -} \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/DeploymentManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/DeploymentManagementTest.java index 9dbc591cd..1530d91d0 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/DeploymentManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/DeploymentManagementTest.java @@ -56,6 +56,7 @@ import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetExcepti import org.eclipse.hawkbit.repository.exception.InvalidDistributionSetException; import org.eclipse.hawkbit.repository.exception.MultiAssignmentIsNotEnabledException; import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest; +import org.eclipse.hawkbit.repository.jpa.JpaManagementHelper; import org.eclipse.hawkbit.repository.jpa.model.JpaAction; import org.eclipse.hawkbit.repository.jpa.model.JpaActionStatus; import org.eclipse.hawkbit.repository.jpa.model.JpaAction_; @@ -65,7 +66,6 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaTarget; import org.eclipse.hawkbit.repository.jpa.model.JpaTarget_; import org.eclipse.hawkbit.repository.jpa.repository.TargetRepository; import org.eclipse.hawkbit.repository.jpa.specifications.DistributionSetSpecification; -import org.eclipse.hawkbit.repository.jpa.specifications.SpecificationsBuilder; import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action.ActionStatusCreate; import org.eclipse.hawkbit.repository.model.Action.ActionType; @@ -1354,9 +1354,9 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest { List allFoundDS = distributionSetManagement.findAll(PAGE).getContent(); assertThat(allFoundDS).as("no ds should be founded").isEmpty(); - assertThat(distributionSetRepository.findAll(SpecificationsBuilder.combineWithAnd(Arrays - .asList(DistributionSetSpecification.isDeleted(true), DistributionSetSpecification.isCompleted(true))), - PAGE).getContent()).as("wrong size of founded ds").hasSize(noOfDistributionSets); + assertThat(distributionSetRepository.findAll(JpaManagementHelper.combineWithAnd( + List.of(DistributionSetSpecification.isDeleted(true), DistributionSetSpecification.isCompleted(true))), PAGE).getContent()) + .as("wrong size of founded ds").hasSize(noOfDistributionSets); IntStream.range(0, deploymentResult.getDistributionSets().size()).forEach(i -> testdataFactory.sendUpdateActionStatusToTargets( deploymentResult.getDeployedTargets(), Status.FINISHED, Collections.singletonList("blabla alles gut"))); @@ -1367,9 +1367,9 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest { // successfully and no activeAction is referring to created distribution sets allFoundDS = distributionSetManagement.findAll(pageRequest).getContent(); assertThat(allFoundDS).as("no ds should be founded").isEmpty(); - assertThat(distributionSetRepository.findAll(SpecificationsBuilder.combineWithAnd(Arrays - .asList(DistributionSetSpecification.isDeleted(true), DistributionSetSpecification.isCompleted(true))), - PAGE).getContent()).as("wrong size of founded ds").hasSize(noOfDistributionSets); + assertThat(distributionSetRepository.findAll(JpaManagementHelper.combineWithAnd( + List.of(DistributionSetSpecification.isDeleted(true), DistributionSetSpecification.isCompleted(true))), PAGE).getContent()) + .as("wrong size of founded ds").hasSize(noOfDistributionSets); } /** diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/specifications/SpecificationsBuilderTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/specifications/SpecificationsBuilderTest.java deleted file mode 100644 index 91cefdd82..000000000 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/specifications/SpecificationsBuilderTest.java +++ /dev/null @@ -1,111 +0,0 @@ -/** - * Copyright (c) 2015 Bosch Software Innovations GmbH and others - * - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.eclipse.hawkbit.repository.jpa.specifications; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import jakarta.persistence.criteria.CriteriaBuilder; -import jakarta.persistence.criteria.CriteriaQuery; -import jakarta.persistence.criteria.Expression; -import jakarta.persistence.criteria.Path; -import jakarta.persistence.criteria.Predicate; -import jakarta.persistence.criteria.Root; - -import org.junit.jupiter.api.Test; -import org.springframework.data.jpa.domain.Specification; - -/** - * Feature: Unit Tests - Repository
- * Story: Specifications builder - */ -class SpecificationsBuilderTest { - - /** - * Test the combination of specs on an empty list which returns null - */ - @Test - void combineWithAndEmptyList() { - final List> specList = Collections.emptyList(); - assertThat(SpecificationsBuilder.combineWithAnd(specList)).isNull(); - } - - /** - * Test the combination of specs on an immutable list with one entry - */ - @Test - void combineWithAndSingleImmutableList() { - final Specification spec = (root, query, cb) -> cb.equal(root.get("field1"), "testValue"); - final List> specList = Collections.singletonList(spec); - final Specification specifications = SpecificationsBuilder.combineWithAnd(specList); - assertThat(specifications).as("Specifications").isNotNull(); - - // mocks to call toPredicate on specifications - final CriteriaBuilder criteriaBuilder = mock(CriteriaBuilder.class); - final Path field1 = mock(Path.class); - final Predicate equalPredicate = mock(Predicate.class); - final CriteriaQuery query = mock(CriteriaQuery.class); - final Root root = mock(Root.class); - - when(criteriaBuilder.equal(any(Expression.class), anyString())).thenReturn(equalPredicate); - when(root.get("field1")).thenReturn(field1); - - final Predicate predicate = specifications.toPredicate(root, query, criteriaBuilder); - - assertThat(predicate).isEqualTo(equalPredicate); - - } - - /** - * Test the combination of specs on a list with multiple entries - */ - @Test - void combineWithAndList() { - final Specification spec1 = (root, query, cb) -> cb.equal(root.get("field1"), "testValue1"); - final Specification spec2 = (root, query, cb) -> cb.equal(root.get("field2"), "testValue2"); - - final List> specList = new ArrayList<>(2); - specList.add(spec1); - specList.add(spec2); - - final Specification specifications = SpecificationsBuilder.combineWithAnd(specList); - assertThat(specifications).as("Specifications").isNotNull(); - - // mocks to call toPredicate on specifications - final CriteriaBuilder criteriaBuilder = mock(CriteriaBuilder.class); - final Path field1 = mock(Path.class); - final Path field2 = mock(Path.class); - final Predicate equalPredicate1 = mock(Predicate.class); - final Predicate equalPredicate2 = mock(Predicate.class); - final Predicate combinedPredicate = mock(Predicate.class); - final CriteriaQuery query = mock(CriteriaQuery.class); - final Root root = mock(Root.class); - - when(criteriaBuilder.equal(any(Path.class), eq("testValue1"))).thenReturn(equalPredicate1); - when(criteriaBuilder.equal(any(Path.class), eq("testValue2"))).thenReturn(equalPredicate2); - when(criteriaBuilder.and(equalPredicate1, equalPredicate2)).thenReturn(combinedPredicate); - when(root.get("field1")).thenReturn(field1); - when(root.get("field2")).thenReturn(field2); - - final Predicate predicate = specifications.toPredicate(root, query, criteriaBuilder); - - assertThat(predicate).as("Combined predicate").isEqualTo(combinedPredicate); - - } - -} \ No newline at end of file