From 452d8618d722ae1e169523526884f6c32116ce72 Mon Sep 17 00:00:00 2001 From: Avgustin Marinov Date: Wed, 21 May 2025 13:45:18 +0300 Subject: [PATCH] JpaDistributionSet#metadata made Map (#2411) Signed-off-by: Avgustin Marinov --- .../DistributionSetMetadataFields.java | 33 --- .../rest/api/MgmtDistributionSetRestApi.java | 136 ++++------ .../resource/MgmtDistributionSetMapper.java | 39 ++- .../resource/MgmtDistributionSetResource.java | 63 ++--- .../rest/resource/util/PagingUtility.java | 7 +- .../MgmtDistributionSetResourceTest.java | 136 +++------- .../repository/DistributionSetManagement.java | 73 ++---- .../hawkbit/repository/EntityFactory.java | 14 -- .../repository/model/DistributionSet.java | 10 + .../model/DistributionSetMetadata.java | 26 -- .../repository/jpa/JpaEntityFactory.java | 6 - .../RepositoryApplicationConfiguration.java | 4 +- .../JpaDistributionSetManagement.java | 163 ++++++------ .../jpa/model/DsMetadataCompositeKey.java | 76 ------ .../jpa/model/JpaDistributionSet.java | 31 ++- .../jpa/model/JpaDistributionSetMetadata.java | 86 ------- .../DistributionSetMetadataRepository.java | 39 --- .../DistributionSetAccessControllerTest.java | 48 ++-- .../TargetAccessControllerTest.java | 38 +-- .../TargetTypeAccessControllerTest.java | 28 +-- .../controller/TestAccessControlManger.java | 23 +- ...DistributionSetManagementSecurityTest.java | 60 ++--- .../DistributionSetManagementTest.java | 131 ++++------ .../rsql/RSQLDistributionSetFieldTest.java | 234 ------------------ ...RSQLDistributionSetMetadataFieldsTest.java | 81 ------ .../test/util/AbstractIntegrationTest.java | 13 +- 26 files changed, 382 insertions(+), 1216 deletions(-) delete mode 100644 hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/DistributionSetMetadataFields.java delete mode 100644 hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/DistributionSetMetadata.java delete mode 100644 hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/DsMetadataCompositeKey.java delete mode 100644 hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaDistributionSetMetadata.java delete mode 100644 hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/DistributionSetMetadataRepository.java delete mode 100644 hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLDistributionSetFieldTest.java delete mode 100644 hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLDistributionSetMetadataFieldsTest.java diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/DistributionSetMetadataFields.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/DistributionSetMetadataFields.java deleted file mode 100644 index d46adb2e2..000000000 --- a/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/DistributionSetMetadataFields.java +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright (c) 2015 Bosch Software Innovations GmbH and others - * - * 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 lombok.Getter; - -/** - * Sort fields for DistributionSetMetadata. - */ -@Getter -public enum DistributionSetMetadataFields implements RsqlQueryField { - - KEY("key"), - VALUE("value"); - - private final String jpaEntityFieldName; - - DistributionSetMetadataFields(final String jpaEntityFieldName) { - this.jpaEntityFieldName = jpaEntityFieldName; - } - - @Override - public String identifierFieldName() { - return KEY.getJpaEntityFieldName(); - } -} \ No newline at end of file diff --git a/hawkbit-mgmt/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtDistributionSetRestApi.java b/hawkbit-mgmt/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtDistributionSetRestApi.java index d05bc84d8..445bebcb1 100644 --- a/hawkbit-mgmt/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtDistributionSetRestApi.java +++ b/hawkbit-mgmt/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtDistributionSetRestApi.java @@ -35,6 +35,7 @@ import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTarget; import org.eclipse.hawkbit.mgmt.json.model.targetfilter.MgmtTargetFilterQuery; import org.eclipse.hawkbit.rest.json.model.ExceptionInfo; import org.springframework.hateoas.MediaTypes; +import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; @@ -44,6 +45,7 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseStatus; /** * REST Resource handling for DistributionSet CRUD operations. @@ -473,15 +475,50 @@ public interface MgmtDistributionSetRestApi { @RequestBody List assignments, @RequestParam(value = "offline", required = false) Boolean offline); + /** + * Creates a list of meta-data for a specific distribution set. + * + * @param distributionSetId the ID of the distribution set to create meta data for + * @param metadataRest the list of meta-data entries to create + */ + @Operation(summary = "Create a list of meta data for a specific distribution set", + description = "Create a list of meta data entries Required permissions: READ_REPOSITORY and UPDATE_TARGET") + @ApiResponses(value = { + @ApiResponse(responseCode = "201", description = "Successfully created"), + @ApiResponse(responseCode = "400", description = "Bad Request - e.g. invalid parameters", + content = @Content(mediaType = "application/json", schema = @Schema(implementation = ExceptionInfo.class))), + @ApiResponse(responseCode = "401", description = "The request requires user authentication.", + content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))), + @ApiResponse(responseCode = "403", description = "Insufficient permissions, entity is not allowed to be " + + "changed (i.e. read-only) or data volume restriction applies.", + content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))), + @ApiResponse(responseCode = "404", description = "Distribution Set not found.", + content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))), + @ApiResponse(responseCode = "405", description = "The http request method is not allowed on the resource.", + content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))), + @ApiResponse(responseCode = "406", description = "In case accept header is specified and not application/json.", + content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))), + @ApiResponse(responseCode = "409", description = "E.g. in case an entity is created or modified by another " + + "user in another request at the same time. You may retry your modification request.", + content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))), + @ApiResponse(responseCode = "415", description = "The request was attempt with a media-type which is not " + + "supported by the server for this resource.", + content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))), + @ApiResponse(responseCode = "429", description = "Too many requests. The server will refuse further attempts " + + "and the client has to wait another second.", + content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))) + }) + @PostMapping(value = MgmtRestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING + "/{distributionSetId}/metadata", + consumes = { MediaType.APPLICATION_JSON_VALUE, MediaTypes.HAL_JSON_VALUE }) + @ResponseStatus(HttpStatus.CREATED) + void createMetadata( + @PathVariable("distributionSetId") Long distributionSetId, + @RequestBody List metadataRest); + /** * Gets a paged list of meta-data for a distribution set. * * @param distributionSetId the ID of the distribution set for the meta-data - * @param pagingOffsetParam the offset of list of targets for pagination, might not be present in the rest request then default value will - * be applied - * @param pagingLimitParam the limit of the paged request, might not be present in the rest request then default value will be applied - * @param sortParam the sorting parameter in the request URL, syntax {@code field:direction, field:direction} - * @param rsqlParam the search parameter in the request URL, syntax {@code q=key==abc} * @return status OK if get request is successful with the paged list of meta data */ @Operation(summary = "Return meta data for Distribution Set", description = "Get a paged list of meta data for a " + @@ -507,37 +544,14 @@ public interface MgmtDistributionSetRestApi { }) @GetMapping(value = MgmtRestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING + "/{distributionSetId}/metadata", produces = { MediaTypes.HAL_JSON_VALUE, MediaType.APPLICATION_JSON_VALUE }) - ResponseEntity> getMetadata( - @PathVariable("distributionSetId") Long distributionSetId, - @RequestParam( - value = MgmtRestConstants.REQUEST_PARAMETER_PAGING_OFFSET, - defaultValue = MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) - @Schema(description = "The paging offset (default is 0)") - int pagingOffsetParam, - @RequestParam( - value = MgmtRestConstants.REQUEST_PARAMETER_PAGING_LIMIT, - defaultValue = MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) - @Schema(description = "The maximum number of entries in a page (default is 50)") - int pagingLimitParam, - @RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_SORTING, required = false) - @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, - @RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_SEARCH, required = false) - @Schema(description = """ - Query fields based on the Feed Item Query Language (FIQL). See Entity Definitions for - available fields.""") - String rsqlParam); + ResponseEntity> getMetadata(@PathVariable("distributionSetId") Long distributionSetId); /** - * Gets a single meta data value for a specific key of a distribution set. + * Gets a single meta-data value for a specific key of a distribution set. * - * @param distributionSetId the ID of the distribution set to get the meta data from - * @param metadataKey the key of the meta data entry to retrieve the value from - * @return status OK if get request is successful with the value of the meta data + * @param distributionSetId the ID of the distribution set to get the meta-data from + * @param metadataKey the key of the meta-data entry to retrieve the value from + * @return status OK with the value of the meta-data, if the request is successful */ @Operation(summary = "Return single meta data value for a specific key of a Distribution Set", description = "Get a single meta data value for a meta data key. Required permission: READ_REPOSITORY") @@ -567,12 +581,11 @@ public interface MgmtDistributionSetRestApi { @PathVariable("metadataKey") String metadataKey); /** - * Updates a single meta data value of a distribution set. + * Updates a single meta-data value of a distribution set. * * @param distributionSetId the ID of the distribution set to update the meta data entry - * @param metadataKey the key of the meta data to update the value + * @param metadataKey the key of the meta-data to update the value * @param metadata update body - * @return status OK if the update request is successful and the updated meta data result */ @Operation(summary = "Update single meta data value of a distribution set", description = "Update a single meta " + "data value for speficic key. Required permission: UPDATE_REPOSITORY") @@ -601,9 +614,9 @@ public interface MgmtDistributionSetRestApi { "and the client has to wait another second.", content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))) }) - @PutMapping(value = MgmtRestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING + "/{distributionSetId}/metadata/{metadataKey}", - produces = { MediaTypes.HAL_JSON_VALUE, MediaType.APPLICATION_JSON_VALUE }) - ResponseEntity updateMetadata( + @PutMapping(value = MgmtRestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING + "/{distributionSetId}/metadata/{metadataKey}") + @ResponseStatus(HttpStatus.OK) + void updateMetadata( @PathVariable("distributionSetId") Long distributionSetId, @PathVariable("metadataKey") String metadataKey, @RequestBody MgmtMetadataBodyPut metadata); @@ -613,7 +626,6 @@ public interface MgmtDistributionSetRestApi { * * @param distributionSetId the ID of the distribution set to delete the meta data entry * @param metadataKey the key of the meta data to delete - * @return status OK if the delete request is successful */ @Operation(summary = "Delete a single meta data entry from the distribution set", description = "Delete a single " + "meta data. Required permission: UPDATE_REPOSITORY") @@ -637,51 +649,11 @@ public interface MgmtDistributionSetRestApi { content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))) }) @DeleteMapping(value = MgmtRestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING + "/{distributionSetId}/metadata/{metadataKey}") - ResponseEntity deleteMetadata( + @ResponseStatus(HttpStatus.OK) + void deleteMetadata( @PathVariable("distributionSetId") Long distributionSetId, @PathVariable("metadataKey") String metadataKey); - /** - * Creates a list of meta data for a specific distribution set. - * - * @param distributionSetId the ID of the distribution set to create meta data for - * @param metadataRest the list of meta data entries to create - * @return status created if post request is successful with the value of the created meta data - */ - @Operation(summary = "Create a list of meta data for a specific distribution set", description = "Create a list " + - "of meta data entries Required permissions: READ_REPOSITORY and UPDATE_TARGET") - @ApiResponses(value = { - @ApiResponse(responseCode = "201", description = "Successfully created"), - @ApiResponse(responseCode = "400", description = "Bad Request - e.g. invalid parameters", - content = @Content(mediaType = "application/json", schema = @Schema(implementation = ExceptionInfo.class))), - @ApiResponse(responseCode = "401", description = "The request requires user authentication.", - content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))), - @ApiResponse(responseCode = "403", description = "Insufficient permissions, entity is not allowed to be " + - "changed (i.e. read-only) or data volume restriction applies.", - content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))), - @ApiResponse(responseCode = "404", description = "Distribution Set not found.", - content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))), - @ApiResponse(responseCode = "405", description = "The http request method is not allowed on the resource.", - content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))), - @ApiResponse(responseCode = "406", description = "In case accept header is specified and not application/json.", - content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))), - @ApiResponse(responseCode = "409", description = "E.g. in case an entity is created or modified by another " + - "user in another request at the same time. You may retry your modification request.", - content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))), - @ApiResponse(responseCode = "415", description = "The request was attempt with a media-type which is not " + - "supported by the server for this resource.", - content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))), - @ApiResponse(responseCode = "429", description = "Too many requests. The server will refuse further attempts " + - "and the client has to wait another second.", - content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))) - }) - @PostMapping(value = MgmtRestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING + "/{distributionSetId}/metadata", - consumes = { MediaType.APPLICATION_JSON_VALUE, MediaTypes.HAL_JSON_VALUE }, - produces = { MediaTypes.HAL_JSON_VALUE, MediaType.APPLICATION_JSON_VALUE }) - ResponseEntity> createMetadata( - @PathVariable("distributionSetId") Long distributionSetId, - @RequestBody List metadataRest); - /** * Assigns a list of software modules to a distribution set. * diff --git a/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetMapper.java b/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetMapper.java index 082626e2a..56516f653 100644 --- a/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetMapper.java +++ b/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetMapper.java @@ -16,6 +16,8 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; import lombok.AccessLevel; import lombok.NoArgsConstructor; @@ -31,7 +33,6 @@ import org.eclipse.hawkbit.repository.EntityFactory; import org.eclipse.hawkbit.repository.builder.DistributionSetCreate; import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult; -import org.eclipse.hawkbit.repository.model.DistributionSetMetadata; import org.eclipse.hawkbit.repository.model.MetaData; import org.eclipse.hawkbit.rest.json.model.ResponseList; @@ -53,16 +54,6 @@ public final class MgmtDistributionSetMapper { return sets.stream().map(dsRest -> fromRequest(dsRest, entityFactory)).toList(); } - static List fromRequestDsMetadata(final List metadata, final EntityFactory entityFactory) { - if (metadata == null) { - return Collections.emptyList(); - } - - return metadata.stream() - .map(metadataRest -> entityFactory.generateDsMetadata(metadataRest.getKey(), metadataRest.getValue())) - .toList(); - } - static MgmtDistributionSet toResponse(final DistributionSet distributionSet) { if (distributionSet == null) { return null; @@ -100,10 +91,8 @@ public final class MgmtDistributionSetMapper { response.add(linkTo(methodOn(MgmtDistributionSetTypeRestApi.class) .getDistributionSetType(distributionSet.getType().getId())).withRel("type").expand()); - response.add(linkTo(methodOn(MgmtDistributionSetRestApi.class).getMetadata(response.getId(), - MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET_VALUE, - MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT_VALUE, null, null)).withRel("metadata") - .expand()); + response.add(linkTo(methodOn(MgmtDistributionSetRestApi.class).getMetadata(response.getId())) + .withRel("metadata").expand()); } static MgmtTargetAssignmentResponseBody toResponse(final DistributionSetAssignmentResult dsAssignmentResult) { @@ -138,19 +127,21 @@ public final class MgmtDistributionSetMapper { sets.stream().map(MgmtDistributionSetMapper::toResponse).toList()); } - static MgmtMetadata toResponseDsMetadata(final DistributionSetMetadata metadata) { + static MgmtMetadata toResponseDsMetadata(final String key, String value) { final MgmtMetadata metadataRest = new MgmtMetadata(); - metadataRest.setKey(metadata.getKey()); - metadataRest.setValue(metadata.getValue()); + metadataRest.setKey(key); + metadataRest.setValue(value); return metadataRest; } - static List toResponseDsMetadata(final List metadata) { - final List mappedList = new ArrayList<>(metadata.size()); - for (final DistributionSetMetadata distributionSetMetadata : metadata) { - mappedList.add(toResponseDsMetadata(distributionSetMetadata)); - } - return mappedList; + static Map fromRequestDsMetadata(final List metadata) { + return metadata == null + ? Collections.emptyMap() + : metadata.stream().collect(Collectors.toMap(MgmtMetadata::getKey, MgmtMetadata::getValue)); + } + + static List toResponseDsMetadata(final Map metadata) { + return metadata.entrySet().stream().map(e -> toResponseDsMetadata(e.getKey(), e.getValue())).toList(); } static List toResponseFromDsList(final List sets) { diff --git a/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetResource.java b/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetResource.java index c69dc4901..cfa202d2d 100644 --- a/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetResource.java +++ b/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetResource.java @@ -14,6 +14,7 @@ import java.util.AbstractMap.SimpleEntry; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Map.Entry; import java.util.Optional; import java.util.stream.Collectors; @@ -54,7 +55,6 @@ import org.eclipse.hawkbit.repository.model.DeploymentRequest; import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult; import org.eclipse.hawkbit.repository.model.DistributionSetInvalidation; -import org.eclipse.hawkbit.repository.model.DistributionSetMetadata; import org.eclipse.hawkbit.repository.model.DistributionSetType; import org.eclipse.hawkbit.repository.model.SoftwareModule; import org.eclipse.hawkbit.repository.model.Target; @@ -279,64 +279,37 @@ public class MgmtDistributionSetResource implements MgmtDistributionSetRestApi { } @Override - public ResponseEntity> getMetadata( - final Long distributionSetId, - final int pagingOffsetParam, final int pagingLimitParam, final String sortParam, final String rsqlParam) { - final int sanitizedOffsetParam = PagingUtility.sanitizeOffsetParam(pagingOffsetParam); - final int sanitizedLimitParam = PagingUtility.sanitizePageLimitParam(pagingLimitParam); - final Sort sorting = PagingUtility.sanitizeDistributionSetMetadataSortParam(sortParam); + public void createMetadata(final Long distributionSetId, final List metadataRest) { + distributionSetManagement.createMetadata(distributionSetId, MgmtDistributionSetMapper.fromRequestDsMetadata(metadataRest)); + } - final Pageable pageable = new OffsetBasedPageRequest(sanitizedOffsetParam, sanitizedLimitParam, sorting); - final Page metaDataPage; - - if (rsqlParam != null) { - metaDataPage = distributionSetManagement.findMetaDataByDistributionSetIdAndRsql(distributionSetId, rsqlParam, pageable - ); - } else { - metaDataPage = distributionSetManagement.findMetaDataByDistributionSetId(distributionSetId, pageable); - } - - return ResponseEntity - .ok(new PagedList<>(MgmtDistributionSetMapper.toResponseDsMetadata(metaDataPage.getContent()), metaDataPage.getTotalElements())); + @Override + public ResponseEntity> getMetadata(final Long distributionSetId) { + final Map metadata = distributionSetManagement.getMetadata(distributionSetId); + return ResponseEntity.ok(new PagedList<>(MgmtDistributionSetMapper.toResponseDsMetadata(metadata), metadata.size())); } @Override public ResponseEntity getMetadataValue(final Long distributionSetId, final String metadataKey) { - // check if distribution set exists otherwise throw exception immediately - final DistributionSetMetadata findOne = distributionSetManagement - .findMetaDataByDistributionSetId(distributionSetId, metadataKey) - .orElseThrow(() -> new EntityNotFoundException(DistributionSetMetadata.class, distributionSetId, metadataKey)); - return ResponseEntity.ok(MgmtDistributionSetMapper.toResponseDsMetadata(findOne)); + final String metadataValue = distributionSetManagement.getMetadata(distributionSetId).get(metadataKey); + if (metadataValue == null) { + throw new EntityNotFoundException("Target metadata", distributionSetId + ":" + metadataKey); + } + return ResponseEntity.ok(MgmtDistributionSetMapper.toResponseDsMetadata(metadataKey, metadataValue)); } @Override - public ResponseEntity updateMetadata( - final Long distributionSetId, final String metadataKey, final MgmtMetadataBodyPut metadata) { - // check if distribution set exists otherwise throw exception immediately - final DistributionSetMetadata updated = distributionSetManagement.updateMetaData(distributionSetId, - entityFactory.generateDsMetadata(metadataKey, metadata.getValue())); - return ResponseEntity.ok(MgmtDistributionSetMapper.toResponseDsMetadata(updated)); + public void updateMetadata(final Long distributionSetId, final String metadataKey, final MgmtMetadataBodyPut metadata) { + distributionSetManagement.updateMetadata(distributionSetId, metadataKey, metadata.getValue()); } @Override - public ResponseEntity deleteMetadata(final Long distributionSetId, final String metadataKey) { - // check if distribution set exists otherwise throw exception immediately - distributionSetManagement.deleteMetaData(distributionSetId, metadataKey); - return ResponseEntity.ok().build(); + public void deleteMetadata(final Long distributionSetId, final String metadataKey) { + distributionSetManagement.deleteMetadata(distributionSetId, metadataKey); } @Override - public ResponseEntity> createMetadata(final Long distributionSetId, final List metadataRest) { - // check if distribution set exists otherwise throw exception immediately - final List created = distributionSetManagement.putMetaData(distributionSetId, - MgmtDistributionSetMapper.fromRequestDsMetadata(metadataRest, entityFactory)); - return new ResponseEntity<>(MgmtDistributionSetMapper.toResponseDsMetadata(created), HttpStatus.CREATED); - - } - - @Override - public ResponseEntity assignSoftwareModules( - final Long distributionSetId, final List softwareModuleIDs) { + public ResponseEntity assignSoftwareModules(final Long distributionSetId, final List softwareModuleIDs) { distributionSetManagement.assignSoftwareModules( distributionSetId, softwareModuleIDs.stream() diff --git a/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/util/PagingUtility.java b/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/util/PagingUtility.java index 6cf237773..cab10e7cb 100644 --- a/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/util/PagingUtility.java +++ b/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/util/PagingUtility.java @@ -15,7 +15,6 @@ import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants; import org.eclipse.hawkbit.repository.ActionFields; import org.eclipse.hawkbit.repository.ActionStatusFields; import org.eclipse.hawkbit.repository.DistributionSetFields; -import org.eclipse.hawkbit.repository.DistributionSetMetadataFields; import org.eclipse.hawkbit.repository.DistributionSetTypeFields; import org.eclipse.hawkbit.repository.RolloutFields; import org.eclipse.hawkbit.repository.RolloutGroupFields; @@ -133,12 +132,12 @@ public final class PagingUtility { return Sort.by(SortUtility.parse(ActionStatusFields.class, sortParam)); } - public static Sort sanitizeDistributionSetMetadataSortParam(final String sortParam) { + public static Sort sanitizeMetadataSortParam(final String sortParam) { if (sortParam == null) { // default - return Sort.by(Direction.ASC, DistributionSetMetadataFields.KEY.getJpaEntityFieldName()); + return Sort.by(Direction.ASC, SoftwareModuleMetadataFields.KEY.getJpaEntityFieldName()); } - return Sort.by(SortUtility.parse(DistributionSetMetadataFields.class, sortParam)); + return Sort.by(SortUtility.parse(SoftwareModuleMetadataFields.class, sortParam)); } public static Sort sanitizeSoftwareModuleMetadataSortParam(final String sortParam) { diff --git a/hawkbit-mgmt/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetResourceTest.java b/hawkbit-mgmt/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetResourceTest.java index 57cbed70a..104e69453 100644 --- a/hawkbit-mgmt/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetResourceTest.java +++ b/hawkbit-mgmt/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetResourceTest.java @@ -30,6 +30,7 @@ import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.stream.IntStream; @@ -51,7 +52,6 @@ import org.eclipse.hawkbit.repository.jpa.specifications.ActionSpecifications; import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action.Status; import org.eclipse.hawkbit.repository.model.DistributionSet; -import org.eclipse.hawkbit.repository.model.DistributionSetMetadata; import org.eclipse.hawkbit.repository.model.DistributionSetType; import org.eclipse.hawkbit.repository.model.NamedEntity; import org.eclipse.hawkbit.repository.model.Rollout; @@ -890,20 +890,20 @@ class MgmtDistributionSetResourceTest extends AbstractManagementApiIntegrationTe .getWithDetails(distributionSetManagement.findByRsql("name==three", PAGE).getContent().get(0).getId()) .get(); - assertThat((Object)JsonPath.compile("[0]_links.self.href").read(mvcResult.getResponse().getContentAsString())) + assertThat((Object) JsonPath.compile("[0]_links.self.href").read(mvcResult.getResponse().getContentAsString())) .hasToString("http://localhost/rest/v1/distributionsets/" + one.getId()); - assertThat((Object)JsonPath.compile("[0]id").read(mvcResult.getResponse().getContentAsString())) + assertThat((Object) JsonPath.compile("[0]id").read(mvcResult.getResponse().getContentAsString())) .hasToString(String.valueOf(one.getId())); - assertThat((Object)JsonPath.compile("[1]_links.self.href").read(mvcResult.getResponse().getContentAsString())) + assertThat((Object) JsonPath.compile("[1]_links.self.href").read(mvcResult.getResponse().getContentAsString())) .hasToString("http://localhost/rest/v1/distributionsets/" + two.getId()); - assertThat((Object)JsonPath.compile("[1]id").read(mvcResult.getResponse().getContentAsString())) + assertThat((Object) JsonPath.compile("[1]id").read(mvcResult.getResponse().getContentAsString())) .hasToString(String.valueOf(two.getId())); - assertThat((Object)JsonPath.compile("[2]_links.self.href").read(mvcResult.getResponse().getContentAsString())) + assertThat((Object) JsonPath.compile("[2]_links.self.href").read(mvcResult.getResponse().getContentAsString())) .hasToString("http://localhost/rest/v1/distributionsets/" + three.getId()); - assertThat((Object)JsonPath.compile("[2]id").read(mvcResult.getResponse().getContentAsString())) + assertThat((Object) JsonPath.compile("[2]id").read(mvcResult.getResponse().getContentAsString())) .hasToString(String.valueOf(three.getId())); // check in database @@ -1105,23 +1105,14 @@ class MgmtDistributionSetResourceTest extends AbstractManagementApiIntegrationTe metaData1.put(new JSONObject().put("key", knownKey1).put("value", knownValue1)); metaData1.put(new JSONObject().put("key", knownKey2).put("value", knownValue2)); - mvc.perform(post("/rest/v1/distributionsets/{dsId}/metadata", testDS.getId()).accept(MediaType.APPLICATION_JSON) - .contentType(MediaType.APPLICATION_JSON).content(metaData1.toString())) + mvc.perform(post("/rest/v1/distributionsets/{dsId}/metadata", testDS.getId()) + .contentType(MediaType.APPLICATION_JSON) + .content(metaData1.toString())) .andDo(MockMvcResultPrinter.print()) - .andExpect(status().isCreated()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE)) - .andExpect(jsonPath("[0]key", equalTo(knownKey1))) - .andExpect(jsonPath("[0]value", equalTo(knownValue1))) - .andExpect(jsonPath("[1]key", equalTo(knownKey2))) - .andExpect(jsonPath("[1]value", equalTo(knownValue2))); + .andExpect(status().isCreated()); - final DistributionSetMetadata metaKey1 = distributionSetManagement - .findMetaDataByDistributionSetId(testDS.getId(), knownKey1).get(); - final DistributionSetMetadata metaKey2 = distributionSetManagement - .findMetaDataByDistributionSetId(testDS.getId(), knownKey2).get(); - - assertThat(metaKey1.getValue()).isEqualTo(knownValue1); - assertThat(metaKey2.getValue()).isEqualTo(knownValue2); + assertThat(distributionSetManagement.getMetadata(testDS.getId()).get(knownKey1)).isEqualTo(knownValue1); + assertThat(distributionSetManagement.getMetadata(testDS.getId()).get(knownKey2)).isEqualTo(knownValue2); // verify quota enforcement final int maxMetaData = quotaManagement.getMaxMetaDataEntriesPerDistributionSet(); @@ -1131,17 +1122,14 @@ class MgmtDistributionSetResourceTest extends AbstractManagementApiIntegrationTe metaData2.put(new JSONObject().put("key", knownKey1 + i).put("value", knownValue1 + i)); } - mvc.perform(post("/rest/v1/distributionsets/{dsId}/metadata", testDS.getId()).accept(MediaType.APPLICATION_JSON) - .contentType(MediaType.APPLICATION_JSON).content(metaData2.toString())) + mvc.perform(post("/rest/v1/distributionsets/{dsId}/metadata", testDS.getId()) + .contentType(MediaType.APPLICATION_JSON) + .content(metaData2.toString())) .andDo(MockMvcResultPrinter.print()) .andExpect(status().isForbidden()); - // verify that the number of meta data entries has not changed - // (we cannot use the PAGE constant here as it tries to sort by ID) - assertThat(distributionSetManagement - .findMetaDataByDistributionSetId(testDS.getId(), PageRequest.of(0, Integer.MAX_VALUE)) - .getTotalElements()).isEqualTo(metaData1.length()); - + // verify that the number of meta-data entries has not changed (we cannot use the PAGE constant here as it tries to sort by ID) + assertThat(distributionSetManagement.getMetadata(testDS.getId())).hasSize(metaData1.length()); } @Test @@ -1151,25 +1139,18 @@ class MgmtDistributionSetResourceTest extends AbstractManagementApiIntegrationTe final String knownKey = "knownKey"; final String knownValue = "knownValue"; final String updateValue = "valueForUpdate"; - final DistributionSet testDS = testdataFactory.createDistributionSet("one"); - createDistributionSetMetadata(testDS.getId(), entityFactory.generateDsMetadata(knownKey, knownValue)); + distributionSetManagement.createMetadata(testDS.getId(), Map.of(knownKey, knownValue)); final JSONObject jsonObject = new JSONObject().put("key", knownKey).put("value", updateValue); mvc.perform(put("/rest/v1/distributionsets/{dsId}/metadata/{key}", testDS.getId(), knownKey) - .accept(MediaType.APPLICATION_JSON).contentType(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON) .content(jsonObject.toString())) .andDo(MockMvcResultPrinter.print()) - .andExpect(status().isOk()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE)) - .andExpect(jsonPath("key", equalTo(knownKey))) - .andExpect(jsonPath("value", equalTo(updateValue))); - - final DistributionSetMetadata assertDS = distributionSetManagement - .findMetaDataByDistributionSetId(testDS.getId(), knownKey).get(); - assertThat(assertDS.getValue()).isEqualTo(updateValue); + .andExpect(status().isOk()); + assertThat(distributionSetManagement.getMetadata(testDS.getId()).get(knownKey)).isEqualTo(updateValue); } @Test @@ -1180,13 +1161,18 @@ class MgmtDistributionSetResourceTest extends AbstractManagementApiIntegrationTe final String knownValue = "knownValue"; final DistributionSet testDS = testdataFactory.createDistributionSet("one"); - createDistributionSetMetadata(testDS.getId(), entityFactory.generateDsMetadata(knownKey, knownValue)); + distributionSetManagement.createMetadata(testDS.getId(), Map.of(knownKey, knownValue)); mvc.perform(delete("/rest/v1/distributionsets/{dsId}/metadata/{key}", testDS.getId(), knownKey)) .andDo(MockMvcResultPrinter.print()) .andExpect(status().isOk()); - assertThat(distributionSetManagement.findMetaDataByDistributionSetId(testDS.getId(), knownKey)).isNotPresent(); + // already deleted + mvc.perform(delete("/rest/v1/distributionsets/{dsId}/metadata/{key}", testDS.getId(), knownKey)) + .andDo(MockMvcResultPrinter.print()) + .andExpect(status().isNotFound()); + + assertThat(distributionSetManagement.getMetadata(testDS.getId()).get(knownKey)).isNull(); } @Test @@ -1195,9 +1181,8 @@ class MgmtDistributionSetResourceTest extends AbstractManagementApiIntegrationTe // prepare and create metadata for deletion final String knownKey = "knownKey"; final String knownValue = "knownValue"; - final DistributionSet testDS = testdataFactory.createDistributionSet("one"); - createDistributionSetMetadata(testDS.getId(), entityFactory.generateDsMetadata(knownKey, knownValue)); + distributionSetManagement.createMetadata(testDS.getId(), Map.of(knownKey, knownValue)); mvc.perform(delete("/rest/v1/distributionsets/{dsId}/metadata/XXX", testDS.getId(), knownKey)) .andDo(MockMvcResultPrinter.print()) @@ -1207,17 +1192,17 @@ class MgmtDistributionSetResourceTest extends AbstractManagementApiIntegrationTe .andDo(MockMvcResultPrinter.print()) .andExpect(status().isNotFound()); - assertThat(distributionSetManagement.findMetaDataByDistributionSetId(testDS.getId(), knownKey)).isPresent(); + assertThat(distributionSetManagement.getMetadata(testDS.getId()).get(knownKey)).isNotNull(); } @Test @Description("Ensures that a metadata entry selection through API reflects the repository content.") - void geteMetadataKey() throws Exception { + void getMetadataKey() throws Exception { // prepare and create metadata final String knownKey = "knownKey"; final String knownValue = "knownValue"; final DistributionSet testDS = testdataFactory.createDistributionSet("one"); - createDistributionSetMetadata(testDS.getId(), entityFactory.generateDsMetadata(knownKey, knownValue)); + distributionSetManagement.createMetadata(testDS.getId(), Map.of(knownKey, knownValue)); mvc.perform(get("/rest/v1/distributionsets/{dsId}/metadata/{key}", testDS.getId(), knownKey)) .andDo(MockMvcResultPrinter.print()) @@ -1234,43 +1219,15 @@ class MgmtDistributionSetResourceTest extends AbstractManagementApiIntegrationTe final String knownValuePrefix = "knownValue"; final DistributionSet testDS = testdataFactory.createDistributionSet("one"); for (int index = 0; index < totalMetadata; index++) { - distributionSetManagement.putMetaData(testDS.getId(), - List.of(entityFactory.generateDsMetadata(knownKeyPrefix + index, knownValuePrefix + index))); + distributionSetManagement.createMetadata(testDS.getId(), Map.of(knownKeyPrefix + index, knownValuePrefix + index)); } - mvc.perform(get(MgmtRestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING + "/{distributionSetId}/metadata", - testDS.getId())) + mvc.perform(get(MgmtRestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING + "/{distributionSetId}/metadata", testDS.getId())) .andDo(MockMvcResultPrinter.print()) .andExpect(status().isOk()) .andExpect(content().contentType(MediaTypes.HAL_JSON)); } - @Test - @Description("Ensures that a metadata entry paged list selection through API reflectes the repository content.") - void getPagedListOfMetadata() throws Exception { - - final int totalMetadata = 10; - final int limitParam = 5; - final String offsetParam = "0"; - final String knownKeyPrefix = "knownKey"; - final String knownValuePrefix = "knownValue"; - final DistributionSet testDS = testdataFactory.createDistributionSet("one"); - for (int index = 0; index < totalMetadata; index++) { - createDistributionSetMetadata(testDS.getId(), - entityFactory.generateDsMetadata(knownKeyPrefix + index, knownValuePrefix + index)); - } - - mvc.perform(get("/rest/v1/distributionsets/{dsId}/metadata?offset=" + offsetParam + "&limit=" + limitParam, - testDS.getId())) - .andDo(MockMvcResultPrinter.print()) - .andExpect(status().isOk()) - .andExpect(jsonPath("size", equalTo(limitParam))) - .andExpect(jsonPath("total", equalTo(totalMetadata))) - .andExpect(jsonPath("content[0].key", equalTo("knownKey0"))) - .andExpect(jsonPath("content[0].value", equalTo("knownValue0"))); - - } - @Test @Description("Ensures that a DS search with query parameters returns the expected result.") void searchDistributionSetRsql() throws Exception { @@ -1334,29 +1291,6 @@ class MgmtDistributionSetResourceTest extends AbstractManagementApiIntegrationTe .andExpect(jsonPath("content[0].controllerId", equalTo("1"))); } - @Test - @Description("Ensures that a DS metadata filtered query with value==knownValue1 parameter returns only the metadata entries with that value.") - void searchDistributionSetMetadataRsql() throws Exception { - final int totalMetadata = 10; - final String knownKeyPrefix = "knownKey"; - final String knownValuePrefix = "knownValue"; - final DistributionSet testDS = testdataFactory.createDistributionSet("one"); - for (int index = 0; index < totalMetadata; index++) { - createDistributionSetMetadata(testDS.getId(), - entityFactory.generateDsMetadata(knownKeyPrefix + index, knownValuePrefix + index)); - } - - final String rsqlSearchValue1 = "value==knownValue1"; - - mvc.perform(get("/rest/v1/distributionsets/{dsId}/metadata?q=" + rsqlSearchValue1, testDS.getId())) - .andDo(MockMvcResultPrinter.print()) - .andExpect(status().isOk()) - .andExpect(jsonPath("size", equalTo(1))) - .andExpect(jsonPath("total", equalTo(1))) - .andExpect(jsonPath("content[0].key", equalTo("knownKey1"))) - .andExpect(jsonPath("content[0].value", equalTo("knownValue1"))); - } - @Test @Description("Ensures that multi target assignment through API is reflected by the repository in the case of " + "DOWNLOAD_ONLY.") diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DistributionSetManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DistributionSetManagement.java index 6d198d09d..6873c0808 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DistributionSetManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DistributionSetManagement.java @@ -11,6 +11,7 @@ package org.eclipse.hawkbit.repository; import java.util.Collection; import java.util.List; +import java.util.Map; import java.util.Optional; import jakarta.validation.constraints.NotEmpty; @@ -30,7 +31,6 @@ import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldExc import org.eclipse.hawkbit.repository.exception.UnsupportedSoftwareModuleForThisDistributionSetException; import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSetFilter; -import org.eclipse.hawkbit.repository.model.DistributionSetMetadata; import org.eclipse.hawkbit.repository.model.DistributionSetTag; import org.eclipse.hawkbit.repository.model.MetaData; import org.eclipse.hawkbit.repository.model.SoftwareModule; @@ -124,29 +124,38 @@ public interface DistributionSetManagement extends RepositoryManagement unassignTag(@NotEmpty Collection ids, long tagId); /** - * Creates a list of distribution set meta-data entries. + * Creates a map of distribution set meta-data entries. * * @param id if the {@link DistributionSet} the metadata has to be created for * @param metadata the meta-data entries to create or update - * @return the updated or created distribution set meta-data entries * @throws EntityNotFoundException if given set does not exist * @throws EntityAlreadyExistsException in case one of the meta-data entry already exists for the specific key * @throws AssignmentQuotaExceededException if the maximum number of {@link MetaData} entries is exceeded for the addressed * {@link DistributionSet} */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY) - List putMetaData(long id, @NotEmpty Collection metadata); + void createMetadata(long id, @NotEmpty Map metadata); /** - * Updates a distribution set meta-data value if corresponding entry exists. + * Finds all meta-data by the given distribution set id. + * + * @param id the distribution set id to retrieve the meta-data from + * @return a paged result of all meta-data entries for a given distribution set id + * @throws EntityNotFoundException if distribution set with given ID does not exist + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY) + Map getMetadata(long id); + + /** + * Updates a distribution set meta-data values by adding them. * * @param id {@link DistributionSet} of the meta-data entry to be updated - * @param metadata meta-data entry to be updated - * @return the updated meta-data entry + * @param key meta data-entry key to be updated + * @param value meta data-entry to be new value * @throws EntityNotFoundException in case the meta-data entry does not exist and cannot be updated */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY) - DistributionSetMetadata updateMetaData(long id, @NotNull MetaData metadata); + void updateMetadata(long id, @NotNull String key, @NotNull String value); /** * Deletes a distribution set meta-data entry. @@ -156,7 +165,7 @@ public interface DistributionSetManagement extends RepositoryManagement findByNameAndVersion(@NotEmpty String distributionName, @NotEmpty String version); - /** - * Finds all meta-data by the given distribution set id. - * - * @param id the distribution set id to retrieve the meta-data from - * @param pageable the page request to page the result - * @return a paged result of all meta-data entries for a given distribution - * set id - * @throws EntityNotFoundException if distribution set with given ID does not exist - */ - @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY) - Page findMetaDataByDistributionSetId(long id, @NotNull Pageable pageable); - - /** - * Finds all meta-data by the given distribution set id. - * - * @param id the distribution set id to retrieve the meta-data from - * @param rsqlParam rsql query string - * @param pageable the page request to page the result - * @return a paged result of all meta-data entries for a given distribution set id - * @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 - * @throws EntityNotFoundException of distribution set with given ID does not exist - */ - @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY) - Page findMetaDataByDistributionSetIdAndRsql(long id, @NotNull String rsqlParam, @NotNull Pageable pageable); - /** * Finds all {@link DistributionSet}s based on completeness. * @@ -295,25 +277,6 @@ public interface DistributionSetManagement extends RepositoryManagement findByRsqlAndTag(@NotNull String rsqlParam, long tagId, @NotNull Pageable pageable); - /** - * Finds a single distribution set meta-data by its id. - * - * @param id of the {@link DistributionSet} - * @param key of the meta-data element - * @return the found DistributionSetMetadata - * @throws EntityNotFoundException is set with given ID does not exist - */ - @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY) - Optional findMetaDataByDistributionSetId(long id, @NotEmpty String key);/** - * Counts all meta-data by the given distribution set id. - * - * @param id the distribution set id to retrieve the meta-data count from - * @return count of ds metadata - * @throws EntityNotFoundException if distribution set with given ID does not exist - */ - @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY) - long countMetaDataByDistributionSetId(long id); - /** * Counts all {@link DistributionSet}s based on completeness. * diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/EntityFactory.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/EntityFactory.java index 8f12cca14..847b4b204 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/EntityFactory.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/EntityFactory.java @@ -9,9 +9,6 @@ */ package org.eclipse.hawkbit.repository; -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Size; - import org.eclipse.hawkbit.repository.builder.ActionStatusBuilder; import org.eclipse.hawkbit.repository.builder.DistributionSetBuilder; import org.eclipse.hawkbit.repository.builder.DistributionSetTypeBuilder; @@ -43,17 +40,6 @@ public interface EntityFactory { */ DistributionSetBuilder distributionSet(); - /** - * Generates an {@link MetaData} element for distribution set without - * persisting it. - * - * @param key {@link MetaData#getKey()} - * @param value {@link MetaData#getValue()} - * @return {@link MetaData} object - */ - MetaData generateDsMetadata(@Size(min = 1, max = MetaData.KEY_MAX_SIZE) @NotNull String key, - @Size(max = MetaData.VALUE_MAX_SIZE) String value); - /** * @return {@link SoftwareModuleMetadataBuilder} object */ diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/DistributionSet.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/DistributionSet.java index 002570b38..2852c0d1c 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/DistributionSet.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/DistributionSet.java @@ -23,6 +23,16 @@ import java.util.Set; */ public interface DistributionSet extends NamedVersionedEntity { + /** + * Maximum length of metadata key. + */ + int METADATA_KEY_SIZE = 128; + + /** + * Maximum length of metadata value. + */ + int METADATA_VALUE_SIZE = 4000; + /** * @return type of the {@link DistributionSet}. */ diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/DistributionSetMetadata.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/DistributionSetMetadata.java deleted file mode 100644 index bab3ab6e8..000000000 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/DistributionSetMetadata.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2015 Bosch Software Innovations GmbH and others - * - * 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.model; - -/** - * {@link MetaData} of a {@link DistributionSet}. - */ -public interface DistributionSetMetadata extends MetaData { - - /** - * @return {@link DistributionSet} of this {@link MetaData} entry. - */ - DistributionSet getDistributionSet(); - - @Override - default Long getEntityId() { - return getDistributionSet().getId(); - } -} diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaEntityFactory.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaEntityFactory.java index 3c7628e38..207ff7248 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaEntityFactory.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaEntityFactory.java @@ -26,7 +26,6 @@ import org.eclipse.hawkbit.repository.jpa.builder.JpaActionStatusBuilder; import org.eclipse.hawkbit.repository.jpa.builder.JpaRolloutGroupBuilder; import org.eclipse.hawkbit.repository.jpa.builder.JpaSoftwareModuleTypeBuilder; import org.eclipse.hawkbit.repository.jpa.builder.JpaTagBuilder; -import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetMetadata; import org.eclipse.hawkbit.repository.model.MetaData; import org.springframework.validation.annotation.Validated; @@ -71,11 +70,6 @@ public class JpaEntityFactory implements EntityFactory { return distributionSetBuilder; } - @Override - public MetaData generateDsMetadata(final String key, final String value) { - return new JpaDistributionSetMetadata(key, value == null ? null : value.strip()); - } - @Override public SoftwareModuleMetadataBuilder softwareModuleMetadata() { return softwareModuleMetadataBuilder; diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/RepositoryApplicationConfiguration.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/RepositoryApplicationConfiguration.java index d9c95f98e..7d0bf8811 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/RepositoryApplicationConfiguration.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/RepositoryApplicationConfiguration.java @@ -119,7 +119,6 @@ import org.eclipse.hawkbit.repository.jpa.model.helper.SecurityTokenGeneratorHol import org.eclipse.hawkbit.repository.jpa.model.helper.TenantAwareHolder; import org.eclipse.hawkbit.repository.jpa.repository.ActionRepository; import org.eclipse.hawkbit.repository.jpa.repository.ActionStatusRepository; -import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetMetadataRepository; import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetRepository; import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetTagRepository; import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetTypeRepository; @@ -536,7 +535,6 @@ public class RepositoryApplicationConfiguration { final DistributionSetRepository distributionSetRepository, final DistributionSetTagManagement distributionSetTagManagement, final SystemManagement systemManagement, final DistributionSetTypeManagement distributionSetTypeManagement, final QuotaManagement quotaManagement, - final DistributionSetMetadataRepository distributionSetMetadataRepository, final TargetRepository targetRepository, final TargetFilterQueryRepository targetFilterQueryRepository, final ActionRepository actionRepository, final SystemSecurityContext systemSecurityContext, final TenantConfigurationManagement tenantConfigurationManagement, @@ -544,7 +542,7 @@ public class RepositoryApplicationConfiguration { final DistributionSetTagRepository distributionSetTagRepository, final JpaProperties properties, final RepositoryProperties repositoryProperties) { return new JpaDistributionSetManagement(entityManager, distributionSetRepository, distributionSetTagManagement, - systemManagement, distributionSetTypeManagement, quotaManagement, distributionSetMetadataRepository, + systemManagement, distributionSetTypeManagement, quotaManagement, targetRepository, targetFilterQueryRepository, actionRepository, TenantConfigHelper.usingContext(systemSecurityContext, tenantConfigurationManagement), virtualPropertyReplacer, softwareModuleRepository, distributionSetTagRepository, diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaDistributionSetManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaDistributionSetManagement.java index 18f87f001..b14cd8295 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaDistributionSetManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaDistributionSetManagement.java @@ -12,9 +12,9 @@ package org.eclipse.hawkbit.repository.jpa.management; import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.IMPLICIT_LOCK_ENABLED; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Optional; @@ -24,11 +24,15 @@ import java.util.function.Function; import java.util.stream.Collectors; import jakarta.persistence.EntityManager; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.MapJoin; +import jakarta.persistence.criteria.Root; +import jakarta.persistence.metamodel.MapAttribute; import jakarta.validation.constraints.NotNull; import org.eclipse.hawkbit.repository.DistributionSetFields; import org.eclipse.hawkbit.repository.DistributionSetManagement; -import org.eclipse.hawkbit.repository.DistributionSetMetadataFields; import org.eclipse.hawkbit.repository.DistributionSetTagManagement; import org.eclipse.hawkbit.repository.DistributionSetTypeManagement; import org.eclipse.hawkbit.repository.QuotaManagement; @@ -49,14 +53,10 @@ import org.eclipse.hawkbit.repository.jpa.acm.AccessController; import org.eclipse.hawkbit.repository.jpa.builder.JpaDistributionSetCreate; import org.eclipse.hawkbit.repository.jpa.configuration.Constants; import org.eclipse.hawkbit.repository.jpa.model.AbstractJpaBaseEntity_; -import org.eclipse.hawkbit.repository.jpa.model.DsMetadataCompositeKey; import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet; -import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetMetadata; -import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetMetadata_; import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet_; import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule; import org.eclipse.hawkbit.repository.jpa.repository.ActionRepository; -import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetMetadataRepository; import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetRepository; import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetTagRepository; import org.eclipse.hawkbit.repository.jpa.repository.SoftwareModuleRepository; @@ -69,10 +69,8 @@ import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper; import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSetFilter; -import org.eclipse.hawkbit.repository.model.DistributionSetMetadata; import org.eclipse.hawkbit.repository.model.DistributionSetTag; import org.eclipse.hawkbit.repository.model.DistributionSetType; -import org.eclipse.hawkbit.repository.model.MetaData; import org.eclipse.hawkbit.repository.model.SoftwareModule; import org.eclipse.hawkbit.repository.model.Statistic; import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer; @@ -103,7 +101,6 @@ public class JpaDistributionSetManagement implements DistributionSetManagement { private final SystemManagement systemManagement; private final DistributionSetTypeManagement distributionSetTypeManagement; private final QuotaManagement quotaManagement; - private final DistributionSetMetadataRepository distributionSetMetadataRepository; private final TargetRepository targetRepository; private final TargetFilterQueryRepository targetFilterQueryRepository; private final ActionRepository actionRepository; @@ -120,7 +117,6 @@ public class JpaDistributionSetManagement implements DistributionSetManagement { final DistributionSetRepository distributionSetRepository, final DistributionSetTagManagement distributionSetTagManagement, final SystemManagement systemManagement, final DistributionSetTypeManagement distributionSetTypeManagement, final QuotaManagement quotaManagement, - final DistributionSetMetadataRepository distributionSetMetadataRepository, final TargetRepository targetRepository, final TargetFilterQueryRepository targetFilterQueryRepository, final ActionRepository actionRepository, final TenantConfigHelper tenantConfigHelper, @@ -135,7 +131,6 @@ public class JpaDistributionSetManagement implements DistributionSetManagement { this.systemManagement = systemManagement; this.distributionSetTypeManagement = distributionSetTypeManagement; this.quotaManagement = quotaManagement; - this.distributionSetMetadataRepository = distributionSetMetadataRepository; this.targetRepository = targetRepository; this.targetFilterQueryRepository = targetFilterQueryRepository; this.actionRepository = actionRepository; @@ -360,21 +355,6 @@ public class JpaDistributionSetManagement implements DistributionSetManagement { }); } - @Override - @Transactional - @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, - backoff = @Backoff(delay = Constants.TX_RT_DELAY)) - public DistributionSetMetadata updateMetaData(final long id, final MetaData md) { - // check if exists otherwise throw entity not found exception - final JpaDistributionSetMetadata toUpdate = (JpaDistributionSetMetadata) findMetaDataByDistributionSetId(id, md.getKey()) - .orElseThrow(() -> new EntityNotFoundException(DistributionSetMetadata.class, id, md.getKey())); - toUpdate.setValue(md.getValue()); - - // touch it to update the lock revision because we are modifying the DS indirectly, it will, also check UPDATE access - JpaManagementHelper.touch(entityManager, distributionSetRepository, (JpaDistributionSet) getValid(id)); - return distributionSetMetadataRepository.save(toUpdate); - } - @Override @Transactional @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, @@ -394,34 +374,60 @@ public class JpaDistributionSetManagement implements DistributionSetManagement { @Transactional @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) - public List putMetaData(final long id, final Collection md) { + public void createMetadata(final long id, final Map md) { final JpaDistributionSet distributionSet = (JpaDistributionSet) getValid(id); - assertMetaDataQuota(id, md.size()); - md.forEach(meta -> checkAndThrowIfDistributionSetMetadataAlreadyExists( - new DsMetadataCompositeKey(id, meta.getKey()))); + // get the modifiable metadata map + final Map metadata = distributionSet.getMetadata(); + md.keySet().forEach(key -> { + if (metadata.containsKey(key)) { + throw new EntityAlreadyExistsException("Metadata entry with key '" + key + "' already exists"); + } + }); + metadata.putAll(md); - JpaManagementHelper.touch(entityManager, distributionSetRepository, distributionSet); + assertMetaDataQuota(id, metadata.size()); - return md.stream() - .map(meta -> distributionSetMetadataRepository - .save(new JpaDistributionSetMetadata(meta.getKey(), distributionSet, meta.getValue()))) - .collect(Collectors.toUnmodifiableList()); + distributionSetRepository.save(distributionSet); + } + + @Override + public Map getMetadata(final long id) { + assertDistributionSetExists(id); + return getMap(id, JpaDistributionSet_.metadata); } @Override @Transactional @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) - public void deleteMetaData(final long id, final String key) { - final JpaDistributionSetMetadata metadata = (JpaDistributionSetMetadata) findMetaDataByDistributionSetId( - id, key) - .orElseThrow(() -> new EntityNotFoundException(DistributionSetMetadata.class, id, key)); + public void updateMetadata(final long id, final String key, final String value) { + final JpaDistributionSet distributionSet = (JpaDistributionSet) getValid(id); - // touch it to update the lock revision because we are modifying the - // DS indirectly, it will, also check UPDATE access - JpaManagementHelper.touch(entityManager, distributionSetRepository, metadata.getDistributionSet()); - distributionSetMetadataRepository.deleteById(metadata.getId()); + // get the modifiable metadata map + final Map metadata = distributionSet.getMetadata(); + if (!metadata.containsKey(key)) { + throw new EntityNotFoundException("DistributionSet metadata", id + ":" + key); + } + metadata.put(key, value); + + distributionSetRepository.save(distributionSet); + } + + @Override + @Transactional + @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, + backoff = @Backoff(delay = Constants.TX_RT_DELAY)) + public void deleteMetadata(final long id, final String key) { + final JpaDistributionSet distributionSet = (JpaDistributionSet) getValid(id); + + // get the modifiable metadata map + final Map metadata = distributionSet.getMetadata(); + if (metadata.remove(key) == null) { + throw new EntityNotFoundException("DistributionSet metadata", id + ":" + key); + } + + distributionSetRepository.save(distributionSet); } @Override @@ -504,33 +510,6 @@ public class JpaDistributionSetManagement implements DistributionSetManagement { return distributionSet; } - @Override - public Page findMetaDataByDistributionSetId(final long id, final Pageable pageable) { - assertDistributionSetExists(id); - - return JpaManagementHelper.findAllWithCountBySpec(distributionSetMetadataRepository, Collections.singletonList(byDsIdSpec(id)), pageable - ); - } - - @Override - public long countMetaDataByDistributionSetId(final long id) { - assertDistributionSetExists(id); - - return distributionSetMetadataRepository.countByDistributionSetId(id); - } - - @Override - public Page findMetaDataByDistributionSetIdAndRsql(final long id, final String rsqlParam, - final Pageable pageable) { - assertDistributionSetExists(id); - - final List> specList = Arrays - .asList(RSQLUtility.buildRsqlSpecification(rsqlParam, DistributionSetMetadataFields.class, - virtualPropertyReplacer, database), byDsIdSpec(id)); - - return JpaManagementHelper.findAllWithCountBySpec(distributionSetMetadataRepository, specList, pageable); - } - @Override public Slice findByCompleted(final Pageable pageReq, final Boolean complete) { final List> specifications = buildSpecsByComplete(complete); @@ -577,15 +556,6 @@ public class JpaDistributionSetManagement implements DistributionSetManagement { DistributionSetSpecification.hasTag(tagId), DistributionSetSpecification.isNotDeleted()), pageable); } - @Override - public Optional findMetaDataByDistributionSetId(final long id, final String key) { - assertDistributionSetExists(id); - - return distributionSetMetadataRepository - .findById(new DsMetadataCompositeKey(id, key)) - .map(DistributionSetMetadata.class::cast); - } - @Override public long countByTypeId(final long typeId) { if (!distributionSetTypeManagement.exists(typeId)) { @@ -732,6 +702,24 @@ public class JpaDistributionSetManagement implements DistributionSetManagement { } } + private Map getMap(final long id, final MapAttribute mapAttribute) { + final CriteriaBuilder cb = entityManager.getCriteriaBuilder(); + final CriteriaQuery query = cb.createQuery(Object[].class); + + final Root targetRoot = query.from(JpaDistributionSet.class); + query.where(cb.equal(targetRoot.get(AbstractJpaBaseEntity_.ID), id)); + + final MapJoin mapJoin = targetRoot.join(mapAttribute); + query.multiselect(mapJoin.key(), mapJoin.value()); + query.orderBy(cb.asc(mapJoin.key())); + + return entityManager + .createQuery(query) + .getResultList() + .stream() + .collect(Collectors.toMap(entry -> (String) entry[0], entry -> (String) entry[1], (v1, v2) -> v1, LinkedHashMap::new)); + } + private JpaSoftwareModule findSoftwareModuleAndThrowExceptionIfNotFound(final Long softwareModuleId) { return softwareModuleRepository.findById(softwareModuleId) .orElseThrow(() -> new EntityNotFoundException(SoftwareModule.class, softwareModuleId)); @@ -754,9 +742,8 @@ public class JpaDistributionSetManagement implements DistributionSetManagement { } private void assertMetaDataQuota(final Long dsId, final int requested) { - QuotaHelper.assertAssignmentQuota(dsId, requested, quotaManagement.getMaxMetaDataEntriesPerDistributionSet(), - DistributionSetMetadata.class, DistributionSet.class, - distributionSetMetadataRepository::countByDistributionSetId); + final int limit = quotaManagement.getMaxMetaDataEntriesPerDistributionSet(); + QuotaHelper.assertAssignmentQuota(dsId, requested, limit, "Metadata", DistributionSet.class.getSimpleName(), null); } private void assertSoftwareModuleQuota(final Long id, final int requested) { @@ -764,11 +751,6 @@ public class JpaDistributionSetManagement implements DistributionSetManagement { SoftwareModule.class, DistributionSet.class, softwareModuleRepository::countByAssignedToId); } - private Specification byDsIdSpec(final long dsId) { - return (root, query, cb) -> cb - .equal(root.get(JpaDistributionSetMetadata_.distributionSet).get(AbstractJpaBaseEntity_.id), dsId); - } - private void assertDistributionSetIsNotAssignedToTargets(final Long distributionSet) { if (actionRepository.countByDistributionSetId(distributionSet) > 0) { throw new EntityReadOnlyException(String.format( @@ -776,13 +758,6 @@ public class JpaDistributionSetManagement implements DistributionSetManagement { } } - private void checkAndThrowIfDistributionSetMetadataAlreadyExists(final DsMetadataCompositeKey metadataId) { - if (distributionSetMetadataRepository.existsById(metadataId)) { - throw new EntityAlreadyExistsException( - "Metadata entry with key '" + metadataId.getKey() + "' already exists"); - } - } - private List getDistributionSets(final Collection ids) { final List foundDs = distributionSetRepository.findAllById(ids); if (foundDs.size() != ids.size()) { diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/DsMetadataCompositeKey.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/DsMetadataCompositeKey.java deleted file mode 100644 index 12d835d3f..000000000 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/DsMetadataCompositeKey.java +++ /dev/null @@ -1,76 +0,0 @@ -/** - * Copyright (c) 2015 Bosch Software Innovations GmbH and others - * - * 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.model; - -import java.io.Serial; -import java.io.Serializable; - -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - -/** - * The DistributionSet Metadata composite key which contains the meta-data key and the ID of the DistributionSet itself. - */ -@NoArgsConstructor // Default constructor for JPA -@Setter -@Getter -public final class DsMetadataCompositeKey implements Serializable { - - @Serial - private static final long serialVersionUID = 1L; - - private String key; - private Long distributionSet; - - /** - * @param distributionSet the distribution set for this meta data - * @param key the key of the meta data - */ - public DsMetadataCompositeKey(final Long distributionSet, final String key) { - this.distributionSet = distributionSet; - this.key = key; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + (distributionSet == null ? 0 : distributionSet.hashCode()); - result = prime * result + (key == null ? 0 : key.hashCode()); - return result; - } - - @Override - public boolean equals(final Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final DsMetadataCompositeKey other = (DsMetadataCompositeKey) obj; - if (distributionSet == null) { - if (other.distributionSet != null) { - return false; - } - } else if (!distributionSet.equals(other.distributionSet)) { - return false; - } - if (key == null) { - return other.key == null; - } else { - return key.equals(other.key); - } - } -} \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaDistributionSet.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaDistributionSet.java index 9dad18789..c65f0c2ac 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaDistributionSet.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaDistributionSet.java @@ -13,13 +13,14 @@ import java.io.Serial; import java.util.Collection; import java.util.Collections; import java.util.HashSet; -import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Set; -import jakarta.persistence.CascadeType; +import jakarta.persistence.CollectionTable; import jakarta.persistence.Column; import jakarta.persistence.ConstraintMode; +import jakarta.persistence.ElementCollection; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; import jakarta.persistence.ForeignKey; @@ -28,9 +29,9 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.JoinTable; import jakarta.persistence.ManyToMany; import jakarta.persistence.ManyToOne; +import jakarta.persistence.MapKeyColumn; import jakarta.persistence.NamedAttributeNode; import jakarta.persistence.NamedEntityGraph; -import jakarta.persistence.OneToMany; import jakarta.persistence.Table; import jakarta.persistence.UniqueConstraint; import jakarta.validation.constraints.NotNull; @@ -47,7 +48,6 @@ import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetExcepti import org.eclipse.hawkbit.repository.exception.LockedException; import org.eclipse.hawkbit.repository.exception.UnsupportedSoftwareModuleForThisDistributionSetException; import org.eclipse.hawkbit.repository.model.DistributionSet; -import org.eclipse.hawkbit.repository.model.DistributionSetMetadata; import org.eclipse.hawkbit.repository.model.DistributionSetTag; import org.eclipse.hawkbit.repository.model.DistributionSetType; import org.eclipse.hawkbit.repository.model.SoftwareModule; @@ -109,11 +109,16 @@ public class JpaDistributionSet extends AbstractJpaNamedVersionedEntity implemen foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_ds_tag_tag")) }) private Set tags = new HashSet<>(); - @ToString.Exclude - @OneToMany(mappedBy = "distributionSet", fetch = FetchType.LAZY, - cascade = { CascadeType.REMOVE }, - targetEntity = JpaDistributionSetMetadata.class) - private List metadata; + // no cascade option on an ElementCollection, the target objects are always persisted, merged, removed with their parent + @Getter + @ElementCollection + @CollectionTable( + name = "sp_ds_metadata", + joinColumns = { @JoinColumn(name = "ds", nullable = false) }, + foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_ds_metadata_ds")) + @MapKeyColumn(name = "meta_key", length = DistributionSet.METADATA_KEY_SIZE) + @Column(name = "meta_value", length = DistributionSet.METADATA_VALUE_SIZE) + private Map metadata; @Column(name = "complete") private boolean complete; @@ -217,14 +222,6 @@ public class JpaDistributionSet extends AbstractJpaNamedVersionedEntity implemen return tags.remove(tag); } - public List getMetadata() { - if (metadata == null) { - return Collections.emptyList(); - } - - return Collections.unmodifiableList(metadata); - } - public void lock() { if (!isComplete()) { throw new IncompleteDistributionSetException("Could not be locked while incomplete!"); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaDistributionSetMetadata.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaDistributionSetMetadata.java deleted file mode 100644 index 7ce807430..000000000 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaDistributionSetMetadata.java +++ /dev/null @@ -1,86 +0,0 @@ -/** - * Copyright (c) 2015 Bosch Software Innovations GmbH and others - * - * 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.model; - -import java.io.Serial; - -import jakarta.persistence.ConstraintMode; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.ForeignKey; -import jakarta.persistence.Id; -import jakarta.persistence.IdClass; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.ManyToOne; -import jakarta.persistence.Table; - -import lombok.Getter; -import lombok.NoArgsConstructor; -import org.eclipse.hawkbit.repository.model.DistributionSet; -import org.eclipse.hawkbit.repository.model.DistributionSetMetadata; - -/** - * Meta data for {@link DistributionSet}. - */ -@NoArgsConstructor // Default constructor needed for JPA entities. -@Getter -@IdClass(DsMetadataCompositeKey.class) -@Entity -@Table(name = "sp_ds_metadata") -public class JpaDistributionSetMetadata extends AbstractJpaMetaData implements DistributionSetMetadata { - - @Serial - private static final long serialVersionUID = 1L; - - @Id - @ManyToOne(fetch = FetchType.LAZY, optional = false) - @JoinColumn(name = "ds", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_ds_metadata_ds")) - private JpaDistributionSet distributionSet; - - public JpaDistributionSetMetadata(final String key, final String value) { - super(key, value); - } - - public JpaDistributionSetMetadata(final String key, final DistributionSet distributionSet, final String value) { - super(key, value); - this.distributionSet = (JpaDistributionSet) distributionSet; - } - - public DsMetadataCompositeKey getId() { - return new DsMetadataCompositeKey(distributionSet.getId(), getKey()); - } - - public void setDistributionSet(final DistributionSet distributionSet) { - this.distributionSet = (JpaDistributionSet) distributionSet; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = super.hashCode(); - result = prime * result + ((distributionSet == null) ? 0 : distributionSet.hashCode()); - return result; - } - - @Override - // exception squid:S2259 - obj is checked for null in super - @SuppressWarnings("squid:S2259") - public boolean equals(final Object obj) { - if (!super.equals(obj)) { - return false; - } - final JpaDistributionSetMetadata other = (JpaDistributionSetMetadata) obj; - if (distributionSet == null) { - return other.distributionSet == null; - } else { - return distributionSet.equals(other.distributionSet); - } - } -} diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/DistributionSetMetadataRepository.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/DistributionSetMetadataRepository.java deleted file mode 100644 index 790755953..000000000 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/DistributionSetMetadataRepository.java +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright (c) 2015 Bosch Software Innovations GmbH and others - * - * 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.repository; - -import org.eclipse.hawkbit.repository.jpa.model.DsMetadataCompositeKey; -import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetMetadata; -import org.eclipse.hawkbit.repository.model.DistributionSetMetadata; -import org.springframework.data.jpa.repository.JpaSpecificationExecutor; -import org.springframework.data.repository.CrudRepository; -import org.springframework.data.repository.PagingAndSortingRepository; -import org.springframework.data.repository.query.Param; -import org.springframework.transaction.annotation.Transactional; - -/** - * {@link DistributionSetMetadata} repository. - */ -@Transactional(readOnly = true) -public interface DistributionSetMetadataRepository - extends PagingAndSortingRepository, - CrudRepository, - JpaSpecificationExecutor { - - /** - * Counts the meta data entries that match the given distribution set ID. - *

