Fix portable event on setting confirmation status (#2146)

Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
Avgustin Marinov
2024-12-12 12:37:03 +02:00
committed by GitHub
parent c84fb13609
commit af50e8c938
12 changed files with 180 additions and 200 deletions

View File

@@ -15,6 +15,8 @@ import java.util.Optional;
import jakarta.persistence.EntityManager;
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;
@@ -32,13 +34,12 @@ import org.springframework.util.StringUtils;
/**
* A collection of static helper methods for the management classes
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class JpaManagementHelper {
private JpaManagementHelper() {
}
public static <T, J extends T> Optional<J> findOneBySpec(final JpaSpecificationExecutor<J> repository,
final List<Specification<J>> specList) {
public static <T, J extends T> Optional<J> findOneBySpec(
final JpaSpecificationExecutor<J> repository, final List<Specification<J>> specList) {
return repository.findOne(combineWithAnd(specList));
}
@@ -84,8 +85,8 @@ public final class JpaManagementHelper {
return repository.count(combineWithAnd(specList));
}
public static <J extends AbstractJpaBaseEntity> J touch(final EntityManager entityManager,
final CrudRepository<J, ?> repository, final J entity) {
public static <J extends AbstractJpaBaseEntity> J touch(
final EntityManager entityManager, final CrudRepository<J, ?> repository, final J entity) {
// merge base entity so optLockRevision gets updated and audit
// log written because modifying e.g. metadata is modifying the base
// entity itself for auditing purposes.

View File

@@ -835,9 +835,9 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
ConfirmationManagement confirmationManagement(final TargetRepository targetRepository,
final ActionRepository actionRepository, final ActionStatusRepository actionStatusRepository,
final RepositoryProperties repositoryProperties, final QuotaManagement quotaManagement,
final EntityFactory entityFactory) {
final EntityManager entityManager, final EntityFactory entityFactory) {
return new JpaConfirmationManagement(targetRepository, actionRepository, actionStatusRepository,
repositoryProperties, quotaManagement, entityFactory);
repositoryProperties, quotaManagement, entityManager, entityFactory);
}
/**

View File

@@ -16,6 +16,8 @@ import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import jakarta.persistence.EntityManager;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.repository.ConfirmationManagement;
import org.eclipse.hawkbit.repository.EntityFactory;
@@ -25,6 +27,7 @@ import org.eclipse.hawkbit.repository.RepositoryProperties;
import org.eclipse.hawkbit.repository.builder.ActionStatusCreate;
import org.eclipse.hawkbit.repository.exception.AutoConfirmationAlreadyActiveException;
import org.eclipse.hawkbit.repository.exception.InvalidConfirmationFeedbackException;
import org.eclipse.hawkbit.repository.jpa.JpaManagementHelper;
import org.eclipse.hawkbit.repository.jpa.builder.JpaActionStatusCreate;
import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
@@ -56,6 +59,7 @@ public class JpaConfirmationManagement extends JpaActionManagement implements Co
public static final String CONFIRMATION_CODE_MSG_PREFIX = "Confirmation status code: %d";
private final EntityManager entityManager;
private final EntityFactory entityFactory;
private final TargetRepository targetRepository;
@@ -63,9 +67,10 @@ public class JpaConfirmationManagement extends JpaActionManagement implements Co
final TargetRepository targetRepository,
final ActionRepository actionRepository, final ActionStatusRepository actionStatusRepository,
final RepositoryProperties repositoryProperties, final QuotaManagement quotaManagement,
final EntityFactory entityFactory) {
final EntityManager entityManager, final EntityFactory entityFactory) {
super(actionRepository, actionStatusRepository, quotaManagement, repositoryProperties);
this.targetRepository = targetRepository;
this.entityManager = entityManager;
this.entityFactory = entityFactory;
}
@@ -86,6 +91,8 @@ public class JpaConfirmationManagement extends JpaActionManagement implements Co
}
final JpaAutoConfirmationStatus confirmationStatus = new JpaAutoConfirmationStatus(initiator, remark, target);
target.setAutoConfirmationStatus(confirmationStatus);
// since the status is not part of the JpaTarget table (just ref) it might be needed to touch the entity to have updated lastModifiedAt
JpaManagementHelper.touch(entityManager, targetRepository, target);
final JpaTarget updatedTarget = targetRepository.save(target);
final AutoConfirmationStatus autoConfStatus = updatedTarget.getAutoConfirmationStatus();
if (autoConfStatus == null) {
@@ -159,6 +166,8 @@ public class JpaConfirmationManagement extends JpaActionManagement implements Co
log.debug("Deactivate auto confirmation for controllerId '{}'", controllerId);
final JpaTarget target = targetRepository.getByControllerId(controllerId);
target.setAutoConfirmationStatus(null);
// since the status is not part of the JpaTarget table (just ref) it might be needed to touch the entity to have updated lastModifiedAt
JpaManagementHelper.touch(entityManager, targetRepository, target);
targetRepository.save(target);
}

View File

@@ -740,8 +740,7 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
final Long dsId, final Collection<TargetWithActionType> targetsWithActionType, final String actionMessage,
final AbstractDsAssignmentStrategy assignmentStrategy) {
final RetryCallback<DistributionSetAssignmentResult, ConcurrencyFailureException> retryCallback =
retryContext -> assignDistributionSetToTargets(
initiatedBy, dsId, targetsWithActionType, actionMessage, assignmentStrategy);
retryContext -> assignDistributionSetToTargets(initiatedBy, dsId, targetsWithActionType, actionMessage, assignmentStrategy);
return retryTemplate.execute(retryCallback);
}
@@ -764,10 +763,11 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
* @param actionMessage an optional message to be written into the action status
* @param assignmentStrategy the assignment strategy (online /offline)
* @return the assignment result
* @throws 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,
private DistributionSetAssignmentResult assignDistributionSetToTargets(
final String initiatedBy, final Long dsId,
final Collection<TargetWithActionType> targetsWithActionType, final String actionMessage,
final AbstractDsAssignmentStrategy assignmentStrategy) {
final JpaDistributionSet distributionSet = (JpaDistributionSet) distributionSetManagement.getValidAndComplete(dsId);
@@ -780,28 +780,28 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
});
}
final List<String> providedTargetIds = targetsWithActionType.stream().map(TargetWithActionType::getControllerId)
.distinct().toList();
final List<String> providedTargetIds = targetsWithActionType.stream()
.map(TargetWithActionType::getControllerId)
.distinct()
.toList();
final List<String> existingTargetIds = ListUtils.partition(providedTargetIds, Constants.MAX_ENTRIES_IN_STATEMENT)
.stream()
.map(ids -> targetRepository.findAll(AccessController.Operation.UPDATE,
TargetSpecifications.hasControllerIdIn(ids)))
.flatMap(List::stream).map(JpaTarget::getControllerId).toList();
final List<JpaTarget> targetEntities = assignmentStrategy.findTargetsForAssignment(existingTargetIds,
distributionSet.getId());
.map(ids -> targetRepository.findAll(AccessController.Operation.UPDATE, TargetSpecifications.hasControllerIdIn(ids)))
.flatMap(List::stream).map(JpaTarget::getControllerId)
.toList();
final List<JpaTarget> targetEntities = assignmentStrategy.findTargetsForAssignment(existingTargetIds, distributionSet.getId());
if (targetEntities.isEmpty()) {
return allTargetsAlreadyAssignedResult(distributionSet, existingTargetIds.size());
}
final List<TargetWithActionType> existingTargetsWithActionType = targetsWithActionType.stream()
.filter(target -> existingTargetIds.contains(target.getControllerId())).toList();
.filter(target -> existingTargetIds.contains(target.getControllerId()))
.toList();
final List<JpaAction> assignedActions = doAssignDistributionSetToTargets(initiatedBy,
existingTargetsWithActionType, actionMessage, assignmentStrategy, distributionSet,
targetEntities);
final List<JpaAction> assignedActions = doAssignDistributionSetToTargets(
initiatedBy, existingTargetsWithActionType, actionMessage, assignmentStrategy, distributionSet, targetEntities);
return buildAssignmentResult(distributionSet, assignedActions, existingTargetsWithActionType.size());
}
@@ -824,17 +824,14 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
closeOrCancelActiveActions(assignmentStrategy, targetEntitiesIdsChunks);
}
// cancel all scheduled actions which are in-active, these actions were
// not active before and the manual assignment which has been done
// cancels them
// not active before and the manual assignment which has been done cancels them
targetEntitiesIdsChunks.forEach(this::cancelInactiveScheduledActionsForTargets);
setAssignedDistributionSetAndTargetUpdateStatus(assignmentStrategy, distributionSetEntity,
targetEntitiesIdsChunks);
final Map<TargetWithActionType, JpaAction> assignedActions = createActions(initiatedBy, targetsWithActionType,
targetEntities, assignmentStrategy, distributionSetEntity);
// create initial action status when action is created so we remember
setAssignedDistributionSetAndTargetUpdateStatus(assignmentStrategy, distributionSetEntity, targetEntitiesIdsChunks);
final Map<TargetWithActionType, JpaAction> assignedActions = createActions(
targetsWithActionType, targetEntities, distributionSetEntity, assignmentStrategy, initiatedBy);
// create initial action status when action is created, so we remember
// the initial running status because we will change the status
// of the action itself and with this action status we have a nicer
// action history.
// of the action itself and with this action status we have a nicer action history.
createActionsStatus(assignedActions, assignmentStrategy, actionMessage);
detachEntitiesAndSendTargetUpdatedEvents(distributionSetEntity, targetEntities, assignmentStrategy);
@@ -872,12 +869,10 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
assignmentStrategy.setAssignedDistributionSetAndTargetStatus(set, targetIdsChunks, currentUser);
}
private Map<TargetWithActionType, JpaAction> createActions(final String initiatedBy,
final Collection<TargetWithActionType> targetsWithActionType, final List<JpaTarget> targets,
final AbstractDsAssignmentStrategy assignmentStrategy, final JpaDistributionSet set) {
private Map<TargetWithActionType, JpaAction> createActions(
final Collection<TargetWithActionType> targetsWithActionType, final List<JpaTarget> targets, final JpaDistributionSet set,
final AbstractDsAssignmentStrategy assignmentStrategy, final String initiatedBy) {
final Map<TargetWithActionType, JpaAction> persistedActions = new LinkedHashMap<>();
for (final TargetWithActionType twt : targetsWithActionType) {
final JpaAction targetAction = assignmentStrategy.createTargetAction(initiatedBy, twt, targets, set);
if (targetAction != null) {
@@ -887,7 +882,8 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
return persistedActions;
}
private void createActionsStatus(final Map<TargetWithActionType, JpaAction> actions,
private void createActionsStatus(
final Map<TargetWithActionType, JpaAction> actions,
final AbstractDsAssignmentStrategy assignmentStrategy, final String actionMessage) {
actionStatusRepository.saveAll(actions.entrySet().stream().map(entry -> {
final JpaAction action = entry.getValue();

View File

@@ -88,22 +88,19 @@ public class JpaDistributionSetTypeManagement implements DistributionSetTypeMana
@Override
public Optional<DistributionSetType> getByKey(final String key) {
return distributionSetTypeRepository
.findOne(DistributionSetTypeSpecification.byKey(key)).map(DistributionSetType.class::cast);
return distributionSetTypeRepository.findOne(DistributionSetTypeSpecification.byKey(key)).map(DistributionSetType.class::cast);
}
@Override
public Optional<DistributionSetType> getByName(final String name) {
return distributionSetTypeRepository
.findOne(DistributionSetTypeSpecification.byName(name)).map(DistributionSetType.class::cast);
return distributionSetTypeRepository.findOne(DistributionSetTypeSpecification.byName(name)).map(DistributionSetType.class::cast);
}
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public DistributionSetType assignOptionalSoftwareModuleTypes(final long id,
final Collection<Long> softwareModulesTypeIds) {
public DistributionSetType assignOptionalSoftwareModuleTypes(final long id, final Collection<Long> softwareModulesTypeIds) {
return assignSoftwareModuleTypes(id, softwareModulesTypeIds, false);
}
@@ -111,8 +108,7 @@ public class JpaDistributionSetTypeManagement implements DistributionSetTypeMana
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public DistributionSetType assignMandatorySoftwareModuleTypes(final long id,
final Collection<Long> softwareModuleTypeIds) {
public DistributionSetType assignMandatorySoftwareModuleTypes(final long id, final Collection<Long> softwareModuleTypeIds) {
return assignSoftwareModuleTypes(id, softwareModuleTypeIds, true);
}
@@ -122,11 +118,8 @@ public class JpaDistributionSetTypeManagement implements DistributionSetTypeMana
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public DistributionSetType unassignSoftwareModuleType(final long id, final long softwareModuleTypeId) {
final JpaDistributionSetType type = findDistributionSetTypeAndThrowExceptionIfNotFound(id);
checkDistributionSetTypeNotAssigned(id);
type.removeModuleType(softwareModuleTypeId);
return distributionSetTypeRepository.save(type);
}
@@ -135,9 +128,9 @@ public class JpaDistributionSetTypeManagement implements DistributionSetTypeMana
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public List<DistributionSetType> create(final Collection<DistributionSetTypeCreate> types) {
final List<JpaDistributionSetType> typesToCreate = types.stream().map(JpaDistributionSetTypeCreate.class::cast)
final List<JpaDistributionSetType> typesToCreate = types.stream()
.map(JpaDistributionSetTypeCreate.class::cast)
.map(JpaDistributionSetTypeCreate::build).toList();
return Collections.unmodifiableList(
distributionSetTypeRepository.saveAll(AccessController.Operation.CREATE, typesToCreate));
}
@@ -148,7 +141,6 @@ public class JpaDistributionSetTypeManagement implements DistributionSetTypeMana
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public DistributionSetType create(final DistributionSetTypeCreate c) {
final JpaDistributionSetType distributionSetType = ((JpaDistributionSetTypeCreate) c).build();
return distributionSetTypeRepository.save(AccessController.Operation.CREATE, distributionSetType);
}
@@ -158,9 +150,7 @@ public class JpaDistributionSetTypeManagement implements DistributionSetTypeMana
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public DistributionSetType update(final DistributionSetTypeUpdate u) {
final GenericDistributionSetTypeUpdate update = (GenericDistributionSetTypeUpdate) u;
final JpaDistributionSetType type = findDistributionSetTypeAndThrowExceptionIfNotFound(update.getId());
update.getDescription().ifPresent(type::setDescription);
update.getColour().ifPresent(type::setColour);
@@ -245,13 +235,12 @@ public class JpaDistributionSetTypeManagement implements DistributionSetTypeMana
@Override
public Page<DistributionSetType> findByRsql(final Pageable pageable, final String rsqlParam) {
return JpaManagementHelper.findAllWithCountBySpec(distributionSetTypeRepository, pageable, List.of(
RSQLUtility.buildRsqlSpecification(rsqlParam, DistributionSetTypeFields.class, virtualPropertyReplacer,
database),
RSQLUtility.buildRsqlSpecification(rsqlParam, DistributionSetTypeFields.class, virtualPropertyReplacer, database),
DistributionSetTypeSpecification.isNotDeleted()));
}
private static void removeModuleTypes(final Collection<Long> currentSmTypeIds,
final Collection<Long> updatedSmTypeIds,
private static void removeModuleTypes(
final Collection<Long> currentSmTypeIds, final Collection<Long> updatedSmTypeIds,
final LongFunction<JpaDistributionSetType> removeModuleTypeCallback) {
final Set<Long> smTypeIdsToRemove = currentSmTypeIds.stream().filter(id -> !updatedSmTypeIds.contains(id))
.collect(Collectors.toSet());
@@ -264,10 +253,10 @@ public class JpaDistributionSetTypeManagement implements DistributionSetTypeMana
return update.getOptional().isPresent() || update.getMandatory().isPresent();
}
private void addModuleTypes(final Collection<Long> currentSmTypeIds, final Collection<Long> updatedSmTypeIds,
private void addModuleTypes(
final Collection<Long> currentSmTypeIds, final Collection<Long> updatedSmTypeIds,
final Function<SoftwareModuleType, JpaDistributionSetType> addModuleTypeCallback) {
final Set<Long> smTypeIdsToAdd = updatedSmTypeIds.stream().filter(id -> !currentSmTypeIds.contains(id))
.collect(Collectors.toSet());
final Set<Long> smTypeIdsToAdd = updatedSmTypeIds.stream().filter(id -> !currentSmTypeIds.contains(id)).collect(Collectors.toSet());
if (!CollectionUtils.isEmpty(smTypeIdsToAdd)) {
softwareModuleTypeRepository.findAllById(smTypeIdsToAdd).forEach(addModuleTypeCallback::apply);
}
@@ -275,8 +264,7 @@ public class JpaDistributionSetTypeManagement implements DistributionSetTypeMana
private DistributionSetType assignSoftwareModuleTypes(
final long dsTypeId, final Collection<Long> softwareModulesTypeIds, final boolean mandatory) {
final Collection<JpaSoftwareModuleType> foundModules =
softwareModuleTypeRepository.findAllById(softwareModulesTypeIds);
final Collection<JpaSoftwareModuleType> foundModules = softwareModuleTypeRepository.findAllById(softwareModulesTypeIds);
if (foundModules.size() < softwareModulesTypeIds.size()) {
throw new EntityNotFoundException(
SoftwareModuleType.class, softwareModulesTypeIds, foundModules.stream().map(SoftwareModuleType::getId).toList());
@@ -315,8 +303,7 @@ public class JpaDistributionSetTypeManagement implements DistributionSetTypeMana
}
private JpaDistributionSetType findDistributionSetTypeAndThrowExceptionIfNotFound(final Long setId) {
return (JpaDistributionSetType) get(setId)
.orElseThrow(() -> new EntityNotFoundException(DistributionSetType.class, setId));
return (JpaDistributionSetType) get(setId).orElseThrow(() -> new EntityNotFoundException(DistributionSetType.class, setId));
}
private void checkDistributionSetTypeNotAssigned(final Long id) {
@@ -325,4 +312,4 @@ public class JpaDistributionSetTypeManagement implements DistributionSetTypeMana
"Distribution set type %s is already assigned to distribution sets and cannot be changed!", id));
}
}
}
}