Introduce soft deleted list option for soft deletable entities (#3093)
* Introduce soft deleted list option for soft deletable entities Signed-off-by: strailov <Stanislav.Trailov@bosch.io> * fix verify build Signed-off-by: strailov <Stanislav.Trailov@bosch.io> * fix typo in license Signed-off-by: strailov <Stanislav.Trailov@bosch.io> * Add sorting option on deleted field Signed-off-by: strailov <Stanislav.Trailov@bosch.io> * add missing import in tests Signed-off-by: strailov <Stanislav.Trailov@bosch.io> * Rename SoftDeletedFilter to SoftDeletedMode and its values Signed-off-by: strailov <Stanislav.Trailov@bosch.io> * Introduce MgmtSoftDeletedMode on api layer Signed-off-by: strailov <Stanislav.Trailov@bosch.io> * remove unused imports Signed-off-by: strailov <Stanislav.Trailov@bosch.io> * Integrate the enum on API layer Signed-off-by: strailov <Stanislav.Trailov@bosch.io> * Fix OpenApi spec Signed-off-by: strailov <Stanislav.Trailov@bosch.io> * address some comments Signed-off-by: strailov <Stanislav.Trailov@bosch.io> * Get rid of count(SoftDeletedMode) at all Signed-off-by: strailov <Stanislav.Trailov@bosch.io> * remove formatter for enum - stop supporting lowercase values in API Signed-off-by: strailov <Stanislav.Trailov@bosch.io> --------- Signed-off-by: strailov <Stanislav.Trailov@bosch.io>
This commit is contained in:
committed by
GitHub
parent
95680962cc
commit
f44b6268b0
@@ -131,6 +131,7 @@ public class HawkbitMcpToolProvider {
|
||||
request.getOffsetOrDefault(),
|
||||
request.getLimitOrDefault(),
|
||||
null,
|
||||
null,
|
||||
null);
|
||||
|
||||
return toPagedResponse(response.getBody(), request);
|
||||
@@ -148,6 +149,7 @@ public class HawkbitMcpToolProvider {
|
||||
request.getRsqlOrNull(),
|
||||
request.getOffsetOrDefault(),
|
||||
request.getLimitOrDefault(),
|
||||
null,
|
||||
null);
|
||||
|
||||
return toPagedResponse(response.getBody(), request);
|
||||
@@ -183,7 +185,7 @@ public class HawkbitMcpToolProvider {
|
||||
request.getRsqlOrNull(),
|
||||
request.getOffsetOrDefault(),
|
||||
request.getLimitOrDefault(),
|
||||
null);
|
||||
null, null);
|
||||
|
||||
return toPagedResponse(response.getBody(), request);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
package org.eclipse.hawkbit.mgmt.rest.api;
|
||||
|
||||
import static org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants.DISTRIBUTION_SET_ORDER;
|
||||
import static org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants.REQUEST_PARAMETER_LIST_SOFT_DELETED_MODE;
|
||||
import static org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants.REQUEST_PARAMETER_LIST_SOFT_DELETED_MODE_DEFAULT;
|
||||
import static org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT;
|
||||
import static org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET;
|
||||
import static org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants.REQUEST_PARAMETER_PAGING_LIMIT;
|
||||
@@ -103,7 +105,10 @@ public interface MgmtDistributionSetRestApi {
|
||||
@Schema(description = "The query parameter sort allows to define the sort order for the result of a query. " +
|
||||
"A sort criteria consists of the name of a field and the sort direction (ASC for ascending and DESC descending)." +
|
||||
"The sequence of the sort criteria (multiple can be used) defines the sort order of the entities in the result.")
|
||||
String sortParam);
|
||||
String sortParam,
|
||||
@RequestParam(value = REQUEST_PARAMETER_LIST_SOFT_DELETED_MODE, defaultValue = REQUEST_PARAMETER_LIST_SOFT_DELETED_MODE_DEFAULT, required = false)
|
||||
@Schema(description = "Controls whether soft-deleted distribution sets are included in the result.")
|
||||
MgmtSoftDeletedMode softDeletedMode);
|
||||
|
||||
/**
|
||||
* Handles the GET request of retrieving a single DistributionSet .
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
package org.eclipse.hawkbit.mgmt.rest.api;
|
||||
|
||||
import static org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants.DISTRIBUTION_SET_TYPE_ORDER;
|
||||
import static org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants.REQUEST_PARAMETER_LIST_SOFT_DELETED_MODE;
|
||||
import static org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants.REQUEST_PARAMETER_LIST_SOFT_DELETED_MODE_DEFAULT;
|
||||
import static org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT;
|
||||
import static org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET;
|
||||
import static org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants.REQUEST_PARAMETER_PAGING_LIMIT;
|
||||
@@ -91,7 +93,10 @@ public interface MgmtDistributionSetTypeRestApi {
|
||||
@Schema(description = "The query parameter sort allows to define the sort order for the result of a query. " +
|
||||
"A sort criteria consists of the name of a field and the sort direction (ASC for ascending and DESC descending)." +
|
||||
"The sequence of the sort criteria (multiple can be used) defines the sort order of the entities in the result.")
|
||||
String sortParam);
|
||||
String sortParam,
|
||||
@RequestParam(value = REQUEST_PARAMETER_LIST_SOFT_DELETED_MODE, defaultValue = REQUEST_PARAMETER_LIST_SOFT_DELETED_MODE_DEFAULT, required = false)
|
||||
@Schema(description = "Controls whether soft-deleted distribution set types are included in the result.")
|
||||
MgmtSoftDeletedMode softDeletedMode);
|
||||
|
||||
/**
|
||||
* Handles the GET request of retrieving a single DistributionSetType within.
|
||||
|
||||
@@ -39,6 +39,17 @@ public final class MgmtRestConstants {
|
||||
* The request parameter for specifying the representation mode. The value of this parameter can either be "full" or "compact".
|
||||
*/
|
||||
public static final String REQUEST_PARAMETER_REPRESENTATION_MODE = "representation";
|
||||
|
||||
/**
|
||||
* The request parameter for specifying the soft deletion listing of entities. The value of this parameter
|
||||
* can be ONLY_SOFT_DELETED, EXCLUDE_SOFT_DELETED or INCLUDE_SOFT_DELETED
|
||||
*/
|
||||
public static final String REQUEST_PARAMETER_LIST_SOFT_DELETED_MODE = "soft_deleted_mode";
|
||||
/**
|
||||
* Default value of the soft deleted mode parameter.
|
||||
*/
|
||||
public static final String REQUEST_PARAMETER_LIST_SOFT_DELETED_MODE_DEFAULT = "EXCLUDE_SOFT_DELETED";
|
||||
|
||||
/**
|
||||
* The default representation mode.
|
||||
*/
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
*/
|
||||
package org.eclipse.hawkbit.mgmt.rest.api;
|
||||
|
||||
import static org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants.REQUEST_PARAMETER_LIST_SOFT_DELETED_MODE;
|
||||
import static org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants.REQUEST_PARAMETER_LIST_SOFT_DELETED_MODE_DEFAULT;
|
||||
import static org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT;
|
||||
import static org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET;
|
||||
import static org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants.REQUEST_PARAMETER_PAGING_LIMIT;
|
||||
@@ -93,7 +95,10 @@ public interface MgmtRolloutRestApi {
|
||||
"The sequence of the sort criteria (multiple can be used) defines the sort order of the entities in the result.")
|
||||
String sortParam,
|
||||
@RequestParam(value = REQUEST_PARAMETER_REPRESENTATION_MODE, defaultValue = REQUEST_PARAMETER_REPRESENTATION_MODE_DEFAULT)
|
||||
String representationModeParam);
|
||||
String representationModeParam,
|
||||
@RequestParam(value = REQUEST_PARAMETER_LIST_SOFT_DELETED_MODE, defaultValue = REQUEST_PARAMETER_LIST_SOFT_DELETED_MODE_DEFAULT, required = false)
|
||||
@Schema(description = "Controls whether soft-deleted rollouts are included in the result.")
|
||||
MgmtSoftDeletedMode softDeletedMode);
|
||||
|
||||
/**
|
||||
* Handles the GET request of retrieving a single rollout.
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Contributors to the Eclipse Foundation
|
||||
*
|
||||
* This program and the accompanying materials are made
|
||||
* available under the terms of the Eclipse Public License 2.0
|
||||
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.eclipse.hawkbit.mgmt.rest.api;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public enum MgmtSoftDeletedMode {
|
||||
|
||||
ONLY_SOFT_DELETED,
|
||||
EXCLUDE_SOFT_DELETED,
|
||||
INCLUDE_SOFT_DELETED;
|
||||
|
||||
public static Optional<MgmtSoftDeletedMode> fromValue(final String value) {
|
||||
if (value == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
try {
|
||||
return Optional.of(MgmtSoftDeletedMode.valueOf(value.toUpperCase()));
|
||||
} catch (final IllegalArgumentException e) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,8 @@
|
||||
*/
|
||||
package org.eclipse.hawkbit.mgmt.rest.api;
|
||||
|
||||
import static org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants.REQUEST_PARAMETER_LIST_SOFT_DELETED_MODE;
|
||||
import static org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants.REQUEST_PARAMETER_LIST_SOFT_DELETED_MODE_DEFAULT;
|
||||
import static org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT;
|
||||
import static org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET;
|
||||
import static org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants.REQUEST_PARAMETER_PAGING_LIMIT;
|
||||
@@ -186,7 +188,10 @@ public interface MgmtSoftwareModuleRestApi {
|
||||
@Schema(description = "The query parameter sort allows to define the sort order for the result of a query. " +
|
||||
"A sort criteria consists of the name of a field and the sort direction (ASC for ascending and DESC descending)." +
|
||||
"The sequence of the sort criteria (multiple can be used) defines the sort order of the entities in the result.")
|
||||
String sortParam);
|
||||
String sortParam,
|
||||
@RequestParam(value = REQUEST_PARAMETER_LIST_SOFT_DELETED_MODE, defaultValue = REQUEST_PARAMETER_LIST_SOFT_DELETED_MODE_DEFAULT, required = false)
|
||||
@Schema(description = "Controls whether soft-deleted software modules are included in the result.")
|
||||
MgmtSoftDeletedMode softDeletedMode);
|
||||
|
||||
/**
|
||||
* Handles the GET request of retrieving a single software module.
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
*/
|
||||
package org.eclipse.hawkbit.mgmt.rest.api;
|
||||
|
||||
import static org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants.REQUEST_PARAMETER_LIST_SOFT_DELETED_MODE;
|
||||
import static org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants.REQUEST_PARAMETER_LIST_SOFT_DELETED_MODE_DEFAULT;
|
||||
import static org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT;
|
||||
import static org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET;
|
||||
import static org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants.REQUEST_PARAMETER_PAGING_LIMIT;
|
||||
@@ -86,7 +88,10 @@ public interface MgmtSoftwareModuleTypeRestApi {
|
||||
@Schema(description = "The query parameter sort allows to define the sort order for the result of a query. " +
|
||||
"A sort criteria consists of the name of a field and the sort direction (ASC for ascending and DESC descending)." +
|
||||
"The sequence of the sort criteria (multiple can be used) defines the sort order of the entities in the result.")
|
||||
String sortParam);
|
||||
String sortParam,
|
||||
@RequestParam(value = REQUEST_PARAMETER_LIST_SOFT_DELETED_MODE, defaultValue = REQUEST_PARAMETER_LIST_SOFT_DELETED_MODE_DEFAULT, required = false)
|
||||
@Schema(description = "Controls whether soft-deleted software module types are included in the result.")
|
||||
MgmtSoftDeletedMode softDeletedMode);
|
||||
|
||||
/**
|
||||
* Handles the GET request of retrieving a single software module type .
|
||||
|
||||
@@ -51,6 +51,8 @@ import org.eclipse.hawkbit.repository.DeploymentManagement;
|
||||
import org.eclipse.hawkbit.repository.DistributionSetInvalidationManagement;
|
||||
import org.eclipse.hawkbit.repository.DistributionSetManagement;
|
||||
import org.eclipse.hawkbit.repository.DistributionSetTypeManagement;
|
||||
import org.eclipse.hawkbit.mgmt.rest.api.MgmtSoftDeletedMode;
|
||||
import org.eclipse.hawkbit.repository.SoftDeletedMode;
|
||||
import org.eclipse.hawkbit.repository.SoftwareModuleManagement;
|
||||
import org.eclipse.hawkbit.repository.SystemManagement;
|
||||
import org.eclipse.hawkbit.repository.TargetFilterQueryManagement;
|
||||
@@ -112,16 +114,17 @@ public class MgmtDistributionSetResource implements MgmtDistributionSetRestApi {
|
||||
|
||||
@Override
|
||||
public ResponseEntity<PagedList<MgmtDistributionSet>> getDistributionSets(
|
||||
final String rsqlParam, final int pagingOffsetParam, final int pagingLimitParam, final String sortParam) {
|
||||
final String rsqlParam, final int pagingOffsetParam, final int pagingLimitParam, final String sortParam, final MgmtSoftDeletedMode softDeletedModeParam) {
|
||||
if (rsqlParam != null && rsqlParam.toLowerCase().contains("complete")) {
|
||||
LogUtility.logDeprecated("Usage of MgmtDistributionSetResource.getActions with 'complete': 'complete' distribution set search field is limited and may be removed.");
|
||||
}
|
||||
final Pageable pageable = PagingUtility.toPageable(pagingOffsetParam, pagingLimitParam, sanitizeDistributionSetSortParam(sortParam));
|
||||
final SoftDeletedMode softDeletedMode = SoftDeletedMode.valueOf(softDeletedModeParam.name());
|
||||
final Page<? extends DistributionSet> findDsPage;
|
||||
if (rsqlParam != null) {
|
||||
findDsPage = distributionSetManagement.findByRsql(rsqlParam, pageable);
|
||||
findDsPage = distributionSetManagement.findByRsql(rsqlParam, softDeletedMode, pageable);
|
||||
} else {
|
||||
findDsPage = distributionSetManagement.findAll(pageable);
|
||||
findDsPage = distributionSetManagement.findAll(softDeletedMode, pageable);
|
||||
}
|
||||
|
||||
final List<MgmtDistributionSet> rest = MgmtDistributionSetMapper.toResponseFromDsList(findDsPage.getContent());
|
||||
|
||||
@@ -26,6 +26,8 @@ import org.eclipse.hawkbit.mgmt.rest.resource.mapper.MgmtDistributionSetTypeMapp
|
||||
import org.eclipse.hawkbit.mgmt.rest.resource.mapper.MgmtSoftwareModuleTypeMapper;
|
||||
import org.eclipse.hawkbit.mgmt.rest.resource.util.PagingUtility;
|
||||
import org.eclipse.hawkbit.repository.DistributionSetTypeManagement;
|
||||
import org.eclipse.hawkbit.mgmt.rest.api.MgmtSoftDeletedMode;
|
||||
import org.eclipse.hawkbit.repository.SoftDeletedMode;
|
||||
import org.eclipse.hawkbit.repository.SoftwareModuleTypeManagement;
|
||||
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
||||
import org.eclipse.hawkbit.repository.exception.SoftwareModuleTypeNotInDistributionSetTypeException;
|
||||
@@ -59,21 +61,19 @@ public class MgmtDistributionSetTypeResource implements MgmtDistributionSetTypeR
|
||||
|
||||
@Override
|
||||
public ResponseEntity<PagedList<MgmtDistributionSetType>> getDistributionSetTypes(
|
||||
final String rsqlParam, final int pagingOffsetParam, final int pagingLimitParam, final String sortParam) {
|
||||
final String rsqlParam, final int pagingOffsetParam, final int pagingLimitParam, final String sortParam, final MgmtSoftDeletedMode softDeletedModeParam) {
|
||||
final Pageable pageable = PagingUtility.toPageable(
|
||||
pagingOffsetParam, pagingLimitParam, sanitizeDistributionSetTypeSortParam(sortParam));
|
||||
final Slice<? extends DistributionSetType> findModuleTypesAll;
|
||||
long countModulesAll;
|
||||
final SoftDeletedMode softDeletedMode = SoftDeletedMode.valueOf(softDeletedModeParam.name());
|
||||
final Page<? extends DistributionSetType> findModuleTypesAll;
|
||||
if (rsqlParam != null) {
|
||||
findModuleTypesAll = distributionSetTypeManagement.findByRsql(rsqlParam, pageable);
|
||||
countModulesAll = ((Page<?>) findModuleTypesAll).getTotalElements();
|
||||
findModuleTypesAll = distributionSetTypeManagement.findByRsql(rsqlParam, softDeletedMode, pageable);
|
||||
} else {
|
||||
findModuleTypesAll = distributionSetTypeManagement.findAll(pageable);
|
||||
countModulesAll = distributionSetTypeManagement.count();
|
||||
findModuleTypesAll = distributionSetTypeManagement.findAll(softDeletedMode, pageable);
|
||||
}
|
||||
|
||||
final List<MgmtDistributionSetType> rest = MgmtDistributionSetTypeMapper.toListResponse(findModuleTypesAll.getContent());
|
||||
return ResponseEntity.ok(new PagedList<>(rest, countModulesAll));
|
||||
return ResponseEntity.ok(new PagedList<>(rest, findModuleTypesAll.getTotalElements()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -36,6 +36,8 @@ import org.eclipse.hawkbit.repository.RolloutGroupManagement;
|
||||
import org.eclipse.hawkbit.repository.RolloutManagement;
|
||||
import org.eclipse.hawkbit.repository.RolloutManagement.Create;
|
||||
import org.eclipse.hawkbit.repository.RolloutManagement.GroupCreate;
|
||||
import org.eclipse.hawkbit.mgmt.rest.api.MgmtSoftDeletedMode;
|
||||
import org.eclipse.hawkbit.repository.SoftDeletedMode;
|
||||
import org.eclipse.hawkbit.repository.TargetFilterQueryManagement;
|
||||
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
||||
import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException;
|
||||
@@ -78,7 +80,7 @@ public class MgmtRolloutResource implements MgmtRolloutRestApi {
|
||||
@Override
|
||||
public ResponseEntity<PagedList<MgmtRolloutResponseBody>> getRollouts(
|
||||
final String rsqlParam, final int pagingOffsetParam, final int pagingLimitParam, final String sortParam,
|
||||
final String representationModeParam) {
|
||||
final String representationModeParam, final MgmtSoftDeletedMode softDeletedModeParam) {
|
||||
final Pageable pageable = PagingUtility.toPageable(pagingOffsetParam, pagingLimitParam, sanitizeRolloutSortParam(sortParam));
|
||||
final boolean isFullMode = parseRepresentationMode(representationModeParam) == MgmtRepresentationMode.FULL;
|
||||
|
||||
@@ -90,9 +92,10 @@ public class MgmtRolloutResource implements MgmtRolloutRestApi {
|
||||
: rolloutManagement.findByRsqlWithDetailedStatus(rsqlParam, false, pageable);
|
||||
rest = MgmtRolloutMapper.toResponseRolloutWithDetails(rollouts.getContent());
|
||||
} else {
|
||||
final SoftDeletedMode softDeletedMode = SoftDeletedMode.valueOf(softDeletedModeParam.name());
|
||||
rollouts = rsqlParam == null
|
||||
? rolloutManagement.findAll(false, pageable)
|
||||
: rolloutManagement.findByRsql(rsqlParam, false, pageable);
|
||||
? rolloutManagement.findAll(softDeletedMode, pageable)
|
||||
: rolloutManagement.findByRsql(rsqlParam, softDeletedMode, pageable);
|
||||
rest = MgmtRolloutMapper.toResponseRollout(rollouts.getContent());
|
||||
}
|
||||
return ResponseEntity.ok(new PagedList<>(rest, rollouts.getTotalElements()));
|
||||
|
||||
@@ -37,6 +37,8 @@ import org.eclipse.hawkbit.mgmt.rest.api.MgmtSoftwareModuleRestApi;
|
||||
import org.eclipse.hawkbit.mgmt.rest.resource.mapper.MgmtSoftwareModuleMapper;
|
||||
import org.eclipse.hawkbit.mgmt.rest.resource.util.PagingUtility;
|
||||
import org.eclipse.hawkbit.repository.ArtifactManagement;
|
||||
import org.eclipse.hawkbit.mgmt.rest.api.MgmtSoftDeletedMode;
|
||||
import org.eclipse.hawkbit.repository.SoftDeletedMode;
|
||||
import org.eclipse.hawkbit.repository.SoftwareModuleManagement;
|
||||
import org.eclipse.hawkbit.repository.SoftwareModuleTypeManagement;
|
||||
import org.eclipse.hawkbit.repository.SystemManagement;
|
||||
@@ -164,20 +166,19 @@ public class MgmtSoftwareModuleResource implements MgmtSoftwareModuleRestApi {
|
||||
|
||||
@Override
|
||||
public ResponseEntity<PagedList<MgmtSoftwareModule>> getSoftwareModules(
|
||||
final String rsqlParam, final int pagingOffsetParam, final int pagingLimitParam, final String sortParam) {
|
||||
final String rsqlParam, final int pagingOffsetParam, final int pagingLimitParam, final String sortParam, final MgmtSoftDeletedMode softDeletedModeParam) {
|
||||
final Pageable pageable = PagingUtility.toPageable(pagingOffsetParam, pagingLimitParam, sanitizeSoftwareModuleSortParam(sortParam));
|
||||
final Slice<? extends SoftwareModule> findModulesAll;
|
||||
final long countModulesAll;
|
||||
final SoftDeletedMode softDeletedMode = SoftDeletedMode.valueOf(softDeletedModeParam.name());
|
||||
final Page<? extends SoftwareModule> findModulesAll;
|
||||
if (rsqlParam != null) {
|
||||
findModulesAll = softwareModuleManagement.findByRsql(rsqlParam, pageable);
|
||||
countModulesAll = ((Page<?>) findModulesAll).getTotalElements();
|
||||
findModulesAll = softwareModuleManagement.findByRsql(
|
||||
rsqlParam, softDeletedMode, pageable);
|
||||
} else {
|
||||
findModulesAll = softwareModuleManagement.findAll(pageable);
|
||||
countModulesAll = softwareModuleManagement.count();
|
||||
findModulesAll = softwareModuleManagement.findAll(softDeletedMode, pageable);
|
||||
}
|
||||
|
||||
final List<MgmtSoftwareModule> rest = MgmtSoftwareModuleMapper.toResponse(findModulesAll.getContent());
|
||||
return ResponseEntity.ok(new PagedList<>(rest, countModulesAll));
|
||||
return ResponseEntity.ok(new PagedList<>(rest, findModulesAll.getTotalElements()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -21,6 +21,8 @@ import org.eclipse.hawkbit.mgmt.json.model.softwaremoduletype.MgmtSoftwareModule
|
||||
import org.eclipse.hawkbit.mgmt.rest.api.MgmtSoftwareModuleTypeRestApi;
|
||||
import org.eclipse.hawkbit.mgmt.rest.resource.mapper.MgmtSoftwareModuleTypeMapper;
|
||||
import org.eclipse.hawkbit.mgmt.rest.resource.util.PagingUtility;
|
||||
import org.eclipse.hawkbit.mgmt.rest.api.MgmtSoftDeletedMode;
|
||||
import org.eclipse.hawkbit.repository.SoftDeletedMode;
|
||||
import org.eclipse.hawkbit.repository.SoftwareModuleTypeManagement;
|
||||
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
||||
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
|
||||
@@ -45,20 +47,18 @@ public class MgmtSoftwareModuleTypeResource implements MgmtSoftwareModuleTypeRes
|
||||
|
||||
@Override
|
||||
public ResponseEntity<PagedList<MgmtSoftwareModuleType>> getTypes(
|
||||
final String rsqlParam, final int pagingOffsetParam, final int pagingLimitParam, final String sortParam) {
|
||||
final String rsqlParam, final int pagingOffsetParam, final int pagingLimitParam, final String sortParam, final MgmtSoftDeletedMode softDeletedModeParam) {
|
||||
final Pageable pageable = PagingUtility.toPageable(pagingOffsetParam, pagingLimitParam, sanitizeSoftwareModuleTypeSortParam(sortParam));
|
||||
final Slice<? extends SoftwareModuleType> findModuleTypessAll;
|
||||
final long countModulesAll;
|
||||
final SoftDeletedMode softDeletedMode = SoftDeletedMode.valueOf(softDeletedModeParam.name());
|
||||
final Page<? extends SoftwareModuleType> findModuleTypessAll;
|
||||
if (rsqlParam != null) {
|
||||
findModuleTypessAll = softwareModuleTypeManagement.findByRsql(rsqlParam, pageable);
|
||||
countModulesAll = ((Page<?>) findModuleTypessAll).getTotalElements();
|
||||
findModuleTypessAll = softwareModuleTypeManagement.findByRsql(rsqlParam, softDeletedMode, pageable);
|
||||
} else {
|
||||
findModuleTypessAll = softwareModuleTypeManagement.findAll(pageable);
|
||||
countModulesAll = softwareModuleTypeManagement.count();
|
||||
findModuleTypessAll = softwareModuleTypeManagement.findAll(softDeletedMode, pageable);
|
||||
}
|
||||
|
||||
final List<MgmtSoftwareModuleType> rest = MgmtSoftwareModuleTypeMapper.toTypesResponse(findModuleTypessAll.getContent());
|
||||
return ResponseEntity.ok(new PagedList<>(rest, countModulesAll));
|
||||
return ResponseEntity.ok(new PagedList<>(rest, findModuleTypessAll.getTotalElements()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -73,7 +73,7 @@ public final class MgmtTargetFilterQueryMapper {
|
||||
targetRest.add(
|
||||
linkTo(methodOn(MgmtDistributionSetRestApi.class).getDistributionSets(
|
||||
"name==" + distributionSet.getName() + ";version==" + distributionSet.getVersion(), Integer.parseInt(MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET),
|
||||
Integer.parseInt(MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT), null
|
||||
Integer.parseInt(MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT), null, null
|
||||
)).withRel("DS").expand());
|
||||
}
|
||||
|
||||
|
||||
@@ -1026,6 +1026,144 @@ class MgmtDistributionSetResourceTest extends AbstractManagementApiIntegrationTe
|
||||
assertThat(distributionSetManagement.findAll(PAGE)).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getDistributionSetsFilteredBySoftDeletedMode() throws Exception {
|
||||
final DistributionSet activeDs = testdataFactory.createDistributionSet("active");
|
||||
|
||||
final DistributionSet deletedDs = testdataFactory.createDistributionSet("deleted");
|
||||
testdataFactory.createTarget("dsTarget");
|
||||
assignDistributionSet(deletedDs.getId(), "dsTarget");
|
||||
distributionSetManagement.delete(deletedDs.getId());
|
||||
|
||||
// default — only active
|
||||
mvc.perform(get("/rest/v1/distributionsets").accept(APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.content", hasSize(1)))
|
||||
.andExpect(jsonPath("$.total", equalTo(1)))
|
||||
.andExpect(jsonPath("content[0].name", equalTo(activeDs.getName())))
|
||||
.andExpect(jsonPath("content[0].deleted", equalTo(false)));
|
||||
|
||||
// only_soft_deleted — only deleted
|
||||
mvc.perform(get("/rest/v1/distributionsets")
|
||||
.param(MgmtRestConstants.REQUEST_PARAMETER_LIST_SOFT_DELETED_MODE, "ONLY_SOFT_DELETED")
|
||||
.accept(APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.content", hasSize(1)))
|
||||
.andExpect(jsonPath("$.total", equalTo(1)))
|
||||
.andExpect(jsonPath("content[0].name", equalTo(deletedDs.getName())))
|
||||
.andExpect(jsonPath("content[0].deleted", equalTo(true)));
|
||||
|
||||
// include_soft_deleted — both
|
||||
mvc.perform(get("/rest/v1/distributionsets")
|
||||
.param(MgmtRestConstants.REQUEST_PARAMETER_LIST_SOFT_DELETED_MODE, "INCLUDE_SOFT_DELETED")
|
||||
.accept(APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.content", hasSize(2)))
|
||||
.andExpect(jsonPath("$.total", equalTo(2)));
|
||||
|
||||
// exclude_soft_deleted — explicit, same as default
|
||||
mvc.perform(get("/rest/v1/distributionsets")
|
||||
.param(MgmtRestConstants.REQUEST_PARAMETER_LIST_SOFT_DELETED_MODE, "EXCLUDE_SOFT_DELETED")
|
||||
.accept(APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.content", hasSize(1)))
|
||||
.andExpect(jsonPath("$.total", equalTo(1)))
|
||||
.andExpect(jsonPath("content[0].deleted", equalTo(false)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getDistributionSetsFilteredBySoftDeletedModeWithRsql() throws Exception {
|
||||
final DistributionSet activeDs = testdataFactory.createDistributionSet("rsqlActive");
|
||||
|
||||
final DistributionSet deletedDs = testdataFactory.createDistributionSet("rsqlDeleted");
|
||||
testdataFactory.createTarget("rsqlDsTarget");
|
||||
assignDistributionSet(deletedDs.getId(), "rsqlDsTarget");
|
||||
distributionSetManagement.delete(deletedDs.getId());
|
||||
|
||||
// rsql + soft_deleted — find deleted by name
|
||||
mvc.perform(get("/rest/v1/distributionsets")
|
||||
.param("q", "name==" + deletedDs.getName())
|
||||
.param(MgmtRestConstants.REQUEST_PARAMETER_LIST_SOFT_DELETED_MODE, "ONLY_SOFT_DELETED")
|
||||
.accept(APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.content", hasSize(1)))
|
||||
.andExpect(jsonPath("$.total", equalTo(1)))
|
||||
.andExpect(jsonPath("content[0].name", equalTo(deletedDs.getName())))
|
||||
.andExpect(jsonPath("content[0].deleted", equalTo(true)));
|
||||
|
||||
// rsql + not_soft_deleted — deleted not found
|
||||
mvc.perform(get("/rest/v1/distributionsets")
|
||||
.param("q", "name==" + deletedDs.getName())
|
||||
.param(MgmtRestConstants.REQUEST_PARAMETER_LIST_SOFT_DELETED_MODE, "EXCLUDE_SOFT_DELETED")
|
||||
.accept(APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.content", hasSize(0)))
|
||||
.andExpect(jsonPath("$.total", equalTo(0)));
|
||||
|
||||
// rsql + include_soft_deleted — filter by name narrows to one
|
||||
mvc.perform(get("/rest/v1/distributionsets")
|
||||
.param("q", "name==" + activeDs.getName())
|
||||
.param(MgmtRestConstants.REQUEST_PARAMETER_LIST_SOFT_DELETED_MODE, "INCLUDE_SOFT_DELETED")
|
||||
.accept(APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.content", hasSize(1)))
|
||||
.andExpect(jsonPath("$.total", equalTo(1)))
|
||||
.andExpect(jsonPath("content[0].name", equalTo(activeDs.getName())));
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateSoftDeletedDistributionSetRejected() throws Exception {
|
||||
final DistributionSet ds = testdataFactory.createDistributionSet("toDelete");
|
||||
testdataFactory.createTarget("updateTarget");
|
||||
assignDistributionSet(ds.getId(), "updateTarget");
|
||||
distributionSetManagement.delete(ds.getId());
|
||||
|
||||
final String body = new JSONObject().put("description", "updated").toString();
|
||||
mvc.perform(put("/rest/v1/distributionsets/{dsId}", ds.getId()).content(body)
|
||||
.contentType(APPLICATION_JSON).accept(APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isNotFound())
|
||||
.andExpect(jsonPath("$.errorCode", equalTo("hawkbit.server.error.deleted")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void lockSoftDeletedDistributionSetRejected() throws Exception {
|
||||
final DistributionSet ds = testdataFactory.createDistributionSet("toDelete");
|
||||
testdataFactory.createTarget("lockTarget");
|
||||
assignDistributionSet(ds.getId(), "lockTarget");
|
||||
distributionSetManagement.delete(ds.getId());
|
||||
|
||||
final String body = new JSONObject().put("locked", true).toString();
|
||||
mvc.perform(put("/rest/v1/distributionsets/{dsId}", ds.getId()).content(body)
|
||||
.contentType(APPLICATION_JSON).accept(APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isNotFound())
|
||||
.andExpect(jsonPath("$.errorCode", equalTo("hawkbit.server.error.deleted")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void assignSoftwareModuleToSoftDeletedDistributionSetRejected() throws Exception {
|
||||
final DistributionSet ds = testdataFactory.createDistributionSet("toDelete");
|
||||
testdataFactory.createTarget("assignSmTarget");
|
||||
assignDistributionSet(ds.getId(), "assignSmTarget");
|
||||
distributionSetManagement.delete(ds.getId());
|
||||
|
||||
final SoftwareModule sm = testdataFactory.createSoftwareModuleOs("newModule");
|
||||
mvc.perform(post("/rest/v1/distributionsets/{dsId}/assignedSM", ds.getId())
|
||||
.content("[{\"id\":" + sm.getId() + "}]")
|
||||
.contentType(APPLICATION_JSON).accept(APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isNotFound())
|
||||
.andExpect(jsonPath("$.errorCode", equalTo("hawkbit.server.error.deleted")));
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that DS property update request to API is reflected by the repository.
|
||||
*/
|
||||
|
||||
@@ -493,6 +493,142 @@ class MgmtDistributionSetTypeResourceTest extends AbstractManagementApiIntegrati
|
||||
assertThat(distributionSetTypeManagement.count()).isEqualTo(DEFAULT_DS_TYPES);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getDistributionSetTypesFilteredBySoftDeletedMode() throws Exception {
|
||||
final DistributionSetType activeType = distributionSetTypeManagement.create(
|
||||
DistributionSetTypeManagement.Create.builder()
|
||||
.key("activeKey").name("activeType").build());
|
||||
|
||||
// create type + DS using it, then delete type → soft-delete
|
||||
final DistributionSetType deletedType = distributionSetTypeManagement.create(
|
||||
DistributionSetTypeManagement.Create.builder()
|
||||
.key("deletedKey").name("deletedType").build());
|
||||
distributionSetManagement.create(DistributionSetManagement.Create.builder()
|
||||
.type(deletedType).name("ds").version("1.0").build());
|
||||
distributionSetTypeManagement.delete(deletedType.getId());
|
||||
|
||||
// default — built-in + activeType, no deletedType
|
||||
mvc.perform(get("/rest/v1/distributionsettypes").accept(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.content", hasSize(DEFAULT_DS_TYPES + 1)))
|
||||
.andExpect(jsonPath("$.total", equalTo(DEFAULT_DS_TYPES + 1)))
|
||||
.andExpect(jsonPath("$.content.[?(@.key=='deletedKey')]").doesNotExist());
|
||||
|
||||
// only_soft_deleted — only deletedType
|
||||
mvc.perform(get("/rest/v1/distributionsettypes")
|
||||
.param(MgmtRestConstants.REQUEST_PARAMETER_LIST_SOFT_DELETED_MODE, "ONLY_SOFT_DELETED")
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.content", hasSize(1)))
|
||||
.andExpect(jsonPath("$.total", equalTo(1)))
|
||||
.andExpect(jsonPath("content[0].name", equalTo("deletedType")))
|
||||
.andExpect(jsonPath("content[0].deleted", equalTo(true)));
|
||||
|
||||
// include_soft_deleted — everything
|
||||
mvc.perform(get("/rest/v1/distributionsettypes")
|
||||
.param(MgmtRestConstants.REQUEST_PARAMETER_LIST_SOFT_DELETED_MODE, "INCLUDE_SOFT_DELETED")
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.content", hasSize(DEFAULT_DS_TYPES + 2)))
|
||||
.andExpect(jsonPath("$.total", equalTo(DEFAULT_DS_TYPES + 2)));
|
||||
|
||||
// exclude_soft_deleted — explicit, same as default
|
||||
mvc.perform(get("/rest/v1/distributionsettypes")
|
||||
.param(MgmtRestConstants.REQUEST_PARAMETER_LIST_SOFT_DELETED_MODE, "EXCLUDE_SOFT_DELETED")
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.content", hasSize(DEFAULT_DS_TYPES + 1)))
|
||||
.andExpect(jsonPath("$.total", equalTo(DEFAULT_DS_TYPES + 1)))
|
||||
.andExpect(jsonPath("$.content.[?(@.key=='deletedKey')]").doesNotExist());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getDistributionSetTypesFilteredBySoftDeletedModeWithRsql() throws Exception {
|
||||
distributionSetTypeManagement.create(
|
||||
DistributionSetTypeManagement.Create.builder()
|
||||
.key("rsqlActiveKey").name("rsqlActiveType").build());
|
||||
|
||||
final DistributionSetType deletedType = distributionSetTypeManagement.create(
|
||||
DistributionSetTypeManagement.Create.builder()
|
||||
.key("rsqlDeletedKey").name("rsqlDeletedType").build());
|
||||
distributionSetManagement.create(DistributionSetManagement.Create.builder()
|
||||
.type(deletedType).name("ds").version("1.0").build());
|
||||
distributionSetTypeManagement.delete(deletedType.getId());
|
||||
|
||||
// rsql + soft_deleted — find deleted by name
|
||||
mvc.perform(get("/rest/v1/distributionsettypes")
|
||||
.param("q", "name==rsqlDeletedType")
|
||||
.param(MgmtRestConstants.REQUEST_PARAMETER_LIST_SOFT_DELETED_MODE, "ONLY_SOFT_DELETED")
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.content", hasSize(1)))
|
||||
.andExpect(jsonPath("$.total", equalTo(1)))
|
||||
.andExpect(jsonPath("content[0].name", equalTo("rsqlDeletedType")))
|
||||
.andExpect(jsonPath("content[0].deleted", equalTo(true)));
|
||||
|
||||
// rsql + not_soft_deleted — deleted not found
|
||||
mvc.perform(get("/rest/v1/distributionsettypes")
|
||||
.param("q", "name==rsqlDeletedType")
|
||||
.param(MgmtRestConstants.REQUEST_PARAMETER_LIST_SOFT_DELETED_MODE, "EXCLUDE_SOFT_DELETED")
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.content", hasSize(0)))
|
||||
.andExpect(jsonPath("$.total", equalTo(0)));
|
||||
|
||||
// rsql + include_soft_deleted — filter by name narrows to one
|
||||
mvc.perform(get("/rest/v1/distributionsettypes")
|
||||
.param("q", "name==rsqlActiveType")
|
||||
.param(MgmtRestConstants.REQUEST_PARAMETER_LIST_SOFT_DELETED_MODE, "INCLUDE_SOFT_DELETED")
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.content", hasSize(1)))
|
||||
.andExpect(jsonPath("$.total", equalTo(1)))
|
||||
.andExpect(jsonPath("content[0].name", equalTo("rsqlActiveType")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateSoftDeletedDistributionSetTypeRejected() throws Exception {
|
||||
final DistributionSetType deletedType = distributionSetTypeManagement.create(
|
||||
DistributionSetTypeManagement.Create.builder()
|
||||
.key("delKey").name("delType").build());
|
||||
distributionSetManagement.create(DistributionSetManagement.Create.builder()
|
||||
.type(deletedType).name("ds").version("1.0").build());
|
||||
distributionSetTypeManagement.delete(deletedType.getId());
|
||||
|
||||
final String body = new JSONObject().put("description", "updated").toString();
|
||||
mvc.perform(put("/rest/v1/distributionsettypes/{dstId}", deletedType.getId()).content(body)
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isNotFound())
|
||||
.andExpect(jsonPath("$.errorCode", equalTo("hawkbit.server.error.deleted")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void assignSmTypeToSoftDeletedDistributionSetTypeRejected() throws Exception {
|
||||
final DistributionSetType deletedType = distributionSetTypeManagement.create(
|
||||
DistributionSetTypeManagement.Create.builder()
|
||||
.key("delKey2").name("delType2").build());
|
||||
distributionSetManagement.create(DistributionSetManagement.Create.builder()
|
||||
.type(deletedType).name("ds2").version("1.0").build());
|
||||
distributionSetTypeManagement.delete(deletedType.getId());
|
||||
|
||||
final SoftwareModuleType smType = softwareModuleTypeManagement.create(
|
||||
SoftwareModuleTypeManagement.Create.builder().key("newSmType").name("newSmType").build());
|
||||
mvc.perform(post("/rest/v1/distributionsettypes/{dstId}/mandatorymoduletypes", deletedType.getId())
|
||||
.content("{\"id\":" + smType.getId() + "}")
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isNotFound())
|
||||
.andExpect(jsonPath("$.errorCode", equalTo("hawkbit.server.error.deleted")));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the correct behaviour of /rest/v1/distributionsettypes/{ID} PUT requests.
|
||||
*/
|
||||
|
||||
@@ -1495,6 +1495,110 @@ class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void getRolloutsFilteredBySoftDeletedMode() throws Exception {
|
||||
testdataFactory.createTargets(20, "rolloutFilter", "rolloutFilter");
|
||||
final DistributionSet dsA = testdataFactory.createDistributionSet("filterDs");
|
||||
|
||||
// create two rollouts
|
||||
final Rollout rollout1 = createRollout("activeRollout", 4, dsA, "controllerId==rolloutFilter*");
|
||||
final Rollout rollout2 = createRollout("toDeleteRollout", 4, dsA, "controllerId==rolloutFilter*");
|
||||
|
||||
// start and soft-delete rollout2
|
||||
rolloutManagement.start(rollout2.getId());
|
||||
rolloutHandler.handleAll();
|
||||
rolloutManagement.delete(rollout2.getId());
|
||||
rolloutHandler.handleAll();
|
||||
|
||||
// default (exclude_soft_deleted) — only active rollout
|
||||
mvc.perform(get("/rest/v1/rollouts").accept(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.content", hasSize(1)))
|
||||
.andExpect(jsonPath("$.total", equalTo(1)))
|
||||
.andExpect(jsonPath("content[0].name", equalTo(rollout1.getName())))
|
||||
.andExpect(jsonPath("content[0].deleted", equalTo(false)));
|
||||
|
||||
// soft_deleted_mode=only_soft_deleted — only deleted rollout
|
||||
mvc.perform(get("/rest/v1/rollouts")
|
||||
.param(MgmtRestConstants.REQUEST_PARAMETER_LIST_SOFT_DELETED_MODE, "ONLY_SOFT_DELETED")
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.content", hasSize(1)))
|
||||
.andExpect(jsonPath("$.total", equalTo(1)))
|
||||
.andExpect(jsonPath("content[0].name", equalTo(rollout2.getName())))
|
||||
.andExpect(jsonPath("content[0].deleted", equalTo(true)));
|
||||
|
||||
// soft_deleted_mode=include_soft_deleted — both rollouts
|
||||
mvc.perform(get("/rest/v1/rollouts")
|
||||
.param(MgmtRestConstants.REQUEST_PARAMETER_LIST_SOFT_DELETED_MODE, "INCLUDE_SOFT_DELETED")
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.content", hasSize(2)))
|
||||
.andExpect(jsonPath("$.total", equalTo(2)));
|
||||
|
||||
// exclude_soft_deleted — explicit, same as default
|
||||
mvc.perform(get("/rest/v1/rollouts")
|
||||
.param(MgmtRestConstants.REQUEST_PARAMETER_LIST_SOFT_DELETED_MODE, "EXCLUDE_SOFT_DELETED")
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.content", hasSize(1)))
|
||||
.andExpect(jsonPath("$.total", equalTo(1)))
|
||||
.andExpect(jsonPath("content[0].name", equalTo(rollout1.getName())))
|
||||
.andExpect(jsonPath("content[0].deleted", equalTo(false)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getRolloutsFilteredBySoftDeletedModeWithRsql() throws Exception {
|
||||
testdataFactory.createTargets(20, "rolloutRsql", "rolloutRsql");
|
||||
final DistributionSet dsA = testdataFactory.createDistributionSet("rsqlDs");
|
||||
|
||||
final Rollout rollout1 = createRollout("rsqlActive", 4, dsA, "controllerId==rolloutRsql*");
|
||||
final Rollout rollout2 = createRollout("rsqlDeleted", 4, dsA, "controllerId==rolloutRsql*");
|
||||
|
||||
// start and soft-delete rollout2
|
||||
rolloutManagement.start(rollout2.getId());
|
||||
rolloutHandler.handleAll();
|
||||
rolloutManagement.delete(rollout2.getId());
|
||||
rolloutHandler.handleAll();
|
||||
|
||||
// rsql + only_soft_deleted — find deleted rollout by name
|
||||
mvc.perform(get("/rest/v1/rollouts")
|
||||
.param("q", "name==rsqlDeleted")
|
||||
.param(MgmtRestConstants.REQUEST_PARAMETER_LIST_SOFT_DELETED_MODE, "ONLY_SOFT_DELETED")
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.content", hasSize(1)))
|
||||
.andExpect(jsonPath("$.total", equalTo(1)))
|
||||
.andExpect(jsonPath("content[0].name", equalTo(rollout2.getName())))
|
||||
.andExpect(jsonPath("content[0].deleted", equalTo(true)));
|
||||
|
||||
// rsql + not_soft_deleted — deleted rollout not found
|
||||
mvc.perform(get("/rest/v1/rollouts")
|
||||
.param("q", "name==rsqlDeleted")
|
||||
.param(MgmtRestConstants.REQUEST_PARAMETER_LIST_SOFT_DELETED_MODE, "EXCLUDE_SOFT_DELETED")
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.content", hasSize(0)))
|
||||
.andExpect(jsonPath("$.total", equalTo(0)));
|
||||
|
||||
// rsql + include_soft_deleted — both visible, filter by name narrows to one
|
||||
mvc.perform(get("/rest/v1/rollouts")
|
||||
.param("q", "name==rsqlActive")
|
||||
.param(MgmtRestConstants.REQUEST_PARAMETER_LIST_SOFT_DELETED_MODE, "INCLUDE_SOFT_DELETED")
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.content", hasSize(1)))
|
||||
.andExpect(jsonPath("$.total", equalTo(1)))
|
||||
.andExpect(jsonPath("content[0].name", equalTo(rollout1.getName())));
|
||||
}
|
||||
|
||||
@Test
|
||||
void stopRunningRollout() throws Exception {
|
||||
final Rollout rollout = testdataFactory.createAndStartRollout();
|
||||
|
||||
@@ -1228,6 +1228,130 @@ class MgmtSoftwareModuleResourceTest extends AbstractManagementApiIntegrationTes
|
||||
.andExpect(jsonPath("$.errorCode", equalTo("hawkbit.server.error.rest.param.rsqlInvalidField")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getSoftwareModulesFilteredBySoftDeletedMode() throws Exception {
|
||||
final SoftwareModule activeSm = softwareModuleManagement.create(
|
||||
SoftwareModuleManagement.Create.builder().type(osType).name("activeSm").version("1.0").build());
|
||||
|
||||
SoftwareModule deletedSm = softwareModuleManagement.create(
|
||||
SoftwareModuleManagement.Create.builder().type(osType).name("deletedSm").version("1.0").build());
|
||||
testdataFactory.createDistributionSet(List.of(deletedSm));
|
||||
softwareModuleManagement.delete(deletedSm.getId());
|
||||
|
||||
// default — only active
|
||||
mvc.perform(get("/rest/v1/softwaremodules").accept(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.content", hasSize(1)))
|
||||
.andExpect(jsonPath("$.total", equalTo(1)))
|
||||
.andExpect(jsonPath("content[0].name", equalTo("activeSm")))
|
||||
.andExpect(jsonPath("content[0].deleted", equalTo(false)));
|
||||
|
||||
// only_soft_deleted — only deleted
|
||||
mvc.perform(get("/rest/v1/softwaremodules")
|
||||
.param(MgmtRestConstants.REQUEST_PARAMETER_LIST_SOFT_DELETED_MODE, "ONLY_SOFT_DELETED")
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.content", hasSize(1)))
|
||||
.andExpect(jsonPath("$.total", equalTo(1)))
|
||||
.andExpect(jsonPath("content[0].name", equalTo("deletedSm")))
|
||||
.andExpect(jsonPath("content[0].deleted", equalTo(true)));
|
||||
|
||||
// include_soft_deleted — both
|
||||
mvc.perform(get("/rest/v1/softwaremodules")
|
||||
.param(MgmtRestConstants.REQUEST_PARAMETER_LIST_SOFT_DELETED_MODE, "INCLUDE_SOFT_DELETED")
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.content", hasSize(2)))
|
||||
.andExpect(jsonPath("$.total", equalTo(2)));
|
||||
|
||||
// exclude_soft_deleted — explicit, same as default
|
||||
mvc.perform(get("/rest/v1/softwaremodules")
|
||||
.param(MgmtRestConstants.REQUEST_PARAMETER_LIST_SOFT_DELETED_MODE, "EXCLUDE_SOFT_DELETED")
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.content", hasSize(1)))
|
||||
.andExpect(jsonPath("$.total", equalTo(1)))
|
||||
.andExpect(jsonPath("content[0].deleted", equalTo(false)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getSoftwareModulesFilteredBySoftDeletedModeWithRsql() throws Exception {
|
||||
softwareModuleManagement.create(
|
||||
SoftwareModuleManagement.Create.builder().type(osType).name("rsqlActive").version("1.0").build());
|
||||
|
||||
SoftwareModule deletedSm = softwareModuleManagement.create(
|
||||
SoftwareModuleManagement.Create.builder().type(osType).name("rsqlDeleted").version("1.0").build());
|
||||
testdataFactory.createDistributionSet(List.of(deletedSm));
|
||||
softwareModuleManagement.delete(deletedSm.getId());
|
||||
|
||||
// rsql + soft_deleted — find deleted by name
|
||||
mvc.perform(get("/rest/v1/softwaremodules")
|
||||
.param("q", "name==rsqlDeleted")
|
||||
.param(MgmtRestConstants.REQUEST_PARAMETER_LIST_SOFT_DELETED_MODE, "ONLY_SOFT_DELETED")
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.content", hasSize(1)))
|
||||
.andExpect(jsonPath("$.total", equalTo(1)))
|
||||
.andExpect(jsonPath("content[0].name", equalTo("rsqlDeleted")))
|
||||
.andExpect(jsonPath("content[0].deleted", equalTo(true)));
|
||||
|
||||
// rsql + not_soft_deleted — deleted not found
|
||||
mvc.perform(get("/rest/v1/softwaremodules")
|
||||
.param("q", "name==rsqlDeleted")
|
||||
.param(MgmtRestConstants.REQUEST_PARAMETER_LIST_SOFT_DELETED_MODE, "EXCLUDE_SOFT_DELETED")
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.content", hasSize(0)))
|
||||
.andExpect(jsonPath("$.total", equalTo(0)));
|
||||
|
||||
// rsql + include_soft_deleted — filter by name narrows to one
|
||||
mvc.perform(get("/rest/v1/softwaremodules")
|
||||
.param("q", "name==rsqlActive")
|
||||
.param(MgmtRestConstants.REQUEST_PARAMETER_LIST_SOFT_DELETED_MODE, "INCLUDE_SOFT_DELETED")
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.content", hasSize(1)))
|
||||
.andExpect(jsonPath("$.total", equalTo(1)))
|
||||
.andExpect(jsonPath("content[0].name", equalTo("rsqlActive")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateSoftDeletedSoftwareModuleRejected() throws Exception {
|
||||
SoftwareModule sm = softwareModuleManagement.create(
|
||||
SoftwareModuleManagement.Create.builder().type(osType).name("delSm").version("1.0").build());
|
||||
testdataFactory.createDistributionSet(List.of(sm));
|
||||
softwareModuleManagement.delete(sm.getId());
|
||||
|
||||
final String body = new JSONObject().put("description", "updated").toString();
|
||||
mvc.perform(put("/rest/v1/softwaremodules/{smId}", sm.getId()).content(body)
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isNotFound())
|
||||
.andExpect(jsonPath("$.errorCode", equalTo("hawkbit.server.error.deleted")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void lockSoftDeletedSoftwareModuleRejected() throws Exception {
|
||||
SoftwareModule sm = softwareModuleManagement.create(
|
||||
SoftwareModuleManagement.Create.builder().type(osType).name("delSmLock").version("1.0").build());
|
||||
testdataFactory.createDistributionSet(List.of(sm));
|
||||
softwareModuleManagement.delete(sm.getId());
|
||||
|
||||
final String body = new JSONObject().put("locked", true).toString();
|
||||
mvc.perform(put("/rest/v1/softwaremodules/{smId}", sm.getId()).content(body)
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isNotFound())
|
||||
.andExpect(jsonPath("$.errorCode", equalTo("hawkbit.server.error.deleted")));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests GET request on /rest/v1/softwaremodules/{smId}.
|
||||
*/
|
||||
|
||||
@@ -489,6 +489,121 @@ public class MgmtSoftwareModuleTypeResourceTest extends AbstractManagementApiInt
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void getSoftwareModuleTypesFilteredBySoftDeletedMode() throws Exception {
|
||||
// 3 built-in types exist (os, runtime, application)
|
||||
final int builtInTypes = 3;
|
||||
|
||||
final SoftwareModuleType activeType = softwareModuleTypeManagement.create(
|
||||
SoftwareModuleTypeManagement.Create.builder().key("activeKey").name("activeType").build());
|
||||
|
||||
// create type + SM using it, then delete type → soft-delete
|
||||
final SoftwareModuleType deletedType = softwareModuleTypeManagement.create(
|
||||
SoftwareModuleTypeManagement.Create.builder().key("deletedKey").name("deletedType").build());
|
||||
softwareModuleManagement.create(
|
||||
SoftwareModuleManagement.Create.builder().type(deletedType).name("sm").version("1.0").build());
|
||||
softwareModuleTypeManagement.delete(deletedType.getId());
|
||||
|
||||
// default — built-in + activeType, no deletedType
|
||||
mvc.perform(get("/rest/v1/softwaremoduletypes").accept(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.content", hasSize(builtInTypes + 1)))
|
||||
.andExpect(jsonPath("$.total", equalTo(builtInTypes + 1)))
|
||||
.andExpect(jsonPath("$.content.[?(@.key=='deletedKey')]").doesNotExist());
|
||||
|
||||
// only_soft_deleted — only deletedType
|
||||
mvc.perform(get("/rest/v1/softwaremoduletypes")
|
||||
.param(MgmtRestConstants.REQUEST_PARAMETER_LIST_SOFT_DELETED_MODE, "ONLY_SOFT_DELETED")
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.content", hasSize(1)))
|
||||
.andExpect(jsonPath("$.total", equalTo(1)))
|
||||
.andExpect(jsonPath("content[0].name", equalTo("deletedType")))
|
||||
.andExpect(jsonPath("content[0].deleted", equalTo(true)));
|
||||
|
||||
// include_soft_deleted — everything
|
||||
mvc.perform(get("/rest/v1/softwaremoduletypes")
|
||||
.param(MgmtRestConstants.REQUEST_PARAMETER_LIST_SOFT_DELETED_MODE, "INCLUDE_SOFT_DELETED")
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.content", hasSize(builtInTypes + 2)))
|
||||
.andExpect(jsonPath("$.total", equalTo(builtInTypes + 2)));
|
||||
|
||||
// exclude_soft_deleted — explicit, same as default
|
||||
mvc.perform(get("/rest/v1/softwaremoduletypes")
|
||||
.param(MgmtRestConstants.REQUEST_PARAMETER_LIST_SOFT_DELETED_MODE, "EXCLUDE_SOFT_DELETED")
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.content", hasSize(builtInTypes + 1)))
|
||||
.andExpect(jsonPath("$.total", equalTo(builtInTypes + 1)))
|
||||
.andExpect(jsonPath("$.content.[?(@.key=='deletedKey')]").doesNotExist());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getSoftwareModuleTypesFilteredBySoftDeletedModeWithRsql() throws Exception {
|
||||
softwareModuleTypeManagement.create(
|
||||
SoftwareModuleTypeManagement.Create.builder().key("rsqlActiveKey").name("rsqlActiveType").build());
|
||||
|
||||
final SoftwareModuleType deletedType = softwareModuleTypeManagement.create(
|
||||
SoftwareModuleTypeManagement.Create.builder().key("rsqlDeletedKey").name("rsqlDeletedType").build());
|
||||
softwareModuleManagement.create(
|
||||
SoftwareModuleManagement.Create.builder().type(deletedType).name("sm").version("1.0").build());
|
||||
softwareModuleTypeManagement.delete(deletedType.getId());
|
||||
|
||||
// rsql + soft_deleted — find deleted by name
|
||||
mvc.perform(get("/rest/v1/softwaremoduletypes")
|
||||
.param("q", "name==rsqlDeletedType")
|
||||
.param(MgmtRestConstants.REQUEST_PARAMETER_LIST_SOFT_DELETED_MODE, "ONLY_SOFT_DELETED")
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.content", hasSize(1)))
|
||||
.andExpect(jsonPath("$.total", equalTo(1)))
|
||||
.andExpect(jsonPath("content[0].name", equalTo("rsqlDeletedType")))
|
||||
.andExpect(jsonPath("content[0].deleted", equalTo(true)));
|
||||
|
||||
// rsql + not_soft_deleted — deleted not found
|
||||
mvc.perform(get("/rest/v1/softwaremoduletypes")
|
||||
.param("q", "name==rsqlDeletedType")
|
||||
.param(MgmtRestConstants.REQUEST_PARAMETER_LIST_SOFT_DELETED_MODE, "EXCLUDE_SOFT_DELETED")
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.content", hasSize(0)))
|
||||
.andExpect(jsonPath("$.total", equalTo(0)));
|
||||
|
||||
// rsql + include_soft_deleted — filter by name narrows to one
|
||||
mvc.perform(get("/rest/v1/softwaremoduletypes")
|
||||
.param("q", "name==rsqlActiveType")
|
||||
.param(MgmtRestConstants.REQUEST_PARAMETER_LIST_SOFT_DELETED_MODE, "INCLUDE_SOFT_DELETED")
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.content", hasSize(1)))
|
||||
.andExpect(jsonPath("$.total", equalTo(1)))
|
||||
.andExpect(jsonPath("content[0].name", equalTo("rsqlActiveType")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateSoftDeletedSoftwareModuleTypeRejected() throws Exception {
|
||||
final SoftwareModuleType deletedType = softwareModuleTypeManagement.create(
|
||||
SoftwareModuleTypeManagement.Create.builder().key("delKey").name("delType").build());
|
||||
softwareModuleManagement.create(
|
||||
SoftwareModuleManagement.Create.builder().type(deletedType).name("sm").version("1.0").build());
|
||||
softwareModuleTypeManagement.delete(deletedType.getId());
|
||||
|
||||
final String body = new JSONObject().put("description", "updated").toString();
|
||||
mvc.perform(put("/rest/v1/softwaremoduletypes/{smtId}", deletedType.getId()).content(body)
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isNotFound())
|
||||
.andExpect(jsonPath("$.errorCode", equalTo("hawkbit.server.error.deleted")));
|
||||
}
|
||||
|
||||
private SoftwareModuleType createTestType() {
|
||||
final SoftwareModuleType testType = softwareModuleTypeManagement.create(SoftwareModuleTypeManagement.Create.builder()
|
||||
.key("test123").name("TestName123").description("Desc123").colour("colour").maxAssignments(5).build());
|
||||
|
||||
@@ -13,3 +13,8 @@ logging.level.root=WARN
|
||||
#logging.level.org.eclipse.hawkbit.rest.util.MockMvcResultPrinter=DEBUG
|
||||
# Logging END
|
||||
hawkbit.events.remote.enabled=false
|
||||
|
||||
logging.level.org.eclipse.persistence=DEBUG
|
||||
spring.jpa.properties.eclipselink.logging.level=FINE
|
||||
spring.jpa.properties.eclipselink.logging.level.sql=FINE
|
||||
spring.jpa.properties.eclipselink.logging.parameters=true
|
||||
|
||||
@@ -50,7 +50,7 @@ import org.springframework.security.access.prepost.PreAuthorize;
|
||||
* Management service for {@link DistributionSet}s.
|
||||
*/
|
||||
public interface DistributionSetManagement<T extends DistributionSet>
|
||||
extends RepositoryManagement<T, DistributionSetManagement.Create, DistributionSetManagement.Update>, MetadataSupport<String> {
|
||||
extends SoftDeletableRepositoryManagement<T, DistributionSetManagement.Create, DistributionSetManagement.Update>, MetadataSupport<String> {
|
||||
|
||||
@Override
|
||||
default String permissionGroup() {
|
||||
|
||||
@@ -37,7 +37,7 @@ import org.springframework.security.access.prepost.PreAuthorize;
|
||||
* Management service for {@link DistributionSetType}s.
|
||||
*/
|
||||
public interface DistributionSetTypeManagement<T extends DistributionSetType>
|
||||
extends RepositoryManagement<T, DistributionSetTypeManagement.Create, DistributionSetTypeManagement.Update> {
|
||||
extends SoftDeletableRepositoryManagement<T, DistributionSetTypeManagement.Create, DistributionSetTypeManagement.Update> {
|
||||
|
||||
@Override
|
||||
default String permissionGroup() {
|
||||
|
||||
@@ -164,6 +164,16 @@ public interface RolloutManagement extends PermissionSupport {
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_READ_REPOSITORY)
|
||||
Page<Rollout> findAll(boolean deleted, @NotNull Pageable pageable);
|
||||
|
||||
/**
|
||||
* Retrieves all rollouts filtered by their soft-deleted state.
|
||||
*
|
||||
* @param softDeletedMode the filter defining which rollouts to return based on their soft-deleted status
|
||||
* @param pageable the page request to sort and limit the result
|
||||
* @return a page of found rollouts
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_READ_REPOSITORY)
|
||||
Page<Rollout> findAll(SoftDeletedMode softDeletedMode, @NotNull Pageable pageable);
|
||||
|
||||
/**
|
||||
* Get count of targets in different status in rollout.
|
||||
*
|
||||
@@ -188,6 +198,20 @@ public interface RolloutManagement extends PermissionSupport {
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_READ_REPOSITORY)
|
||||
Page<Rollout> findByRsql(@NotNull String rsql, boolean deleted, @NotNull Pageable pageable);
|
||||
|
||||
/**
|
||||
* Retrieves all rollouts matching the given RSQL filter, filtered by their soft-deleted state.
|
||||
*
|
||||
* @param rsql the specification to filter rollouts
|
||||
* @param softDeletedMode the filter defining which rollouts to return based on their soft-deleted status
|
||||
* @param pageable the page request to sort and limit the result
|
||||
* @return a page of found rollouts
|
||||
* @throws RSQLParameterUnsupportedFieldException if a field in the RSQL string is used but not provided by the given
|
||||
* {@code fieldNameProvider}
|
||||
* @throws RSQLParameterSyntaxException if the RSQL syntax is wrong
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_READ_REPOSITORY)
|
||||
Page<Rollout> findByRsql(@NotNull String rsql, SoftDeletedMode softDeletedMode, @NotNull Pageable pageable);
|
||||
|
||||
/**
|
||||
* Finds rollouts by given text in name or description.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Contributors to the Eclipse Foundation
|
||||
*
|
||||
* This program and the accompanying materials are made
|
||||
* available under the terms of the Eclipse Public License 2.0
|
||||
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import org.eclipse.hawkbit.auth.SpringEvalExpressions;
|
||||
import org.eclipse.hawkbit.repository.model.BaseEntity;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
|
||||
/**
|
||||
* Extension of {@link RepositoryManagement} for entities that support soft deletion,
|
||||
* providing query methods that allow filtering by soft-deleted state.
|
||||
*
|
||||
* @param <T> type of the {@link BaseEntity}
|
||||
* @param <C> type of the create request
|
||||
* @param <U> type of the update request
|
||||
*/
|
||||
public interface SoftDeletableRepositoryManagement<T extends BaseEntity, C, U extends Identifiable<Long>>
|
||||
extends RepositoryManagement<T, C, U> {
|
||||
|
||||
/**
|
||||
* Retrieves a {@link Page} of all {@link BaseEntity}s filtered by their soft-deleted state.
|
||||
*
|
||||
* @param softDeletedMode the filter defining which entities to return based on their soft-deleted status
|
||||
* @param pageable the page request to sort and limit the result
|
||||
* @return a page of found entities matching the given soft-deleted filter
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_READ_REPOSITORY)
|
||||
Page<T> findAll(@NotNull SoftDeletedMode softDeletedMode, @NotNull Pageable pageable);
|
||||
|
||||
/**
|
||||
* Retrieves a {@link Page} of {@link BaseEntity}s matching the given RSQL filter,
|
||||
* filtered by their soft-deleted state.
|
||||
*
|
||||
* @param rsql filter definition in RSQL syntax
|
||||
* @param softDeletedMode the filter defining which entities to return based on their soft-deleted status
|
||||
* @param pageable the page request to sort and limit the result
|
||||
* @return a page of found entities matching both the RSQL filter and the soft-deleted filter
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_READ_REPOSITORY)
|
||||
Page<T> findByRsql(@NotNull String rsql, @NotNull SoftDeletedMode softDeletedMode, @NotNull Pageable pageable);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Contributors to the Eclipse Foundation
|
||||
*
|
||||
* This program and the accompanying materials are made
|
||||
* available under the terms of the Eclipse Public License 2.0
|
||||
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository;
|
||||
|
||||
public enum SoftDeletedMode {
|
||||
ONLY_SOFT_DELETED,
|
||||
EXCLUDE_SOFT_DELETED,
|
||||
INCLUDE_SOFT_DELETED
|
||||
}
|
||||
@@ -37,7 +37,7 @@ import org.springframework.security.access.prepost.PreAuthorize;
|
||||
* Service for managing {@link SoftwareModule}s.
|
||||
*/
|
||||
public interface SoftwareModuleManagement<T extends SoftwareModule>
|
||||
extends RepositoryManagement<T, SoftwareModuleManagement.Create, SoftwareModuleManagement.Update>, MetadataSupport<MetadataValue> {
|
||||
extends SoftDeletableRepositoryManagement<T, SoftwareModuleManagement.Create, SoftwareModuleManagement.Update>, MetadataSupport<MetadataValue> {
|
||||
|
||||
@Override
|
||||
default String permissionGroup() {
|
||||
|
||||
@@ -31,7 +31,7 @@ import org.springframework.security.access.prepost.PreAuthorize;
|
||||
* Service for managing {@link SoftwareModuleType}s.
|
||||
*/
|
||||
public interface SoftwareModuleTypeManagement<T extends SoftwareModuleType>
|
||||
extends RepositoryManagement<T, SoftwareModuleTypeManagement.Create, SoftwareModuleTypeManagement.Update> {
|
||||
extends SoftDeletableRepositoryManagement<T, SoftwareModuleTypeManagement.Create, SoftwareModuleTypeManagement.Update> {
|
||||
|
||||
@Override
|
||||
default String permissionGroup() {
|
||||
|
||||
@@ -38,6 +38,7 @@ import org.eclipse.hawkbit.ql.jpa.QLSupport;
|
||||
import org.eclipse.hawkbit.repository.DistributionSetManagement;
|
||||
import org.eclipse.hawkbit.repository.Identifiable;
|
||||
import org.eclipse.hawkbit.repository.RepositoryManagement;
|
||||
import org.eclipse.hawkbit.repository.SoftDeletedMode;
|
||||
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
||||
import org.eclipse.hawkbit.repository.exception.InvalidDistributionSetException;
|
||||
import org.eclipse.hawkbit.repository.jpa.Jpa;
|
||||
@@ -382,7 +383,13 @@ abstract class AbstractJpaRepositoryManagement<T extends AbstractJpaBaseEntity,
|
||||
|
||||
private final Supplier<Optional<Specification<T>>> isNotDeletedSupplier = SingletonSupplier.of(this::isNotDeleted0);
|
||||
|
||||
private Optional<Specification<T>> isNotDeleted() {
|
||||
protected Optional<Specification<T>> isSoftDeleted() {
|
||||
return supportSoftDelete()
|
||||
? Optional.of((root, query, cb) -> cb.equal(root.get(DELETED), true))
|
||||
: Optional.empty();
|
||||
}
|
||||
|
||||
protected Optional<Specification<T>> isNotDeleted() {
|
||||
return isNotDeletedSupplier.get();
|
||||
}
|
||||
|
||||
|
||||
@@ -30,8 +30,10 @@ import org.eclipse.hawkbit.ql.Node;
|
||||
import org.eclipse.hawkbit.ql.jpa.QLSupport;
|
||||
import org.eclipse.hawkbit.repository.DistributionSetManagement;
|
||||
import org.eclipse.hawkbit.repository.DistributionSetTagManagement;
|
||||
import org.eclipse.hawkbit.repository.Identifiable;
|
||||
import org.eclipse.hawkbit.repository.QuotaManagement;
|
||||
import org.eclipse.hawkbit.repository.RepositoryProperties;
|
||||
import org.eclipse.hawkbit.repository.SoftDeletedMode;
|
||||
import org.eclipse.hawkbit.repository.exception.DeletedException;
|
||||
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
||||
import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetException;
|
||||
@@ -105,46 +107,23 @@ public class JpaDistributionSetManagement
|
||||
@Override
|
||||
@SuppressWarnings("java:S3776") // java:S3776 - just too complex
|
||||
public Page<JpaDistributionSet> findByRsql(final String rsql, final Pageable pageable) {
|
||||
if (rsql != null && rsql.toLowerCase().contains(COMPLETE)) {
|
||||
// limited support for 'complete' - could be removed in future
|
||||
final Node node = QLSupport.getInstance().parse(rsql);
|
||||
final Specification<JpaDistributionSet> notDeleted = (root, query, cb) -> cb.equal(root.get(DELETED), false);
|
||||
final List<Specification<JpaDistributionSet>> specList = new ArrayList<>();
|
||||
specList.add(notDeleted);
|
||||
final AtomicReference<Node.Comparison> completedComparison = new AtomicReference<>();
|
||||
if (node instanceof Node.Comparison comparison && COMPLETE.equalsIgnoreCase(comparison.getKey())) {
|
||||
// all not deleted, won't add anything to spec
|
||||
completedComparison.set(comparison);
|
||||
} else if (node instanceof Node.Logical logical && logical.getOp() == Node.Logical.Operator.AND) {
|
||||
final List<Node> sanitizedChildren = new ArrayList<>();
|
||||
logical.getChildren().forEach(child -> {
|
||||
if (child instanceof Node.Comparison comparison && COMPLETE.equalsIgnoreCase(comparison.getKey())) {
|
||||
if (completedComparison.get() != null) {
|
||||
throw new RSQLParameterSyntaxException("Multiple 'complete' comparisons are not supported");
|
||||
}
|
||||
completedComparison.set(comparison);
|
||||
} else {
|
||||
sanitizedChildren.add(child);
|
||||
}
|
||||
});
|
||||
specList.add(QLSupport.getInstance().buildSpec(
|
||||
sanitizedChildren.size() == 1
|
||||
? sanitizedChildren.get(0)
|
||||
: new Node.Logical(Node.Logical.Operator.AND, sanitizedChildren),
|
||||
DistributionSetFields.class));
|
||||
}
|
||||
if (completedComparison.get() != null) { // really a comparison
|
||||
log.warn("Usage of 'complete' is limited and may be removed: {}", node);
|
||||
final boolean completed = completeComparison(completedComparison);
|
||||
return filter(JpaManagementHelper.findAllWithCountBySpec(jpaRepository, specList, pageable), completed);
|
||||
}
|
||||
}
|
||||
return findByRsqlAndDeleted(rsql, SoftDeletedMode.EXCLUDE_SOFT_DELETED, pageable);
|
||||
}
|
||||
|
||||
return super.findByRsql(rsql, pageable);
|
||||
@Override
|
||||
public Page<JpaDistributionSet> findByRsql(String rsql, SoftDeletedMode softDeletedMode, Pageable pageable) {
|
||||
return findByRsqlAndDeleted(rsql, softDeletedMode, pageable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<JpaDistributionSet> findAll(SoftDeletedMode softDeletedMode, Pageable pageable) {
|
||||
return jpaRepository.findAll(deletedSpecification(softDeletedMode), pageable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JpaDistributionSet update(final Update update) {
|
||||
assertDistributionSetIsNotDeleted(jpaRepository.getById(update.getId()));
|
||||
|
||||
final JpaDistributionSet updated = super.update(update);
|
||||
if (Boolean.TRUE.equals(update.getLocked())) {
|
||||
lockSoftwareModules(updated);
|
||||
@@ -154,6 +133,10 @@ public class JpaDistributionSetManagement
|
||||
|
||||
@Override
|
||||
public Map<Long, JpaDistributionSet> update(final Collection<Update> updates) {
|
||||
final List<Long> ids = updates.stream().map(Identifiable::getId).toList();
|
||||
jpaRepository.findAllById(ids)
|
||||
.forEach(this::assertDistributionSetIsNotDeleted);
|
||||
|
||||
final Map<Long, JpaDistributionSet> updated = super.update(updates);
|
||||
for (final Update update : updates) {
|
||||
final JpaDistributionSet updatedSet = updated.get(update.getId());
|
||||
@@ -228,6 +211,8 @@ public class JpaDistributionSetManagement
|
||||
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
|
||||
public JpaDistributionSet lock(final DistributionSet distributionSet) {
|
||||
final JpaDistributionSet jpaDistributionSet = toJpaDistributionSet(distributionSet);
|
||||
assertDistributionSetIsNotDeleted(jpaDistributionSet);
|
||||
|
||||
if (distributionSet.isLocked()) {
|
||||
return jpaDistributionSet;
|
||||
} else {
|
||||
@@ -245,6 +230,8 @@ public class JpaDistributionSetManagement
|
||||
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
|
||||
public JpaDistributionSet unlock(final DistributionSet distributionSet) {
|
||||
final JpaDistributionSet jpaDistributionSet = toJpaDistributionSet(distributionSet);
|
||||
assertDistributionSetIsNotDeleted(jpaDistributionSet);
|
||||
|
||||
if (jpaDistributionSet.isLocked()) {
|
||||
jpaDistributionSet.setLocked(false);
|
||||
return jpaRepository.save(jpaDistributionSet);
|
||||
@@ -257,6 +244,7 @@ public class JpaDistributionSetManagement
|
||||
@Transactional
|
||||
public JpaDistributionSet invalidate(final DistributionSet distributionSet) {
|
||||
final JpaDistributionSet jpaDistributionSet = toJpaDistributionSet(distributionSet);
|
||||
assertDistributionSetIsNotDeleted(jpaDistributionSet);
|
||||
jpaDistributionSet.invalidate();
|
||||
return jpaRepository.save(jpaDistributionSet);
|
||||
}
|
||||
@@ -296,6 +284,7 @@ public class JpaDistributionSetManagement
|
||||
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
|
||||
public List<JpaDistributionSet> assignTag(final Collection<Long> ids, final long dsTagId) {
|
||||
return updateTag(ids, dsTagId, (tag, distributionSet) -> {
|
||||
assertDistributionSetIsNotDeleted(distributionSet);
|
||||
if (distributionSet.getTags().contains(tag)) {
|
||||
return distributionSet;
|
||||
} else {
|
||||
@@ -310,6 +299,7 @@ public class JpaDistributionSetManagement
|
||||
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
|
||||
public List<JpaDistributionSet> unassignTag(final Collection<Long> ids, final long dsTagId) {
|
||||
return updateTag(ids, dsTagId, (tag, distributionSet) -> {
|
||||
assertDistributionSetIsNotDeleted(distributionSet);
|
||||
if (distributionSet.getTags().contains(tag)) {
|
||||
distributionSet.removeTag(tag);
|
||||
return jpaRepository.save(distributionSet);
|
||||
@@ -332,9 +322,6 @@ public class JpaDistributionSetManagement
|
||||
throw new IncompleteDistributionSetException(
|
||||
"Distribution set of type " + distributionSet.getType().getKey() + " is incomplete: " + distributionSet.getId());
|
||||
}
|
||||
if (distributionSet.isDeleted()) {
|
||||
throw new DeletedException(DistributionSet.class, id);
|
||||
}
|
||||
return distributionSet;
|
||||
}
|
||||
|
||||
@@ -384,6 +371,62 @@ public class JpaDistributionSetManagement
|
||||
QuotaHelper.assertAssignmentQuota(requested, maxMetaData, String.class, DistributionSet.class);
|
||||
}
|
||||
|
||||
private Page<JpaDistributionSet> findByRsqlAndDeleted(String rsql, SoftDeletedMode deletedMode, Pageable pageable) {
|
||||
if (rsql != null && rsql.toLowerCase().contains(COMPLETE)) {
|
||||
// limited support for 'complete' - could be removed in future
|
||||
final Node node = QLSupport.getInstance().parse(rsql);
|
||||
final Specification<JpaDistributionSet> deletedSpec = deletedSpecification(deletedMode);
|
||||
final List<Specification<JpaDistributionSet>> specList = new ArrayList<>();
|
||||
specList.add(deletedSpec);
|
||||
final AtomicReference<Node.Comparison> completedComparison = new AtomicReference<>();
|
||||
if (node instanceof Node.Comparison comparison && COMPLETE.equalsIgnoreCase(comparison.getKey())) {
|
||||
// all not deleted, won't add anything to spec
|
||||
completedComparison.set(comparison);
|
||||
} else if (node instanceof Node.Logical logical && logical.getOp() == Node.Logical.Operator.AND) {
|
||||
final List<Node> sanitizedChildren = new ArrayList<>();
|
||||
logical.getChildren().forEach(child -> {
|
||||
if (child instanceof Node.Comparison comparison && COMPLETE.equalsIgnoreCase(comparison.getKey())) {
|
||||
if (completedComparison.get() != null) {
|
||||
throw new RSQLParameterSyntaxException("Multiple 'complete' comparisons are not supported");
|
||||
}
|
||||
completedComparison.set(comparison);
|
||||
} else {
|
||||
sanitizedChildren.add(child);
|
||||
}
|
||||
});
|
||||
specList.add(QLSupport.getInstance().buildSpec(
|
||||
sanitizedChildren.size() == 1
|
||||
? sanitizedChildren.get(0)
|
||||
: new Node.Logical(Node.Logical.Operator.AND, sanitizedChildren),
|
||||
DistributionSetFields.class));
|
||||
}
|
||||
if (completedComparison.get() != null) { // really a comparison
|
||||
log.warn("Usage of 'complete' is limited and may be removed: {}", node);
|
||||
final boolean completed = completeComparison(completedComparison);
|
||||
return filter(JpaManagementHelper.findAllWithCountBySpec(jpaRepository, specList, pageable), completed);
|
||||
}
|
||||
}
|
||||
|
||||
// fallback
|
||||
final List<Specification<JpaDistributionSet>> specList = new ArrayList<>();
|
||||
final Specification<JpaDistributionSet> rslqSpec =
|
||||
rsql != null ? QLSupport.getInstance().buildSpec(rsql, DistributionSetFields.class) : Specification.unrestricted();
|
||||
specList.add(deletedSpecification(deletedMode));
|
||||
specList.add(rslqSpec);
|
||||
return JpaManagementHelper.findAllWithCountBySpec(jpaRepository, specList, pageable);
|
||||
}
|
||||
|
||||
private Specification<JpaDistributionSet> deletedSpecification(final SoftDeletedMode softDeletedMode) {
|
||||
return switch (softDeletedMode) {
|
||||
case ONLY_SOFT_DELETED ->
|
||||
(root, query, cb) -> cb.equal(root.get(DELETED), true);
|
||||
case EXCLUDE_SOFT_DELETED ->
|
||||
(root, query, cb) -> cb.equal(root.get(DELETED), false);
|
||||
case INCLUDE_SOFT_DELETED ->
|
||||
Specification.unrestricted();
|
||||
};
|
||||
}
|
||||
|
||||
private static boolean completeComparison(final AtomicReference<Node.Comparison> completeComparison) {
|
||||
final Node.Comparison comparison = completeComparison.get();
|
||||
if (comparison.getOp() == Node.Comparison.Operator.EQ) {
|
||||
@@ -410,6 +453,7 @@ public class JpaDistributionSetManagement
|
||||
|
||||
private JpaDistributionSet getValid0(final long id) {
|
||||
final JpaDistributionSet distributionSet = jpaRepository.getById(id);
|
||||
assertDistributionSetIsNotDeleted(distributionSet);
|
||||
if (!distributionSet.isValid()) {
|
||||
throw new InvalidDistributionSetException(
|
||||
"Distribution set of type " + distributionSet.getType().getKey() + " is invalid: " + distributionSet.getId());
|
||||
@@ -473,4 +517,10 @@ public class JpaDistributionSetManagement
|
||||
throw new EntityNotFoundException(DistributionSetTag.class, tagId);
|
||||
}
|
||||
}
|
||||
|
||||
private void assertDistributionSetIsNotDeleted(final JpaDistributionSet jpaDistributionSet) {
|
||||
if (jpaDistributionSet.isDeleted()) {
|
||||
throw new DeletedException(DistributionSet.class, jpaDistributionSet.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,15 +11,21 @@ package org.eclipse.hawkbit.repository.jpa.management;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import jakarta.persistence.EntityManager;
|
||||
|
||||
import org.eclipse.hawkbit.ql.jpa.QLSupport;
|
||||
import org.eclipse.hawkbit.repository.DistributionSetTypeManagement;
|
||||
import org.eclipse.hawkbit.repository.Identifiable;
|
||||
import org.eclipse.hawkbit.repository.QuotaManagement;
|
||||
import org.eclipse.hawkbit.repository.SoftDeletedMode;
|
||||
import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException;
|
||||
import org.eclipse.hawkbit.repository.exception.DeletedException;
|
||||
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
||||
import org.eclipse.hawkbit.repository.exception.EntityReadOnlyException;
|
||||
import org.eclipse.hawkbit.repository.jpa.JpaManagementHelper;
|
||||
import org.eclipse.hawkbit.repository.jpa.acm.AccessController;
|
||||
import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetType;
|
||||
@@ -38,6 +44,9 @@ import org.eclipse.hawkbit.tenancy.TenantAwareCacheManager;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
|
||||
import org.springframework.cache.Cache;
|
||||
import org.springframework.dao.ConcurrencyFailureException;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
import org.springframework.resilience.annotation.Retryable;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
@@ -48,6 +57,7 @@ public class JpaDistributionSetTypeManagement
|
||||
extends AbstractJpaRepositoryManagement<JpaDistributionSetType, DistributionSetTypeManagement.Create, DistributionSetTypeManagement.Update, DistributionSetTypeRepository, DistributionSetTypeFields>
|
||||
implements DistributionSetTypeManagement<JpaDistributionSetType> {
|
||||
|
||||
private final DistributionSetTypeRepository distributionSetTypeRepository;
|
||||
private final SoftwareModuleTypeRepository softwareModuleTypeRepository;
|
||||
private final DistributionSetRepository distributionSetRepository;
|
||||
private final TargetTypeRepository targetTypeRepository;
|
||||
@@ -60,6 +70,7 @@ public class JpaDistributionSetTypeManagement
|
||||
final DistributionSetRepository distributionSetRepository, final TargetTypeRepository targetTypeRepository,
|
||||
final QuotaManagement quotaManagement) {
|
||||
super(distributionSetTypeRepository, entityManager);
|
||||
this.distributionSetTypeRepository = distributionSetTypeRepository;
|
||||
this.softwareModuleTypeRepository = softwareModuleTypeRepository;
|
||||
this.distributionSetRepository = distributionSetRepository;
|
||||
this.targetTypeRepository = targetTypeRepository;
|
||||
@@ -117,11 +128,53 @@ public class JpaDistributionSetTypeManagement
|
||||
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
|
||||
public JpaDistributionSetType unassignSoftwareModuleType(final long id, final long softwareModuleTypeId) {
|
||||
final JpaDistributionSetType type = jpaRepository.getById(id);
|
||||
assertDistributionSetTypeIsNotDeleted(type);
|
||||
checkDistributionSetTypeNotAssigned(id);
|
||||
type.removeModuleType(softwareModuleTypeRepository.getById(softwareModuleTypeId));
|
||||
return jpaRepository.save(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<JpaDistributionSetType> findAll(SoftDeletedMode softDeletedMode, Pageable pageable) {
|
||||
if (softDeletedMode != SoftDeletedMode.INCLUDE_SOFT_DELETED) {
|
||||
final Specification<JpaDistributionSetType> deletedSpec =
|
||||
DistributionSetTypeSpecification.isDeleted(softDeletedMode == SoftDeletedMode.ONLY_SOFT_DELETED);
|
||||
|
||||
return distributionSetTypeRepository.findAll(deletedSpec, pageable);
|
||||
}
|
||||
return distributionSetTypeRepository.findAll(pageable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<JpaDistributionSetType> findByRsql(String rsql, SoftDeletedMode softDeletedMode, Pageable pageable) {
|
||||
final Specification<JpaDistributionSetType> rsqlSpec = QLSupport.getInstance().buildSpec(rsql, DistributionSetTypeFields.class);
|
||||
if (softDeletedMode != SoftDeletedMode.INCLUDE_SOFT_DELETED) {
|
||||
final Specification<JpaDistributionSetType> deletedSpec =
|
||||
DistributionSetTypeSpecification.isDeleted(softDeletedMode == SoftDeletedMode.ONLY_SOFT_DELETED);
|
||||
|
||||
return distributionSetTypeRepository.findAll(JpaManagementHelper.combineWithAnd(List.of(rsqlSpec, deletedSpec)), pageable);
|
||||
}
|
||||
return distributionSetTypeRepository.findAll(rsqlSpec, pageable);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
|
||||
public JpaDistributionSetType update(final DistributionSetTypeManagement.Update update) {
|
||||
final JpaDistributionSetType distributionSetType = distributionSetTypeRepository.getById(update.getId());
|
||||
assertDistributionSetTypeIsNotDeleted(distributionSetType);
|
||||
return super.update(update);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
|
||||
public Map<Long, JpaDistributionSetType> update(final Collection<DistributionSetTypeManagement.Update> updates) {
|
||||
final List<Long> ids = updates.stream().map(Identifiable::getId).toList();
|
||||
distributionSetTypeRepository.findAllById(ids).forEach(this::assertDistributionSetTypeIsNotDeleted);
|
||||
return super.update(updates);
|
||||
}
|
||||
|
||||
private JpaDistributionSetType assignSoftwareModuleTypes(
|
||||
final long dsTypeId, final Collection<Long> softwareModulesTypeIds, final boolean mandatory) {
|
||||
final Collection<JpaSoftwareModuleType> foundModules = softwareModuleTypeRepository.findAllById(softwareModulesTypeIds);
|
||||
@@ -132,6 +185,7 @@ public class JpaDistributionSetTypeManagement
|
||||
|
||||
final JpaDistributionSetType type = jpaRepository.getById(dsTypeId);
|
||||
|
||||
assertDistributionSetTypeIsNotDeleted(type);
|
||||
checkDistributionSetTypeNotAssigned(dsTypeId);
|
||||
assertSoftwareModuleTypeQuota(dsTypeId, softwareModulesTypeIds.size());
|
||||
|
||||
@@ -168,4 +222,10 @@ public class JpaDistributionSetTypeManagement
|
||||
"Distribution set type %s is already assigned to distribution sets and cannot be changed!", id));
|
||||
}
|
||||
}
|
||||
|
||||
private void assertDistributionSetTypeIsNotDeleted(final DistributionSetType distributionSetType){
|
||||
if (distributionSetType.isDeleted()) {
|
||||
throw new DeletedException(DistributionSetType.class, distributionSetType.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,6 +41,7 @@ import org.eclipse.hawkbit.repository.RolloutApprovalStrategy;
|
||||
import org.eclipse.hawkbit.repository.RolloutHelper;
|
||||
import org.eclipse.hawkbit.repository.RolloutManagement;
|
||||
import org.eclipse.hawkbit.repository.RolloutStatusCache;
|
||||
import org.eclipse.hawkbit.repository.SoftDeletedMode;
|
||||
import org.eclipse.hawkbit.repository.TargetManagement;
|
||||
import org.eclipse.hawkbit.repository.event.EventPublisherHolder;
|
||||
import org.eclipse.hawkbit.repository.event.remote.entity.RolloutGroupCreatedEvent;
|
||||
@@ -266,6 +267,18 @@ public class JpaRolloutManagement implements RolloutManagement {
|
||||
rolloutRepository.findAll(RolloutSpecification.isDeleted(deleted, pageable.getSort()), pageable), pageable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<Rollout> findAll(final SoftDeletedMode softDeletedMode, Pageable pageable) {
|
||||
return switch (softDeletedMode) {
|
||||
case EXCLUDE_SOFT_DELETED -> JpaManagementHelper.convertPage(
|
||||
rolloutRepository.findAll(RolloutSpecification.isDeleted(false, pageable.getSort()), pageable), pageable);
|
||||
case ONLY_SOFT_DELETED -> JpaManagementHelper.convertPage(
|
||||
rolloutRepository.findAll(RolloutSpecification.isDeleted(true, pageable.getSort()), pageable), pageable);
|
||||
case INCLUDE_SOFT_DELETED -> JpaManagementHelper.convertPage(
|
||||
rolloutRepository.findAll(Specification.unrestricted(), pageable), pageable);
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<Rollout> findAllWithDetailedStatus(final boolean deleted, final Pageable pageable) {
|
||||
return appendStatusDetails(JpaManagementHelper.convertPage(
|
||||
@@ -281,6 +294,21 @@ public class JpaRolloutManagement implements RolloutManagement {
|
||||
return JpaManagementHelper.convertPage(rolloutRepository.findAll(JpaManagementHelper.combineWithAnd(specList), pageable), pageable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<Rollout> findByRsql(String rsql, SoftDeletedMode softDeletedMode, Pageable pageable) {
|
||||
final Specification<JpaRollout> rsqlSpec = QLSupport.getInstance().buildSpec(rsql, RolloutFields.class);
|
||||
|
||||
if (softDeletedMode != SoftDeletedMode.INCLUDE_SOFT_DELETED) {
|
||||
final Specification<JpaRollout> deletedSpec = RolloutSpecification.isDeleted(
|
||||
softDeletedMode == SoftDeletedMode.ONLY_SOFT_DELETED, pageable.getSort());
|
||||
return JpaManagementHelper.convertPage(
|
||||
rolloutRepository.findAll(JpaManagementHelper.combineWithAnd(List.of(rsqlSpec, deletedSpec)), pageable), pageable);
|
||||
} else {
|
||||
return JpaManagementHelper.convertPage(rolloutRepository.findAll(rsqlSpec, pageable), pageable);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<Rollout> findByRsqlWithDetailedStatus(final String rsql, final boolean deleted, final Pageable pageable) {
|
||||
final List<Specification<JpaRollout>> specList = List.of(
|
||||
|
||||
@@ -22,9 +22,13 @@ import java.util.stream.Collectors;
|
||||
import jakarta.persistence.EntityManager;
|
||||
|
||||
import org.eclipse.hawkbit.artifact.encryption.ArtifactEncryptionService;
|
||||
import org.eclipse.hawkbit.ql.jpa.QLSupport;
|
||||
import org.eclipse.hawkbit.repository.ArtifactManagement;
|
||||
import org.eclipse.hawkbit.repository.Identifiable;
|
||||
import org.eclipse.hawkbit.repository.QuotaManagement;
|
||||
import org.eclipse.hawkbit.repository.SoftDeletedMode;
|
||||
import org.eclipse.hawkbit.repository.SoftwareModuleManagement;
|
||||
import org.eclipse.hawkbit.repository.exception.DeletedException;
|
||||
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
||||
import org.eclipse.hawkbit.repository.exception.IncompleteSoftwareModuleException;
|
||||
import org.eclipse.hawkbit.repository.exception.LockedException;
|
||||
@@ -45,6 +49,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProp
|
||||
import org.springframework.dao.ConcurrencyFailureException;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
import org.springframework.resilience.annotation.Retryable;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
@@ -56,6 +61,7 @@ public class JpaSoftwareModuleManagement extends
|
||||
AbstractJpaRepositoryWithMetadataManagement<JpaSoftwareModule, SoftwareModuleManagement.Create, SoftwareModuleManagement.Update, SoftwareModuleRepository, SoftwareModuleFields, MetadataValue, JpaSoftwareModule.JpaMetadataValue>
|
||||
implements SoftwareModuleManagement<JpaSoftwareModule> {
|
||||
|
||||
private final SoftwareModuleRepository softwareModuleRepository;
|
||||
private final DistributionSetRepository distributionSetRepository;
|
||||
private final ArtifactManagement artifactManagement;
|
||||
private final QuotaManagement quotaManagement;
|
||||
@@ -64,6 +70,7 @@ public class JpaSoftwareModuleManagement extends
|
||||
final DistributionSetRepository distributionSetRepository, final ArtifactManagement artifactManagement,
|
||||
final QuotaManagement quotaManagement) {
|
||||
super(softwareModuleRepository, entityManager);
|
||||
this.softwareModuleRepository = softwareModuleRepository;
|
||||
this.distributionSetRepository = distributionSetRepository;
|
||||
this.artifactManagement = artifactManagement;
|
||||
this.quotaManagement = quotaManagement;
|
||||
@@ -96,6 +103,25 @@ public class JpaSoftwareModuleManagement extends
|
||||
return createdModule;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
|
||||
public JpaSoftwareModule update(final Update update) {
|
||||
final JpaSoftwareModule softwareModule = softwareModuleRepository.getById(update.getId());
|
||||
assertSoftwareModuleIsNotDeleted(softwareModule);
|
||||
return super.update(update);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
|
||||
public Map<Long, JpaSoftwareModule> update(final Collection<SoftwareModuleManagement.Update> updates) {
|
||||
final List<Long> ids = updates.stream().map(Identifiable::getId).toList();
|
||||
|
||||
softwareModuleRepository.findAllById(ids).forEach(this::assertSoftwareModuleIsNotDeleted);
|
||||
return super.update(updates);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<JpaSoftwareModule> softDelete(final Collection<JpaSoftwareModule> toDelete) {
|
||||
return toDelete.stream().filter(swModule -> {
|
||||
@@ -138,6 +164,7 @@ public class JpaSoftwareModuleManagement extends
|
||||
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
|
||||
public JpaSoftwareModule lock(final SoftwareModule softwareModule) {
|
||||
final JpaSoftwareModule jpaSoftwareModule = toJpaSoftwareModule(softwareModule);
|
||||
assertSoftwareModuleIsNotDeleted(jpaSoftwareModule);
|
||||
if (jpaSoftwareModule.isLocked()) {
|
||||
return jpaSoftwareModule;
|
||||
} else {
|
||||
@@ -154,6 +181,7 @@ public class JpaSoftwareModuleManagement extends
|
||||
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
|
||||
public JpaSoftwareModule unlock(final SoftwareModule softwareModule) {
|
||||
final JpaSoftwareModule jpaSoftwareModule = toJpaSoftwareModule(softwareModule);
|
||||
assertSoftwareModuleIsNotDeleted(jpaSoftwareModule);
|
||||
if (softwareModule.isLocked()) {
|
||||
jpaSoftwareModule.unlock();
|
||||
return jpaRepository.save(jpaSoftwareModule);
|
||||
@@ -162,6 +190,30 @@ public class JpaSoftwareModuleManagement extends
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<JpaSoftwareModule> findAll(SoftDeletedMode softDeletedMode, Pageable pageable) {
|
||||
if (softDeletedMode != SoftDeletedMode.INCLUDE_SOFT_DELETED) {
|
||||
Specification<JpaSoftwareModule> softDeletedSpec =
|
||||
SoftwareModuleSpecification.isDeleted(softDeletedMode == SoftDeletedMode.ONLY_SOFT_DELETED);
|
||||
return softwareModuleRepository.findAll(softDeletedSpec, pageable);
|
||||
}
|
||||
|
||||
return softwareModuleRepository.findAll(pageable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<JpaSoftwareModule> findByRsql(String rsql, SoftDeletedMode softDeletedMode, Pageable pageable) {
|
||||
final Specification<JpaSoftwareModule> rsqlSpec = QLSupport.getInstance().buildSpec(rsql, SoftwareModuleFields.class);
|
||||
if (softDeletedMode != SoftDeletedMode.INCLUDE_SOFT_DELETED) {
|
||||
final Specification<JpaSoftwareModule> softDeletedSpec =
|
||||
SoftwareModuleSpecification.isDeleted(softDeletedMode == SoftDeletedMode.ONLY_SOFT_DELETED);
|
||||
return softwareModuleRepository.findAll(
|
||||
JpaManagementHelper.combineWithAnd(List.of(rsqlSpec, softDeletedSpec)), pageable);
|
||||
}
|
||||
|
||||
return softwareModuleRepository.findAll(rsqlSpec, pageable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<JpaSoftwareModule> findByAssignedTo(final long distributionSetId, final Pageable pageable) {
|
||||
assertDistributionSetExists(distributionSetId);
|
||||
@@ -201,4 +253,10 @@ public class JpaSoftwareModuleManagement extends
|
||||
throw new EntityNotFoundException(DistributionSet.class, distributionSetId);
|
||||
}
|
||||
}
|
||||
|
||||
private void assertSoftwareModuleIsNotDeleted(final SoftwareModule softwareModule) {
|
||||
if (softwareModule.isDeleted()) {
|
||||
throw new DeletedException(SoftwareModule.class, softwareModule.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,20 +10,36 @@
|
||||
package org.eclipse.hawkbit.repository.jpa.management;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import jakarta.persistence.EntityManager;
|
||||
|
||||
import org.eclipse.hawkbit.ql.jpa.QLSupport;
|
||||
import org.eclipse.hawkbit.repository.Identifiable;
|
||||
import org.eclipse.hawkbit.repository.SoftDeletedMode;
|
||||
import org.eclipse.hawkbit.repository.SoftwareModuleTypeManagement;
|
||||
import org.eclipse.hawkbit.repository.exception.DeletedException;
|
||||
import org.eclipse.hawkbit.repository.jpa.JpaManagementHelper;
|
||||
import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModuleType;
|
||||
import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetTypeRepository;
|
||||
import org.eclipse.hawkbit.repository.jpa.repository.SoftwareModuleRepository;
|
||||
import org.eclipse.hawkbit.repository.jpa.repository.SoftwareModuleTypeRepository;
|
||||
import org.eclipse.hawkbit.repository.jpa.specifications.SoftwareModuleTypeSpecification;
|
||||
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
|
||||
import org.eclipse.hawkbit.repository.qfields.SoftwareModuleTypeFields;
|
||||
import org.eclipse.hawkbit.tenancy.TenantAwareCacheManager;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
|
||||
import org.springframework.cache.Cache;
|
||||
import org.springframework.dao.ConcurrencyFailureException;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
import org.springframework.resilience.annotation.Retryable;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@ConditionalOnBooleanProperty(prefix = "hawkbit.jpa", name = { "enabled", "software-module-type=management" }, matchIfMissing = true)
|
||||
@@ -31,6 +47,7 @@ public class JpaSoftwareModuleTypeManagement
|
||||
extends AbstractJpaRepositoryManagement<JpaSoftwareModuleType, SoftwareModuleTypeManagement.Create, SoftwareModuleTypeManagement.Update, SoftwareModuleTypeRepository, SoftwareModuleTypeFields>
|
||||
implements SoftwareModuleTypeManagement<JpaSoftwareModuleType> {
|
||||
|
||||
private final SoftwareModuleTypeRepository softwareModuleTypeRepository;
|
||||
private final DistributionSetTypeRepository distributionSetTypeRepository;
|
||||
private final SoftwareModuleRepository softwareModuleRepository;
|
||||
|
||||
@@ -40,6 +57,7 @@ public class JpaSoftwareModuleTypeManagement
|
||||
final DistributionSetTypeRepository distributionSetTypeRepository,
|
||||
final SoftwareModuleRepository softwareModuleRepository) {
|
||||
super(softwareModuleTypeRepository, entityManager);
|
||||
this.softwareModuleTypeRepository = softwareModuleTypeRepository;
|
||||
this.distributionSetTypeRepository = distributionSetTypeRepository;
|
||||
this.softwareModuleRepository = softwareModuleRepository;
|
||||
}
|
||||
@@ -54,9 +72,56 @@ public class JpaSoftwareModuleTypeManagement
|
||||
return jpaRepository.findByKey(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<JpaSoftwareModuleType> findAll(SoftDeletedMode softDeletedMode, Pageable pageable) {
|
||||
if (softDeletedMode != SoftDeletedMode.INCLUDE_SOFT_DELETED) {
|
||||
final Specification<JpaSoftwareModuleType> deletedSpec =
|
||||
SoftwareModuleTypeSpecification.isDeleted(softDeletedMode == SoftDeletedMode.ONLY_SOFT_DELETED);
|
||||
|
||||
return softwareModuleTypeRepository.findAll(deletedSpec, pageable);
|
||||
}
|
||||
return softwareModuleTypeRepository.findAll(pageable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<JpaSoftwareModuleType> findByRsql(String rsql, SoftDeletedMode softDeletedMode, Pageable pageable) {
|
||||
final Specification<JpaSoftwareModuleType> rsqlSpec = QLSupport.getInstance().buildSpec(rsql, SoftwareModuleTypeFields.class);
|
||||
if (softDeletedMode != SoftDeletedMode.INCLUDE_SOFT_DELETED) {
|
||||
final Specification<JpaSoftwareModuleType> deletedSpec =
|
||||
SoftwareModuleTypeSpecification.isDeleted(softDeletedMode == SoftDeletedMode.ONLY_SOFT_DELETED);
|
||||
|
||||
return softwareModuleTypeRepository.findAll(JpaManagementHelper.combineWithAnd(List.of(rsqlSpec, deletedSpec)), pageable);
|
||||
}
|
||||
return softwareModuleTypeRepository.findAll(rsqlSpec, pageable);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
|
||||
public JpaSoftwareModuleType update(final SoftwareModuleTypeManagement.Update update) {
|
||||
final JpaSoftwareModuleType softwareModuleType = softwareModuleTypeRepository.getById(update.getId());
|
||||
assertSoftwareModuleTypeIsNotDeleted(softwareModuleType);
|
||||
return super.update(update);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
|
||||
public Map<Long, JpaSoftwareModuleType> update(final Collection<SoftwareModuleTypeManagement.Update> updates) {
|
||||
final List<Long> ids = updates.stream().map(Identifiable::getId).toList();
|
||||
softwareModuleTypeRepository.findAllById(ids).forEach(this::assertSoftwareModuleTypeIsNotDeleted);
|
||||
return super.update(updates);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Collection<JpaSoftwareModuleType> softDelete(final Collection<JpaSoftwareModuleType> toDelete) {
|
||||
return toDelete.stream().filter(smt ->
|
||||
softwareModuleRepository.countByType(smt) > 0 || distributionSetTypeRepository.countByElementsSmType(smt) > 0).toList();
|
||||
}
|
||||
|
||||
private void assertSoftwareModuleTypeIsNotDeleted(final JpaSoftwareModuleType softwareModuleType){
|
||||
if (softwareModuleType.isDeleted()) {
|
||||
throw new DeletedException(SoftwareModuleType.class, softwareModuleType.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.AbstractJpaTypeEntity_;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetType;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetType_;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSetType;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
@@ -34,4 +35,9 @@ public final class DistributionSetTypeSpecification {
|
||||
public static Specification<JpaDistributionSetType> byKey(final String key) {
|
||||
return (targetRoot, query, cb) -> cb.equal(targetRoot.get(AbstractJpaTypeEntity_.key), key);
|
||||
}
|
||||
|
||||
public static Specification<JpaDistributionSetType> isDeleted(final Boolean isDeleted) {
|
||||
return (root, query, cb) ->
|
||||
cb.equal(root.<Boolean> get(JpaDistributionSetType_.deleted), isDeleted);
|
||||
}
|
||||
}
|
||||
@@ -32,4 +32,9 @@ public final class SoftwareModuleSpecification {
|
||||
return cb.equal(join.get(AbstractJpaBaseEntity_.ID), dsId);
|
||||
};
|
||||
}
|
||||
|
||||
public static Specification<JpaSoftwareModule> isDeleted(final Boolean isDeleted) {
|
||||
return (root, query, cb) ->
|
||||
cb.equal(root.<Boolean> get(JpaSoftwareModule_.deleted), isDeleted);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Contributors to the Eclipse Foundation
|
||||
*
|
||||
* This program and the accompanying materials are made
|
||||
* available under the terms of the Eclipse Public License 2.0
|
||||
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.jpa.specifications;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModuleType;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModuleType_;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class SoftwareModuleTypeSpecification {
|
||||
|
||||
public static Specification<JpaSoftwareModuleType> isDeleted(final Boolean isDeleted) {
|
||||
return (root, query, cb) ->
|
||||
cb.equal(root.<Boolean> get(JpaSoftwareModuleType_.deleted), isDeleted);
|
||||
}
|
||||
}
|
||||
@@ -457,16 +457,16 @@ public abstract class AbstractRepositoryManagementTest<T extends BaseEntity, C,
|
||||
if (resolvableType.getRawClass() == targetSuperClass) {
|
||||
return Optional.of(resolvableType);
|
||||
}
|
||||
final Optional<ResolvableType> inInterfaces = Arrays.stream(resolvableType.getInterfaces())
|
||||
.filter(superInterface -> superInterface.getRawClass() == targetSuperClass)
|
||||
.findAny();
|
||||
if (inInterfaces.isPresent()) {
|
||||
return inInterfaces;
|
||||
} else if (resolvableType.getSuperType() != ResolvableType.NONE) {
|
||||
return findGenericSuperType(resolvableType.getSuperType(), targetSuperClass);
|
||||
} else {
|
||||
return Optional.empty();
|
||||
for (final ResolvableType superInterface : resolvableType.getInterfaces()) {
|
||||
final Optional<ResolvableType> found = findGenericSuperType(superInterface, targetSuperClass);
|
||||
if (found.isPresent()) {
|
||||
return found;
|
||||
}
|
||||
}
|
||||
if (resolvableType.getSuperType() != ResolvableType.NONE) {
|
||||
return findGenericSuperType(resolvableType.getSuperType(), targetSuperClass);
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private void assertEquals(final Object actual, final Object expected, final Deque<String> path) {
|
||||
|
||||
@@ -32,6 +32,7 @@ import org.eclipse.hawkbit.repository.DistributionSetManagement.Update;
|
||||
import org.eclipse.hawkbit.repository.DistributionSetTagManagement;
|
||||
import org.eclipse.hawkbit.repository.DistributionSetTypeManagement;
|
||||
import org.eclipse.hawkbit.repository.Identifiable;
|
||||
import org.eclipse.hawkbit.repository.exception.DeletedException;
|
||||
import org.eclipse.hawkbit.repository.RepositoryProperties;
|
||||
import org.eclipse.hawkbit.repository.SoftwareModuleManagement;
|
||||
import org.eclipse.hawkbit.repository.SoftwareModuleTypeManagement;
|
||||
@@ -936,4 +937,19 @@ class DistributionSetManagementTest extends AbstractRepositoryManagementWithMeta
|
||||
.as("entity with too short version should not be updated")
|
||||
.isThrownBy(() -> distributionSetManagement.update(distributionSetUpdate2));
|
||||
}
|
||||
|
||||
@Test
|
||||
void bulkUpdateSoftDeletedDistributionSetRejected() {
|
||||
final DistributionSet active = testdataFactory.createDistributionSet("active");
|
||||
final DistributionSet toDelete = testdataFactory.createDistributionSet("toDelete");
|
||||
testdataFactory.createTarget("bulkTarget");
|
||||
assignDistributionSet(toDelete.getId(), "bulkTarget");
|
||||
distributionSetManagement.delete(toDelete.getId());
|
||||
|
||||
final List<Update> updates = List.of(
|
||||
Update.builder().id(active.getId()).description("ok").build(),
|
||||
Update.builder().id(toDelete.getId()).description("should fail").build());
|
||||
assertThatThrownBy(() -> distributionSetManagement.update(updates))
|
||||
.isInstanceOf(DeletedException.class);
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,7 @@ import org.eclipse.hawkbit.repository.SoftwareModuleManagement.Create;
|
||||
import org.eclipse.hawkbit.repository.SoftwareModuleManagement.Update;
|
||||
import org.eclipse.hawkbit.repository.SoftwareModuleTypeManagement;
|
||||
import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleCreatedEvent;
|
||||
import org.eclipse.hawkbit.repository.exception.DeletedException;
|
||||
import org.eclipse.hawkbit.repository.exception.IncompleteSoftwareModuleException;
|
||||
import org.eclipse.hawkbit.repository.exception.LockedException;
|
||||
import org.eclipse.hawkbit.repository.jpa.RandomGeneratedInputStream;
|
||||
@@ -486,6 +487,22 @@ class SoftwareModuleManagementTest
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void bulkUpdateSoftDeletedSoftwareModuleRejected() {
|
||||
final SoftwareModule active = softwareModuleManagement.create(
|
||||
Create.builder().type(osType).name("active").version("1.0").build());
|
||||
SoftwareModule toDelete = softwareModuleManagement.create(
|
||||
Create.builder().type(osType).name("toDelete").version("1.0").build());
|
||||
testdataFactory.createDistributionSet(List.of(toDelete));
|
||||
softwareModuleManagement.delete(toDelete.getId());
|
||||
|
||||
final List<Update> updates = List.of(
|
||||
Update.builder().id(active.getId()).description("ok").build(),
|
||||
Update.builder().id(toDelete.getId()).description("should fail").build());
|
||||
assertThatExceptionOfType(DeletedException.class)
|
||||
.isThrownBy(() -> softwareModuleManagement.update(updates));
|
||||
}
|
||||
|
||||
private void assertArtifactDoesntExist(final Artifact... results) {
|
||||
for (final Artifact result : results) {
|
||||
final String currentTenant = AccessContext.tenant();
|
||||
|
||||
@@ -95,7 +95,7 @@ public class DistributionSetView extends TableView<MgmtDistributionSet, Long> {
|
||||
(query, rsqlFilter) -> Optional.ofNullable(
|
||||
hawkbitClient.getDistributionSetRestApi()
|
||||
.getDistributionSets(rsqlFilter, query.getOffset(), query.getPageSize(), Utils.getSortParam(query
|
||||
.getSortOrders()))
|
||||
.getSortOrders()), null)
|
||||
.getBody())
|
||||
.stream().flatMap(body -> body.getContent().stream()),
|
||||
e -> new CreateDialog(hawkbitClient).result(),
|
||||
@@ -158,7 +158,7 @@ public class DistributionSetView extends TableView<MgmtDistributionSet, Long> {
|
||||
type.setItemLabelGenerator(MgmtDistributionSetType::getName);
|
||||
type.setItems(Optional.ofNullable(
|
||||
hawkbitClient.getDistributionSetTypeRestApi()
|
||||
.getDistributionSetTypes(null, 0, 20, Constants.NAME_ASC)
|
||||
.getDistributionSetTypes(null, 0, 20, Constants.NAME_ASC, null)
|
||||
.getBody())
|
||||
.map(PagedList::getContent)
|
||||
.orElseGet(Collections::emptyList));
|
||||
@@ -266,7 +266,7 @@ public class DistributionSetView extends TableView<MgmtDistributionSet, Long> {
|
||||
this::readyToCreate,
|
||||
Optional.ofNullable(
|
||||
hawkbitClient.getDistributionSetTypeRestApi()
|
||||
.getDistributionSetTypes(null, 0, 30, Constants.CREATED_AT_DESC)
|
||||
.getDistributionSetTypes(null, 0, 30, Constants.CREATED_AT_DESC, null)
|
||||
.getBody())
|
||||
.map(body -> body.getContent().toArray(new MgmtDistributionSetType[0]))
|
||||
.orElseGet(() -> new MgmtDistributionSetType[0]));
|
||||
@@ -353,6 +353,8 @@ public class DistributionSetView extends TableView<MgmtDistributionSet, Long> {
|
||||
v -> new Utils.BaseDialog<Void>("Add Software Modules") {
|
||||
|
||||
{
|
||||
setHeight("80vh");
|
||||
setWidth("80vw");
|
||||
final SoftwareModuleView softwareModulesView = new SoftwareModuleView(false, hawkbitClient);
|
||||
add(softwareModulesView);
|
||||
final Button addBtn = new Button("Add");
|
||||
|
||||
@@ -96,7 +96,7 @@ public final class RolloutView extends TableView<MgmtRolloutResponseBody, Long>
|
||||
(query, rsqlFilter) -> Optional.ofNullable(
|
||||
hawkbitClient.getRolloutRestApi()
|
||||
.getRollouts(
|
||||
rsqlFilter, query.getOffset(), query.getPageSize(), Constants.NAME_ASC, "full")
|
||||
rsqlFilter, query.getOffset(), query.getPageSize(), Constants.NAME_ASC, "full", null)
|
||||
.getBody()).stream().flatMap(page -> page.getContent().stream()),
|
||||
selectionGrid -> new CreateDialog(hawkbitClient).result(),
|
||||
selectionGrid -> {
|
||||
@@ -344,7 +344,7 @@ public final class RolloutView extends TableView<MgmtRolloutResponseBody, Long>
|
||||
"Distribution Set",
|
||||
this::readyToCreate,
|
||||
query -> hawkbitClient.getDistributionSetRestApi()
|
||||
.getDistributionSets(query.getFilter().orElse(null), query.getOffset(), query.getPageSize(), Constants.NAME_ASC)
|
||||
.getDistributionSets(query.getFilter().orElse(null), query.getOffset(), query.getPageSize(), Constants.NAME_ASC, null)
|
||||
.getBody().getContent().stream());
|
||||
distributionSet.setRequiredIndicatorVisible(true);
|
||||
distributionSet.setItemLabelGenerator(distributionSetO -> distributionSetO.getName() + ":" + distributionSetO.getVersion());
|
||||
|
||||
@@ -98,7 +98,8 @@ public class SoftwareModuleView extends TableView<MgmtSoftwareModule, Long> {
|
||||
},
|
||||
(query, rsqlFilter) -> Optional.ofNullable(
|
||||
hawkbitClient.getSoftwareModuleRestApi()
|
||||
.getSoftwareModules(rsqlFilter, query.getOffset(), query.getPageSize(), Constants.NAME_ASC)
|
||||
.getSoftwareModules(rsqlFilter, query.getOffset(), query.getPageSize(), Constants.NAME_ASC,
|
||||
null)
|
||||
.getBody())
|
||||
.stream().map(PagedList::getContent).flatMap(List::stream),
|
||||
isParent ? v -> new CreateDialog(hawkbitClient).result() : null,
|
||||
@@ -137,7 +138,7 @@ public class SoftwareModuleView extends TableView<MgmtSoftwareModule, Long> {
|
||||
type.setItemLabelGenerator(MgmtSoftwareModuleType::getName);
|
||||
type.setItems(Optional.ofNullable(
|
||||
hawkbitClient.getSoftwareModuleTypeRestApi()
|
||||
.getTypes(null, 0, 20, Constants.NAME_ASC)
|
||||
.getTypes(null, 0, 20, Constants.NAME_ASC, null)
|
||||
.getBody())
|
||||
.map(PagedList::getContent)
|
||||
.orElseGet(Collections::emptyList));
|
||||
@@ -231,7 +232,7 @@ public class SoftwareModuleView extends TableView<MgmtSoftwareModule, Long> {
|
||||
this::readyToCreate,
|
||||
Optional.ofNullable(
|
||||
hawkbitClient.getSoftwareModuleTypeRestApi()
|
||||
.getTypes(null, 0, 30, Constants.NAME_ASC)
|
||||
.getTypes(null, 0, 30, Constants.NAME_ASC, null)
|
||||
.getBody())
|
||||
.map(body -> body.getContent().toArray(new MgmtSoftwareModuleType[0]))
|
||||
.orElseGet(() -> new MgmtSoftwareModuleType[0]));
|
||||
@@ -264,7 +265,7 @@ public class SoftwareModuleView extends TableView<MgmtSoftwareModule, Long> {
|
||||
distType.setItems(
|
||||
Optional.ofNullable(
|
||||
hawkbitClient.getDistributionSetTypeRestApi()
|
||||
.getDistributionSetTypes(null, 0, 30, Constants.NAME_ASC)
|
||||
.getDistributionSetTypes(null, 0, 30, Constants.NAME_ASC, null)
|
||||
.getBody())
|
||||
.map(body -> body.getContent().toArray(new MgmtDistributionSetType[0]))
|
||||
.orElseGet(() -> new MgmtDistributionSetType[0]));
|
||||
|
||||
@@ -256,7 +256,8 @@ public class TargetFilterQueryView extends TableView<TargetFilterQueryView.Targe
|
||||
query.getFilter().orElse(null),
|
||||
query.getOffset(),
|
||||
query.getLimit(),
|
||||
Constants.NAME_ASC)
|
||||
Constants.NAME_ASC,
|
||||
null)
|
||||
.getBody()).stream().flatMap(body -> body.getContent().stream()));
|
||||
distributionSet.setItemLabelGenerator(ds -> ds.getName() + ":" + ds.getVersion());
|
||||
distributionSet.focus();
|
||||
|
||||
@@ -869,7 +869,7 @@ public final class TargetView extends TableView<TargetView.TargetWithDs, String>
|
||||
"Distribution Set",
|
||||
this::readyToAssign,
|
||||
query -> hawkbitClient.getDistributionSetRestApi()
|
||||
.getDistributionSets(query.getFilter().orElse(null), query.getOffset(), query.getPageSize(), Constants.NAME_ASC)
|
||||
.getDistributionSets(query.getFilter().orElse(null), query.getOffset(), query.getPageSize(), Constants.NAME_ASC, null)
|
||||
.getBody().getContent().stream());
|
||||
distributionSet.setRequiredIndicatorVisible(true);
|
||||
distributionSet.setItemLabelGenerator(distributionSetO -> distributionSetO.getName() + ":" + distributionSetO.getVersion());
|
||||
|
||||
Reference in New Issue
Block a user