Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add JpaSpecificationEntityGraphExecutor #2120

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.eclipse.hawkbit.repository.jpa.builder.JpaActionStatusCreate;
import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
import org.eclipse.hawkbit.repository.jpa.model.JpaActionStatus;
import org.eclipse.hawkbit.repository.jpa.model.JpaAction_;
import org.eclipse.hawkbit.repository.jpa.repository.ActionRepository;
import org.eclipse.hawkbit.repository.jpa.repository.ActionStatusRepository;
import org.eclipse.hawkbit.repository.jpa.specifications.ActionSpecifications;
Expand All @@ -32,7 +33,7 @@
import org.eclipse.hawkbit.repository.model.ActionStatus;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;

/**
* Implements utility methods for managing {@link Action}s
Expand Down Expand Up @@ -103,17 +104,20 @@ protected void assertActionStatusMessageQuota(final JpaActionStatus actionStatus
}

protected List<Action> findActiveActionsWithHighestWeightConsideringDefault(final String controllerId, final int maxActionCount) {
final Pageable pageable = PageRequest.of(0, maxActionCount);
return Stream.concat(
// get the highest actions with weight
actionRepository
.findFetchDsByTarget_ControllerIdAndActiveIsTrueAndWeightNotNullOrderByWeightDescIdAsc(controllerId, pageable)
.stream(),
actionRepository.findAll(
ActionSpecifications.byTargetControllerIdAndActiveAndWeightIsNull(controllerId, false),
JpaAction_.GRAPH_ACTION_DS,
PageRequest.of(0, maxActionCount, Sort.by(Sort.Order.desc(JpaAction_.WEIGHT), Sort.Order.asc(JpaAction_.ID)))).stream(),
// get the oldest actions without weight
actionRepository.findFetchDsByTarget_ControllerIdAndActiveIsTrueAndWeightIsNullOrderByIdAsc(controllerId, pageable)
.stream())
actionRepository.findAll(
ActionSpecifications.byTargetControllerIdAndActiveAndWeightIsNull(controllerId, true),
JpaAction_.GRAPH_ACTION_DS,
PageRequest.of(0, maxActionCount, Sort.by(Sort.Order.asc(JpaAction_.ID)))).stream())
.sorted(Comparator.comparingInt(this::getWeightConsideringDefault).reversed().thenComparing(Action::getId))
.limit(maxActionCount)
.map(Action.class::cast)
.toList();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -287,12 +287,15 @@ public Action addUpdateActionStatus(final ActionStatusCreate statusCreate) {
public Optional<Action> findActiveActionWithHighestWeight(final String controllerId) {
return Stream.concat(
// get the highest action with weight
actionRepository
.findByTarget_ControllerIdAndActiveIsTrueAndWeightNotNullOrderByWeightDescIdAsc(controllerId, PAGEABLE_1)
.stream(),
actionRepository.findAll(
ActionSpecifications.byTargetControllerIdAndActiveAndWeightIsNull(controllerId, false),
PageRequest.of(0, 1, Sort.by(Sort.Order.desc(JpaAction_.WEIGHT), Sort.Order.asc(JpaAction_.ID)))).stream(),
// get the oldest action without weight
actionRepository.findByTarget_ControllerIdAndActiveIsTrueAndWeightIsNullOrderByIdAsc(controllerId, PAGEABLE_1).stream())
.min(Comparator.comparingInt(this::getWeightConsideringDefault).reversed().thenComparing(Action::getId));
actionRepository.findAll(
ActionSpecifications.byTargetControllerIdAndActiveAndWeightIsNull(controllerId, false),
PageRequest.of(0, 1, Sort.by(Sort.Order.asc(JpaAction_.ID)))).stream())
.min(Comparator.comparingInt(this::getWeightConsideringDefault).reversed().thenComparing(Action::getId))
.map(Action.class::cast);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,6 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction> {
Optional<Action> findFirstByTargetIdAndDistributionSetIdAndStatusOrderByIdDesc(
@Param("target") long targetId, @Param("ds") Long dsId, @Param("status") Action.Status status);

List<Action> findByTarget_ControllerIdAndActiveIsTrueAndWeightIsNullOrderByIdAsc(String controllerId, Pageable pageable);
List<Action> findByTarget_ControllerIdAndActiveIsTrueAndWeightNotNullOrderByWeightDescIdAsc(String controllerId, Pageable pageable);

@EntityGraph(value = "Action.ds", type = EntityGraphType.LOAD)
List<Action> findFetchDsByTarget_ControllerIdAndActiveIsTrueAndWeightIsNullOrderByIdAsc(String controllerId, Pageable pageable);
@EntityGraph(value = "Action.ds", type = EntityGraphType.LOAD)
List<Action> findFetchDsByTarget_ControllerIdAndActiveIsTrueAndWeightNotNullOrderByWeightDescIdAsc(String controllerId, Pageable pageable);

/**
* Switches the status of actions from one specific status into another, only if the actions are in a specific status. This should be
* an atomic operation.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,20 @@
package org.eclipse.hawkbit.repository.jpa.repository;

import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import jakarta.persistence.EntityGraph;
import jakarta.persistence.EntityManager;

import org.eclipse.hawkbit.repository.jpa.acm.AccessController;
import org.eclipse.hawkbit.repository.jpa.model.AbstractJpaBaseEntity_;
import org.eclipse.hawkbit.repository.jpa.model.AbstractJpaTenantAwareBaseEntity;
import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.repository.CrudRepository;
Expand All @@ -36,8 +40,8 @@
@NoRepositoryBean
@Transactional(readOnly = true)
public interface BaseEntityRepository<T extends AbstractJpaTenantAwareBaseEntity>
extends PagingAndSortingRepository<T, Long>, CrudRepository<T, Long>, JpaSpecificationExecutor<T>, NoCountSliceRepository<T>,
ACMRepository<T> {
extends PagingAndSortingRepository<T, Long>, CrudRepository<T, Long>, JpaSpecificationExecutor<T>,
JpaSpecificationEntityGraphExecutor<T>, NoCountSliceRepository<T>, ACMRepository<T> {

/**
* Overrides {@link org.springframework.data.repository.CrudRepository#saveAll(Iterable)} to return a list of created entities instead
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.util.Set;
import java.util.function.Function;

import jakarta.persistence.EntityManager;
import jakarta.transaction.Transactional;

import lombok.extern.slf4j.Slf4j;
Expand Down Expand Up @@ -287,6 +288,30 @@ public Class<T> getDomainClass() {
return repository.getDomainClass();
}

@Override
public Optional<T> findOne(final Specification<T> spec, final String entityGraph) {
return repository.findOne(
accessController.appendAccessRules(AccessController.Operation.READ, spec), entityGraph);
}

@Override
public List<T> findAll(final Specification<T> spec, final String entityGraph) {
return repository.findAll(
accessController.appendAccessRules(AccessController.Operation.READ, spec), entityGraph);
}

@Override
public Page<T> findAll(final Specification<T> spec, final String entityGraph, final Pageable pageable) {
return repository.findAll(
accessController.appendAccessRules(AccessController.Operation.READ, spec), entityGraph, pageable);
}

@Override
public List<T> findAll(final Specification<T> spec, final String entityGraph, final Sort sort) {
return repository.findAll(
accessController.appendAccessRules(AccessController.Operation.READ, spec), entityGraph, sort);
}

@SuppressWarnings("unchecked")
static <T extends AbstractJpaTenantAwareBaseEntity, R extends BaseEntityRepository<T>> R of(
final R repository, @NonNull final AccessController<T> accessController) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,15 @@
package org.eclipse.hawkbit.repository.jpa.repository;

import java.io.Serializable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import jakarta.persistence.EntityGraph;
import jakarta.persistence.EntityManager;
import jakarta.persistence.NoResultException;
import jakarta.persistence.Query;
import jakarta.persistence.TypedQuery;
import jakarta.transaction.Transactional;

Expand All @@ -23,11 +28,13 @@
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.util.ObjectUtils;

/**
* Repository implementation that allows findAll with disabled count query.
Expand All @@ -36,14 +43,18 @@
* @param <ID> the type of the id of the entity the repository manages
*/
public class HawkbitBaseRepository<T, ID extends Serializable> extends SimpleJpaRepository<T, ID>
implements NoCountSliceRepository<T>, ACMRepository<T> {
implements JpaSpecificationEntityGraphExecutor<T>, NoCountSliceRepository<T>, ACMRepository<T> {

public HawkbitBaseRepository(final Class<T> domainClass, final EntityManager em) {
super(domainClass, em);
private final EntityManager entityManager;

public HawkbitBaseRepository(final Class<T> domainClass, final EntityManager entityManager) {
super(domainClass, entityManager);
this.entityManager = entityManager;
}

public HawkbitBaseRepository(final JpaEntityInformation<T, ?> entityInformation, final EntityManager entityManager) {
super(entityInformation, entityManager);
this.entityManager = entityManager;
}

@Override
Expand Down Expand Up @@ -93,6 +104,32 @@ public long count(@Nullable final AccessController.Operation operation, @Nullabl
return count(spec);
}

@Override
public Optional<T> findOne(final Specification<T> spec, final String entityGraph) {
try {
return Optional.of(withEntityGraph(getQuery(spec, Sort.unsorted()), entityGraph).setMaxResults(2).getSingleResult());
} catch (NoResultException e) {
return Optional.empty();
}
}

@Override
public List<T> findAll(final Specification<T> spec, final String entityGraph) {
return withEntityGraph(getQuery(spec, Sort.unsorted()), entityGraph).getResultList();
}

@Override
public Page<T> findAll(final Specification<T> spec, final String entityGraph, final Pageable pageable) {
final TypedQuery<T> query = withEntityGraph(getQuery(spec, pageable), entityGraph);
return pageable.isUnpaged() ? new PageImpl<>(query.getResultList())
: readPage(query, getDomainClass(), pageable, spec);
}

@Override
public List<T> findAll(final Specification<T> spec, final String entityGraph, final Sort sort) {
return withEntityGraph(getQuery(spec, sort), entityGraph).getResultList();
}

@NonNull
@Override
public Slice<T> findAllWithoutCount(@Nullable final AccessController.Operation operation, @Nullable Specification<T> spec,
Expand All @@ -111,6 +148,11 @@ public String toString() {
return getClass().getSimpleName() + '<' + getDomainClass().getSimpleName() + '>';
}

private TypedQuery<T> withEntityGraph(final TypedQuery<T> query, final String entityGraph) {
final EntityGraph<?> graph = ObjectUtils.isEmpty(entityGraph) ? null : entityManager.createEntityGraph(entityGraph);
return graph == null ? query : query.setHint("javax.persistence.loadgraph", graph);
}

private <S extends T> Page<S> readPageWithoutCount(final TypedQuery<S> query, final Pageable pageable) {
query.setFirstResult((int) pageable.getOffset());
query.setMaxResults(pageable.getPageSize());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/**
* Copyright (c) 2024 Contributors to the Eclipse Foundation
*
* 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.repository;

import java.util.List;
import java.util.Optional;
import java.util.function.Function;

import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Root;

import org.eclipse.hawkbit.repository.model.BaseEntity;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.repository.query.FluentQuery;
import org.springframework.lang.Nullable;

/**
* Repository interface that offers JpaSpecificationExecutor#findOne/All methods with entity graph loading
*
* @param <T> entity type
*/
public interface JpaSpecificationEntityGraphExecutor<T> {

/**
* Returns a single entity matching the given {@link Specification} with the entity graph hint or {@link Optional#empty()} if none found.
* @see org.springframework.data.jpa.repository.JpaSpecificationExecutor#findOne(Specification)
*
* @param spec must not be {@literal null}.
* @param entityGraph the entity graph hint to use
* @return never {@literal null}.
* @throws org.springframework.dao.IncorrectResultSizeDataAccessException if more than one entity found.
*/
Optional<T> findOne(Specification<T> spec, String entityGraph);

/**
* Returns all entities matching the given {@link Specification} with the entity graph hint.
* <p>
* @see org.springframework.data.jpa.repository.JpaSpecificationExecutor#findAll(Specification)
*
* @param spec can be {@literal null}.
* @param entityGraph the entity graph hint to use
* @return never {@literal null}.
*/
List<T> findAll(@Nullable Specification<T> spec, String entityGraph);

/**
* Returns a {@link Page} of entities matching the given {@link Specification} with the entity graph hint.
* <p>
* @see org.springframework.data.jpa.repository.JpaSpecificationExecutor#findAll(Specification, Pageable)
*
* @param spec can be {@literal null}.
* @param entityGraph the entity graph hint to use
* @param pageable must not be {@literal null}.
* @return never {@literal null}.
*/
Page<T> findAll(@Nullable Specification<T> spec, String entityGraph, Pageable pageable);

/**
* Returns all entities matching the given {@link Specification} and {@link Sort} with the entity graph hint.
* <p>
* @see org.springframework.data.jpa.repository.JpaSpecificationExecutor#findAll(Specification, Sort)
*
* @param spec can be {@literal null}.
* @param entityGraph the entity graph hint to use
* @param sort must not be {@literal null}.
* @return never {@literal null}.
*/
List<T> findAll(@Nullable Specification<T> spec, String entityGraph, Sort sort);
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,16 +78,17 @@ public static Specification<JpaAction> byTargetIdsAndActiveAndStatusAndDSNotRequ

/**
* Returns active actions by target controller that has null or non-null depending on <code>isNull</code> value.
* Fetches action's distribution set.
*
* @param controllerId controller id
* @param isNull if <code>true</code> return with <code>null</code> weight, otherwise with non-<code>null</code>
* @return the matching action s.
*/
public static Specification<JpaAction> byTargetControllerIdAndActive(final String controllerId) {
public static Specification<JpaAction> byTargetControllerIdAndActiveAndWeightIsNull(final String controllerId, final boolean isNull) {
return (root, query, cb) ->
cb.and(
cb.equal(root.get(JpaAction_.target).get(JpaTarget_.controllerId), controllerId),
cb.equal(root.get(JpaAction_.active), true));
cb.equal(root.get(JpaAction_.target).get(JpaTarget_.controllerId), controllerId),
cb.equal(root.get(JpaAction_.active), true),
isNull ? cb.isNull(root.get(JpaAction_.weight)) : cb.isNotNull(root.get(JpaAction_.weight)));
}

public static Specification<JpaAction> byDistributionSetId(final Long distributionSetId) {
Expand Down
Loading