Feature - TargetType compatibility check (#1180)
* added targettype compatibility check in deployment Signed-off-by: Robert Sing <robert.sing@bosch-si.com> * added targettype compatibility for autssignment Signed-off-by: Robert Sing <robert.sing@bosch-si.com> * added / fixed tests for auto assignment compatibility check Signed-off-by: Robert Sing <robert.sing@bosch-si.com> * adapted rollout creation to use JPA specifications for compatibility checks Signed-off-by: Robert Sing <robert.sing@bosch-si.com> * fix unit tests and javadoc Signed-off-by: Robert Sing <robert.sing@bosch-si.com> * fix copyright header Signed-off-by: Robert Sing <robert.sing@bosch-si.com> * fixed review findings Signed-off-by: Robert Sing <robert.sing@bosch-si.com> * fixed review findings Signed-off-by: Robert Sing <robert.sing@bosch-si.com> * replaced validated-DS management calls Signed-off-by: Robert Sing <robert.sing@bosch-si.com> * improved compatibility check in DeploymentManagementTest Signed-off-by: Robert Sing <robert.sing@bosch-si.com> * fixed review findings Signed-off-by: Robert Sing <robert.sing@bosch-si.com>
This commit is contained in:
@@ -42,6 +42,7 @@ import org.eclipse.hawkbit.repository.RepositoryProperties;
|
||||
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
|
||||
import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent;
|
||||
import org.eclipse.hawkbit.repository.exception.CancelActionNotAllowedException;
|
||||
import org.eclipse.hawkbit.repository.exception.DistributionSetTypeNotInTargetTypeException;
|
||||
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
||||
import org.eclipse.hawkbit.repository.exception.ForceQuitActionNotAllowedException;
|
||||
import org.eclipse.hawkbit.repository.exception.MultiAssignmentIsNotEnabledException;
|
||||
@@ -71,6 +72,7 @@ import org.eclipse.hawkbit.repository.model.DistributionSetInvalidation.Cancelat
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSetType;
|
||||
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
|
||||
import org.eclipse.hawkbit.repository.model.Target;
|
||||
import org.eclipse.hawkbit.repository.model.TargetType;
|
||||
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
|
||||
import org.eclipse.hawkbit.repository.model.TargetWithActionType;
|
||||
import org.eclipse.hawkbit.repository.model.helper.EventPublisherHolder;
|
||||
@@ -229,10 +231,60 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
|
||||
deploymentRequests = deploymentRequests.stream().distinct().collect(Collectors.toList());
|
||||
checkIfRequiresMultiAssignment(deploymentRequests);
|
||||
}
|
||||
checkForTargetTypeCompatibility(deploymentRequests);
|
||||
checkQuotaForAssignment(deploymentRequests);
|
||||
return deploymentRequests;
|
||||
}
|
||||
|
||||
private void checkForTargetTypeCompatibility(final List<DeploymentRequest> deploymentRequests) {
|
||||
final List<String> controllerIds = deploymentRequests.stream().map(DeploymentRequest::getControllerId)
|
||||
.distinct().collect(Collectors.toList());
|
||||
final List<Long> distSetIds = deploymentRequests.stream().map(DeploymentRequest::getDistributionSetId)
|
||||
.distinct().collect(Collectors.toList());
|
||||
|
||||
if (controllerIds.size() > 1 && distSetIds.size() > 1) {
|
||||
throw new IllegalStateException(
|
||||
"Assigning multiple Targets to multiple Distribution Sets simultaneously is not allowed!");
|
||||
}
|
||||
|
||||
if (distSetIds.size() == 1) {
|
||||
checkCompatibilityForSingleDsAssignment(distSetIds.iterator().next(), controllerIds);
|
||||
} else {
|
||||
checkCompatibilityForMultiDsAssignment(controllerIds.iterator().next(), distSetIds);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkCompatibilityForSingleDsAssignment(final Long distSetId, final List<String> controllerIds) {
|
||||
final DistributionSetType distSetType = distributionSetManagement.getValidAndComplete(distSetId).getType();
|
||||
final Set<Long> incompatibleTargetTypes = Lists.partition(controllerIds, Constants.MAX_ENTRIES_IN_STATEMENT)
|
||||
.stream()
|
||||
.map(ids -> targetRepository.findAll(TargetSpecifications.hasControllerIdIn(ids)
|
||||
.and(TargetSpecifications.notCompatibleWithDistributionSetType(distSetType.getId()))))
|
||||
.flatMap(List::stream).map(Target::getTargetType).map(TargetType::getId).collect(Collectors.toSet());
|
||||
|
||||
if (!incompatibleTargetTypes.isEmpty()) {
|
||||
throw new DistributionSetTypeNotInTargetTypeException(distSetType.getId(), incompatibleTargetTypes);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkCompatibilityForMultiDsAssignment(final String controllerId, final List<Long> distSetIds) {
|
||||
final Target target = targetRepository.findOne(TargetSpecifications.hasControllerId(controllerId))
|
||||
.orElseThrow(() -> new EntityNotFoundException(Target.class, controllerId));
|
||||
|
||||
if (target.getTargetType() != null) {
|
||||
// we assume that list of assigned DS is less than MAX_ENTRIES_IN_STATEMENT
|
||||
final Set<DistributionSetType> incompatibleDistSetTypes = distributionSetManagement.get(distSetIds).stream()
|
||||
.map(DistributionSet::getType).collect(Collectors.toSet());
|
||||
incompatibleDistSetTypes.removeAll(target.getTargetType().getCompatibleDistributionSetTypes());
|
||||
|
||||
if (!incompatibleDistSetTypes.isEmpty()) {
|
||||
final Set<Long> distSetTypeIds = incompatibleDistSetTypes.stream().map(DistributionSetType::getId)
|
||||
.collect(Collectors.toSet());
|
||||
throw new DistributionSetTypeNotInTargetTypeException(distSetTypeIds, target.getTargetType().getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Map<Long, List<TargetWithActionType>> convertRequest(
|
||||
final Collection<DeploymentRequest> deploymentRequests) {
|
||||
return deploymentRequests.stream().collect(Collectors.groupingBy(DeploymentRequest::getDistributionSetId,
|
||||
@@ -256,8 +308,8 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
|
||||
}
|
||||
|
||||
/**
|
||||
* method assigns the {@link DistributionSet} to all {@link Target}s by
|
||||
* their IDs with a specific {@link ActionType} and {@code forcetime}.
|
||||
* method assigns the {@link DistributionSet} to all {@link Target}s by their
|
||||
* IDs with a specific {@link ActionType} and {@code forcetime}.
|
||||
*
|
||||
*
|
||||
* In case the update was executed offline (i.e. not managed by hawkBit) the
|
||||
@@ -265,8 +317,8 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
|
||||
* A. it ignores targets completely that are in
|
||||
* {@link TargetUpdateStatus#PENDING}.<br/>
|
||||
* B. it created completed actions.<br/>
|
||||
* C. sets both installed and assigned DS on the target and switches the
|
||||
* status to {@link TargetUpdateStatus#IN_SYNC} <br/>
|
||||
* C. sets both installed and assigned DS on the target and switches the status
|
||||
* to {@link TargetUpdateStatus#IN_SYNC} <br/>
|
||||
* D. does not send a {@link TargetAssignDistributionSetEvent}.<br/>
|
||||
*
|
||||
* @param initiatedBy
|
||||
@@ -281,9 +333,9 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
|
||||
* the assignment strategy (online /offline)
|
||||
* @return the assignment result
|
||||
*
|
||||
* @throw IncompleteDistributionSetException if mandatory
|
||||
* {@link SoftwareModuleType} are not assigned as define by the
|
||||
* {@link DistributionSetType}.
|
||||
* @throws IncompleteDistributionSetException
|
||||
* if mandatory {@link SoftwareModuleType} are not assigned as
|
||||
* define by the {@link DistributionSetType}.
|
||||
*/
|
||||
private DistributionSetAssignmentResult assignDistributionSetToTargets(final String initiatedBy, final Long dsID,
|
||||
final Collection<TargetWithActionType> targetsWithActionType, final String actionMessage,
|
||||
@@ -351,9 +403,9 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
|
||||
}
|
||||
|
||||
/**
|
||||
* split tIDs length into max entries in-statement because many database
|
||||
* have constraint of max entries in in-statements e.g. Oracle with maximum
|
||||
* 1000 elements, so we need to split the entries here and execute multiple
|
||||
* split tIDs length into max entries in-statement because many database have
|
||||
* constraint of max entries in in-statements e.g. Oracle with maximum 1000
|
||||
* elements, so we need to split the entries here and execute multiple
|
||||
* statements
|
||||
*/
|
||||
private static List<List<Long>> getTargetEntitiesAsChunks(final List<JpaTarget> targetEntities) {
|
||||
@@ -813,10 +865,10 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
|
||||
return 0;
|
||||
}
|
||||
/*
|
||||
* We use a native query here because Spring JPA does not support to
|
||||
* specify a LIMIT clause on a DELETE statement. However, for this
|
||||
* specific use case (action cleanup), we must specify a row limit to
|
||||
* reduce the overall load on the database.
|
||||
* We use a native query here because Spring JPA does not support to specify a
|
||||
* LIMIT clause on a DELETE statement. However, for this specific use case
|
||||
* (action cleanup), we must specify a row limit to reduce the overall load on
|
||||
* the database.
|
||||
*/
|
||||
|
||||
final int statusCount = status.size();
|
||||
|
||||
@@ -530,7 +530,8 @@ public class JpaRolloutExecutor implements RolloutExecutor {
|
||||
|
||||
final long targetsInGroupFilter = DeploymentHelper.runInNewTransaction(txManager,
|
||||
"countAllTargetsByTargetFilterQueryAndNotInRolloutGroups",
|
||||
count -> targetManagement.countByRsqlAndNotInRolloutGroups(readyGroups, groupTargetFilter));
|
||||
count -> targetManagement.countByRsqlAndNotInRolloutGroupsAndCompatible(readyGroups, groupTargetFilter,
|
||||
rollout.getDistributionSet().getType()));
|
||||
final long expectedInGroup = Math
|
||||
.round((double) (group.getTargetPercentage() / 100) * (double) targetsInGroupFilter);
|
||||
final long currentlyInGroup = DeploymentHelper.runInNewTransaction(txManager,
|
||||
@@ -574,8 +575,8 @@ public class JpaRolloutExecutor implements RolloutExecutor {
|
||||
final PageRequest pageRequest = PageRequest.of(0, Math.toIntExact(limit));
|
||||
final List<Long> readyGroups = RolloutHelper.getGroupsByStatusIncludingGroup(rollout.getRolloutGroups(),
|
||||
RolloutGroupStatus.READY, group);
|
||||
final Page<Target> targets = targetManagement.findByTargetFilterQueryAndNotInRolloutGroups(pageRequest,
|
||||
readyGroups, targetFilter);
|
||||
final Page<Target> targets = targetManagement.findByTargetFilterQueryAndNotInRolloutGroupsAndCompatible(
|
||||
pageRequest, readyGroups, targetFilter, rollout.getDistributionSet().getType());
|
||||
|
||||
createAssignmentOfTargetsToGroup(targets, group);
|
||||
|
||||
@@ -584,8 +585,8 @@ public class JpaRolloutExecutor implements RolloutExecutor {
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules a group of the rollout. Scheduled Actions are created to
|
||||
* achieve this. The creation of those Actions is allowed to fail.
|
||||
* Schedules a group of the rollout. Scheduled Actions are created to achieve
|
||||
* this. The creation of those Actions is allowed to fail.
|
||||
*/
|
||||
private boolean scheduleRolloutGroup(final JpaRollout rollout, final JpaRolloutGroup group) {
|
||||
final long targetsInGroup = rolloutTargetGroupRepository.countByRolloutGroup(group);
|
||||
@@ -648,8 +649,8 @@ public class JpaRolloutExecutor implements RolloutExecutor {
|
||||
|
||||
/**
|
||||
* Creates an action entry into the action repository. In case of existing
|
||||
* scheduled actions the scheduled actions gets canceled. A scheduled action
|
||||
* is created in-active.
|
||||
* scheduled actions the scheduled actions gets canceled. A scheduled action is
|
||||
* created in-active.
|
||||
*/
|
||||
private void createScheduledAction(final Collection<Target> targets, final DistributionSet distributionSet,
|
||||
final ActionType actionType, final Long forcedTime, final Rollout rollout,
|
||||
|
||||
@@ -27,6 +27,7 @@ import javax.persistence.criteria.MapJoin;
|
||||
import javax.persistence.criteria.Predicate;
|
||||
import javax.persistence.criteria.Root;
|
||||
|
||||
import org.eclipse.hawkbit.repository.DistributionSetManagement;
|
||||
import org.eclipse.hawkbit.repository.FilterParams;
|
||||
import org.eclipse.hawkbit.repository.QuotaManagement;
|
||||
import org.eclipse.hawkbit.repository.TargetFields;
|
||||
@@ -57,6 +58,7 @@ 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;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSetType;
|
||||
import org.eclipse.hawkbit.repository.model.MetaData;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutGroup;
|
||||
import org.eclipse.hawkbit.repository.model.Target;
|
||||
@@ -84,8 +86,6 @@ import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
/**
|
||||
* JPA implementation of {@link TargetManagement}.
|
||||
*
|
||||
@@ -96,6 +96,8 @@ public class JpaTargetManagement implements TargetManagement {
|
||||
|
||||
private final EntityManager entityManager;
|
||||
|
||||
private final DistributionSetManagement distributionSetManagement;
|
||||
|
||||
private final QuotaManagement quotaManagement;
|
||||
|
||||
private final TargetRepository targetRepository;
|
||||
@@ -106,8 +108,6 @@ public class JpaTargetManagement implements TargetManagement {
|
||||
|
||||
private final RolloutGroupRepository rolloutGroupRepository;
|
||||
|
||||
private final DistributionSetRepository distributionSetRepository;
|
||||
|
||||
private final TargetFilterQueryRepository targetFilterQueryRepository;
|
||||
|
||||
private final TargetTagRepository targetTagRepository;
|
||||
@@ -122,22 +122,22 @@ public class JpaTargetManagement implements TargetManagement {
|
||||
|
||||
private final Database database;
|
||||
|
||||
public JpaTargetManagement(final EntityManager entityManager, final QuotaManagement quotaManagement,
|
||||
public JpaTargetManagement(final EntityManager entityManager,
|
||||
final DistributionSetManagement distributionSetManagement, final QuotaManagement quotaManagement,
|
||||
final TargetRepository targetRepository, final TargetTypeRepository targetTypeRepository,
|
||||
final TargetMetadataRepository targetMetadataRepository,
|
||||
final RolloutGroupRepository rolloutGroupRepository,
|
||||
final DistributionSetRepository distributionSetRepository,
|
||||
final TargetFilterQueryRepository targetFilterQueryRepository,
|
||||
final TargetTagRepository targetTagRepository, final EventPublisherHolder eventPublisherHolder,
|
||||
final TenantAware tenantAware, final AfterTransactionCommitExecutor afterCommit,
|
||||
final VirtualPropertyReplacer virtualPropertyReplacer, final Database database) {
|
||||
this.entityManager = entityManager;
|
||||
this.distributionSetManagement = distributionSetManagement;
|
||||
this.quotaManagement = quotaManagement;
|
||||
this.targetRepository = targetRepository;
|
||||
this.targetTypeRepository = targetTypeRepository;
|
||||
this.targetMetadataRepository = targetMetadataRepository;
|
||||
this.rolloutGroupRepository = rolloutGroupRepository;
|
||||
this.distributionSetRepository = distributionSetRepository;
|
||||
this.targetFilterQueryRepository = targetFilterQueryRepository;
|
||||
this.targetTagRepository = targetTagRepository;
|
||||
this.eventPublisherHolder = eventPublisherHolder;
|
||||
@@ -383,17 +383,16 @@ public class JpaTargetManagement implements TargetManagement {
|
||||
|
||||
@Override
|
||||
public Page<Target> findByAssignedDistributionSet(final Pageable pageReq, final long distributionSetID) {
|
||||
throwEntityNotFoundIfDsDoesNotExist(distributionSetID);
|
||||
final DistributionSet validDistSet = distributionSetManagement.getOrElseThrowException(distributionSetID);
|
||||
|
||||
return convertPage(
|
||||
targetRepository.findAll(TargetSpecifications.hasAssignedDistributionSet(distributionSetID), pageReq),
|
||||
pageReq);
|
||||
return convertPage(targetRepository
|
||||
.findAll(TargetSpecifications.hasAssignedDistributionSet(validDistSet.getId()), pageReq), pageReq);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<Target> findByAssignedDistributionSetAndRsql(final Pageable pageReq, final long distributionSetID,
|
||||
final String rsqlParam) {
|
||||
throwEntityNotFoundIfDsDoesNotExist(distributionSetID);
|
||||
final DistributionSet validDistSet = distributionSetManagement.getOrElseThrowException(distributionSetID);
|
||||
|
||||
final Specification<JpaTarget> spec = RSQLUtility.buildRsqlSpecification(rsqlParam, TargetFields.class,
|
||||
virtualPropertyReplacer, database);
|
||||
@@ -401,18 +400,12 @@ public class JpaTargetManagement implements TargetManagement {
|
||||
return convertPage(
|
||||
targetRepository
|
||||
.findAll((Specification<JpaTarget>) (root, query,
|
||||
cb) -> cb.and(TargetSpecifications.hasAssignedDistributionSet(distributionSetID)
|
||||
cb) -> cb.and(TargetSpecifications.hasAssignedDistributionSet(validDistSet.getId())
|
||||
.toPredicate(root, query, cb), spec.toPredicate(root, query, cb)),
|
||||
pageReq),
|
||||
pageReq);
|
||||
}
|
||||
|
||||
private void throwEntityNotFoundIfDsDoesNotExist(final Long distributionSetID) {
|
||||
if (!distributionSetRepository.existsById(distributionSetID)) {
|
||||
throw new EntityNotFoundException(DistributionSet.class, distributionSetID);
|
||||
}
|
||||
}
|
||||
|
||||
public static Page<Target> convertPage(final Page<JpaTarget> findAll, final Pageable pageable) {
|
||||
return new PageImpl<>(Collections.unmodifiableList(findAll.getContent()), pageable, findAll.getTotalElements());
|
||||
}
|
||||
@@ -423,16 +416,15 @@ public class JpaTargetManagement implements TargetManagement {
|
||||
|
||||
@Override
|
||||
public Page<Target> findByInstalledDistributionSet(final Pageable pageReq, final long distributionSetID) {
|
||||
throwEntityNotFoundIfDsDoesNotExist(distributionSetID);
|
||||
return convertPage(
|
||||
targetRepository.findAll(TargetSpecifications.hasInstalledDistributionSet(distributionSetID), pageReq),
|
||||
pageReq);
|
||||
final DistributionSet validDistSet = distributionSetManagement.getOrElseThrowException(distributionSetID);
|
||||
return convertPage(targetRepository
|
||||
.findAll(TargetSpecifications.hasInstalledDistributionSet(validDistSet.getId()), pageReq), pageReq);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<Target> findByInstalledDistributionSetAndRsql(final Pageable pageable, final long distributionSetId,
|
||||
final String rsqlParam) {
|
||||
throwEntityNotFoundIfDsDoesNotExist(distributionSetId);
|
||||
final DistributionSet validDistSet = distributionSetManagement.getOrElseThrowException(distributionSetId);
|
||||
|
||||
final Specification<JpaTarget> spec = RSQLUtility.buildRsqlSpecification(rsqlParam, TargetFields.class,
|
||||
virtualPropertyReplacer, database);
|
||||
@@ -440,7 +432,7 @@ public class JpaTargetManagement implements TargetManagement {
|
||||
return convertPage(
|
||||
targetRepository
|
||||
.findAll((Specification<JpaTarget>) (root, query,
|
||||
cb) -> cb.and(TargetSpecifications.hasInstalledDistributionSet(distributionSetId)
|
||||
cb) -> cb.and(TargetSpecifications.hasInstalledDistributionSet(validDistSet.getId())
|
||||
.toPredicate(root, query, cb), spec.toPredicate(root, query, cb)),
|
||||
pageable),
|
||||
pageable);
|
||||
@@ -476,10 +468,10 @@ public class JpaTargetManagement implements TargetManagement {
|
||||
specList.add(TargetSpecifications.isOverdue(TimestampCalculator.calculateOverdueTimestamp()));
|
||||
}
|
||||
if (filterParams.getFilterByDistributionId() != null) {
|
||||
throwEntityNotFoundIfDsDoesNotExist(filterParams.getFilterByDistributionId());
|
||||
final DistributionSet validDistSet = distributionSetManagement
|
||||
.getOrElseThrowException(filterParams.getFilterByDistributionId());
|
||||
|
||||
specList.add(TargetSpecifications
|
||||
.hasInstalledOrAssignedDistributionSet(filterParams.getFilterByDistributionId()));
|
||||
specList.add(TargetSpecifications.hasInstalledOrAssignedDistributionSet(validDistSet.getId()));
|
||||
}
|
||||
if (!StringUtils.isEmpty(filterParams.getFilterBySearchText())) {
|
||||
specList.add(TargetSpecifications
|
||||
@@ -669,51 +661,53 @@ public class JpaTargetManagement implements TargetManagement {
|
||||
|
||||
@Override
|
||||
public long countByAssignedDistributionSet(final long distId) {
|
||||
throwEntityNotFoundIfDsDoesNotExist(distId);
|
||||
final DistributionSet validDistSet = distributionSetManagement.getOrElseThrowException((distId));
|
||||
|
||||
return targetRepository.count(TargetSpecifications.hasAssignedDistributionSet(distId));
|
||||
return targetRepository.count(TargetSpecifications.hasAssignedDistributionSet(validDistSet.getId()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public long countByInstalledDistributionSet(final long distId) {
|
||||
throwEntityNotFoundIfDsDoesNotExist(distId);
|
||||
final DistributionSet validDistSet = distributionSetManagement.getOrElseThrowException((distId));
|
||||
|
||||
return targetRepository.count(TargetSpecifications.hasInstalledDistributionSet(distId));
|
||||
return targetRepository.count(TargetSpecifications.hasInstalledDistributionSet(validDistSet.getId()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean existsByInstalledOrAssignedDistributionSet(final long distId) {
|
||||
throwEntityNotFoundIfDsDoesNotExist(distId);
|
||||
final DistributionSet validDistSet = distributionSetManagement.getOrElseThrowException((distId));
|
||||
|
||||
return targetRepository.exists(TargetSpecifications.hasInstalledOrAssignedDistributionSet(distId));
|
||||
return targetRepository
|
||||
.exists(TargetSpecifications.hasInstalledOrAssignedDistributionSet(validDistSet.getId()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<Target> findByTargetFilterQueryAndNonDS(final Pageable pageRequest, final long distributionSetId,
|
||||
final String targetFilterQuery) {
|
||||
throwEntityNotFoundIfDsDoesNotExist(distributionSetId);
|
||||
public Page<Target> findByTargetFilterQueryAndNonDSAndCompatible(final Pageable pageRequest,
|
||||
final long distributionSetId, final String targetFilterQuery) {
|
||||
final DistributionSet jpaDistributionSet = distributionSetManagement.getOrElseThrowException(distributionSetId);
|
||||
final Long distSetTypeId = jpaDistributionSet.getType().getId();
|
||||
|
||||
final Specification<JpaTarget> spec = RSQLUtility.buildRsqlSpecification(targetFilterQuery, TargetFields.class,
|
||||
virtualPropertyReplacer, database);
|
||||
final Specification<JpaTarget> dsNotInActions = TargetSpecifications
|
||||
.hasNotDistributionSetInActions(distributionSetId);
|
||||
final Specification<JpaTarget> isCompatibleWithDsType = TargetSpecifications
|
||||
.isCompatibleWithDistributionSetType(distSetTypeId);
|
||||
|
||||
return findTargetsBySpec(
|
||||
(root, cq,
|
||||
cb) -> cb.and(spec.toPredicate(root, cq, cb), TargetSpecifications
|
||||
.hasNotDistributionSetInActions(distributionSetId).toPredicate(root, cq, cb)),
|
||||
pageRequest);
|
||||
|
||||
return findTargetsBySpec(spec.and(dsNotInActions).and(isCompatibleWithDsType), pageRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<Target> findByTargetFilterQueryAndNotInRolloutGroups(final Pageable pageRequest,
|
||||
final Collection<Long> groups, final String targetFilterQuery) {
|
||||
public Page<Target> findByTargetFilterQueryAndNotInRolloutGroupsAndCompatible(final Pageable pageRequest,
|
||||
final Collection<Long> groups, final String targetFilterQuery, final DistributionSetType dsType) {
|
||||
|
||||
final Specification<JpaTarget> spec = RSQLUtility.buildRsqlSpecification(targetFilterQuery, TargetFields.class,
|
||||
virtualPropertyReplacer, database);
|
||||
final Specification<JpaTarget> notInRolloutGroups = TargetSpecifications.isNotInRolloutGroups(groups);
|
||||
final Specification<JpaTarget> isCompatibleWithDsType = TargetSpecifications
|
||||
.isCompatibleWithDistributionSetType(dsType.getId());
|
||||
|
||||
return findTargetsBySpec((root, cq, cb) -> cb.and(spec.toPredicate(root, cq, cb),
|
||||
TargetSpecifications.isNotInRolloutGroups(groups).toPredicate(root, cq, cb)), pageRequest);
|
||||
|
||||
return findTargetsBySpec((spec.and(notInRolloutGroups).and(isCompatibleWithDsType)), pageRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -728,24 +722,27 @@ public class JpaTargetManagement implements TargetManagement {
|
||||
}
|
||||
|
||||
@Override
|
||||
public long countByRsqlAndNotInRolloutGroups(final Collection<Long> groups, final String targetFilterQuery) {
|
||||
public long countByRsqlAndNotInRolloutGroupsAndCompatible(final Collection<Long> groups,
|
||||
final String targetFilterQuery, final DistributionSetType dsType) {
|
||||
final Specification<JpaTarget> spec = RSQLUtility.buildRsqlSpecification(targetFilterQuery, TargetFields.class,
|
||||
virtualPropertyReplacer, database);
|
||||
final List<Specification<JpaTarget>> specList = Arrays.asList(spec,
|
||||
TargetSpecifications.isNotInRolloutGroups(groups));
|
||||
TargetSpecifications.isNotInRolloutGroups(groups),
|
||||
TargetSpecifications.isCompatibleWithDistributionSetType(dsType.getId()));
|
||||
|
||||
return countByCriteriaAPI(specList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long countByRsqlAndNonDS(final long distributionSetId, final String targetFilterQuery) {
|
||||
throwEntityNotFoundIfDsDoesNotExist(distributionSetId);
|
||||
public long countByRsqlAndNonDSAndCompatible(final long distributionSetId, final String targetFilterQuery) {
|
||||
final DistributionSet jpaDistributionSet = distributionSetManagement.getOrElseThrowException(distributionSetId);
|
||||
final Long distSetTypeId = jpaDistributionSet.getType().getId();
|
||||
|
||||
final Specification<JpaTarget> spec = RSQLUtility.buildRsqlSpecification(targetFilterQuery, TargetFields.class,
|
||||
virtualPropertyReplacer, database);
|
||||
final List<Specification<JpaTarget>> specList = Lists.newArrayListWithExpectedSize(2);
|
||||
specList.add(spec);
|
||||
specList.add(TargetSpecifications.hasNotDistributionSetInActions(distributionSetId));
|
||||
final List<Specification<JpaTarget>> specList = Arrays.asList(spec,
|
||||
TargetSpecifications.hasNotDistributionSetInActions(distributionSetId),
|
||||
TargetSpecifications.isCompatibleWithDistributionSetType(distSetTypeId));
|
||||
|
||||
return countByCriteriaAPI(specList);
|
||||
}
|
||||
|
||||
@@ -244,8 +244,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
|
||||
|
||||
/**
|
||||
* @param dsTypeManagement
|
||||
* for loading
|
||||
* {@link TargetType#getCompatibleDistributionSetTypes()}
|
||||
* for loading {@link TargetType#getCompatibleDistributionSetTypes()}
|
||||
* @return TargetTypeBuilder bean
|
||||
*/
|
||||
@Bean
|
||||
@@ -261,9 +260,8 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
|
||||
|
||||
/**
|
||||
* @param softwareManagement
|
||||
* for loading
|
||||
* {@link DistributionSetType#getMandatoryModuleTypes()} and
|
||||
* {@link DistributionSetType#getOptionalModuleTypes()}
|
||||
* for loading {@link DistributionSetType#getMandatoryModuleTypes()}
|
||||
* and {@link DistributionSetType#getOptionalModuleTypes()}
|
||||
* @return DistributionSetTypeBuilder bean
|
||||
*/
|
||||
@Bean
|
||||
@@ -305,8 +303,8 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
|
||||
|
||||
/**
|
||||
* @return the {@link SystemSecurityContext} singleton bean which make it
|
||||
* accessible in beans which cannot access the service directly,
|
||||
* e.g. JPA entities.
|
||||
* accessible in beans which cannot access the service directly, e.g.
|
||||
* JPA entities.
|
||||
*/
|
||||
@Bean
|
||||
SystemSecurityContextHolder systemSecurityContextHolder() {
|
||||
@@ -314,9 +312,9 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the {@link TenantConfigurationManagement} singleton bean which
|
||||
* make it accessible in beans which cannot access the service
|
||||
* directly, e.g. JPA entities.
|
||||
* @return the {@link TenantConfigurationManagement} singleton bean which make
|
||||
* it accessible in beans which cannot access the service directly, e.g.
|
||||
* JPA entities.
|
||||
*/
|
||||
@Bean
|
||||
TenantConfigurationManagementHolder tenantConfigurationManagementHolder() {
|
||||
@@ -325,9 +323,8 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
|
||||
|
||||
/**
|
||||
* @return the {@link SystemManagementHolder} singleton bean which holds the
|
||||
* current {@link SystemManagement} service and make it accessible
|
||||
* in beans which cannot access the service directly, e.g. JPA
|
||||
* entities.
|
||||
* current {@link SystemManagement} service and make it accessible in
|
||||
* beans which cannot access the service directly, e.g. JPA entities.
|
||||
*/
|
||||
@Bean
|
||||
SystemManagementHolder systemManagementHolder() {
|
||||
@@ -335,10 +332,9 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the {@link TenantAwareHolder} singleton bean which holds the
|
||||
* current {@link TenantAware} service and make it accessible in
|
||||
* beans which cannot access the service directly, e.g. JPA
|
||||
* entities.
|
||||
* @return the {@link TenantAwareHolder} singleton bean which holds the current
|
||||
* {@link TenantAware} service and make it accessible in beans which
|
||||
* cannot access the service directly, e.g. JPA entities.
|
||||
*/
|
||||
@Bean
|
||||
TenantAwareHolder tenantAwareHolder() {
|
||||
@@ -346,10 +342,9 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the {@link SecurityTokenGeneratorHolder} singleton bean which
|
||||
* holds the current {@link SecurityTokenGenerator} service and make
|
||||
* it accessible in beans which cannot access the service via
|
||||
* injection
|
||||
* @return the {@link SecurityTokenGeneratorHolder} singleton bean which holds
|
||||
* the current {@link SecurityTokenGenerator} service and make it
|
||||
* accessible in beans which cannot access the service via injection
|
||||
*/
|
||||
@Bean
|
||||
SecurityTokenGeneratorHolder securityTokenGeneratorHolder() {
|
||||
@@ -532,25 +527,24 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link JpaTenantConfigurationManagement} bean.
|
||||
* {@link JpaTargetManagement} bean.
|
||||
*
|
||||
* @return a new {@link TenantConfigurationManagement}
|
||||
* @return a new {@link JpaTargetManagement}
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
TargetManagement targetManagement(final EntityManager entityManager, final QuotaManagement quotaManagement,
|
||||
final TargetRepository targetRepository, final TargetMetadataRepository targetMetadataRepository,
|
||||
final RolloutGroupRepository rolloutGroupRepository,
|
||||
final DistributionSetRepository distributionSetRepository,
|
||||
final TargetFilterQueryRepository targetFilterQueryRepository,
|
||||
final TargetTypeRepository targetTypeRepository, final TargetTagRepository targetTagRepository,
|
||||
final EventPublisherHolder eventPublisherHolder, final TenantAware tenantAware,
|
||||
final AfterTransactionCommitExecutor afterCommit, final VirtualPropertyReplacer virtualPropertyReplacer,
|
||||
final JpaProperties properties) {
|
||||
return new JpaTargetManagement(entityManager, quotaManagement, targetRepository, targetTypeRepository,
|
||||
targetMetadataRepository, rolloutGroupRepository, distributionSetRepository,
|
||||
targetFilterQueryRepository, targetTagRepository, eventPublisherHolder, tenantAware, afterCommit,
|
||||
virtualPropertyReplacer, properties.getDatabase());
|
||||
final JpaProperties properties, final DistributionSetManagement distributionSetManagement) {
|
||||
return new JpaTargetManagement(entityManager, distributionSetManagement, quotaManagement, targetRepository,
|
||||
targetTypeRepository, targetMetadataRepository, rolloutGroupRepository, targetFilterQueryRepository,
|
||||
targetTagRepository, eventPublisherHolder, tenantAware, afterCommit, virtualPropertyReplacer,
|
||||
properties.getDatabase());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -84,14 +84,15 @@ public class AutoAssignChecker extends AbstractAutoAssignExecutor {
|
||||
do {
|
||||
|
||||
final List<String> controllerIds = targetManagement
|
||||
.findByTargetFilterQueryAndNonDS(PageRequest.of(0, Constants.MAX_ENTRIES_IN_STATEMENT),
|
||||
.findByTargetFilterQueryAndNonDSAndCompatible(
|
||||
PageRequest.of(0, Constants.MAX_ENTRIES_IN_STATEMENT),
|
||||
targetFilterQuery.getAutoAssignDistributionSet().getId(), targetFilterQuery.getQuery())
|
||||
.getContent().stream().map(Target::getControllerId).collect(Collectors.toList());
|
||||
count = runTransactionalAssignment(targetFilterQuery, controllerIds);
|
||||
|
||||
} while (count == Constants.MAX_ENTRIES_IN_STATEMENT);
|
||||
|
||||
} catch (PersistenceException | AbstractServerRtException e) {
|
||||
} catch (final PersistenceException | AbstractServerRtException e) {
|
||||
LOGGER.error("Error during auto assign check of target filter query " + targetFilterQuery.getId(), e);
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import javax.persistence.criteria.CriteriaBuilder;
|
||||
import javax.persistence.criteria.Join;
|
||||
import javax.persistence.criteria.JoinType;
|
||||
import javax.persistence.criteria.ListJoin;
|
||||
import javax.persistence.criteria.MapJoin;
|
||||
@@ -25,18 +26,24 @@ import javax.validation.constraints.NotNull;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaAction_;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetType;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetType_;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet_;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaRolloutGroup_;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetTag;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetTag_;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetType;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetType_;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget_;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.RolloutTargetGroup;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.RolloutTargetGroup_;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSetType;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutGroup;
|
||||
import org.eclipse.hawkbit.repository.model.Target;
|
||||
import org.eclipse.hawkbit.repository.model.TargetTag;
|
||||
import org.eclipse.hawkbit.repository.model.TargetType;
|
||||
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
|
||||
@@ -150,7 +157,7 @@ public final class TargetSpecifications {
|
||||
*
|
||||
* @param updateStatus
|
||||
* to be filtered on
|
||||
*
|
||||
*
|
||||
* @return the {@link Target} {@link Specification}
|
||||
*/
|
||||
public static Specification<JpaTarget> hasTargetUpdateStatus(final Collection<TargetUpdateStatus> updateStatus) {
|
||||
@@ -176,7 +183,7 @@ public final class TargetSpecifications {
|
||||
*
|
||||
* @param updateStatus
|
||||
* to be filtered on
|
||||
*
|
||||
*
|
||||
* @return the {@link Target} {@link Specification}
|
||||
*/
|
||||
public static Specification<JpaTarget> notEqualToTargetUpdateStatus(final TargetUpdateStatus updateStatus) {
|
||||
@@ -185,15 +192,14 @@ public final class TargetSpecifications {
|
||||
|
||||
/**
|
||||
* {@link Specification} for retrieving {@link Target}s that are overdue. A
|
||||
* target is overdue if it did not respond during the configured
|
||||
* intervals:<br>
|
||||
* target is overdue if it did not respond during the configured intervals:<br>
|
||||
* <em>poll_itvl + overdue_itvl</em>
|
||||
*
|
||||
* @param overdueTimestamp
|
||||
* the calculated timestamp to compare with the last respond of a
|
||||
* target (lastTargetQuery).<br>
|
||||
* The <code>overdueTimestamp</code> has to be calculated with
|
||||
* the following expression:<br>
|
||||
* The <code>overdueTimestamp</code> has to be calculated with the
|
||||
* following expression:<br>
|
||||
* <em>overdueTimestamp = nowTimestamp - poll_itvl -
|
||||
* overdue_itvl</em>
|
||||
*
|
||||
@@ -205,8 +211,8 @@ public final class TargetSpecifications {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Specification} for retrieving {@link Target}s by "like
|
||||
* controllerId or like name or like description".
|
||||
* {@link Specification} for retrieving {@link Target}s by "like controllerId or
|
||||
* like name or like description".
|
||||
*
|
||||
* @param searchText
|
||||
* to be filtered on
|
||||
@@ -240,8 +246,8 @@ public final class TargetSpecifications {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Specification} for retrieving {@link Target}s by "like
|
||||
* controllerId or like name or like description or like attribute value".
|
||||
* {@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
|
||||
@@ -252,8 +258,7 @@ public final class TargetSpecifications {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Specification} for retrieving {@link Target}s by "like
|
||||
* controllerId".
|
||||
* {@link Specification} for retrieving {@link Target}s by "like controllerId".
|
||||
*
|
||||
* @param distributionId
|
||||
* to be filtered on
|
||||
@@ -264,8 +269,8 @@ public final class TargetSpecifications {
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all targets by given {@link Target#getControllerId()}s and which
|
||||
* are not yet assigned to given {@link DistributionSet}.
|
||||
* Finds all targets by given {@link Target#getControllerId()}s and which are
|
||||
* not yet assigned to given {@link DistributionSet}.
|
||||
*
|
||||
* @param tIDs
|
||||
* to search for.
|
||||
@@ -298,8 +303,8 @@ public final class TargetSpecifications {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Specification} for retrieving {@link Target}s by "has no tag
|
||||
* names"or "has at least on of the given tag names".
|
||||
* {@link Specification} for retrieving {@link Target}s by "has no tag names"or
|
||||
* "has at least on of the given tag names".
|
||||
*
|
||||
* @param tagNames
|
||||
* to be filtered on
|
||||
@@ -341,8 +346,8 @@ public final class TargetSpecifications {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Specification} for retrieving {@link Target}s by assigned
|
||||
* distribution set.
|
||||
* {@link Specification} for retrieving {@link Target}s by assigned distribution
|
||||
* set.
|
||||
*
|
||||
* @param distributionSetId
|
||||
* the ID of the distribution set which must be assigned
|
||||
@@ -372,6 +377,57 @@ public final class TargetSpecifications {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Specification} for retrieving {@link Target}s that are compatible with
|
||||
* given {@link DistributionSetType}. Compatibility is evaluated by checking the
|
||||
* {@link TargetType} of a target. Targets that don't have a {@link TargetType}
|
||||
* are compatible with all {@link DistributionSetType}
|
||||
*
|
||||
* @param distributionSetTypeId
|
||||
* the ID of the distribution set type which must be compatible
|
||||
* @return the {@link Target} {@link Specification}
|
||||
*/
|
||||
public static Specification<JpaTarget> isCompatibleWithDistributionSetType(final Long distributionSetTypeId) {
|
||||
return (targetRoot, query, cb) -> {
|
||||
// Since the targetRoot is changed by joining we need to get the
|
||||
// isNull predicate first
|
||||
final Predicate targetTypeIsNull = targetRoot.get(JpaTarget_.targetType).isNull();
|
||||
|
||||
return cb.or(targetTypeIsNull, getDistSetTypeEqualPredicate(targetRoot, cb, distributionSetTypeId));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Specification} for retrieving {@link Target}s that are NOT compatible
|
||||
* with given {@link DistributionSetType}. Compatibility is evaluated by
|
||||
* checking the {@link TargetType} of a target. Targets that don't have a
|
||||
* {@link TargetType} are compatible with all {@link DistributionSetType}
|
||||
*
|
||||
* @param distributionSetTypeId
|
||||
* the ID of the distribution set type which must be incompatible
|
||||
* @return the {@link Target} {@link Specification}
|
||||
*/
|
||||
public static Specification<JpaTarget> notCompatibleWithDistributionSetType(final Long distributionSetTypeId) {
|
||||
return (targetRoot, query, cb) -> {
|
||||
// Since the targetRoot is changed by joining we need to get the
|
||||
// isNotNull predicate first
|
||||
final Predicate targetTypeNotNull = targetRoot.get(JpaTarget_.targetType).isNotNull();
|
||||
|
||||
return cb.and(targetTypeNotNull,
|
||||
cb.isNull(getDistSetTypeEqualPredicate(targetRoot, cb, distributionSetTypeId)));
|
||||
};
|
||||
}
|
||||
|
||||
private static Predicate getDistSetTypeEqualPredicate(final Root<JpaTarget> root, final CriteriaBuilder cb,
|
||||
final Long dsTypeId) {
|
||||
final Join<JpaTarget, JpaTargetType> targetTypeJoin = root.join(JpaTarget_.targetType, JoinType.LEFT);
|
||||
targetTypeJoin.fetch(JpaTargetType_.distributionSetTypes);
|
||||
final SetJoin<JpaTargetType, JpaDistributionSetType> dsTypeTargetTypeJoin = targetTypeJoin
|
||||
.join(JpaTargetType_.distributionSetTypes, JoinType.LEFT);
|
||||
|
||||
return cb.equal(dsTypeTargetTypeJoin.get(JpaDistributionSetType_.id), dsTypeId);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Specification} for retrieving {@link Target}s that are in a given
|
||||
* {@link RolloutGroup}
|
||||
@@ -423,8 +479,8 @@ public final class TargetSpecifications {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Specification} for retrieving {@link Target}s that have no Action
|
||||
* of the {@link RolloutGroup}.
|
||||
* {@link Specification} for retrieving {@link Target}s that have no Action of
|
||||
* the {@link RolloutGroup}.
|
||||
*
|
||||
* @param group
|
||||
* the {@link RolloutGroup}
|
||||
@@ -445,8 +501,8 @@ public final class TargetSpecifications {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Specification} for retrieving {@link Target}s by assigned
|
||||
* distribution set.
|
||||
* {@link Specification} for retrieving {@link Target}s by assigned distribution
|
||||
* set.
|
||||
*
|
||||
* @param distributionSetId
|
||||
* the ID of the distribution set which must be assigned
|
||||
@@ -471,4 +527,14 @@ public final class TargetSpecifications {
|
||||
return cb.equal(tags.get(JpaTargetTag_.id), tagId);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Specification} for retrieving {@link Target}s that have a
|
||||
* {@link org.eclipse.hawkbit.repository.model.TargetType} assigned
|
||||
*
|
||||
* @return the {@link Target} {@link Specification}
|
||||
*/
|
||||
public static Specification<JpaTarget> hasTargetType() {
|
||||
return (targetRoot, query, cb) -> cb.isNotNull(targetRoot.get(JpaTarget_.targetType));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user