diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetManagement.java index 8e2d480c5..080b6757d 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetManagement.java @@ -666,14 +666,10 @@ public interface TargetManagement { /** * Assign a {@link TargetTag} assignment to given {@link Target}s. * - * @param controllerIds - * to assign for - * @param targetTagId - * to assign + * @param controllerIds to assign for + * @param targetTagId to assign * @return list of assigned targets - * - * @throws EntityNotFoundException - * if given tagId or at least one of the targets do not exist + * @throws EntityNotFoundException if given targetTagId or at least one of the targets do not exist */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY_AND_UPDATE_TARGET) List assignTag(@NotEmpty Collection controllerIds, long targetTagId); @@ -683,11 +679,11 @@ public interface TargetManagement { * * @param controllerIds to un-assign for * @param targetTagId to un-assign - * @return the unassigned target or if no target is unassigned - * @throws EntityNotFoundException if Tag with given ID does not exist + * @return list of unassigned targets + * @throws EntityNotFoundException if given targetTagId or at least one of the targets do not exist */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET) - List unassignTag(@NotEmpty List controllerIds, long targetTagId); + List unassignTag(@NotEmpty Collection controllerIds, long targetTagId); /** * Un-assign a {@link TargetType} assignment to given {@link Target}. 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 cdf12e8c0..7a910e8f8 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 @@ -20,6 +20,9 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Function; import java.util.stream.Collectors; import jakarta.persistence.EntityManager; @@ -533,50 +536,42 @@ public class JpaTargetManagement implements TargetManagement { @Retryable(include = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) public List assignTag(final Collection controllerIds, final long targetTagId) { - final JpaTargetTag tag = targetTagRepository.findById(targetTagId) - .orElseThrow(() -> new EntityNotFoundException(TargetTag.class, targetTagId)); - - final List allTargets = targetRepository - .findAll(TargetSpecifications.byControllerIdWithTagsInJoin(controllerIds)); - if (allTargets.size() < controllerIds.size()) { - throw new EntityNotFoundException(Target.class, controllerIds, - allTargets.stream().map(Target::getControllerId).toList()); - } - - targetRepository.getAccessController() - .ifPresent(acm -> acm.assertOperationAllowed(AccessController.Operation.UPDATE, allTargets)); - - allTargets.forEach(target -> target.addTag(tag)); - - final List result = allTargets.stream().map(targetRepository::save).map(Target.class::cast).toList(); - - // No reason to save the tag - entityManager.detach(tag); - return result; + return updateTag(controllerIds, targetTagId, (tag, target) -> { + if (target.getTags().contains(tag)) { + return target; + } else { + target.addTag(tag); + return targetRepository.save(target); + } + }); } - @Override @Transactional @Retryable(include = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) - public List unassignTag(final List controllerIds, final long targetTagId) { + public List unassignTag(final Collection controllerIds, final long targetTagId) { + return updateTag(controllerIds, targetTagId, (tag, target) -> { + if (target.getTags().contains(tag)) { + target.removeTag(tag); + return targetRepository.save(target); + } else { + return target; + } + }); + } + private List updateTag(final Collection controllerIds, final long targetTagId, final BiFunction updater) { final JpaTargetTag tag = targetTagRepository.findById(targetTagId) .orElseThrow(() -> new EntityNotFoundException(TargetTag.class, targetTagId)); - - final List allTargets = targetRepository + final List targets = targetRepository .findAll(TargetSpecifications.byControllerIdWithTagsInJoin(controllerIds)); - if (allTargets.size() < controllerIds.size()) { - throw new EntityNotFoundException(Target.class, controllerIds, - allTargets.stream().map(Target::getControllerId).toList()); + if (targets.size() < controllerIds.size()) { + throw new EntityNotFoundException(Target.class, notFound(controllerIds, targets)); } - targetRepository.getAccessController() - .ifPresent(acm -> acm.assertOperationAllowed(AccessController.Operation.UPDATE, allTargets)); - - allTargets.forEach(target -> target.removeTag(tag)); - - final List result = allTargets.stream().map(targetRepository::save).map(Target.class::cast).toList(); + .ifPresent(acm -> acm.assertOperationAllowed(AccessController.Operation.UPDATE, targets)); + // apply update and collect modified targets + final List result = targets.stream().map(target -> updater.apply(tag, target)).toList(); // No reason to save the tag entityManager.detach(tag); return result; @@ -919,6 +914,12 @@ public class JpaTargetManagement implements TargetManagement { List.of(TargetSpecifications.hasRequestControllerAttributesTrue())); } + private static Collection notFound(final Collection controllerIds, final List foundTargets) { + final Map foundTargetMap = foundTargets.stream() + .collect(Collectors.toMap(Target::getControllerId, Function.identity())); + return controllerIds.stream().filter(id -> !foundTargetMap.containsKey(id)).toList(); + } + @Override @Transactional @Retryable(include = {