Remove org.eclipse.hawkbit.repository.jpa.specifications.SpecificationsBuilder and fix deprecated Specification.where (#2760)

Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
Avgustin Marinov
2025-10-16 16:01:29 +03:00
committed by GitHub
parent 8da851a75a
commit 4b3c3cc870
7 changed files with 25 additions and 193 deletions

View File

@@ -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 <T, J extends T> Optional<J> findOneBySpec(
final JpaSpecificationExecutor<J> repository, final List<Specification<J>> specList) {
return repository.findOne(combineWithAnd(specList));
}
public static <T, J extends T> Page<T> findAllWithCountBySpec(
final JpaSpecificationExecutor<J> repository, final List<Specification<J>> 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 <J> Specification<J> combineWithAnd(final List<Specification<J>> specList) {
if (ObjectUtils.isEmpty(specList)) {
return Specification.where(null);
return Specification.unrestricted();
} else if (specList.size() == 1) {
return specList.get(0);
} else {
Specification<J> specs = specList.get(0);
for (final Specification<J> specification : specList.subList(1, specList.size())) {
specs = specs.and(specification);
}
return specs;
}
return specList.size() == 1 ? specList.get(0) : SpecificationsBuilder.combineWithAnd(specList);
}
public static <T, J extends T> Slice<T> findAllWithoutCountBySpec(
@@ -76,7 +76,7 @@ public final class JpaManagementHelper {
public static <J> long countBySpec(final JpaSpecificationExecutor<J> repository, final List<Specification<J>> 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 };
}
}

View File

@@ -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<JpaTarget> combinedSpecification = Objects
.requireNonNull(SpecificationsBuilder.combineWithAnd(specList));
.requireNonNull(JpaManagementHelper.combineWithAnd(specList));
return jpaRepository.exists(AccessController.Operation.UPDATE, combinedSpecification);
}

View File

@@ -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

View File

@@ -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<JpaTargetType> {
default List<JpaTargetType> findByDsType(@Param("id") final Long dsTypeId) {
return findAll(Specification.where(TargetTypeSpecification.hasDsSetType(dsTypeId)));
return findAll(TargetTypeSpecification.hasDsSetType(dsTypeId));
}
@Modifying

View File

@@ -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 <null> if the given specification list is empty
*/
public static <T> Specification<T> combineWithAnd(final List<Specification<T>> specList) {
if (specList.isEmpty()) {
return null;
}
Specification<T> specs = specList.get(0);
for (final Specification<T> specification : specList.subList(1, specList.size())) {
specs = specs.and(specification);
}
return specs;
}
}

View File

@@ -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<? extends DistributionSet> 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);
}
/**

View File

@@ -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<br/>
* Story: Specifications builder
*/
class SpecificationsBuilderTest {
/**
* Test the combination of specs on an empty list which returns null
*/
@Test
void combineWithAndEmptyList() {
final List<Specification<Object>> 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<Object> spec = (root, query, cb) -> cb.equal(root.get("field1"), "testValue");
final List<Specification<Object>> specList = Collections.singletonList(spec);
final Specification<Object> 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<Object[]> query = mock(CriteriaQuery.class);
final Root<Object> 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<Object> spec1 = (root, query, cb) -> cb.equal(root.get("field1"), "testValue1");
final Specification<Object> spec2 = (root, query, cb) -> cb.equal(root.get("field2"), "testValue2");
final List<Specification<Object>> specList = new ArrayList<>(2);
specList.add(spec1);
specList.add(spec2);
final Specification<Object> 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<Object[]> query = mock(CriteriaQuery.class);
final Root<Object> 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);
}
}