From 39861e77904689a4f90788c7d66407bfd49cd338 Mon Sep 17 00:00:00 2001 From: Avgustin Marinov Date: Thu, 5 Dec 2024 11:41:41 +0200 Subject: [PATCH] Refactor action repository (#2118) Signed-off-by: Avgustin Marinov --- .../ddi/rest/resource/DdiRootController.java | 4 +- .../amqp/AmqpMessageDispatcherService.java | 99 +++++----- .../amqp/AmqpMessageHandlerService.java | 42 ++-- ...ssageDispatcherServiceIntegrationTest.java | 5 +- .../repository/ControllerManagement.java | 186 ++++++------------ .../repository/SoftwareModuleManagement.java | 12 +- .../hawkbit/repository/model/BaseEntity.java | 3 +- .../jpa/management/JpaActionManagement.java | 45 ++--- .../management/JpaControllerManagement.java | 30 +-- .../management/JpaRolloutGroupManagement.java | 2 +- .../jpa/management/JpaRolloutManagement.java | 3 +- .../JpaSoftwareModuleManagement.java | 10 +- .../repository/jpa/model/JpaActionStatus.java | 9 +- .../repository/jpa/model/JpaTarget.java | 27 ++- .../jpa/repository/ActionRepository.java | 127 ++++++------ .../repository/ActionStatusRepository.java | 10 +- .../repository/BaseEntityRepositoryACM.java | 6 +- .../SoftwareModuleMetadataRepository.java | 29 ++- .../specifications/ActionSpecifications.java | 16 +- .../management/ControllerManagementTest.java | 40 ++-- .../test/matcher/EventVerifier.java | 6 + 21 files changed, 318 insertions(+), 393 deletions(-) diff --git a/hawkbit-ddi/hawkbit-ddi-resource/src/main/java/org/eclipse/hawkbit/ddi/rest/resource/DdiRootController.java b/hawkbit-ddi/hawkbit-ddi-resource/src/main/java/org/eclipse/hawkbit/ddi/rest/resource/DdiRootController.java index 9fd779ff6..06c8b3114 100644 --- a/hawkbit-ddi/hawkbit-ddi-resource/src/main/java/org/eclipse/hawkbit/ddi/rest/resource/DdiRootController.java +++ b/hawkbit-ddi/hawkbit-ddi-resource/src/main/java/org/eclipse/hawkbit/ddi/rest/resource/DdiRootController.java @@ -144,9 +144,7 @@ public class DdiRootController implements DdiRootControllerRestApi { } @Override - public ResponseEntity getControllerBase( - final String tenant, - final String controllerId) { + public ResponseEntity getControllerBase(final String tenant, final String controllerId) { log.debug("getControllerBase({})", controllerId); final Target target = controllerManagement.findOrRegisterTargetIfItDoesNotExist(controllerId, IpUtil diff --git a/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherService.java b/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherService.java index 34b040545..9cdf91421 100644 --- a/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherService.java +++ b/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherService.java @@ -81,9 +81,8 @@ import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.util.CollectionUtils; /** - * {@link AmqpMessageDispatcherService} create all outgoing AMQP messages and - * delegate the messages to a {@link AmqpMessageSenderService}. - * + * {@link AmqpMessageDispatcherService} create all outgoing AMQP messages and delegate the messages to a {@link AmqpMessageSenderService}. + *

* Additionally, the dispatcher listener/subscribe for some target events e.g. assignment. */ @Slf4j @@ -173,7 +172,7 @@ public class AmqpMessageDispatcherService extends BaseAmqpService { } log.debug("MultiActionEvent received for {}", multiActionEvent.getControllerIds()); - sendMultiActionRequestMessages(multiActionEvent.getTenant(), multiActionEvent.getControllerIds()); + sendMultiActionRequestMessages(multiActionEvent.getControllerIds()); } protected void sendUpdateMessageToTarget( @@ -184,27 +183,6 @@ public class AmqpMessageDispatcherService extends BaseAmqpService { sendUpdateMessageToTargets(actionProp, Collections.singletonList(target), softwareModules); } - protected void sendMultiActionRequestToTarget( - final String tenant, final Target target, final List actions, - final Function>> getSoftwareModuleMetaData) { - final URI targetAddress = target.getAddress(); - if (!IpUtil.isAmqpUri(targetAddress) || CollectionUtils.isEmpty(actions)) { - return; - } - - final DmfMultiActionRequest multiActionRequest = new DmfMultiActionRequest(); - actions.forEach(action -> { - final DmfActionRequest actionRequest = createDmfActionRequest(target, action, getSoftwareModuleMetaData.apply(action)); - final int weight = deploymentManagement.getWeightConsideringDefault(action); - multiActionRequest.addElement(getEventTypeForAction(action), actionRequest, weight); - }); - - final Message message = getMessageConverter().toMessage( - multiActionRequest, - createConnectorMessagePropertiesEvent(tenant, target.getControllerId(), EventTopic.MULTI_ACTION)); - amqpSenderService.sendMessage(message, targetAddress); - } - protected DmfDownloadAndUpdateRequest createDownloadAndUpdateRequest( final Target target, final Long actionId, final Map> softwareModules) { @@ -303,14 +281,6 @@ public class AmqpMessageDispatcherService extends BaseAmqpService { return dmfTarget; } - /** - * Creates a Confirmation request. - * - * @param target the target - * @param actionId the actionId - * @param softwareModules the software modules - * @return confirm request - */ protected DmfConfirmRequest createConfirmRequest( final Target target, final Long actionId, final Map> softwareModules) { final DmfConfirmRequest request = new DmfConfirmRequest(); @@ -325,6 +295,33 @@ public class AmqpMessageDispatcherService extends BaseAmqpService { return request; } + void sendMultiActionRequestToTarget( + final Target target, final List actions, + final Function> getSoftwareModuleMetaData) { + final URI targetAddress = target.getAddress(); + if (!IpUtil.isAmqpUri(targetAddress) || CollectionUtils.isEmpty(actions)) { + return; + } + + final DmfMultiActionRequest multiActionRequest = new DmfMultiActionRequest(); + actions.forEach(action -> { + final DmfActionRequest actionRequest = createDmfActionRequest( + target, action, + action.getDistributionSet().getModules().stream() + .collect(Collectors.toMap(Function.identity(), module -> { + final List softwareModuleMetadata = getSoftwareModuleMetaData.apply(module); + return softwareModuleMetadata == null ? Collections.emptyList() : softwareModuleMetadata; + }))); + final int weight = deploymentManagement.getWeightConsideringDefault(action); + multiActionRequest.addElement(getEventTypeForAction(action), actionRequest, weight); + }); + + final Message message = getMessageConverter().toMessage( + multiActionRequest, + createConnectorMessagePropertiesEvent(target.getTenant(), target.getControllerId(), EventTopic.MULTI_ACTION)); + amqpSenderService.sendMessage(message, targetAddress); + } + private static DmfActionRequest createPlainActionRequest(final Action action) { final DmfActionRequest actionRequest = new DmfActionRequest(); actionRequest.setActionId(action.getId()); @@ -463,23 +460,27 @@ public class AmqpMessageDispatcherService extends BaseAmqpService { } } - private void sendMultiActionRequestMessages(final String tenant, final List controllerIds) { - final Map> softwareModuleMetadata = new HashMap<>(); - targetManagement.getByControllerID(controllerIds).stream() - .filter(target -> IpUtil.isAmqpUri(target.getAddress())).forEach(target -> { - final List activeActions = deploymentManagement - .findActiveActionsWithHighestWeight(target.getControllerId(), MAX_ACTION_COUNT); + private void sendMultiActionRequestMessages(final List controllerIds) { + final Map> controllerIdToActions = controllerIds.stream() + .collect(Collectors.toMap( + Function.identity(), + controllerId -> deploymentManagement.findActiveActionsWithHighestWeight(controllerId, MAX_ACTION_COUNT))); - activeActions.forEach(action -> - action.getDistributionSet().getModules().forEach(module -> - softwareModuleMetadata.computeIfAbsent(module, this::getSoftwareModuleMetadata))); + // gets all software modules for all action at once + final Set allSmIds = controllerIdToActions.values().stream() + .flatMap(actions -> actions.stream() + .map(Action::getDistributionSet) + .flatMap(ds -> ds.getModules().stream()) + .map(SoftwareModule::getId)) + .collect(Collectors.toSet()); + final Map> getSoftwareModuleMetadata = + allSmIds.isEmpty() + ? Collections.emptyMap() + : softwareModuleManagement.findMetaDataBySoftwareModuleIdsAndTargetVisible(allSmIds); - if (!activeActions.isEmpty()) { - sendMultiActionRequestToTarget(tenant, target, activeActions, - action -> action.getDistributionSet().getModules().stream() - .collect(Collectors.toMap(m -> m, softwareModuleMetadata::get))); - } - }); + targetManagement.getByControllerID(controllerIds).forEach(target -> + sendMultiActionRequestToTarget( + target, controllerIdToActions.get(target.getControllerId()), module -> getSoftwareModuleMetadata.get(module.getId()))); } private DmfActionRequest createDmfActionRequest( @@ -602,7 +603,7 @@ public class AmqpMessageDispatcherService extends BaseAmqpService { } private Map> getSoftwareModulesWithMetadata(final DistributionSet distributionSet) { - return distributionSet.getModules().stream().collect(Collectors.toMap(m -> m, this::getSoftwareModuleMetadata)); + return distributionSet.getModules().stream().collect(Collectors.toMap(Function.identity(), this::getSoftwareModuleMetadata)); } private List getSoftwareModuleMetadata(final SoftwareModule module) { diff --git a/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerService.java b/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerService.java index df02c8ede..a6db87128 100644 --- a/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerService.java +++ b/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerService.java @@ -20,6 +20,7 @@ import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.UUID; +import java.util.function.Function; import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; @@ -67,9 +68,8 @@ import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; /** - * {@link AmqpMessageHandlerService} handles all incoming target interaction - * AMQP messages (e.g. create target, check for updates etc.) for the queue - * which is configured for the property hawkbit.dmf.rabbitmq.receiverQueue. + * {@link AmqpMessageHandlerService} handles all incoming target interaction AMQP messages (e.g. create target, check for updates etc.) for the + * queue which is configured for the property hawkbit.dmf.rabbitmq.receiverQueue. */ @Slf4j public class AmqpMessageHandlerService extends BaseAmqpService { @@ -344,41 +344,39 @@ public class AmqpMessageHandlerService extends BaseAmqpService { private void sendCurrentActionsAsMultiActionToTarget(final Target target) { final List actions = controllerManagement.findActiveActionsWithHighestWeight(target.getControllerId(), MAX_ACTION_COUNT); - final Set distributionSets = actions.stream().map(Action::getDistributionSet).collect(Collectors.toSet()); - final Map>> softwareModulesPerDistributionSet = distributionSets - .stream().collect(Collectors.toMap(DistributionSet::getId, this::getSoftwareModulesWithMetadata)); + // gets all software modules for all action at once + final Set allSmIds = actions.stream() + .map(Action::getDistributionSet) + .flatMap(ds -> ds.getModules().stream()) + .map(SoftwareModule::getId) + .collect(Collectors.toSet()); + final Map> getSoftwareModuleMetadata = + allSmIds.isEmpty() ? Collections.emptyMap() : controllerManagement.findTargetVisibleMetaDataBySoftwareModuleId(allSmIds); - amqpMessageDispatcherService.sendMultiActionRequestToTarget( - target.getTenant(), target, actions, - action -> softwareModulesPerDistributionSet.get(action.getDistributionSet().getId())); + amqpMessageDispatcherService.sendMultiActionRequestToTarget(target, actions, module -> getSoftwareModuleMetadata.get(module.getId())); } private void sendOldestActionToTarget(final Target target) { final Optional actionOptional = controllerManagement.findActiveActionWithHighestWeight(target.getControllerId()); - if (actionOptional.isEmpty()) { return; } final Action action = actionOptional.get(); if (action.isCancelingOrCanceled()) { - amqpMessageDispatcherService.sendCancelMessageToTarget(target.getTenant(), target.getControllerId(), - action.getId(), target.getAddress()); + amqpMessageDispatcherService.sendCancelMessageToTarget( + target.getTenant(), target.getControllerId(), action.getId(), target.getAddress()); } else { - amqpMessageDispatcherService.sendUpdateMessageToTarget(new ActionProperties(action), action.getTarget(), - getSoftwareModulesWithMetadata(action.getDistributionSet())); + amqpMessageDispatcherService.sendUpdateMessageToTarget( + new ActionProperties(action), action.getTarget(), getSoftwareModulesWithMetadata(action.getDistributionSet())); } } private Map> getSoftwareModulesWithMetadata(final DistributionSet distributionSet) { - final List smIds = distributionSet.getModules().stream().map(SoftwareModule::getId) - .collect(Collectors.toList()); - - final Map> metadata = controllerManagement - .findTargetVisibleMetaDataBySoftwareModuleId(smIds); - - return distributionSet.getModules().stream() - .collect(Collectors.toMap(sm -> sm, sm -> metadata.getOrDefault(sm.getId(), Collections.emptyList()))); + final List smIds = distributionSet.getModules().stream().map(SoftwareModule::getId).collect(Collectors.toList()); + final Map> metadata = controllerManagement.findTargetVisibleMetaDataBySoftwareModuleId(smIds); + return distributionSet.getModules().stream().collect(Collectors.toMap( + Function.identity(), sm -> metadata.getOrDefault(sm.getId(), Collections.emptyList()))); } diff --git a/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/AmqpMessageDispatcherServiceIntegrationTest.java b/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/AmqpMessageDispatcherServiceIntegrationTest.java index ccc2fc5e8..6544d1b95 100644 --- a/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/AmqpMessageDispatcherServiceIntegrationTest.java +++ b/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/AmqpMessageDispatcherServiceIntegrationTest.java @@ -465,8 +465,9 @@ public class AmqpMessageDispatcherServiceIntegrationTest extends AbstractAmqpSer assertLatestMultiActionMessageContainsInstallMessages(controllerId, Arrays.asList(smIds1, smIds2, smIds1)); final List installActions = getLatestMultiActionMessageActions(controllerId).stream() - .filter(entry -> entry.getValue().equals(EventTopic.DOWNLOAD_AND_INSTALL)).map(Entry::getKey) - .collect(Collectors.toList()); + .filter(entry -> entry.getValue().equals(EventTopic.DOWNLOAD_AND_INSTALL)) + .map(Entry::getKey) + .toList(); updateActionViaDmfClient(controllerId, installActions.get(0), DmfActionStatus.FINISHED); waitUntilEventMessagesAreDispatchedToTarget(EventTopic.REQUEST_ATTRIBUTES_UPDATE, EventTopic.MULTI_ACTION); diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/ControllerManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/ControllerManagement.java index b56d7a682..8fd132ede 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/ControllerManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/ControllerManagement.java @@ -41,23 +41,19 @@ import org.springframework.data.domain.Pageable; import org.springframework.security.access.prepost.PreAuthorize; /** - * Service layer for all operations of the DDI API (with access permissions only - * for the controller). + * Service layer for all operations of the DDI API (with access permissions only for the controller). */ public interface ControllerManagement { /** - * Adds an {@link ActionStatus} for a cancel {@link Action} including - * potential state changes for the target and the {@link Action} itself. + * Adds an {@link ActionStatus} for a cancel {@link Action} including potential state changes for the target and the {@link Action} itself. * * @param create to be added * @return the updated {@link Action} * @throws EntityAlreadyExistsException if a given entity already exists - * @throws AssignmentQuotaExceededException if more than the allowed number of status entries or messages - * per entry are inserted + * @throws AssignmentQuotaExceededException if more than the allowed number of status entries or messages per entry are inserted * @throws EntityNotFoundException if given action does not exist - * @throws ConstraintViolationException if fields are not filled as specified. Check - * {@link ActionStatusCreate} for field constraints. + * @throws ConstraintViolationException if fields are not filled as specified. Check {@link ActionStatusCreate} for field constraints. */ @PreAuthorize(SpringEvalExpressions.IS_CONTROLLER) Action addCancelActionStatus(@NotNull @Valid ActionStatusCreate create); @@ -72,54 +68,45 @@ public interface ControllerManagement { Optional getSoftwareModule(long moduleId); /** - * Retrieves {@link SoftwareModuleMetadata} where - * {@link SoftwareModuleMetadata#isTargetVisible()}. + * Retrieves {@link SoftwareModuleMetadata} where {@link SoftwareModuleMetadata#isTargetVisible()}. * * @param moduleId of the {@link SoftwareModule} - * @return list of {@link SoftwareModuleMetadata} with maximum size of + * @return the map of software module id to {@link SoftwareModuleMetadata} with maximum size of * {@link RepositoryConstants#MAX_META_DATA_COUNT} */ @PreAuthorize(SpringEvalExpressions.IS_CONTROLLER) - Map> findTargetVisibleMetaDataBySoftwareModuleId( - @NotNull Collection moduleId); + Map> findTargetVisibleMetaDataBySoftwareModuleId(@NotNull Collection moduleId); /** - * Simple addition of a new {@link ActionStatus} entry to the - * {@link Action}. No state changes. + * Simple addition of a new {@link ActionStatus} entry to the {@link Action}. No state changes. * * @param create to add to the action * @return created {@link ActionStatus} entity - * @throws AssignmentQuotaExceededException if more than the allowed number of status entries or messages - * per entry are inserted + * @throws AssignmentQuotaExceededException if more than the allowed number of status entries or messages per entry are inserted * @throws EntityNotFoundException if given action does not exist - * @throws ConstraintViolationException if fields are not filled as specified. Check - * {@link ActionStatusCreate} for field constraints. + * @throws ConstraintViolationException if fields are not filled as specified. Check {@link ActionStatusCreate} for field constraints. */ @PreAuthorize(SpringEvalExpressions.IS_CONTROLLER) ActionStatus addInformationalActionStatus(@NotNull @Valid ActionStatusCreate create); /** - * Adds an {@link ActionStatus} entry for an update {@link Action} including - * potential state changes for the target and the {@link Action} itself. + * Adds an {@link ActionStatus} entry for an update {@link Action} including potential state changes for the target and the {@link Action} + * itself. * * @param create to be added * @return the updated {@link Action} * @throws EntityAlreadyExistsException if a given entity already exists - * @throws AssignmentQuotaExceededException if more than the allowed number of status entries or messages - * per entry are inserted + * @throws AssignmentQuotaExceededException if more than the allowed number of status entries or messages per entry are inserted * @throws EntityNotFoundException if action status not exist - * @throws ConstraintViolationException if fields are not filled as specified. Check - * {@link ActionStatusCreate} for field constraints. + * @throws ConstraintViolationException if fields are not filled as specified. Check {@link ActionStatusCreate} for field constraints. */ @PreAuthorize(SpringEvalExpressions.IS_CONTROLLER) Action addUpdateActionStatus(@NotNull @Valid ActionStatusCreate create); /** - * Retrieves active {@link Action} with highest priority that is assigned to - * a {@link Target}. + * Retrieves active {@link Action} with the highest priority that is assigned to a {@link Target}. * - * For performance reasons this method does not throw - * {@link EntityNotFoundException} in case target with given controllerId + * For performance reasons this method does not throw {@link EntityNotFoundException} in case target with given controllerId * does not exist but will return an {@link Optional#empty()} instead. * * @param controllerId identifies the target to retrieve the action from @@ -140,17 +127,7 @@ public interface ControllerManagement { List findActiveActionsWithHighestWeight(@NotEmpty String controllerId, int maxActionCount); /** - * Get weight of an Action. Returns the default value if the weight is null - * according to the properties. - * - * @param action to extract the weight from - * @return weight of the action - */ - int getWeightConsideringDefault(final Action action); - - /** - * Get the {@link Action} entity for given actionId with all lazy - * attributes. + * Get the {@link Action} entity for given actionId with all lazy attributes. * * @param actionId to be id of the action * @return the corresponding {@link Action} @@ -159,8 +136,7 @@ public interface ControllerManagement { Optional findActionWithDetails(long actionId); /** - * Retrieves all the {@link ActionStatus} entries of the given - * {@link Action}. + * Retrieves all the {@link ActionStatus} entries of the given {@link Action}. * * @param pageReq pagination parameter * @param actionId to be filtered on @@ -171,11 +147,8 @@ public interface ControllerManagement { Page findActionStatusByAction(@NotNull Pageable pageReq, long actionId); /** - * Register new target in the repository (plug-and-play) and in case it - * already exists updates {@link Target#getAddress()} and - * {@link Target#getLastTargetQuery()} and switches if - * {@link TargetUpdateStatus#UNKNOWN} to - * {@link TargetUpdateStatus#REGISTERED}. + * Register new target in the repository (plug-and-play) and in case it already exists updates {@link Target#getAddress()} and + * {@link Target#getLastTargetQuery()} and switches if {@link TargetUpdateStatus#UNKNOWN} to {@link TargetUpdateStatus#REGISTERED}. * * @param controllerId reference * @param address the client IP address of the target, might be {@code null} @@ -199,16 +172,13 @@ public interface ControllerManagement { * @return target reference */ @PreAuthorize(SpringEvalExpressions.IS_CONTROLLER) - Target findOrRegisterTargetIfItDoesNotExist(@NotEmpty String controllerId, @NotNull URI address, String name, - String type); + Target findOrRegisterTargetIfItDoesNotExist(@NotEmpty String controllerId, @NotNull URI address, String name, String type); /** - * Retrieves last {@link Action} for a download of an artifact of given - * module and target if exists and is not canceled. + * Retrieves last {@link Action} for a download of an artifact of given module and target if exists and is not canceled. * * @param controllerId to look for - * @param moduleId of the the {@link SoftwareModule} that should be assigned to - * the target + * @param moduleId of the {@link SoftwareModule} that should be assigned to the target * @return last {@link Action} for given combination * @throws EntityNotFoundException if target with given ID does not exist */ @@ -216,8 +186,7 @@ public interface ControllerManagement { Optional getActionForDownloadByTargetAndSoftwareModule(@NotEmpty String controllerId, long moduleId); /** - * Returns configured polling interval at which the controller polls hawkBit - * server. + * Returns configured polling interval at which the controller polls hawkBit server. * * @return current {@link TenantConfigurationKey#POLLING_TIME_INTERVAL}. */ @@ -233,44 +202,33 @@ public interface ControllerManagement { String getMinPollingTime(); /** - * Returns the count to be used for reducing polling interval while calling - * {@link ControllerManagement#getPollingTimeForAction(long)}. + * Returns the count to be used for reducing polling interval while calling {@link ControllerManagement#getPollingTimeForAction(long)}. * - * @return configured value of - * {@link TenantConfigurationKey#MAINTENANCE_WINDOW_POLL_COUNT}. + * @return configured value of {@link TenantConfigurationKey#MAINTENANCE_WINDOW_POLL_COUNT}. */ @PreAuthorize(SpringEvalExpressions.IS_CONTROLLER) int getMaintenanceWindowPollCount(); /** - * Returns polling time based on the maintenance window for an action. - * Server will reduce the polling interval as the start time for maintenance - * window approaches, so that at least these many attempts are made between - * current polling until start of maintenance window. Poll time keeps - * reducing with MinPollingTime as lower limit - * {@link TenantConfigurationKey#MIN_POLLING_TIME_INTERVAL}. After the start - * of maintenance window, it resets to default - * {@link TenantConfigurationKey#POLLING_TIME_INTERVAL}. + * Returns polling time based on the maintenance window for an action. Server will reduce the polling interval as the start time for + * maintenance window approaches, so that at least these many attempts are made between current polling until start of maintenance window. + * Poll time keeps reducing with MinPollingTime as lower limit {@link TenantConfigurationKey#MIN_POLLING_TIME_INTERVAL}. After the start + * of maintenance window, it resets to default {@link TenantConfigurationKey#POLLING_TIME_INTERVAL}. * - * @param actionId id the {@link Action} for which polling time is calculated - * based on it having maintenance window or not + * @param actionId id the {@link Action} for which polling time is calculated based on it having maintenance window or not * @return current {@link TenantConfigurationKey#POLLING_TIME_INTERVAL}. */ @PreAuthorize(SpringEvalExpressions.IS_CONTROLLER) String getPollingTimeForAction(long actionId); /** - * Checks if a given target has currently or has even been assigned to the - * given artifact through the action history list. This can e.g. indicate if - * a target is allowed to download a given artifact because it has currently - * assigned or had ever been assigned to the target and so it's visible to a - * specific target e.g. for downloading. + * Checks if a given target has currently or has even been assigned to the given artifact through the action history list. This can e.g. + * indicate if a target is allowed to download a given artifact because it has currently assigned or had ever been assigned to the target + * and so it's visible to a specific target e.g. for downloading. * * @param controllerId the ID of the target to check - * @param sha1Hash of the artifact to verify if the given target had even been - * assigned to - * @return {@code true} if the given target has currently or had ever a - * relation to the given artifact through the action history, + * @param sha1Hash of the artifact to verify if the given target had even been assigned to + * @return {@code true} if the given target has currently or had ever a relation to the given artifact through the action history, * otherwise {@code false} * @throws EntityNotFoundException if target with given ID does not exist */ @@ -278,17 +236,13 @@ public interface ControllerManagement { boolean hasTargetArtifactAssigned(@NotEmpty String controllerId, @NotEmpty String sha1Hash); /** - * Checks if a given target has currently or has even been assigned to the - * given artifact through the action history list. This can e.g. indicate if - * a target is allowed to download a given artifact because it has currently - * assigned or had ever been assigned to the target and so it's visible to a - * specific target e.g. for downloading. + * Checks if a given target has currently or has even been assigned to the given artifact through the action history list. This can e.g. + * indicate if a target is allowed to download a given artifact because it has currently assigned or had ever been assigned to the target + * and so it's visible to a specific target e.g. for downloading. * * @param targetId the ID of the target to check - * @param sha1Hash of the artifact to verify if the given target had even been - * assigned to - * @return {@code true} if the given target has currently or had ever a - * relation to the given artifact through the action history, + * @param sha1Hash of the artifact to verify if the given target had even been assigned to + * @return {@code true} if the given target has currently or had ever a relation to the given artifact through the action history, * otherwise {@code false} * @throws EntityNotFoundException if target with given ID does not exist */ @@ -296,21 +250,18 @@ public interface ControllerManagement { boolean hasTargetArtifactAssigned(long targetId, @NotEmpty String sha1Hash); /** - * Registers retrieved status for given {@link Target} and {@link Action} if - * it does not exist yet. + * Registers retrieved status for given {@link Target} and {@link Action} if it does not exist yet. * * @param actionId to the handle status for * @param message for the status - * @return the update action in case the status has been changed to - * {@link Status#RETRIEVED} + * @return the update action in case the status has been changed to {@link Status#RETRIEVED} * @throws EntityNotFoundException if action with given ID does not exist */ @PreAuthorize(SpringEvalExpressions.IS_CONTROLLER) Action registerRetrieved(long actionId, String message); /** - * Updates attributes of the controller according to the given - * {@link UpdateMode}. + * Updates attributes of the controller according to the given {@link UpdateMode}. * * @param controllerId to update * @param attributes to insert @@ -324,47 +275,37 @@ public interface ControllerManagement { Target updateControllerAttributes(@NotEmpty String controllerId, @NotNull Map attributes, UpdateMode mode); /** - * Finds {@link Target} based on given controller ID returns found Target - * without details, i.e. NO {@link Target#getTags()} and - * {@link Target#getActions()} possible. + * Finds {@link Target} based on given controller ID returns found Target without details, i.e. + * NO {@link Target#getTags()} and {@link Target#getActions()} possible. * * @param controllerId to look for. * @return {@link Target} or {@code null} if it does not exist * @see Target#getControllerId() */ - @PreAuthorize(SpringEvalExpressions.IS_CONTROLLER + SpringEvalExpressions.HAS_AUTH_OR - + SpringEvalExpressions.IS_SYSTEM_CODE) + @PreAuthorize(SpringEvalExpressions.IS_CONTROLLER + SpringEvalExpressions.HAS_AUTH_OR + SpringEvalExpressions.IS_SYSTEM_CODE) Optional getByControllerId(@NotEmpty String controllerId); /** - * Finds {@link Target} based on given ID returns found Target without - * details, i.e. NO {@link Target#getTags()} and {@link Target#getActions()} + * Finds {@link Target} based on given ID returns found Target without details, i.e. + * NO {@link Target#getTags()} and {@link Target#getActions()} * possible. * * @param targetId to look for. * @return {@link Target} or {@code null} if it does not exist * @see Target#getId() */ - @PreAuthorize(SpringEvalExpressions.IS_CONTROLLER + SpringEvalExpressions.HAS_AUTH_OR - + SpringEvalExpressions.IS_SYSTEM_CODE) + @PreAuthorize(SpringEvalExpressions.IS_CONTROLLER + SpringEvalExpressions.HAS_AUTH_OR + SpringEvalExpressions.IS_SYSTEM_CODE) Optional get(long targetId); /** - * Retrieves the specified number of messages from action history of the - * given {@link Action} based on messageCount. Regardless of the value of - * messageCount, in order to restrict resource utilisation by controllers, - * maximum number of messages that are retrieved from database is limited by - * {@link RepositoryConstants#MAX_ACTION_HISTORY_MSG_COUNT}. messageCount - * less then zero, retrieves the maximum allowed number of action status - * messages from history; messageCount equal zero, does not retrieve any - * message; and messageCount larger then zero, retrieves the specified - * number of messages, limited by maximum allowed number. A controller sends - * the feedback for an {@link ActionStatus} as a list of messages; while - * returning the messages, even though the messages from multiple - * {@link ActionStatus} are retrieved in descending order by the reported - * time ({@link ActionStatus#getOccurredAt()}), i.e. latest ActionStatus - * first, the sub-ordering of messages from within single - * {@link ActionStatus} is unspecified. + * Retrieves the specified number of messages from action history of the given {@link Action} based on messageCount. Regardless of the + * value of messageCount, in order to restrict resource utilisation by controllers, maximum number of messages that are retrieved from + * database is limited by {@link RepositoryConstants#MAX_ACTION_HISTORY_MSG_COUNT}. messageCount less than zero, retrieves the maximum + * allowed number of action status messages from history; messageCount equal zero, does not retrieve any message; and messageCount larger + * than zero, retrieves the specified number of messages, limited by maximum allowed number. A controller sends the feedback for an + * {@link ActionStatus} as a list of messages; while returning the messages, even though the messages from multiple {@link ActionStatus} + * are retrieved in descending order by the reported time ({@link ActionStatus#getOccurredAt()}), i.e. latest ActionStatus first, the + * sub-ordering of messages from within single {@link ActionStatus} is unspecified. * * @param actionId to be filtered on * @param messageCount is the number of messages to return from history @@ -374,10 +315,8 @@ public interface ControllerManagement { List getActionHistoryMessages(long actionId, int messageCount); /** - * Cancels given {@link Action} for this {@link Target}. However, it might - * be possible that the controller will continue to work on the - * cancellation. The controller needs to acknowledge or reject the - * cancellation using {@link DdiRootController#postCancelActionFeedback}. + * Cancels given {@link Action} for this {@link Target}. However, it might be possible that the controller will continue to work on the + * cancellation. The controller needs to acknowledge or reject the cancellation using {@link DdiRootController#postCancelActionFeedback}. * * @param actionId to be canceled * @return canceled {@link Action} @@ -425,8 +364,7 @@ public interface ControllerManagement { * Activate auto confirmation for a given controllerId * * @param controllerId to activate auto-confirmation on - * @param initiator can be set optionally (fallback is the current acting security - * user) + * @param initiator can be set optionally (fallback is the current acting security user) * @param remark (optional) remark * @return the persisted {@link AutoConfirmationStatus} */ @@ -452,4 +390,4 @@ public interface ControllerManagement { */ @PreAuthorize(SpringEvalExpressions.IS_CONTROLLER) boolean updateOfflineAssignedVersion(@NotEmpty String controllerId, String distributionName, String version); -} +} \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/SoftwareModuleManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/SoftwareModuleManagement.java index e90e7043d..5fe881260 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/SoftwareModuleManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/SoftwareModuleManagement.java @@ -11,6 +11,7 @@ package org.eclipse.hawkbit.repository; import java.util.Collection; import java.util.List; +import java.util.Map; import java.util.Optional; import jakarta.validation.Valid; @@ -173,8 +174,7 @@ public interface SoftwareModuleManagement * @throws EntityNotFoundException if software module with given ID does not exist */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY) - Page findMetaDataBySoftwareModuleIdAndTargetVisible(@NotNull Pageable pageable, - long id); + Page findMetaDataBySoftwareModuleIdAndTargetVisible(@NotNull Pageable pageable, long id); /** * Finds all meta data by the given software module id. @@ -190,8 +190,7 @@ public interface SoftwareModuleManagement * @throws EntityNotFoundException if software module with given ID does not exist */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY) - Page findMetaDataByRsql(@NotNull Pageable pageable, long id, - @NotNull String rsqlParam); + Page findMetaDataByRsql(@NotNull Pageable pageable, long id, @NotNull String rsqlParam); /** * Retrieves the {@link SoftwareModule}s by their {@link SoftwareModuleType} @@ -235,4 +234,7 @@ public interface SoftwareModuleManagement */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY) SoftwareModuleMetadata updateMetaData(@NotNull @Valid SoftwareModuleMetadataUpdate update); -} + + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY) + Map> findMetaDataBySoftwareModuleIdsAndTargetVisible(Collection moduleIds); +} \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/BaseEntity.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/BaseEntity.java index 627d77d03..10265016c 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/BaseEntity.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/BaseEntity.java @@ -29,8 +29,7 @@ public interface BaseEntity extends Serializable, Identifiable { String getCreatedBy(); /** - * @return time in {@link TimeUnit#MILLISECONDS} when the {@link BaseEntity} - * was created. + * @return time in {@link TimeUnit#MILLISECONDS} when the {@link BaseEntity} was created. */ long getCreatedAt(); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaActionManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaActionManagement.java index e79132e54..95fe7f1c3 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaActionManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaActionManagement.java @@ -13,10 +13,9 @@ import static org.eclipse.hawkbit.repository.model.Action.ActionType.DOWNLOAD_ON import static org.eclipse.hawkbit.repository.model.Action.Status.ERROR; import static org.eclipse.hawkbit.repository.model.Action.Status.FINISHED; -import java.util.ArrayList; import java.util.Comparator; import java.util.List; -import java.util.stream.Collectors; +import java.util.stream.Stream; import lombok.extern.slf4j.Slf4j; import org.eclipse.hawkbit.repository.QuotaManagement; @@ -25,7 +24,6 @@ import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; 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; @@ -34,7 +32,7 @@ import org.eclipse.hawkbit.repository.model.Action; 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.Sort; +import org.springframework.data.domain.Pageable; /** * Implements utility methods for managing {@link Action}s @@ -104,32 +102,19 @@ public class JpaActionManagement { quotaManagement.getMaxMessagesPerActionStatus(), "Message", ActionStatus.class.getSimpleName(), null); } - List findActiveActionsWithHighestWeightConsideringDefault(final String controllerId, - final int maxActionCount) { - final List actions = new ArrayList<>(); - actions.addAll( - actionRepository - .findAll( - ActionSpecifications.byTargetControllerIdAndActiveAndWeightIsNullFetchDS(controllerId, false), - PageRequest.of( - 0, maxActionCount, - Sort.by( - Sort.Order.desc(JpaAction_.WEIGHT), - Sort.Order.asc(JpaAction_.ID)))) - .getContent()); - - actions.addAll( - actionRepository - .findAll( - ActionSpecifications.byTargetControllerIdAndActiveAndWeightIsNullFetchDS(controllerId, true), - PageRequest.of( - 0, maxActionCount, - Sort.by( - Sort.Order.asc(JpaAction_.ID)))) - .getContent()); - final Comparator actionImportance = Comparator.comparingInt(this::getWeightConsideringDefault) - .reversed().thenComparing(Action::getId); - return actions.stream().sorted(actionImportance).limit(maxActionCount).collect(Collectors.toList()); + protected List 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(), + // get the oldest actions without weight + actionRepository.findFetchDsByTarget_ControllerIdAndActiveIsTrueAndWeightIsNullOrderByIdAsc(controllerId, pageable) + .stream()) + .sorted(Comparator.comparingInt(this::getWeightConsideringDefault).reversed().thenComparing(Action::getId)) + .limit(maxActionCount) + .toList(); } private static boolean isIntermediateStatus(final JpaActionStatus actionStatus) { diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaControllerManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaControllerManagement.java index a6187b609..3cb3cec0e 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaControllerManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaControllerManagement.java @@ -21,6 +21,7 @@ import java.time.temporal.ChronoUnit; import java.time.temporal.TemporalUnit; import java.util.Collection; import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -32,6 +33,7 @@ import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; +import java.util.stream.Stream; import jakarta.persistence.EntityManager; import jakarta.persistence.Query; @@ -250,8 +252,7 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont @Override public Map> findTargetVisibleMetaDataBySoftwareModuleId(final Collection moduleId) { return softwareModuleMetadataRepository - .findBySoftwareModuleIdInAndTargetVisible( - PageRequest.of(0, RepositoryConstants.MAX_META_DATA_COUNT), moduleId, true) + .findBySoftwareModuleIdInAndTargetVisible(moduleId, true, PageRequest.of(0, RepositoryConstants.MAX_META_DATA_COUNT)) .getContent().stream() .collect(Collectors.groupingBy(o -> (Long) o[0], Collectors.mapping(o -> (SoftwareModuleMetadata) o[1], Collectors.toList()))); } @@ -280,14 +281,23 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont return addActionStatus((JpaActionStatusCreate) statusCreate); } + private static final Pageable PAGEABLE_1 = PageRequest.of(0, 1); + @Override public Optional findActiveActionWithHighestWeight(final String controllerId) { - return findActiveActionsWithHighestWeight(controllerId, 1).stream().findFirst(); + return Stream.concat( + // get the highest action with weight + actionRepository + .findByTarget_ControllerIdAndActiveIsTrueAndWeightNotNullOrderByWeightDescIdAsc(controllerId, PAGEABLE_1) + .stream(), + // get the oldest action without weight + actionRepository.findByTarget_ControllerIdAndActiveIsTrueAndWeightIsNullOrderByIdAsc(controllerId, PAGEABLE_1).stream()) + .min(Comparator.comparingInt(this::getWeightConsideringDefault).reversed().thenComparing(Action::getId)); } @Override public List findActiveActionsWithHighestWeight(final String controllerId, final int maxActionCount) { - return findActiveActionsWithHighestWeightConsideringDefault(controllerId, maxActionCount); + return super.findActiveActionsWithHighestWeightConsideringDefault(controllerId, maxActionCount); } @Override @@ -453,7 +463,7 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont @Override public Optional get(final long targetId) { - return targetRepository.findById(targetId).map(t -> (Target) t); + return targetRepository.findById(targetId).map(Target.class::cast); } @Override @@ -463,18 +473,16 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont return Collections.emptyList(); } - // For negative and large value of messageCount, limit the number of - // messages. + // For negative and large value of messageCount, limit the number of messages. final int limit = messageCount < 0 || messageCount >= RepositoryConstants.MAX_ACTION_HISTORY_MSG_COUNT ? RepositoryConstants.MAX_ACTION_HISTORY_MSG_COUNT : messageCount; final PageRequest pageable = PageRequest.of(0, limit, Sort.by(Direction.DESC, "occurredAt")); - final Page messages = actionStatusRepository.findMessagesByActionIdAndMessageNotLike(pageable, actionId, - RepositoryConstants.SERVER_MESSAGE_PREFIX + "%"); + final Page messages = actionStatusRepository.findMessagesByActionIdAndMessageNotLike( + actionId, RepositoryConstants.SERVER_MESSAGE_PREFIX + "%", pageable); - log.debug("Retrieved {} message(s) from action history for action {}.", messages.getNumberOfElements(), - actionId); + log.debug("Retrieved {} message(s) from action history for action {}.", messages.getNumberOfElements(), actionId); return messages.getContent(); } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaRolloutGroupManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaRolloutGroupManagement.java index 389a589a7..6a3d6daf0 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaRolloutGroupManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaRolloutGroupManagement.java @@ -304,7 +304,7 @@ public class JpaRolloutGroupManagement implements RolloutGroupManagement { if (!rolloutGroupIds.isEmpty()) { final List resultList = actionRepository - .getStatusCountByRolloutGroupId(rolloutGroupIds); + .getStatusCountByRolloutGroupIds(rolloutGroupIds); final Map> fromDb = resultList.stream() .collect(Collectors.groupingBy(TotalTargetCountActionStatus::getId)); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaRolloutManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaRolloutManagement.java index c72f43c09..838285beb 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaRolloutManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaRolloutManagement.java @@ -711,8 +711,7 @@ public class JpaRolloutManagement implements RolloutManagement { .collect(Collectors.toList()); if (!rolloutIds.isEmpty()) { - final List resultList = actionRepository - .getStatusCountByRolloutId(rolloutIds); + final List resultList = actionRepository.getStatusCountByRolloutIds(rolloutIds); final Map> fromDb = resultList.stream() .collect(Collectors.groupingBy(TotalTargetCountActionStatus::getId)); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaSoftwareModuleManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaSoftwareModuleManagement.java index 878645a9c..359dcde58 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaSoftwareModuleManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaSoftwareModuleManagement.java @@ -411,7 +411,7 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement { assertSoftwareModuleExists(id); return JpaManagementHelper.convertPage(softwareModuleMetadataRepository.findBySoftwareModuleIdAndTargetVisible( - PageRequest.of(0, RepositoryConstants.MAX_META_DATA_COUNT), id, true), pageable); + id, true, PageRequest.of(0, RepositoryConstants.MAX_META_DATA_COUNT)), pageable); } @Override @@ -486,6 +486,14 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement { return softwareModuleMetadataRepository.save(metadata); } + @Override + public Map> findMetaDataBySoftwareModuleIdsAndTargetVisible(final Collection moduleIds) { + return softwareModuleMetadataRepository + .findBySoftwareModuleIdInAndTargetVisible(moduleIds, true, PageRequest.of(0, RepositoryConstants.MAX_META_DATA_COUNT)) + .getContent().stream() + .collect(Collectors.groupingBy(o -> (Long) o[0], Collectors.mapping(o -> (SoftwareModuleMetadata) o[1], Collectors.toList()))); + } + private static Stream createJpaMetadataCreateStream( final Collection create) { return create.stream().map(JpaSoftwareModuleMetadataCreate.class::cast); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaActionStatus.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaActionStatus.java index 22ae2b8a2..532212f94 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaActionStatus.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaActionStatus.java @@ -83,15 +83,14 @@ public class JpaActionStatus extends AbstractJpaTenantAwareBaseEntity implements @CollectionTable( name = "sp_action_status_messages", joinColumns = @JoinColumn( - name = "action_status_id", insertable = false, updatable = false, nullable = false, + name = "action_status_id", nullable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_stat_msg_act_stat")), - indexes = { - @Index(name = "sp_idx_action_status_msgs_01", columnList = "action_status_id") }) - @Column(name = "detail_message", length = MESSAGE_ENTRY_LENGTH, nullable = false, insertable = false, updatable = false) + indexes = { @Index(name = "sp_idx_action_status_msgs_01", columnList = "action_status_id") }) + @Column(name = "detail_message", length = MESSAGE_ENTRY_LENGTH, nullable = false) private List messages; @Setter - @Column(name = "code", nullable = true, updatable = false) + @Column(name = "code", updatable = false) private Integer code; /** diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaTarget.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaTarget.java index 953dcd00c..31eaefa87 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaTarget.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaTarget.java @@ -16,6 +16,7 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import jakarta.persistence.CascadeType; @@ -69,6 +70,8 @@ import org.eclipse.hawkbit.security.SystemSecurityContext; import org.eclipse.persistence.descriptors.DescriptorEvent; import org.eclipse.persistence.descriptors.DescriptorEventAdapter; import org.eclipse.persistence.queries.UpdateObjectQuery; +import org.hibernate.event.spi.PreUpdateEvent; +import org.hibernate.event.spi.PreUpdateEventListener; /** * JPA implementation of {@link Target}. @@ -174,12 +177,12 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw // no cascade option on an ElementCollection, the target objects are always persisted, merged, removed with their parent. @Getter @ElementCollection - @Column(name = "attribute_value", length = Target.CONTROLLER_ATTRIBUTE_VALUE_SIZE) - @MapKeyColumn(name = "attribute_key", nullable = false, length = Target.CONTROLLER_ATTRIBUTE_KEY_SIZE) @CollectionTable( name = "sp_target_attributes", - joinColumns = { @JoinColumn(name = "target_id", nullable = false, insertable = false, updatable = false) }, + joinColumns = { @JoinColumn(name = "target_id", nullable = false) }, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_targ_attrib_target")) + @Column(name = "attribute_value", length = Target.CONTROLLER_ATTRIBUTE_VALUE_SIZE) + @MapKeyColumn(name = "attribute_key", length = Target.CONTROLLER_ATTRIBUTE_KEY_SIZE) private Map controllerAttributes; @OneToMany(mappedBy = "target", fetch = FetchType.LAZY, cascade = { CascadeType.REMOVE }, targetEntity = JpaTargetMetadata.class) @@ -331,7 +334,7 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw /** * Listens to updates on {@link JpaTarget} entities, Filtering out updates that only change the "lastTargetQuery" or "address" fields. */ - public static class EntityPropertyChangeListener extends DescriptorEventAdapter { + public static class EntityPropertyChangeListener extends DescriptorEventAdapter implements PreUpdateEventListener { private static final List TARGET_UPDATE_EVENT_IGNORE_FIELDS = List.of( "lastTargetQuery", "address", // actual to be skipped @@ -346,5 +349,21 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw doNotify(() -> ((EventAwareEntity) object).fireUpdateEvent()); } } + + @Override + public boolean onPreUpdate(final PreUpdateEvent event) { + final Object[] oldState = event.getOldState(); + final Object[] newState = event.getState(); + for (int i = 0; i < newState.length; i++) { + if (!Objects.equals(oldState[i], newState[i])) { + final String attribute = event.getPersister().getAttributeMapping(i).getAttributeName(); + if (!TARGET_UPDATE_EVENT_IGNORE_FIELDS.contains(attribute)) { + doNotify(() -> ((EventAwareEntity) event.getEntity()).fireUpdateEvent()); + break; + } + } + } + return false; + } } } \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/ActionRepository.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/ActionRepository.java index c29f9065e..a0d6a2ab6 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/ActionRepository.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/ActionRepository.java @@ -52,15 +52,22 @@ public interface ActionRepository extends BaseEntityRepository { * @param targetId the action belongs to * @param dsId of the ds that is assigned to the target * @param status of the action - * @return action if there is one with assigned target and assigned - * {@link DistributionSet}. + * @return action if there is one with assigned target and assigned {@link DistributionSet}. */ - Optional findFirstByTargetIdAndDistributionSetIdAndStatusOrderByIdDesc(@Param("target") long targetId, - @Param("ds") Long dsId, @Param("status") Action.Status status); + Optional findFirstByTargetIdAndDistributionSetIdAndStatusOrderByIdDesc( + @Param("target") long targetId, @Param("ds") Long dsId, @Param("status") Action.Status status); + + List findByTarget_ControllerIdAndActiveIsTrueAndWeightIsNullOrderByIdAsc(String controllerId, Pageable pageable); + List findByTarget_ControllerIdAndActiveIsTrueAndWeightNotNullOrderByWeightDescIdAsc(String controllerId, Pageable pageable); + + @EntityGraph(value = "Action.ds", type = EntityGraphType.LOAD) + List findFetchDsByTarget_ControllerIdAndActiveIsTrueAndWeightIsNullOrderByIdAsc(String controllerId, Pageable pageable); + @EntityGraph(value = "Action.ds", type = EntityGraphType.LOAD) + List 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 a atomic operation. + * 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. *

* No access control applied * @@ -72,7 +79,8 @@ public interface ActionRepository extends BaseEntityRepository { @Modifying @Transactional @Query("UPDATE JpaAction a SET a.status = :statusToSet WHERE a.target.id IN :targetsIds AND a.active = :active AND a.status = :currentStatus AND a.distributionSet.requiredMigrationStep = false") - void switchStatus(@Param("statusToSet") Action.Status statusToSet, @Param("targetsIds") List targetIds, + void switchStatus( + @Param("statusToSet") Action.Status statusToSet, @Param("targetsIds") List targetIds, @Param("active") boolean active, @Param("currentStatus") Action.Status currentStatus); /** @@ -124,8 +132,7 @@ public interface ActionRepository extends BaseEntityRepository { Long countByDistributionSetIdAndActiveIsTrue(Long distributionSet); /** - * Counts all active {@link Action}s referring to the given DistributionSet - * that are not in a given state. + * Counts all active {@link Action}s referring to the given DistributionSet that are not in a given state. *

* No access control applied * @@ -136,43 +143,39 @@ public interface ActionRepository extends BaseEntityRepository { Long countByDistributionSetIdAndActiveIsTrueAndStatusIsNot(Long distributionSet, Status status); /** - * Counts all actions referring to a given rollout and rolloutgroup which - * are currently not in the given status. An in-clause statement does not - * work with the spring-data, so this is specific usecase regarding to the - * rollout-management to find out actions which are not in specific states. + * Counts all actions referring to a given rollout and rolloutgroup which are currently not in the given status. An in-clause statement + * does not work with the spring-data, so this is specific usecase regarding the rollout-management to find out actions which are not in + * specific states. *

* No access control applied * * @param rollout the rollout the actions are belong to - * @param rolloutGroup the rolloutgroup the actions are belong to + * @param rolloutGroup the rollout group the actions are belong to * @param statuses the list of statuses the action should not have - * @return the count of actions referring the rollout and rolloutgroup and - * are not in given states + * @return the count of actions referring the rollout and rollout group and are not in given states */ - Long countByRolloutAndRolloutGroupAndStatusNotIn(JpaRollout rollout, JpaRolloutGroup rolloutGroup, - List statuses); + Long countByRolloutAndRolloutGroupAndStatusNotIn(JpaRollout rollout, JpaRolloutGroup rolloutGroup, List statuses); /** - * Counts all actions referring to a given rollout and rolloutgroup. + * Counts all actions referring to a given rollout and rollout group. *

* No access control applied * * @param rollout the rollout the actions belong to - * @param rolloutGroup the rolloutgroup the actions belong to - * @return the count of actions referring to a rollout and rolloutgroup + * @param rolloutGroup the rollout group the actions belong to + * @return the count of actions referring to a rollout and rollout group */ Long countByRolloutAndRolloutGroup(JpaRollout rollout, JpaRolloutGroup rolloutGroup); /** - * Counts all actions referring to a given rollout, rolloutgroup and status. + * Counts all actions referring to a given rollout, rollout group and status. *

* No access control applied * * @param rolloutId the ID of rollout the actions belong to - * @param rolloutGroupId the ID rolloutgroup the actions belong to + * @param rolloutGroupId the ID rollout group the actions belong to * @param status the status the actions should have - * @return the count of actions referring to a rollout, rolloutgroup and are - * in a given status + * @return the count of actions referring to a rollout, rollout group and are in a given status */ Long countByRolloutIdAndRolloutGroupIdAndStatus(Long rolloutId, Long rolloutGroupId, Action.Status status); @@ -189,50 +192,43 @@ public interface ActionRepository extends BaseEntityRepository { Long countByRolloutIdAndStatus(Long rolloutId, Action.Status status); /** - * Returns {@code true} if actions for the given rollout exists, otherwise - * {@code false} + * Returns {@code true} if actions for the given rollout exists, otherwise {@code false} *

* No access control applied * * @param rolloutId the ID of the rollout the actions belong to - * @return {@code true} if actions for the given rollout exists, otherwise - * {@code false} + * @return {@code true} if actions for the given rollout exists, otherwise {@code false} */ @Query("SELECT CASE WHEN COUNT(a)>0 THEN 'true' ELSE 'false' END FROM JpaAction a WHERE a.rollout.id=:rolloutId") boolean existsByRolloutId(@Param("rolloutId") Long rolloutId); /** - * Returns {@code true} if actions for the given rollout exists, otherwise - * {@code false} + * Returns {@code true} if actions for the given rollout exists, otherwise {@code false} *

* No access control applied * * @param rolloutId the ID of the rollout the actions belong to * @param status the action is not to be in - * @return {@code true} if actions for the given rollout exists, otherwise - * {@code false} + * @return {@code true} if actions for the given rollout exists, otherwise {@code false} */ @Query("SELECT CASE WHEN COUNT(a)>0 THEN 'true' ELSE 'false' END FROM JpaAction a WHERE a.rollout.id=:rolloutId AND a.status != :status") boolean existsByRolloutIdAndStatusNotIn(@Param("rolloutId") Long rolloutId, @Param("status") Status status); /** - * Retrieving all actions referring to a given rollout with a specific action as - * parent reference and a specific status. + * Retrieving all actions referring to a given rollout with a specific action as parent reference and a specific status. *

- * Finding all actions of a specific rolloutgroup parent relation. + * Finding all actions of a specific rollout group parent relation. *

* No access control applied * * @param pageable page parameters * @param rollout the rollout the actions belong to - * @param rolloutGroupParent the parent rolloutgroup the actions should reference + * @param rolloutGroupParent the parent rollout group the actions should reference * @param actionStatus the status the actions have - * @return the actions referring a specific rollout and a specific parent - * rolloutgroup in a specific status + * @return the actions referring a specific rollout and a specific parent rolloutgroup in a specific status */ @EntityGraph(attributePaths = { "target", "target.autoConfirmationStatus", "rolloutGroup" }, type = EntityGraphType.LOAD) - Page findByRolloutIdAndRolloutGroupParentIdAndStatus(Pageable pageable, Long rollout, - Long rolloutGroupParent, Status actionStatus); + Page findByRolloutIdAndRolloutGroupParentIdAndStatus(Pageable pageable, Long rollout, Long rolloutGroupParent, Status actionStatus); /** * Retrieving all actions referring to the first group of a rollout. @@ -242,13 +238,10 @@ public interface ActionRepository extends BaseEntityRepository { * @param pageable page parameters * @param rollout the rollout the actions belong to * @param actionStatus the status the actions have - * @return the actions referring a specific rollout and a specific parent - * rolloutgroup in a specific status + * @return the actions referring a specific rollout and a specific parent rolloutgroup in a specific status */ - @EntityGraph(attributePaths = { "target", "target.autoConfirmationStatus", - "rolloutGroup" }, type = EntityGraphType.LOAD) - Page findByRolloutIdAndRolloutGroupParentIsNullAndStatus(Pageable pageable, Long rollout, - Status actionStatus); + @EntityGraph(attributePaths = { "target", "target.autoConfirmationStatus", "rolloutGroup" }, type = EntityGraphType.LOAD) + Page findByRolloutIdAndRolloutGroupParentIsNullAndStatus(Pageable pageable, Long rollout, Status actionStatus); /** * Retrieves all actions for a specific rollout and in a specific status. @@ -263,44 +256,40 @@ public interface ActionRepository extends BaseEntityRepository { Page findByRolloutIdAndStatus(Pageable pageable, Long rolloutId, Status actionStatus); /** - * Get list of objects which has details of status and count of targets in - * each status in specified rollout. + * Get list of objects which has details of status and count of targets in each status in specified rollout. *

* No access control applied * * @param rolloutId id of {@link Rollout} * @return list of objects with status and target count */ - @Query("SELECT NEW org.eclipse.hawkbit.repository.model.TotalTargetCountActionStatus( a.rollout.id, a.status , COUNT(a.id)) FROM JpaAction a WHERE a.rollout.id IN ?1 GROUP BY a.rollout.id,a.status") - List getStatusCountByRolloutId(List rolloutId); - - /** - * Get list of objects which has details of status and count of targets in - * each status in specified rollout. - *

- * No access control applied - * - * @param rolloutId id of {@link Rollout} - * @return list of objects with status and target count - */ - @Query("SELECT NEW org.eclipse.hawkbit.repository.model.TotalTargetCountActionStatus( a.rollout.id, a.status , COUNT(a.id)) FROM JpaAction a WHERE a.rollout.id = ?1 GROUP BY a.rollout.id,a.status") + @Query("SELECT NEW org.eclipse.hawkbit.repository.model.TotalTargetCountActionStatus(a.rollout.id, a.status, COUNT(a.id)) FROM JpaAction a WHERE a.rollout.id = ?1 GROUP BY a.rollout.id,a.status") List getStatusCountByRolloutId(Long rolloutId); /** - * Get list of objects which has details of status and count of targets in - * each status in specified rollout group. + * Get list of objects which has details of status and count of targets in each status in specified rollout. + *

+ * No access control applied + * + * @param rolloutId id of {@link Rollout} + * @return list of objects with status and target count + */ + @Query("SELECT NEW org.eclipse.hawkbit.repository.model.TotalTargetCountActionStatus(a.rollout.id, a.status, COUNT(a.id)) FROM JpaAction a WHERE a.rollout.id IN ?1 GROUP BY a.rollout.id,a.status") + List getStatusCountByRolloutIds(List rolloutId); + + /** + * Get list of objects which has details of status and count of targets in each status in specified rollout group. *

* No access control applied * * @param rolloutGroupId id of {@link RolloutGroup} * @return list of objects with status and target count */ - @Query("SELECT NEW org.eclipse.hawkbit.repository.model.TotalTargetCountActionStatus(a.rolloutGroup.id, a.status , COUNT(a.id)) FROM JpaAction a WHERE a.rolloutGroup.id = ?1 GROUP BY a.rolloutGroup.id, a.status") + @Query("SELECT NEW org.eclipse.hawkbit.repository.model.TotalTargetCountActionStatus(a.rolloutGroup.id, a.status, COUNT(a.id)) FROM JpaAction a WHERE a.rolloutGroup.id = ?1 GROUP BY a.rolloutGroup.id, a.status") List getStatusCountByRolloutGroupId(Long rolloutGroupId); /** - * Get list of objects which has details of status and count of targets in - * each status in specified rollout group. + * Get list of objects which has details of status and count of targets in each status in specified rollout group. *

* No access control applied * @@ -308,7 +297,7 @@ public interface ActionRepository extends BaseEntityRepository { * @return list of objects with status and target count */ @Query("SELECT NEW org.eclipse.hawkbit.repository.model.TotalTargetCountActionStatus(a.rolloutGroup.id, a.status , COUNT(a.id)) FROM JpaAction a WHERE a.rolloutGroup.id IN ?1 GROUP BY a.rolloutGroup.id, a.status") - List getStatusCountByRolloutGroupId(List rolloutGroupId); + List getStatusCountByRolloutGroupIds(List rolloutGroupId); /** * Updates the externalRef of an action by its actionId. @@ -331,4 +320,4 @@ public interface ActionRepository extends BaseEntityRepository { // Workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=349477 @Query("DELETE FROM JpaAction a WHERE a.id IN ?1") void deleteByIdIn(Collection actionIDs); -} +} \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/ActionStatusRepository.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/ActionStatusRepository.java index 408024990..0ac873dc2 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/ActionStatusRepository.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/ActionStatusRepository.java @@ -52,13 +52,11 @@ public interface ActionStatusRepository extends BaseEntityRepository * No access control applied * - * @param pageable for page configuration * @param actionId for which to get the status messages - * @param filter is the SQL like pattern to use for filtering out or excluding - * the messages + * @param filter is the SQL like pattern to use for filtering out or excluding the messages + * @param pageable for page configuration * @return Page with found status messages. */ @Query("SELECT message FROM JpaActionStatus actionstatus JOIN actionstatus.messages message WHERE actionstatus.action.id = :actionId AND message NOT LIKE :filter") - Page findMessagesByActionIdAndMessageNotLike(Pageable pageable, @Param("actionId") Long actionId, - @Param("filter") String filter); -} + Page findMessagesByActionIdAndMessageNotLike(@Param("actionId") Long actionId, @Param("filter") String filter, Pageable pageable); +} \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/BaseEntityRepositoryACM.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/BaseEntityRepositoryACM.java index b35dad6f4..320f65740 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/BaseEntityRepositoryACM.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/BaseEntityRepositoryACM.java @@ -133,8 +133,7 @@ public class BaseEntityRepositoryACM @Override public void deleteByTenant(final String tenant) { if (accessController.getAccessRules(AccessController.Operation.DELETE).isPresent()) { - throw new InsufficientPermissionException( - "DELETE operation has restriction for given context! deleteAll can't be executed!"); + throw new InsufficientPermissionException("DELETE operation has restriction for given context! deleteAll can't be executed!"); } repository.deleteByTenant(tenant); } @@ -185,8 +184,7 @@ public class BaseEntityRepositoryACM } @Override - public R findBy(final Specification spec, - final Function, R> queryFunction) { + public R findBy(final Specification spec, final Function, R> queryFunction) { return repository.findBy(accessController.appendAccessRules(AccessController.Operation.READ, spec), queryFunction); } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/SoftwareModuleMetadataRepository.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/SoftwareModuleMetadataRepository.java index 46ecb4838..9abcff017 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/SoftwareModuleMetadataRepository.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/SoftwareModuleMetadataRepository.java @@ -33,40 +33,37 @@ public interface SoftwareModuleMetadataRepository JpaSpecificationExecutor { /** - * Locates the meta data entries that match the given software module ID and - * target visibility flag. + * Locates the meta-data entries that match the given software module ID and target visibility flag. * - * @param page The pagination parameters. * @param moduleId The ID of the software module. * @param targetVisible The target visibility flag. - * @return A {@link Page} with the matching meta data entries. + * @param page The pagination parameters. + * @return A {@link Page} with the matching meta-data entries. */ - Page findBySoftwareModuleIdAndTargetVisible(Pageable page, Long moduleId, - boolean targetVisible); + Page findBySoftwareModuleIdAndTargetVisible( + @Param("moduleId") Long moduleId, @Param("targetVisible") boolean targetVisible, Pageable page); /** - * Locates the meta data entries that match the given software module IDs - * and target visibility flag. + * Locates the meta-data entries that match the given software module IDs and target visibility flag. *

* No access control applied * - * @param page The pagination parameters. * @param moduleId List of software module IDs. * @param targetVisible The target visibility flag. - * @return A {@link Page} with the matching meta data entries. + * @param page The pagination parameters. + * @return A {@link Page} with the matching meta-data entries. */ @Query("SELECT smd.softwareModule.id, smd FROM JpaSoftwareModuleMetadata smd WHERE smd.softwareModule.id IN :moduleId AND smd.targetVisible = :targetVisible") - Page findBySoftwareModuleIdInAndTargetVisible(Pageable page, @Param("moduleId") Collection moduleId, - @Param("targetVisible") boolean targetVisible); + Page findBySoftwareModuleIdInAndTargetVisible( + @Param("moduleId") Collection moduleId, @Param("targetVisible") boolean targetVisible, Pageable page); /** - * Counts the meta data entries that are associated with the addressed - * software module. + * Counts the meta-data entries that are associated with the addressed software module. *

* No access control applied * * @param moduleId The ID of the software module. - * @return The number of meta data entries associated with the software module. + * @return The number of meta-data entries associated with the software module. */ long countBySoftwareModuleId(@Param("moduleId") Long moduleId); -} +} \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/ActionSpecifications.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/ActionSpecifications.java index 8b5a6581f..b562517d2 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/ActionSpecifications.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/ActionSpecifications.java @@ -12,7 +12,6 @@ package org.eclipse.hawkbit.repository.jpa.specifications; import java.util.List; import jakarta.persistence.criteria.Join; -import jakarta.persistence.criteria.JoinType; import jakarta.persistence.criteria.ListJoin; import jakarta.persistence.criteria.SetJoin; @@ -82,18 +81,13 @@ public final class ActionSpecifications { * Fetches action's distribution set. * * @param controllerId controller id - * @param isNull if true return with null weight, otherwise with non-null * @return the matching action s. */ - public static Specification byTargetControllerIdAndActiveAndWeightIsNullFetchDS(final String controllerId, - final boolean isNull) { - return (root, query, cb) -> { - root.fetch(JpaAction_.distributionSet, JoinType.LEFT); - return cb.and( + public static Specification byTargetControllerIdAndActive(final String controllerId) { + return (root, query, cb) -> + cb.and( 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))); - }; + cb.equal(root.get(JpaAction_.active), true)); } public static Specification byDistributionSetId(final Long distributionSetId) { @@ -158,4 +152,4 @@ public final class ActionSpecifications { criteriaBuilder.equal(actionRoot.get(JpaAction_.target).get(JpaTarget_.id), targetId)); }; } -} +} \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/ControllerManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/ControllerManagementTest.java index 39b2867b0..df813df60 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/ControllerManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/ControllerManagementTest.java @@ -967,38 +967,29 @@ class ControllerManagementTest extends AbstractJpaIntegrationTest { final Long actionId = createTargetAndAssignDs(); // test and verify - controllerManagement - .addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.RUNNING)); - assertActionStatus(actionId, DEFAULT_CONTROLLER_ID, TargetUpdateStatus.PENDING, Action.Status.RUNNING, - Action.Status.RUNNING, true); + controllerManagement.addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.RUNNING)); + assertActionStatus(actionId, DEFAULT_CONTROLLER_ID, TargetUpdateStatus.PENDING, Action.Status.RUNNING, Action.Status.RUNNING, true); - controllerManagement - .addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.ERROR)); - assertActionStatus(actionId, DEFAULT_CONTROLLER_ID, TargetUpdateStatus.ERROR, Action.Status.ERROR, - Action.Status.ERROR, false); + controllerManagement.addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.ERROR)); + assertActionStatus(actionId, DEFAULT_CONTROLLER_ID, TargetUpdateStatus.ERROR, Action.Status.ERROR, Action.Status.ERROR, false); // try with disabled late feedback repositoryProperties.setRejectActionStatusForClosedAction(true); - controllerManagement - .addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.FINISHED)); + controllerManagement.addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.FINISHED)); // test - assertActionStatus(actionId, DEFAULT_CONTROLLER_ID, TargetUpdateStatus.ERROR, Action.Status.ERROR, - Action.Status.ERROR, false); + assertActionStatus(actionId, DEFAULT_CONTROLLER_ID, TargetUpdateStatus.ERROR, Action.Status.ERROR, Action.Status.ERROR, false); // try with enabled late feedback - should not make a difference as it // only allows intermediate feedback and not multiple close repositoryProperties.setRejectActionStatusForClosedAction(false); - controllerManagement - .addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.FINISHED)); + controllerManagement.addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Action.Status.FINISHED)); // test - assertActionStatus(actionId, DEFAULT_CONTROLLER_ID, TargetUpdateStatus.ERROR, Action.Status.ERROR, - Action.Status.ERROR, false); + assertActionStatus(actionId, DEFAULT_CONTROLLER_ID, TargetUpdateStatus.ERROR, Action.Status.ERROR, Action.Status.ERROR, false); assertThat(actionStatusRepository.count()).isEqualTo(3); assertThat(controllerManagement.findActionStatusByAction(PAGE, actionId).getNumberOfElements()).isEqualTo(3); - } @Test @@ -1716,27 +1707,24 @@ class ControllerManagementTest extends AbstractJpaIntegrationTest { actionStatus, true); } - private void assertActionStatus(final Long actionId, final String controllerId, + private void assertActionStatus( + final Long actionId, final String controllerId, final TargetUpdateStatus expectedTargetUpdateStatus, final Action.Status expectedActionActionStatus, final Action.Status expectedActionStatus, final boolean actionActive) { - final TargetUpdateStatus targetStatus = targetManagement.getByControllerID(controllerId).get() - .getUpdateStatus(); + final TargetUpdateStatus targetStatus = targetManagement.getByControllerID(controllerId).get().getUpdateStatus(); assertThat(targetStatus).isEqualTo(expectedTargetUpdateStatus); final Action action = deploymentManagement.findAction(actionId).get(); assertThat(action.getStatus()).isEqualTo(expectedActionActionStatus); assertThat(action.isActive()).isEqualTo(actionActive); - final List actionStatusList = controllerManagement.findActionStatusByAction(PAGE, actionId) - .getContent(); + final List actionStatusList = controllerManagement.findActionStatusByAction(PAGE, actionId).getContent(); assertThat(actionStatusList.get(actionStatusList.size() - 1).getStatus()).isEqualTo(expectedActionStatus); if (actionActive) { - assertThat(controllerManagement.findActiveActionWithHighestWeight(controllerId).get().getId()) - .isEqualTo(actionId); + assertThat(controllerManagement.findActiveActionWithHighestWeight(controllerId).get().getId()).isEqualTo(actionId); } } private void createTargetType(String targetTypeName) { - systemSecurityContext.runAsSystem(() -> - targetTypeManagement.create(entityFactory.targetType().create().name(targetTypeName))); + systemSecurityContext.runAsSystem(() -> targetTypeManagement.create(entityFactory.targetType().create().name(targetTypeName))); } @Step diff --git a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/matcher/EventVerifier.java b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/matcher/EventVerifier.java index 21ba23f9b..9bc89cb54 100644 --- a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/matcher/EventVerifier.java +++ b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/matcher/EventVerifier.java @@ -72,6 +72,12 @@ public class EventVerifier extends AbstractTestExecutionListener { @Override public void afterTestMethod(final TestContext testContext) { + if (testContext.getTestException() != null) { + // test has failed anyway + // not expected event set could be result of failed test / incomplete steps - no need to check and mess up with real exception + return; + } + final Optional expectedEvents = getExpectationsFrom(testContext.getTestMethod()); try { expectedEvents.ifPresent(this::afterTest);