- * No access control applied - * - * @param id of the distribution set. - * @return The number of matching meta data entries. - */ - long countByDistributionSetId(@Param("id") Long id); -} diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/acm/controller/DistributionSetAccessControllerTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/acm/controller/DistributionSetAccessControllerTest.java index 6d93834ca..5c9658652 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/acm/controller/DistributionSetAccessControllerTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/acm/controller/DistributionSetAccessControllerTest.java @@ -16,6 +16,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Map; import jakarta.persistence.criteria.Predicate; @@ -28,14 +29,12 @@ import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException; import org.eclipse.hawkbit.repository.jpa.acm.AccessController; import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet; -import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetMetadata; import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet_; import org.eclipse.hawkbit.repository.jpa.model.JpaTarget; import org.eclipse.hawkbit.repository.jpa.specifications.TargetSpecifications; import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSetFilter; -import org.eclipse.hawkbit.repository.model.MetaData; import org.eclipse.hawkbit.repository.model.SoftwareModule; import org.eclipse.hawkbit.repository.model.TargetFilterQuery; import org.junit.jupiter.api.Test; @@ -121,12 +120,18 @@ class DistributionSetAccessControllerTest extends AbstractAccessControllerTest { @Description("Verifies read access rules for distribution sets") void verifyDistributionSetUpdates() { // permit all operations first to prepare test setup + permitAllOperations(AccessController.Operation.READ); permitAllOperations(AccessController.Operation.CREATE); permitAllOperations(AccessController.Operation.UPDATE); final DistributionSet permitted = testdataFactory.createDistributionSet(); + final String mdPresetKey = "metadata.preset"; + final String mdPresetValue = "presetValue"; + distributionSetManagement.createMetadata(permitted.getId(), Map.of(mdPresetKey, mdPresetValue)); final DistributionSet readOnly = testdataFactory.createDistributionSet(); + distributionSetManagement.createMetadata(readOnly.getId(), Map.of(mdPresetKey, mdPresetValue)); final DistributionSet hidden = testdataFactory.createDistributionSet(); + distributionSetManagement.createMetadata(hidden.getId(), Map.of(mdPresetKey, mdPresetValue)); final SoftwareModule swModule = testdataFactory.createSoftwareModuleOs(); @@ -134,52 +139,49 @@ class DistributionSetAccessControllerTest extends AbstractAccessControllerTest { testAccessControlManger.deleteAllRules(); // define access controlling rule defineAccess(AccessController.Operation.READ, permitted, readOnly); - - // allow updating the permitted distributionSet - defineAccess(AccessController.Operation.READ, permitted); defineAccess(AccessController.Operation.UPDATE, permitted); // verify distributionSetManagement#assignSoftwareModules - final var singleModuleIdList = Collections.singletonList(swModule.getId()); + final List singleModuleIdList = Collections.singletonList(swModule.getId()); assertThat(distributionSetManagement.assignSoftwareModules(permitted.getId(), singleModuleIdList)) .satisfies(ds -> assertThat(ds.getModules().stream().map(Identifiable::getId).toList()).contains(swModule.getId())); final Long readOnlyId = readOnly.getId(); assertThatThrownBy(() -> distributionSetManagement.assignSoftwareModules(readOnlyId, singleModuleIdList)) .as("Distribution set not allowed to me modified.") - .isInstanceOf(EntityNotFoundException.class); + .isInstanceOf(InsufficientPermissionException.class); final Long hiddenId = hidden.getId(); assertThatThrownBy(() -> distributionSetManagement.assignSoftwareModules(hiddenId, singleModuleIdList)) .as("Distribution set should not be visible.") .isInstanceOf(EntityNotFoundException.class); - final JpaDistributionSetMetadata metadata = new JpaDistributionSetMetadata("test", "test"); + final Map metadata = Map.of("test.create", mdPresetValue); // verify distributionSetManagement#createMetaData - final List metadataList = Collections.singletonList(metadata); - distributionSetManagement.putMetaData(permitted.getId(), metadataList); - assertThatThrownBy(() -> distributionSetManagement.putMetaData(readOnlyId, metadataList)) - .as("Distribution set not allowed to me modified.") - .isInstanceOf(EntityNotFoundException.class); - assertThatThrownBy(() -> distributionSetManagement.putMetaData(hiddenId, metadataList)) + distributionSetManagement.createMetadata(permitted.getId(), metadata); + assertThatThrownBy(() -> distributionSetManagement.createMetadata(readOnlyId, metadata)) + .as("Distribution set not allowed to be modified.") + .isInstanceOf(InsufficientPermissionException.class); + assertThatThrownBy(() -> distributionSetManagement.createMetadata(hiddenId, metadata)) .as("Distribution set should not be visible.") .isInstanceOf(EntityNotFoundException.class); // verify distributionSetManagement#updateMetaData - distributionSetManagement.updateMetaData(permitted.getId(), metadata); - assertThatThrownBy(() -> distributionSetManagement.updateMetaData(readOnlyId, metadata)) + final String newValue = "newValue"; + distributionSetManagement.updateMetadata(permitted.getId(), mdPresetKey, newValue); + assertThatThrownBy(() -> distributionSetManagement.updateMetadata(readOnlyId, mdPresetKey, newValue)) .as("Distribution set not allowed to me modified.") - .isInstanceOf(EntityNotFoundException.class); - assertThatThrownBy(() -> distributionSetManagement.updateMetaData(hiddenId, metadata)) + .isInstanceOf(InsufficientPermissionException.class); + assertThatThrownBy(() -> distributionSetManagement.updateMetadata(hiddenId, mdPresetKey, newValue)) .as("Distribution set should not be visible.") .isInstanceOf(EntityNotFoundException.class); // verify distributionSetManagement#deleteMetaData - final String metadataKey = metadata.getKey(); - distributionSetManagement.deleteMetaData(permitted.getId(), metadataKey); - assertThatThrownBy(() -> distributionSetManagement.deleteMetaData(readOnlyId, metadataKey)) + final String metadataKey = metadata.entrySet().stream().findAny().get().getKey(); + distributionSetManagement.deleteMetadata(permitted.getId(), metadataKey); + assertThatThrownBy(() -> distributionSetManagement.deleteMetadata(readOnlyId, mdPresetKey)) .as("Distribution set not allowed to me modified.") - .isInstanceOf(EntityNotFoundException.class); - assertThatThrownBy(() -> distributionSetManagement.deleteMetaData(hiddenId, metadataKey)) + .isInstanceOf(InsufficientPermissionException.class); + assertThatThrownBy(() -> distributionSetManagement.deleteMetadata(hiddenId, mdPresetKey)) .as("Distribution set should not be visible.") .isInstanceOf(EntityNotFoundException.class); } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/acm/controller/TargetAccessControllerTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/acm/controller/TargetAccessControllerTest.java index 45e430363..eee8a7ce0 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/acm/controller/TargetAccessControllerTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/acm/controller/TargetAccessControllerTest.java @@ -220,13 +220,12 @@ class TargetAccessControllerTest extends AbstractAccessControllerTest { final Target permittedTarget = targetManagement .create(entityFactory.target().create().controllerId("device01").status(TargetUpdateStatus.REGISTERED)); - final String hiddenTargetControllerId = targetManagement .create(entityFactory.target().create().controllerId("device02").status(TargetUpdateStatus.REGISTERED)) .getControllerId(); // define access controlling rule - defineAccess(AccessController.Operation.READ, permittedTarget); + overwriteAccess(AccessController.Operation.READ, permittedTarget); // verify targetManagement#findByUpdateStatus before assignment assertThat(targetManagement.findByUpdateStatus(Pageable.unpaged(), TargetUpdateStatus.REGISTERED).get() @@ -269,18 +268,15 @@ class TargetAccessControllerTest extends AbstractAccessControllerTest { final Target manageableTarget = targetManagement .create(entityFactory.target().create().controllerId("device01").status(TargetUpdateStatus.REGISTERED)); - final Target readOnlyTarget = targetManagement .create(entityFactory.target().create().controllerId("device02").status(TargetUpdateStatus.REGISTERED)); - // define access controlling rule - defineAccess(AccessController.Operation.READ, manageableTarget, readOnlyTarget); - - defineAccess(AccessController.Operation.UPDATE, manageableTarget); + // overwriting full access controlling rule + overwriteAccess(AccessController.Operation.READ, manageableTarget, readOnlyTarget); + overwriteAccess(AccessController.Operation.UPDATE, manageableTarget); // assignment is permitted for manageableTarget - assertThat(assignDistributionSet(firstDsId, manageableTarget.getControllerId()).getAssigned()) - .isEqualTo(1); + assertThat(assignDistributionSet(firstDsId, manageableTarget.getControllerId()).getAssigned()).isEqualTo(1); // assignment is denied for readOnlyTarget (read, but no update permissions) final var readOnlyTargetControllerId = readOnlyTarget.getControllerId(); @@ -309,19 +305,17 @@ class TargetAccessControllerTest extends AbstractAccessControllerTest { final List updateTargets = testdataFactory.createTargets("update1", "update2", "update3"); final List readTargets = testdataFactory.createTargets("read1", "read2", "read3", "read4"); - final List hiddenTargets = testdataFactory.createTargets( - "hidden1", "hidden2", "hidden3", "hidden4", "hidden5"); + final List hiddenTargets = testdataFactory.createTargets("hidden1", "hidden2", "hidden3", "hidden4", "hidden5"); defineAccess(AccessController.Operation.UPDATE, updateTargets); - defineAccess(AccessController.Operation.READ, merge(readTargets, updateTargets)); + overwriteAccess(AccessController.Operation.READ, merge(readTargets, updateTargets)); - final Rollout rollout = testdataFactory.createRolloutByVariables("testRollout", "description", - updateTargets.size(), "id==*", ds, "50", "5"); + final Rollout rollout = testdataFactory.createRolloutByVariables( + "testRollout", "description", updateTargets.size(), "id==*", ds, "50", "5"); assertThat(rollout.getTotalTargets()).isEqualTo(updateTargets.size()); - final List content = rolloutGroupManagement.findByRollout(rollout.getId(), Pageable.unpaged()) - .getContent(); + final List content = rolloutGroupManagement.findByRollout(rollout.getId(), Pageable.unpaged()).getContent(); assertThat(content).hasSize(updateTargets.size()); final List rolloutTargets = content.stream().flatMap( @@ -393,6 +387,18 @@ class TargetAccessControllerTest extends AbstractAccessControllerTest { target -> ids.contains(target.getId())); } + private void overwriteAccess(final AccessController.Operation operation, final Target... target) { + overwriteAccess(operation, List.of(target)); + } + + private void overwriteAccess(final AccessController.Operation operation, final List targets) { + final List ids = targets.stream().map(Target::getId).toList(); + testAccessControlManger.overwriteAccessRule( + JpaTarget.class, operation, + TargetSpecifications.hasIdIn(ids), + target -> ids.contains(target.getId())); + } + private static Specification dsById(final Long distid) { return (dsRoot, query, cb) -> { final Predicate predicate = cb.equal(dsRoot.get(JpaDistributionSet_.id), distid); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/acm/controller/TargetTypeAccessControllerTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/acm/controller/TargetTypeAccessControllerTest.java index bcccc46db..c734bda47 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/acm/controller/TargetTypeAccessControllerTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/acm/controller/TargetTypeAccessControllerTest.java @@ -14,6 +14,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import java.util.Arrays; import java.util.List; +import java.util.stream.Stream; import io.qameta.allure.Description; import io.qameta.allure.Feature; @@ -37,14 +38,9 @@ class TargetTypeAccessControllerTest extends AbstractAccessControllerTest { @Test @Description("Verifies read access rules for target types") void verifyTargetTypeReadOperations() { - permitAllOperations(AccessController.Operation.READ); permitAllOperations(AccessController.Operation.CREATE); - - final TargetType permittedTargetType = targetTypeManagement - .create(entityFactory.targetType().create().name("type1")); - - final TargetType hiddenTargetType = targetTypeManagement - .create(entityFactory.targetType().create().name("type2")); + final TargetType permittedTargetType = targetTypeManagement.create(entityFactory.targetType().create().name("type1")); + final TargetType hiddenTargetType = targetTypeManagement.create(entityFactory.targetType().create().name("type2")); // define access controlling rule defineAccess(AccessController.Operation.READ, permittedTargetType); @@ -67,12 +63,12 @@ class TargetTypeAccessControllerTest extends AbstractAccessControllerTest { assertThat(targetTypeManagement.count()).isEqualTo(1); // verify targetTypeManagement#countByName -// assertThat(targetTypeManagement.countByName(permittedTargetType.getName())).isEqualTo(1); -// assertThat(targetTypeManagement.countByName(hiddenTargetType.getName())).isZero(); + assertThat(targetTypeManagement.countByName(permittedTargetType.getName())).isEqualTo(1); + assertThat(targetTypeManagement.countByName(hiddenTargetType.getName())).isZero(); // verify targetTypeManagement#countByName -// assertThat(targetTypeManagement.countByName(permittedTargetType.getName())).isEqualTo(1); -// assertThat(targetTypeManagement.countByName(hiddenTargetType.getName())).isZero(); + assertThat(targetTypeManagement.countByName(permittedTargetType.getName())).isEqualTo(1); + assertThat(targetTypeManagement.countByName(hiddenTargetType.getName())).isZero(); // verify targetTypeManagement#get by id assertThat(targetTypeManagement.get(permittedTargetType.getId())).isPresent(); @@ -161,15 +157,11 @@ class TargetTypeAccessControllerTest extends AbstractAccessControllerTest { .isInstanceOf(InsufficientPermissionException.class); } - private void defineAccess(final AccessController.Operation operation, final TargetType... targetType) { - defineAccess(operation, List.of(targetType)); - } - - private void defineAccess(final AccessController.Operation operation, final List targetTypes) { - final List ids = targetTypes.stream().map(TargetType::getId).toList(); + private void defineAccess(final AccessController.Operation operation, final TargetType... targetTypes) { + final List ids = Stream.of(targetTypes).map(TargetType::getId).toList(); testAccessControlManger.defineAccessRule( JpaTargetType.class, operation, TargetTypeSpecification.hasIdIn(ids), targetType -> ids.contains(targetType.getId())); } -} +} \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/acm/controller/TestAccessControlManger.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/acm/controller/TestAccessControlManger.java index 5728454fe..d6d3072ae 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/acm/controller/TestAccessControlManger.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/acm/controller/TestAccessControlManger.java @@ -31,7 +31,23 @@ public class TestAccessControlManger { public void defineAccessRule( final Class ruleClass, final AccessController.Operation operation, final Specification specification, final Predicate check) { - accessRules.put(new AccessRuleId<>(ruleClass, operation), new AccessRule<>(specification, check)); + defineAccessRule(ruleClass, operation, specification, check, false); + } + + public void overwriteAccessRule( + final Class ruleClass, final AccessController.Operation operation, + final Specification specification, final Predicate check) { + defineAccessRule(ruleClass, operation, specification, check, true); + } + + private void defineAccessRule( + final Class ruleClass, final AccessController.Operation operation, + final Specification specification, final Predicate check, final boolean overwrite) { + final AccessRuleId ruleId = new AccessRuleId<>(ruleClass, operation); + if (!overwrite && accessRules.containsKey(ruleId)) { + throw new IllegalStateException("Access rule already defined for " + ruleId + "! You should explicitly set overwrite to true."); + } + accessRules.put(ruleId, new AccessRule<>(specification, check)); } public Specification getAccessRule(final Class ruleClass, @@ -53,8 +69,7 @@ public class TestAccessControlManger { } else { for (final T entity : entities) { if (!accessRule.checker.test(entity)) { - throw new InsufficientPermissionException( - "Access to " + ruleClass.getName() + "/" + entity + " not allowed by checker!"); + throw new InsufficientPermissionException("Access to " + ruleClass.getName() + "/" + entity + " not allowed by checker!"); } } } @@ -67,4 +82,4 @@ public class TestAccessControlManger { private record AccessRuleId(Class ruleClass, AccessController.Operation operation) {} private record AccessRule(Specification specification, Predicate checker) {} -} +} \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/DistributionSetManagementSecurityTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/DistributionSetManagementSecurityTest.java index 3b90bb6aa..6090c55a5 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/DistributionSetManagementSecurityTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/DistributionSetManagementSecurityTest.java @@ -10,6 +10,7 @@ package org.eclipse.hawkbit.repository.jpa.management; import java.util.List; +import java.util.Map; import io.qameta.allure.Description; import io.qameta.allure.Feature; @@ -66,17 +67,36 @@ class DistributionSetManagementSecurityTest @Test @Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.") - void createMetaDataPermissionsCheck() { + void createMetadataPermissionsCheck() { assertPermissions( - () -> distributionSetManagement.putMetaData(1L, List.of(entityFactory.generateDsMetadata("key", "value"))), + () -> { + distributionSetManagement.createMetadata(1L, Map.of("key", "value")); + return null; + }, List.of(SpPermission.UPDATE_REPOSITORY)); } @Test @Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.") - void deleteMetaDataPermissionsCheck() { + void getMetadataPermissiosCheck() { + assertPermissions(() -> distributionSetManagement.getMetadata(1L), List.of(SpPermission.READ_REPOSITORY)); + } + + @Test + @Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.") + void updateMetadataPermissionsCheck() { assertPermissions(() -> { - distributionSetManagement.deleteMetaData(1L, "key"); + distributionSetManagement.updateMetadata(1L,"key", "value"); + return null; + }, + List.of(SpPermission.UPDATE_REPOSITORY)); + } + + @Test + @Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.") + void deleteMetadataPermissionsCheck() { + assertPermissions(() -> { + distributionSetManagement.deleteMetadata(1L, "key"); return null; }, List.of(SpPermission.UPDATE_REPOSITORY)); } @@ -135,25 +155,6 @@ class DistributionSetManagementSecurityTest assertPermissions(() -> distributionSetManagement.getOrElseThrowException(1L), List.of(SpPermission.READ_REPOSITORY)); } - @Test - @Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.") - void findMetaDataByDistributionSetIdPermissionsCheck() { - assertPermissions(() -> distributionSetManagement.findMetaDataByDistributionSetId(1L, PAGE), List.of(SpPermission.READ_REPOSITORY)); - } - - @Test - @Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.") - void countMetaDataByDistributionSetIdPermissionsCheck() { - assertPermissions(() -> distributionSetManagement.countMetaDataByDistributionSetId(1L), List.of(SpPermission.READ_REPOSITORY)); - } - - @Test - @Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.") - void findMetaDataByDistributionSetIdAndRsqlPermissionsCheck() { - assertPermissions(() -> distributionSetManagement.findMetaDataByDistributionSetIdAndRsql(1L, "rsql", PAGE), - List.of(SpPermission.READ_REPOSITORY)); - } - @Test @Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.") void findByCompletedPermissionsCheck() { @@ -192,12 +193,6 @@ class DistributionSetManagementSecurityTest assertPermissions(() -> distributionSetManagement.findByRsqlAndTag("rsql", 1L, PAGE), List.of(SpPermission.READ_REPOSITORY)); } - @Test - @Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.") - void getMetaDataByDistributionSetIdPermissionsCheck() { - assertPermissions(() -> distributionSetManagement.findMetaDataByDistributionSetId(1L, "key"), List.of(SpPermission.READ_REPOSITORY)); - } - @Test @Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.") void isInUsePermissionsCheck() { @@ -210,13 +205,6 @@ class DistributionSetManagementSecurityTest assertPermissions(() -> distributionSetManagement.unassignSoftwareModule(1L, 1L), List.of(SpPermission.UPDATE_REPOSITORY)); } - @Test - @Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.") - void updateMetaDataPermissionsCheck() { - assertPermissions(() -> distributionSetManagement.updateMetaData(1L, entityFactory.generateDsMetadata("key", "value")), - List.of(SpPermission.UPDATE_REPOSITORY)); - } - @Test @Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.") void countByTypeIdPermissionsCheck() { diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/DistributionSetManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/DistributionSetManagementTest.java index e569a0ed2..665601c57 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/DistributionSetManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/DistributionSetManagementTest.java @@ -18,7 +18,9 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.NoSuchElementException; import java.util.Optional; import java.util.Set; @@ -49,17 +51,14 @@ import org.eclipse.hawkbit.repository.exception.LockedException; import org.eclipse.hawkbit.repository.exception.UnsupportedSoftwareModuleForThisDistributionSetException; import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest; import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet; -import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetMetadata; import org.eclipse.hawkbit.repository.model.Action.Status; import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSetFilter; import org.eclipse.hawkbit.repository.model.DistributionSetFilter.DistributionSetFilterBuilder; import org.eclipse.hawkbit.repository.model.DistributionSetInvalidation; import org.eclipse.hawkbit.repository.model.DistributionSetInvalidation.CancelationType; -import org.eclipse.hawkbit.repository.model.DistributionSetMetadata; import org.eclipse.hawkbit.repository.model.DistributionSetTag; import org.eclipse.hawkbit.repository.model.DistributionSetType; -import org.eclipse.hawkbit.repository.model.MetaData; import org.eclipse.hawkbit.repository.model.NamedEntity; import org.eclipse.hawkbit.repository.model.NamedVersionedEntity; import org.eclipse.hawkbit.repository.model.Rollout; @@ -71,8 +70,6 @@ import org.eclipse.hawkbit.repository.test.matcher.ExpectEvents; import org.eclipse.hawkbit.repository.test.util.WithUser; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; /** * {@link DistributionSetManagement} tests. @@ -96,7 +93,7 @@ class DistributionSetManagementTest extends AbstractJpaIntegrationTest { assertThat(distributionSetManagement.get(NOT_EXIST_IDL)).isNotPresent(); assertThat(distributionSetManagement.getWithDetails(NOT_EXIST_IDL)).isNotPresent(); assertThat(distributionSetManagement.findByNameAndVersion(NOT_EXIST_ID, NOT_EXIST_ID)).isNotPresent(); - assertThat(distributionSetManagement.findMetaDataByDistributionSetId(set.getId(), NOT_EXIST_ID)).isNotPresent(); + assertThat(distributionSetManagement.getMetadata(set.getId()).get(NOT_EXIST_ID)).isNull(); } @Test @@ -138,44 +135,30 @@ class DistributionSetManagementTest extends AbstractJpaIntegrationTest { verifyThrownExceptionBy(() -> distributionSetManagement.create( entityFactory.distributionSet().create().name("xxx").type(NOT_EXIST_ID)), "DistributionSetType"); - verifyThrownExceptionBy(() -> distributionSetManagement.putMetaData( - NOT_EXIST_IDL, singletonList(entityFactory.generateDsMetadata("123", "123"))), "DistributionSet"); + verifyThrownExceptionBy(() -> distributionSetManagement.createMetadata(NOT_EXIST_IDL, Map.of("123", "123")), "DistributionSet"); verifyThrownExceptionBy(() -> distributionSetManagement.delete(singletonList(NOT_EXIST_IDL)), "DistributionSet"); verifyThrownExceptionBy(() -> distributionSetManagement.delete(NOT_EXIST_IDL), "DistributionSet"); - verifyThrownExceptionBy(() -> distributionSetManagement.deleteMetaData(NOT_EXIST_IDL, "xxx"), - "DistributionSet"); - verifyThrownExceptionBy(() -> distributionSetManagement.deleteMetaData(set.getId(), NOT_EXIST_ID), - "DistributionSetMetadata"); + verifyThrownExceptionBy(() -> distributionSetManagement.deleteMetadata(NOT_EXIST_IDL, "xxx"), "DistributionSet"); + verifyThrownExceptionBy(() -> distributionSetManagement.deleteMetadata(set.getId(), NOT_EXIST_ID), "DistributionSet"); verifyThrownExceptionBy(() -> distributionSetManagement.findByAction(NOT_EXIST_IDL), "Action"); - verifyThrownExceptionBy(() -> distributionSetManagement.findMetaDataByDistributionSetId(NOT_EXIST_IDL, "xxx"), - "DistributionSet"); + verifyThrownExceptionBy(() -> distributionSetManagement.getMetadata(NOT_EXIST_IDL).get("xxx"), "DistributionSet"); - verifyThrownExceptionBy(() -> distributionSetManagement.findMetaDataByDistributionSetId(NOT_EXIST_IDL, PAGE), - "DistributionSet"); - - verifyThrownExceptionBy( - () -> distributionSetManagement.findMetaDataByDistributionSetIdAndRsql(NOT_EXIST_IDL, "name==*", PAGE), - "DistributionSet"); + verifyThrownExceptionBy(() -> distributionSetManagement.getMetadata(NOT_EXIST_IDL).get(PAGE), "DistributionSet"); assertThatThrownBy(() -> distributionSetManagement.isInUse(NOT_EXIST_IDL)) .isInstanceOf(EntityNotFoundException.class).hasMessageContaining(NOT_EXIST_ID) .hasMessageContaining("DistributionSet"); verifyThrownExceptionBy( - () -> distributionSetManagement.update(entityFactory.distributionSet().update(NOT_EXIST_IDL)), - "DistributionSet"); + () -> distributionSetManagement.update(entityFactory.distributionSet().update(NOT_EXIST_IDL)), "DistributionSet"); - verifyThrownExceptionBy(() -> distributionSetManagement.updateMetaData(NOT_EXIST_IDL, - entityFactory.generateDsMetadata("xxx", "xxx")), "DistributionSet"); + verifyThrownExceptionBy(() -> distributionSetManagement.updateMetadata(NOT_EXIST_IDL,"xxx", "xxx"), "DistributionSet"); + verifyThrownExceptionBy(() -> distributionSetManagement.updateMetadata(set.getId(),NOT_EXIST_ID, "xxx"), "DistributionSet"); - verifyThrownExceptionBy(() -> distributionSetManagement.updateMetaData(set.getId(), - entityFactory.generateDsMetadata(NOT_EXIST_ID, "xxx")), "DistributionSetMetadata"); - - verifyThrownExceptionBy(() -> distributionSetManagement.getOrElseThrowException(NOT_EXIST_IDL), - "DistributionSet"); + verifyThrownExceptionBy(() -> distributionSetManagement.getOrElseThrowException(NOT_EXIST_IDL), "DistributionSet"); verifyThrownExceptionBy(() -> distributionSetManagement.getValidAndComplete(NOT_EXIST_IDL), "DistributionSet"); @@ -247,68 +230,57 @@ class DistributionSetManagementTest extends AbstractJpaIntegrationTest { @Test @Description("Checks that metadata for a distribution set can be created.") - void createDistributionSetMetadata() { + void createMetadata() { final String knownKey = "dsMetaKnownKey"; final String knownValue = "dsMetaKnownValue"; final DistributionSet ds = testdataFactory.createDistributionSet("testDs"); - final DistributionSetMetadata metadata = new JpaDistributionSetMetadata(knownKey, ds, knownValue); - final JpaDistributionSetMetadata createdMetadata = (JpaDistributionSetMetadata) createDistributionSetMetadata( - ds.getId(), metadata); - - assertThat(createdMetadata).isNotNull(); - assertThat(createdMetadata.getId().getKey()).isEqualTo(knownKey); - assertThat(createdMetadata.getDistributionSet().getId()).isEqualTo(ds.getId()); - assertThat(createdMetadata.getValue()).isEqualTo(knownValue); + insertMetadata(knownKey, knownValue, ds); // and validate } @Test @Description("Verifies the enforcement of the metadata quota per distribution set.") - void createDistributionSetMetadataUntilQuotaIsExceeded() { + void createMetadataUntilQuotaIsExceeded() { // add meta data one by one final DistributionSet ds1 = testdataFactory.createDistributionSet("ds1"); final int maxMetaData = quotaManagement.getMaxMetaDataEntriesPerDistributionSet(); for (int i = 0; i < maxMetaData; ++i) { - assertThat((JpaDistributionSetMetadata) createDistributionSetMetadata(ds1.getId(), - new JpaDistributionSetMetadata("k" + i, ds1, "v" + i))).isNotNull(); + insertMetadata("k" + i, "v" + i, ds1); } // quota exceeded - final Long ds1Id = ds1.getId(); - final JpaDistributionSetMetadata jpaMaxMetaData = new JpaDistributionSetMetadata("k" + maxMetaData, ds1, "v" + maxMetaData); assertThatExceptionOfType(AssignmentQuotaExceededException.class) - .isThrownBy(() -> createDistributionSetMetadata(ds1Id, jpaMaxMetaData)); + .isThrownBy(() -> insertMetadata("k" + maxMetaData, "v" + maxMetaData, ds1)); // add multiple meta data entries at once final DistributionSet ds2 = testdataFactory.createDistributionSet("ds2"); - final List metaData2 = new ArrayList<>(); + final Map metaData2 = new HashMap<>(); for (int i = 0; i < maxMetaData + 1; ++i) { - metaData2.add(new JpaDistributionSetMetadata("k" + i, ds2, "v" + i)); + metaData2.put("k" + i, "v" + i); } // verify quota is exceeded final Long ds2Id = ds2.getId(); assertThatExceptionOfType(AssignmentQuotaExceededException.class) - .isThrownBy(() -> createDistributionSetMetadata(ds2Id, metaData2)); + .isThrownBy(() -> distributionSetManagement.createMetadata(ds2Id, metaData2)); // add some meta data entries final DistributionSet ds3 = testdataFactory.createDistributionSet("ds3"); final int firstHalf = Math.round((maxMetaData) / 2.f); final Long ds3Id = ds3.getId(); for (int i = 0; i < firstHalf; ++i) { - createDistributionSetMetadata(ds3Id, new JpaDistributionSetMetadata("k" + i, ds3, "v" + i)); + insertMetadata("k" + i, "v" + i, ds3); } // add too many data entries final int secondHalf = maxMetaData - firstHalf; - final List metaData3 = new ArrayList<>(); + final Map metaData3 = new HashMap<>(); for (int i = 0; i < secondHalf + 1; ++i) { - metaData3.add(new JpaDistributionSetMetadata("kk" + i, ds3, "vv" + i)); + metaData3.put("kk" + i, "vv" + i); } // verify quota is exceeded assertThatExceptionOfType(AssignmentQuotaExceededException.class) - .isThrownBy(() -> createDistributionSetMetadata(ds3Id, metaData3)); - + .isThrownBy(() -> distributionSetManagement.createMetadata(ds3Id, metaData3)); } @Test @@ -507,7 +479,7 @@ class DistributionSetManagementTest extends AbstractJpaIntegrationTest { @Test @WithUser(allSpPermissions = true) @Description("Checks that metadata for a distribution set can be updated.") - void updateDistributionSetMetadata() { + void updateMetadata() { final String knownKey = "myKnownKey"; final String knownValue = "myKnownValue"; final String knownUpdateValue = "myNewUpdatedValue"; @@ -519,26 +491,22 @@ class DistributionSetManagementTest extends AbstractJpaIntegrationTest { waitNextMillis(); // create an DS meta data entry - createDistributionSetMetadata(ds.getId(), new JpaDistributionSetMetadata(knownKey, ds, knownValue)); + insertMetadata(knownKey, knownValue, ds); final DistributionSet changedLockRevisionDS = getOrThrow(distributionSetManagement.get(ds.getId())); assertThat(changedLockRevisionDS.getOptLockRevision()).isEqualTo(2); waitNextMillis(); // update the DS metadata - final JpaDistributionSetMetadata updated = (JpaDistributionSetMetadata) distributionSetManagement - .updateMetaData(ds.getId(), entityFactory.generateDsMetadata(knownKey, knownUpdateValue)); + distributionSetManagement.updateMetadata(ds.getId(), knownKey, knownUpdateValue); // we are updating the sw metadata so also modifying the base software // module so opt lock revision must be three final DistributionSet reloadedDS = getOrThrow(distributionSetManagement.get(ds.getId())); assertThat(reloadedDS.getOptLockRevision()).isEqualTo(3); assertThat(reloadedDS.getLastModifiedAt()).isPositive(); - // verify updated meta data contains the updated value - assertThat(updated).isNotNull(); - assertThat(updated.getValue()).isEqualTo(knownUpdateValue); - assertThat(updated.getId().getKey()).isEqualTo(knownKey); - assertThat(updated.getDistributionSet().getId()).isEqualTo(ds.getId()); + // verify updated meta data is the updated value + assertThat(distributionSetManagement.getMetadata(ds.getId()).get(knownKey)).isEqualTo(knownUpdateValue); } @Test @@ -783,37 +751,22 @@ class DistributionSetManagementTest extends AbstractJpaIntegrationTest { } @Test - @Description("Queries and loads the metadata related to a given software module.") - void findAllDistributionSetMetadataByDsId() { + @Description("Queries and loads the metadata related to a given distribution set.") + void getMetadata() { // create a DS final DistributionSet ds1 = testdataFactory.createDistributionSet("testDs1"); final DistributionSet ds2 = testdataFactory.createDistributionSet("testDs2"); for (int index = 0; index < quotaManagement.getMaxMetaDataEntriesPerDistributionSet(); index++) { - createDistributionSetMetadata(ds1.getId(), - new JpaDistributionSetMetadata("key" + index, ds1, "value" + index)); + insertMetadata("key" + index, "value" + index, ds1); } for (int index = 0; index <= quotaManagement.getMaxMetaDataEntriesPerDistributionSet() - 2; index++) { - createDistributionSetMetadata(ds2.getId(), - new JpaDistributionSetMetadata("key" + index, ds2, "value" + index)); + insertMetadata("key" + index, "value" + index, ds2); } - final Page metadataOfDs1 = distributionSetManagement - .findMetaDataByDistributionSetId(ds1.getId(), PageRequest.of(0, 100)); - - final Page metadataOfDs2 = distributionSetManagement - .findMetaDataByDistributionSetId(ds2.getId(), PageRequest.of(0, 100)); - - assertThat(metadataOfDs1.getNumberOfElements()) - .isEqualTo(quotaManagement.getMaxMetaDataEntriesPerDistributionSet()); - assertThat(metadataOfDs1.getTotalElements()) - .isEqualTo(quotaManagement.getMaxMetaDataEntriesPerDistributionSet()); - - assertThat(metadataOfDs2.getNumberOfElements()) - .isEqualTo(quotaManagement.getMaxMetaDataEntriesPerDistributionSet() - 1); - assertThat(metadataOfDs2.getTotalElements()) - .isEqualTo(quotaManagement.getMaxMetaDataEntriesPerDistributionSet() - 1); + assertThat(distributionSetManagement.getMetadata(ds1.getId())).hasSize(quotaManagement.getMaxMetaDataEntriesPerDistributionSet()); + assertThat(distributionSetManagement.getMetadata(ds2.getId())).hasSize(quotaManagement.getMaxMetaDataEntriesPerDistributionSet() - 1); } @Test @@ -897,23 +850,20 @@ class DistributionSetManagementTest extends AbstractJpaIntegrationTest { final String knownUpdateValue = "knownUpdateValue"; final Long dsId = testdataFactory.createDistributionSet().getId(); - distributionSetManagement.putMetaData(dsId, - singletonList(entityFactory.generateDsMetadata(knownKey1, knownValue))); + distributionSetManagement.createMetadata(dsId, Map.of(knownKey1, knownValue)); distributionSetInvalidationManagement.invalidateDistributionSet( new DistributionSetInvalidation(singletonList(dsId), CancelationType.NONE, false)); // assert that no new metadata can be created - final List metadata = singletonList(entityFactory.generateDsMetadata(knownKey2, knownValue)); assertThatExceptionOfType(InvalidDistributionSetException.class) .as("Invalid distributionSet should throw an exception") - .isThrownBy(() -> distributionSetManagement.putMetaData(dsId, metadata)); + .isThrownBy(() -> distributionSetManagement.createMetadata(dsId, Map.of(knownKey2, knownValue))); // assert that an existing metadata can not be updated - final MetaData metadata2 = entityFactory.generateDsMetadata(knownKey1, knownUpdateValue); assertThatExceptionOfType(InvalidDistributionSetException.class) .as("Invalid distributionSet should throw an exception") - .isThrownBy(() -> distributionSetManagement.updateMetaData(dsId, metadata2)); + .isThrownBy(() -> distributionSetManagement.updateMetadata(dsId, knownKey1, knownUpdateValue)); } @Test @@ -1265,6 +1215,11 @@ class DistributionSetManagementTest extends AbstractJpaIntegrationTest { .isComplete(Boolean.FALSE).isDeleted(Boolean.FALSE)); } + private void insertMetadata(final String knownKey, final String knownValue, final DistributionSet distributionSet) { + distributionSetManagement.createMetadata(distributionSet.getId(), Map.of(knownKey, knownValue)); + assertThat(distributionSetManagement.getMetadata(distributionSet.getId()).get(knownKey)).isEqualTo(knownValue); + } + private void assertThatFilterContainsOnlyGivenDistributionSets(final DistributionSetFilterBuilder filterBuilder, final List distributionSets) { final int expectedDsSize = distributionSets.size(); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLDistributionSetFieldTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLDistributionSetFieldTest.java deleted file mode 100644 index f4f89ab61..000000000 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLDistributionSetFieldTest.java +++ /dev/null @@ -1,234 +0,0 @@ -/** - * Copyright (c) 2015 Bosch Software Innovations GmbH and others - * - * 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.rsql; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; - -import java.util.Arrays; -import java.util.Collections; - -import jakarta.persistence.EntityManager; - -import io.qameta.allure.Description; -import io.qameta.allure.Feature; -import io.qameta.allure.Story; -import org.eclipse.hawkbit.repository.DistributionSetFields; -import org.eclipse.hawkbit.repository.SoftwareModuleFields; -import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException; -import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException; -import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest; -import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet; -import org.eclipse.hawkbit.repository.model.DistributionSet; -import org.eclipse.hawkbit.repository.model.DistributionSetTag; -import org.eclipse.hawkbit.repository.model.SoftwareModule; -import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer; -import org.eclipse.hawkbit.repository.test.util.TestdataFactory; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.orm.jpa.vendor.Database; - -@Feature("Component Tests - Repository") -@Story("RSQL filter distribution set") -@SuppressWarnings("java:S6813") // constructor injects are not possible for test classes -class RSQLDistributionSetFieldTest extends AbstractJpaIntegrationTest { - - @Autowired - protected VirtualPropertyReplacer virtualPropertyReplacer; - @Autowired - protected EntityManager entityManager; - - private DistributionSet ds; - private SoftwareModule sm; - - @BeforeEach - void setupBeforeTest() { - - sm = testdataFactory.createSoftwareModuleApp("SM"); - - ds = testdataFactory.createDistributionSet(Collections.singletonList(sm), "DS"); - ds = distributionSetManagement.update(entityFactory.distributionSet().update(ds.getId()).description("DS")); - createDistributionSetMetadata(ds.getId(), entityFactory.generateDsMetadata("metaKey", "metaValue")); - - DistributionSet ds2 = testdataFactory.createDistributionSets("NewDS", 3).get(0); - - ds2 = distributionSetManagement.update(entityFactory.distributionSet().update(ds2.getId()).description("DS%")); - createDistributionSetMetadata(ds2.getId(), entityFactory.generateDsMetadata("metaKey", "value")); - - final DistributionSetTag distSetTag = distributionSetTagManagement - .create(entityFactory.tag().create().name("Tag1")); - distributionSetTagManagement.create(entityFactory.tag().create().name("Tag2")); - distributionSetTagManagement.create(entityFactory.tag().create().name("Tag3")); - distributionSetTagManagement.create(entityFactory.tag().create().name("Tag4")); - - distributionSetManagement.assignTag(Arrays.asList(ds.getId(), ds2.getId()), distSetTag.getId()); - - final DistributionSet ds3 = distributionSetManagement - .create(entityFactory.distributionSet().create().name("test123").version("noDescription")); - distributionSetManagement.invalidate(ds3); - } - - @Test - @Description("Test filter distribution set by id") - void testFilterByParameterId() { - assertRSQLQuery(DistributionSetFields.ID.name() + "==" + ds.getId(), 1); - assertRSQLQuery(DistributionSetFields.ID.name() + "!=" + ds.getId(), 4); - assertRSQLQuery(DistributionSetFields.ID.name() + "==" + -1, 0); - assertRSQLQuery(DistributionSetFields.ID.name() + "!=" + -1, 5); - - // Not supported for numbers - if (Database.POSTGRESQL.equals(getDatabase())) { - return; - } - - assertRSQLQuery(DistributionSetFields.ID.name() + "=in=(" + ds.getId() + ",10000000)", 1); - assertRSQLQuery(DistributionSetFields.ID.name() + "=out=(" + ds.getId() + ",10000000)", 4); - } - - @Test - @Description("Test filter distribution set by name") - void testFilterByParameterName() { - assertRSQLQuery(DistributionSetFields.NAME.name() + "==DS", 1); - assertRSQLQuery(DistributionSetFields.NAME.name() + "!=DS", 4); - assertRSQLQuery(DistributionSetFields.NAME.name() + "==*DS", 4); - assertRSQLQuery(DistributionSetFields.NAME.name() + "==noExist*", 0); - assertRSQLQuery(DistributionSetFields.NAME.name() + "=in=(DS,notexist)", 1); - assertRSQLQuery(DistributionSetFields.NAME.name() + "=out=(DS,notexist)", 4); - } - - @Test - @Description("Test filter distribution set by assigned software module") - void testFilterBySoftwareModule() { - assertRSQLQuery(DistributionSetFields.MODULE.name() + "." + SoftwareModuleFields.NAME.name() + "==" + sm.getName(), 1); - assertRSQLQuery(DistributionSetFields.MODULE.name() + "." + SoftwareModuleFields.ID.name() + "==" + sm.getId(), 1); - assertRSQLQuery(DistributionSetFields.MODULE.name() + "." + SoftwareModuleFields.NAME.name() + "==noExist", 0); - assertRSQLQuery(DistributionSetFields.MODULE.name() + "." + SoftwareModuleFields.ID.name() + "=in=(" + sm.getId() + ", -1)", 1); - assertRSQLQuery(DistributionSetFields.MODULE.name() + "." + SoftwareModuleFields.ID.name() + "=out=(" + sm.getId() + ", -1)", 4); - } - - @Test - @Description("Test filter distribution set by description") - void testFilterByParameterDescription() { - assertRSQLQuery(DistributionSetFields.DESCRIPTION.name() + "==''", 1); - assertRSQLQuery(DistributionSetFields.DESCRIPTION.name() + "!=''", 4); - assertRSQLQuery(DistributionSetFields.DESCRIPTION.name() + "==DS", 1); - assertRSQLQuery(DistributionSetFields.DESCRIPTION.name() + "!=DS*", 3); - assertRSQLQuery(DistributionSetFields.DESCRIPTION.name() + "!=DS", 4); - assertRSQLQuery(DistributionSetFields.DESCRIPTION.name() + "==DS*", 2); - assertRSQLQuery(DistributionSetFields.DESCRIPTION.name() + "==DS%", 1); - assertRSQLQuery(DistributionSetFields.DESCRIPTION.name() + "==noExist*", 0); - assertRSQLQuery(DistributionSetFields.DESCRIPTION.name() + "=in=(DS,notexist)", 1); - assertRSQLQuery(DistributionSetFields.DESCRIPTION.name() + "=out=(DS,notexist)", 4); - } - - @Test - @Description("Test filter distribution set by version") - void testFilterByParameterVersion() { - assertRSQLQuery(DistributionSetFields.VERSION.name() + "==" + TestdataFactory.DEFAULT_VERSION, 1); - assertRSQLQuery(DistributionSetFields.VERSION.name() + "!=" + TestdataFactory.DEFAULT_VERSION, 4); - assertRSQLQuery(DistributionSetFields.VERSION.name() + "=in=(" + TestdataFactory.DEFAULT_VERSION + ",1.0.0,1.0.1)", 3); - assertRSQLQuery(DistributionSetFields.VERSION.name() + "=out=(" + TestdataFactory.DEFAULT_VERSION + ",error)", 4); - } - - @Test - @Description("Test filter distribution set by complete property") - void testFilterByAttributeComplete() { - assertRSQLQuery(DistributionSetFields.COMPLETE.name() + "==true", 3); - final String noExistStar = DistributionSetFields.COMPLETE.name() + "==noExist*"; - assertThatExceptionOfType(RSQLParameterSyntaxException.class) - .isThrownBy(() -> assertRSQLQuery(noExistStar, 0)); - assertRSQLQuery(DistributionSetFields.COMPLETE.name() + "=in=(true)", 3); - assertRSQLQuery(DistributionSetFields.COMPLETE.name() + "=out=(true)", 2); - } - - @Test - @Description("Test filter distribution set by valid property") - void testFilterByAttributeValid() { - assertRSQLQuery(DistributionSetFields.VALID.name() + "==true", 4); - assertRSQLQuery(DistributionSetFields.VALID.name() + "==false", 1); - final String rsqlNoExistStar = DistributionSetFields.VALID.name() + "==noExist*"; - assertThatExceptionOfType(RSQLParameterSyntaxException.class) - .isThrownBy(() -> assertRSQLQuery(rsqlNoExistStar, 0)); - assertRSQLQuery(DistributionSetFields.VALID.name() + "=in=(true)", 4); - assertRSQLQuery(DistributionSetFields.VALID.name() + "=out=(true)", 1); - } - - @Test - @Description("Test filter distribution set by tag name") - void testFilterByTag() { - assertRSQLQuery(DistributionSetFields.TAG.name() + "==Tag1", 2); - assertRSQLQuery(DistributionSetFields.TAG.name() + "!=Tag1", 3); - assertRSQLQuery(DistributionSetFields.TAG.name() + "==T*", 2); - assertRSQLQuery(DistributionSetFields.TAG.name() + "==noExist*", 0); - assertRSQLQuery(DistributionSetFields.TAG.name() + "=in=(Tag1,notexist)", 2); - assertRSQLQuery(DistributionSetFields.TAG.name() + "=out=(null)", 5); - } - - @Test - @Description("Test filter distribution set by type key") - void testFilterByType() { - assertRSQLQuery(DistributionSetFields.TYPE.name() + "==" + TestdataFactory.DS_TYPE_DEFAULT, 4); - assertRSQLQuery(DistributionSetFields.TYPE.name() + "==noExist*", 0); - assertRSQLQuery(DistributionSetFields.TYPE.name() + "=in=(" + TestdataFactory.DS_TYPE_DEFAULT + ",ecl)", 4); - assertRSQLQuery(DistributionSetFields.TYPE.name() + "=out=(" + TestdataFactory.DS_TYPE_DEFAULT + ")", 1); - } - - @Test - @Description("Test filter distribution set by metadata") - void testFilterByMetadata() { - createDistributionSetWithMetadata("key.dot", "value.dot"); - - assertRSQLQuery(DistributionSetFields.METADATA.name() + ".metaKey==metaValue", 1); - assertRSQLQuery(DistributionSetFields.METADATA.name() + ".metaKey==*v*", 2); - assertRSQLQuery(DistributionSetFields.METADATA.name() + ".metaKey==noExist*", 0); - assertRSQLQuery(DistributionSetFields.METADATA.name() + ".metaKey=in=(metaValue,notexist)", 1); - assertRSQLQuery(DistributionSetFields.METADATA.name() + ".metaKey=out=(metaValue,notexist)", 1); - assertRSQLQuery(DistributionSetFields.METADATA.name() + ".notExist==metaValue", 0); - assertRSQLQuery(DistributionSetFields.METADATA.name() + ".key.dot==value.dot", 1); - assertRSQLQuery(DistributionSetFields.METADATA.name() + ".key.dot*==value.dot", 0); - assertRSQLQuery(DistributionSetFields.METADATA.name() + ".key.*==value.dot", 0); - assertRSQLQuery(DistributionSetFields.METADATA.name() + ".key.==value.dot", 0); - assertRSQLQuery(DistributionSetFields.METADATA.name() + ".key*==value.dot", 0); - assertRSQLQuery(DistributionSetFields.METADATA.name() + ".*==value.dot", 0); - assertRSQLQuery(DistributionSetFields.METADATA.name() + "..==value.dot", 0); - assertRSQLQueryThrowsException( - DistributionSetFields.METADATA.name() + ".==value.dot", - RSQLParameterUnsupportedFieldException.class); - assertRSQLQueryThrowsException( - DistributionSetFields.METADATA.name() + "*==value.dot", - RSQLParameterUnsupportedFieldException.class); - assertRSQLQueryThrowsException( - DistributionSetFields.METADATA.name() + "==value.dot", - RSQLParameterUnsupportedFieldException.class); - - } - - private void assertRSQLQuery(final String rsqlParam, final long expectedEntity) { - final Page find = distributionSetManagement.findByRsql(rsqlParam, PageRequest.of(0, 100)); - final long countAll = find.getTotalElements(); - assertThat(find).as("Found entity is should not be null").isNotNull(); - assertThat(countAll).as("Found entity size is wrong").isEqualTo(expectedEntity); - } - - private void assertRSQLQueryThrowsException(final String rsqlParam, final Class expectedException) { - assertThatExceptionOfType(expectedException) - .isThrownBy(() -> RSQLUtility.validateRsqlFor( - rsqlParam, DistributionSetFields.class, JpaDistributionSet.class, virtualPropertyReplacer, entityManager)); - } - - private DistributionSet createDistributionSetWithMetadata(final String metadataKeyName, final String metadataValue) { - final DistributionSet distributionSet = testdataFactory.createDistributionSet(); - createDistributionSetMetadata(distributionSet.getId(), entityFactory.generateDsMetadata(metadataKeyName, metadataValue)); - return distributionSet; - } -} \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLDistributionSetMetadataFieldsTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLDistributionSetMetadataFieldsTest.java deleted file mode 100644 index a71e1856a..000000000 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLDistributionSetMetadataFieldsTest.java +++ /dev/null @@ -1,81 +0,0 @@ -/** - * Copyright (c) 2015 Bosch Software Innovations GmbH and others - * - * 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.rsql; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import io.qameta.allure.Description; -import io.qameta.allure.Feature; -import io.qameta.allure.Story; -import org.eclipse.hawkbit.repository.DistributionSetMetadataFields; -import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest; -import org.eclipse.hawkbit.repository.model.DistributionSet; -import org.eclipse.hawkbit.repository.model.DistributionSetMetadata; -import org.eclipse.hawkbit.repository.model.MetaData; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; - -@Feature("Component Tests - Repository") -@Story("RSQL filter distribution set metadata") -class RSQLDistributionSetMetadataFieldsTest extends AbstractJpaIntegrationTest { - - private Long distributionSetId; - - @BeforeEach - void setupBeforeTest() { - final DistributionSet distributionSet = testdataFactory.createDistributionSet("DS"); - distributionSetId = distributionSet.getId(); - - final List metadata = new ArrayList<>(5); - for (int i = 0; i < 5; i++) { - metadata.add(entityFactory.generateDsMetadata("" + i, "" + i)); - } - - distributionSetManagement.putMetaData(distributionSetId, metadata); - - distributionSetManagement.putMetaData(distributionSetId, - Arrays.asList(entityFactory.generateDsMetadata("emptyValueTest", null))); - } - - @Test - @Description("Test filter distribution set metadata by key") - void testFilterByParameterKey() { - assertRSQLQuery(DistributionSetMetadataFields.KEY.name() + "==1", 1); - assertRSQLQuery(DistributionSetMetadataFields.KEY.name() + "!=1", 5); - assertRSQLQuery(DistributionSetMetadataFields.KEY.name() + "=in=(1,2)", 2); - assertRSQLQuery(DistributionSetMetadataFields.KEY.name() + "=out=(1,2)", 4); - } - - @Test - @Description("Test filter distribution set metadata by value") - void testFilterByParameterValue() { - assertRSQLQuery(DistributionSetMetadataFields.VALUE.name() + "==''", 1); - assertRSQLQuery(DistributionSetMetadataFields.VALUE.name() + "!=''", 5); - assertRSQLQuery(DistributionSetMetadataFields.VALUE.name() + "==1", 1); - assertRSQLQuery(DistributionSetMetadataFields.VALUE.name() + "!=1", 5); - assertRSQLQuery(DistributionSetMetadataFields.VALUE.name() + "=in=(1,2)", 2); - assertRSQLQuery(DistributionSetMetadataFields.VALUE.name() + "=out=(1,2)", 4); - } - - private void assertRSQLQuery(final String rsqlParam, final long expectedEntities) { - - final Page findEnitity = distributionSetManagement - .findMetaDataByDistributionSetIdAndRsql(distributionSetId, rsqlParam, PageRequest.of(0, 100)); - final long countAllEntities = findEnitity.getTotalElements(); - assertThat(findEnitity).isNotNull(); - assertThat(countAllEntities).isEqualTo(expectedEntities); - } -} diff --git a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/AbstractIntegrationTest.java b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/AbstractIntegrationTest.java index b7f8906f7..bd579b2e0 100644 --- a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/AbstractIntegrationTest.java +++ b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/AbstractIntegrationTest.java @@ -21,6 +21,7 @@ import java.time.ZonedDateTime; import java.util.Collections; import java.util.Comparator; import java.util.List; +import java.util.Map; import java.util.NoSuchElementException; import lombok.extern.slf4j.Slf4j; @@ -56,7 +57,6 @@ import org.eclipse.hawkbit.repository.model.Action.ActionType; import org.eclipse.hawkbit.repository.model.DeploymentRequest; import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult; -import org.eclipse.hawkbit.repository.model.DistributionSetMetadata; import org.eclipse.hawkbit.repository.model.DistributionSetType; import org.eclipse.hawkbit.repository.model.MetaData; import org.eclipse.hawkbit.repository.model.RepositoryModelConstants; @@ -412,16 +412,7 @@ public abstract class AbstractIntegrationTest { } protected boolean isConfirmationFlowActive() { - return tenantConfigurationManagement.getConfigurationValue(TenantConfigurationKey.USER_CONFIRMATION_ENABLED, - Boolean.class).getValue(); - } - - protected DistributionSetMetadata createDistributionSetMetadata(final long dsId, final MetaData md) { - return createDistributionSetMetadata(dsId, Collections.singletonList(md)).get(0); - } - - protected List createDistributionSetMetadata(final long dsId, final List md) { - return distributionSetManagement.putMetaData(dsId, md); + return tenantConfigurationManagement.getConfigurationValue(TenantConfigurationKey.USER_CONFIRMATION_ENABLED, Boolean.class).getValue(); } protected Long getOsModule(final DistributionSet ds) {