Refactor action repository (#2118)

Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
Avgustin Marinov
2024-12-05 11:41:41 +02:00
committed by GitHub
parent 1d52d3b102
commit 39861e7790
21 changed files with 318 additions and 393 deletions

View File

@@ -144,9 +144,7 @@ public class DdiRootController implements DdiRootControllerRestApi {
}
@Override
public ResponseEntity<DdiControllerBase> getControllerBase(
final String tenant,
final String controllerId) {
public ResponseEntity<DdiControllerBase> getControllerBase(final String tenant, final String controllerId) {
log.debug("getControllerBase({})", controllerId);
final Target target = controllerManagement.findOrRegisterTargetIfItDoesNotExist(controllerId, IpUtil

View File

@@ -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}.
* <p/>
* 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<Action> actions,
final Function<Action, Map<SoftwareModule, List<SoftwareModuleMetadata>>> 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<SoftwareModule, List<SoftwareModuleMetadata>> 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<SoftwareModule, List<SoftwareModuleMetadata>> softwareModules) {
final DmfConfirmRequest request = new DmfConfirmRequest();
@@ -325,6 +295,33 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
return request;
}
void sendMultiActionRequestToTarget(
final Target target, final List<Action> actions,
final Function<SoftwareModule, List<SoftwareModuleMetadata>> 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> 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<String> controllerIds) {
final Map<SoftwareModule, List<SoftwareModuleMetadata>> softwareModuleMetadata = new HashMap<>();
targetManagement.getByControllerID(controllerIds).stream()
.filter(target -> IpUtil.isAmqpUri(target.getAddress())).forEach(target -> {
final List<Action> activeActions = deploymentManagement
.findActiveActionsWithHighestWeight(target.getControllerId(), MAX_ACTION_COUNT);
private void sendMultiActionRequestMessages(final List<String> controllerIds) {
final Map<String, List<Action>> 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<Long> allSmIds = controllerIdToActions.values().stream()
.flatMap(actions -> actions.stream()
.map(Action::getDistributionSet)
.flatMap(ds -> ds.getModules().stream())
.map(SoftwareModule::getId))
.collect(Collectors.toSet());
final Map<Long, List<SoftwareModuleMetadata>> 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<SoftwareModule, List<SoftwareModuleMetadata>> 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<SoftwareModuleMetadata> getSoftwareModuleMetadata(final SoftwareModule module) {

View File

@@ -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<Action> actions = controllerManagement.findActiveActionsWithHighestWeight(target.getControllerId(), MAX_ACTION_COUNT);
final Set<DistributionSet> distributionSets = actions.stream().map(Action::getDistributionSet).collect(Collectors.toSet());
final Map<Long, Map<SoftwareModule, List<SoftwareModuleMetadata>>> softwareModulesPerDistributionSet = distributionSets
.stream().collect(Collectors.toMap(DistributionSet::getId, this::getSoftwareModulesWithMetadata));
// gets all software modules for all action at once
final Set<Long> allSmIds = actions.stream()
.map(Action::getDistributionSet)
.flatMap(ds -> ds.getModules().stream())
.map(SoftwareModule::getId)
.collect(Collectors.toSet());
final Map<Long, List<SoftwareModuleMetadata>> 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<Action> 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<SoftwareModule, List<SoftwareModuleMetadata>> getSoftwareModulesWithMetadata(final DistributionSet distributionSet) {
final List<Long> smIds = distributionSet.getModules().stream().map(SoftwareModule::getId)
.collect(Collectors.toList());
final Map<Long, List<SoftwareModuleMetadata>> metadata = controllerManagement
.findTargetVisibleMetaDataBySoftwareModuleId(smIds);
return distributionSet.getModules().stream()
.collect(Collectors.toMap(sm -> sm, sm -> metadata.getOrDefault(sm.getId(), Collections.emptyList())));
final List<Long> smIds = distributionSet.getModules().stream().map(SoftwareModule::getId).collect(Collectors.toList());
final Map<Long, List<SoftwareModuleMetadata>> metadata = controllerManagement.findTargetVisibleMetaDataBySoftwareModuleId(smIds);
return distributionSet.getModules().stream().collect(Collectors.toMap(
Function.identity(), sm -> metadata.getOrDefault(sm.getId(), Collections.emptyList())));
}

View File

@@ -465,8 +465,9 @@ public class AmqpMessageDispatcherServiceIntegrationTest extends AbstractAmqpSer
assertLatestMultiActionMessageContainsInstallMessages(controllerId, Arrays.asList(smIds1, smIds2, smIds1));
final List<Long> 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);

View File

@@ -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<SoftwareModule> 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<Long, List<SoftwareModuleMetadata>> findTargetVisibleMetaDataBySoftwareModuleId(
@NotNull Collection<Long> moduleId);
Map<Long, List<SoftwareModuleMetadata>> findTargetVisibleMetaDataBySoftwareModuleId(@NotNull Collection<Long> 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<Action> 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<Action> 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<ActionStatus> 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<Action> 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<String, String> 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<Target> 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<Target> 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<String> 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);
}
}

View File

@@ -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<SoftwareModuleMetadata> findMetaDataBySoftwareModuleIdAndTargetVisible(@NotNull Pageable pageable,
long id);
Page<SoftwareModuleMetadata> 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<SoftwareModuleMetadata> findMetaDataByRsql(@NotNull Pageable pageable, long id,
@NotNull String rsqlParam);
Page<SoftwareModuleMetadata> 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<Long, List<SoftwareModuleMetadata>> findMetaDataBySoftwareModuleIdsAndTargetVisible(Collection<Long> moduleIds);
}

View File

@@ -29,8 +29,7 @@ public interface BaseEntity extends Serializable, Identifiable<Long> {
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();

View File

@@ -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<Action> findActiveActionsWithHighestWeightConsideringDefault(final String controllerId,
final int maxActionCount) {
final List<Action> 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<Action> actionImportance = Comparator.comparingInt(this::getWeightConsideringDefault)
.reversed().thenComparing(Action::getId);
return actions.stream().sorted(actionImportance).limit(maxActionCount).collect(Collectors.toList());
protected List<Action> findActiveActionsWithHighestWeightConsideringDefault(final String controllerId, final int maxActionCount) {
final Pageable pageable = PageRequest.of(0, maxActionCount);
return Stream.concat(
// get the highest actions with weight
actionRepository
.findFetchDsByTarget_ControllerIdAndActiveIsTrueAndWeightNotNullOrderByWeightDescIdAsc(controllerId, pageable)
.stream(),
// 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) {

View File

@@ -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<Long, List<SoftwareModuleMetadata>> findTargetVisibleMetaDataBySoftwareModuleId(final Collection<Long> 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<Action> 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<Action> 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<Target> 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<String> messages = actionStatusRepository.findMessagesByActionIdAndMessageNotLike(pageable, actionId,
RepositoryConstants.SERVER_MESSAGE_PREFIX + "%");
final Page<String> 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();
}

View File

@@ -304,7 +304,7 @@ public class JpaRolloutGroupManagement implements RolloutGroupManagement {
if (!rolloutGroupIds.isEmpty()) {
final List<TotalTargetCountActionStatus> resultList = actionRepository
.getStatusCountByRolloutGroupId(rolloutGroupIds);
.getStatusCountByRolloutGroupIds(rolloutGroupIds);
final Map<Long, List<TotalTargetCountActionStatus>> fromDb = resultList.stream()
.collect(Collectors.groupingBy(TotalTargetCountActionStatus::getId));

View File

@@ -711,8 +711,7 @@ public class JpaRolloutManagement implements RolloutManagement {
.collect(Collectors.toList());
if (!rolloutIds.isEmpty()) {
final List<TotalTargetCountActionStatus> resultList = actionRepository
.getStatusCountByRolloutId(rolloutIds);
final List<TotalTargetCountActionStatus> resultList = actionRepository.getStatusCountByRolloutIds(rolloutIds);
final Map<Long, List<TotalTargetCountActionStatus>> fromDb = resultList.stream()
.collect(Collectors.groupingBy(TotalTargetCountActionStatus::getId));

View File

@@ -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<Long, List<SoftwareModuleMetadata>> findMetaDataBySoftwareModuleIdsAndTargetVisible(final Collection<Long> 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<JpaSoftwareModuleMetadataCreate> createJpaMetadataCreateStream(
final Collection<SoftwareModuleMetadataCreate> create) {
return create.stream().map(JpaSoftwareModuleMetadataCreate.class::cast);

View File

@@ -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<String> messages;
@Setter
@Column(name = "code", nullable = true, updatable = false)
@Column(name = "code", updatable = false)
private Integer code;
/**

View File

@@ -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<String, String> 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<String> 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;
}
}
}

View File

@@ -52,15 +52,22 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction> {
* @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<Action> findFirstByTargetIdAndDistributionSetIdAndStatusOrderByIdDesc(@Param("target") long targetId,
@Param("ds") Long dsId, @Param("status") Action.Status status);
Optional<Action> findFirstByTargetIdAndDistributionSetIdAndStatusOrderByIdDesc(
@Param("target") long targetId, @Param("ds") Long dsId, @Param("status") Action.Status status);
List<Action> findByTarget_ControllerIdAndActiveIsTrueAndWeightIsNullOrderByIdAsc(String controllerId, Pageable pageable);
List<Action> findByTarget_ControllerIdAndActiveIsTrueAndWeightNotNullOrderByWeightDescIdAsc(String controllerId, Pageable pageable);
@EntityGraph(value = "Action.ds", type = EntityGraphType.LOAD)
List<Action> findFetchDsByTarget_ControllerIdAndActiveIsTrueAndWeightIsNullOrderByIdAsc(String controllerId, Pageable pageable);
@EntityGraph(value = "Action.ds", type = EntityGraphType.LOAD)
List<Action> findFetchDsByTarget_ControllerIdAndActiveIsTrueAndWeightNotNullOrderByWeightDescIdAsc(String controllerId, Pageable pageable);
/**
* Switches the status of actions from one specific status into another, only if
* the actions are in a specific status. This should be 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.
* <p/>
* No access control applied
*
@@ -72,7 +79,8 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction> {
@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<Long> targetIds,
void switchStatus(
@Param("statusToSet") Action.Status statusToSet, @Param("targetsIds") List<Long> targetIds,
@Param("active") boolean active, @Param("currentStatus") Action.Status currentStatus);
/**
@@ -124,8 +132,7 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction> {
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.
* <p/>
* No access control applied
*
@@ -136,43 +143,39 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction> {
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.
* <p/>
* 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<Status> statuses);
Long countByRolloutAndRolloutGroupAndStatusNotIn(JpaRollout rollout, JpaRolloutGroup rolloutGroup, List<Status> statuses);
/**
* Counts all actions referring to a given rollout and rolloutgroup.
* Counts all actions referring to a given rollout and rollout group.
* <p/>
* 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.
* <p/>
* 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<JpaAction> {
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}
* <p/>
* 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}
* <p/>
* 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.
* <p/>
* Finding all actions of a specific rolloutgroup parent relation.
* Finding all actions of a specific rollout group parent relation.
* <p/>
* 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<Action> findByRolloutIdAndRolloutGroupParentIdAndStatus(Pageable pageable, Long rollout,
Long rolloutGroupParent, Status actionStatus);
Page<Action> 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<JpaAction> {
* @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<Action> findByRolloutIdAndRolloutGroupParentIsNullAndStatus(Pageable pageable, Long rollout,
Status actionStatus);
@EntityGraph(attributePaths = { "target", "target.autoConfirmationStatus", "rolloutGroup" }, type = EntityGraphType.LOAD)
Page<Action> 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<JpaAction> {
Page<JpaAction> 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.
* <p/>
* 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<TotalTargetCountActionStatus> getStatusCountByRolloutId(List<Long> rolloutId);
/**
* Get list of objects which has details of status and count of targets in
* each status in specified rollout.
* <p/>
* 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<TotalTargetCountActionStatus> 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.
* <p/>
* 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<TotalTargetCountActionStatus> getStatusCountByRolloutIds(List<Long> rolloutId);
/**
* Get list of objects which has details of status and count of targets in each status in specified rollout group.
* <p/>
* 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<TotalTargetCountActionStatus> 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.
* <p/>
* No access control applied
*
@@ -308,7 +297,7 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction> {
* @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<TotalTargetCountActionStatus> getStatusCountByRolloutGroupId(List<Long> rolloutGroupId);
List<TotalTargetCountActionStatus> getStatusCountByRolloutGroupIds(List<Long> rolloutGroupId);
/**
* Updates the externalRef of an action by its actionId.
@@ -331,4 +320,4 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction> {
// 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<Long> actionIDs);
}
}

View File

@@ -52,13 +52,11 @@ public interface ActionStatusRepository extends BaseEntityRepository<JpaActionSt
* <p/>
* 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<String> findMessagesByActionIdAndMessageNotLike(Pageable pageable, @Param("actionId") Long actionId,
@Param("filter") String filter);
}
Page<String> findMessagesByActionIdAndMessageNotLike(@Param("actionId") Long actionId, @Param("filter") String filter, Pageable pageable);
}

View File

@@ -133,8 +133,7 @@ public class BaseEntityRepositoryACM<T extends AbstractJpaTenantAwareBaseEntity>
@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<T extends AbstractJpaTenantAwareBaseEntity>
}
@Override
public <S extends T, R> R findBy(final Specification<T> spec,
final Function<FluentQuery.FetchableFluentQuery<S>, R> queryFunction) {
public <S extends T, R> R findBy(final Specification<T> spec, final Function<FluentQuery.FetchableFluentQuery<S>, R> queryFunction) {
return repository.findBy(accessController.appendAccessRules(AccessController.Operation.READ, spec), queryFunction);
}

View File

@@ -33,40 +33,37 @@ public interface SoftwareModuleMetadataRepository
JpaSpecificationExecutor<JpaSoftwareModuleMetadata> {
/**
* 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<JpaSoftwareModuleMetadata> findBySoftwareModuleIdAndTargetVisible(Pageable page, Long moduleId,
boolean targetVisible);
Page<JpaSoftwareModuleMetadata> 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.
* <p/>
* 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<Object[]> findBySoftwareModuleIdInAndTargetVisible(Pageable page, @Param("moduleId") Collection<Long> moduleId,
@Param("targetVisible") boolean targetVisible);
Page<Object[]> findBySoftwareModuleIdInAndTargetVisible(
@Param("moduleId") Collection<Long> 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.
* <p/>
* 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);
}
}

View File

@@ -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 <code>true</code> return with <code>null</code> weight, otherwise with non-<code>null</code>
* @return the matching action s.
*/
public static Specification<JpaAction> byTargetControllerIdAndActiveAndWeightIsNullFetchDS(final String controllerId,
final boolean isNull) {
return (root, query, cb) -> {
root.fetch(JpaAction_.distributionSet, JoinType.LEFT);
return cb.and(
public static Specification<JpaAction> 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<JpaAction> byDistributionSetId(final Long distributionSetId) {
@@ -158,4 +152,4 @@ public final class ActionSpecifications {
criteriaBuilder.equal(actionRoot.get(JpaAction_.target).get(JpaTarget_.id), targetId));
};
}
}
}

View File

@@ -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<ActionStatus> actionStatusList = controllerManagement.findActionStatusByAction(PAGE, actionId)
.getContent();
final List<ActionStatus> 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

View File

@@ -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<Expect[]> expectedEvents = getExpectationsFrom(testContext.getTestMethod());
try {
expectedEvents.ifPresent(this::afterTest);