From ceba4f5cfbef8c9e8f70c0192e37869031c61b62 Mon Sep 17 00:00:00 2001 From: Avgustin Marinov Date: Wed, 21 May 2025 11:26:02 +0300 Subject: [PATCH] Unify target attributes and metadata (#2408) * Unify target attributes and metadata Currently, the target attributes are Map while the metadata, which has the same concept is List. This PR unifies them making the metadata also a Map Signed-off-by: Avgustin Marinov --- .../hawkbit/repository/TargetFields.java | 9 +- .../repository/TargetMetadataFields.java | 33 --- .../hawkbit/mgmt/json/model/MgmtMetadata.java | 6 +- .../mgmt/rest/api/MgmtTargetRestApi.java | 131 ++++----- .../mgmt/rest/resource/MgmtTargetMapper.java | 37 +-- .../rest/resource/MgmtTargetResource.java | 53 ++-- .../rest/resource/MgmtTargetResourceTest.java | 146 +++------- .../hawkbit/repository/EntityFactory.java | 10 - .../hawkbit/repository/TargetManagement.java | 173 +++++------- .../exception/EntityNotFoundException.java | 16 +- .../hawkbit/repository/model/Target.java | 10 + .../repository/model/TargetMetadata.java | 26 -- .../repository/jpa/JpaEntityFactory.java | 6 - .../RepositoryApplicationConfiguration.java | 5 +- .../jpa/builder/JpaTargetCreate.java | 7 +- .../jpa/management/JpaTargetManagement.java | 258 +++++++----------- .../repository/jpa/model/JpaTarget.java | 27 +- .../jpa/model/JpaTargetMetadata.java | 100 ------- .../jpa/model/TargetMetadataCompositeKey.java | 39 --- .../repository/TargetMetadataRepository.java | 39 --- .../repository/jpa/utils/QuotaHelper.java | 72 +++-- .../jpa/AbstractJpaIntegrationTest.java | 2 +- ...DistributionSetManagementSecurityTest.java | 2 +- .../TargetManagementSecurityTest.java | 95 +++---- .../jpa/management/TargetManagementTest.java | 190 ++++++------- .../jpa/rsql/RSQLTargetFieldTest.java | 6 +- .../rsql/RSQLTargetMetadataFieldsTest.java | 81 ------ .../src/test/resources/jpa-test.properties | 8 +- .../test/util/AbstractIntegrationTest.java | 10 +- 29 files changed, 490 insertions(+), 1107 deletions(-) delete mode 100644 hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/TargetMetadataFields.java delete mode 100644 hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/TargetMetadata.java delete mode 100644 hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaTargetMetadata.java delete mode 100644 hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/TargetMetadataCompositeKey.java delete mode 100644 hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/TargetMetadataRepository.java delete mode 100644 hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLTargetMetadataFieldsTest.java diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/TargetFields.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/TargetFields.java index 471846ac5..e9cfa679f 100644 --- a/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/TargetFields.java +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/TargetFields.java @@ -9,7 +9,6 @@ */ package org.eclipse.hawkbit.repository; -import java.util.AbstractMap.SimpleImmutableEntry; import java.util.Collections; import java.util.List; import java.util.Map.Entry; @@ -41,7 +40,7 @@ public enum TargetFields implements RsqlQueryField { DistributionSetFields.NAME.getJpaEntityFieldName(), DistributionSetFields.VERSION.getJpaEntityFieldName()), TAG("tags", TagFields.NAME.getJpaEntityFieldName()), LASTCONTROLLERREQUESTAT("lastTargetQuery"), - METADATA("metadata", new SimpleImmutableEntry<>("key", "value")), + METADATA("metadata"), TARGETTYPE("targetType", TargetTypeFields.KEY.getJpaEntityFieldName(), TargetTypeFields.NAME.getJpaEntityFieldName()); private final String jpaEntityFieldName; @@ -56,10 +55,6 @@ public enum TargetFields implements RsqlQueryField { this(jpaEntityFieldName, List.of(subEntityAttributes), null); } - TargetFields(final String jpaEntityFieldName, final Entry subEntityMapTuple) { - this(jpaEntityFieldName, Collections.emptyList(), subEntityMapTuple); - } - TargetFields(final String jpaEntityFieldName, final List subEntityAttributes, final Entry subEntityMapTuple) { this.jpaEntityFieldName = jpaEntityFieldName; this.subEntityAttributes = subEntityAttributes; @@ -73,6 +68,6 @@ public enum TargetFields implements RsqlQueryField { @Override public boolean isMap() { - return this == ATTRIBUTE || getSubEntityMapTuple().isPresent(); + return this == ATTRIBUTE || this == METADATA; } } \ No newline at end of file diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/TargetMetadataFields.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/TargetMetadataFields.java deleted file mode 100644 index 8e25d43bd..000000000 --- a/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/TargetMetadataFields.java +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright (c) 2018 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 TargetMetadata. - */ -@Getter -public enum TargetMetadataFields implements RsqlQueryField { - - KEY("key"), - VALUE("value"); - - private final String jpaEntityFieldName; - - TargetMetadataFields(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/json/model/MgmtMetadata.java b/hawkbit-mgmt/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/MgmtMetadata.java index ff7205c26..c7bfb90c8 100644 --- a/hawkbit-mgmt/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/MgmtMetadata.java +++ b/hawkbit-mgmt/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/MgmtMetadata.java @@ -19,7 +19,7 @@ import lombok.ToString; import lombok.experimental.Accessors; /** - * The representation of an meta data in the REST API for POST/Create. + * The representation of a meta-data in the REST API for POST/Create. */ @Data @Accessors(chain = true) @@ -29,9 +29,9 @@ import lombok.experimental.Accessors; public class MgmtMetadata { @JsonProperty(required = true) - @Schema(description = "Metadata property key", example = "someKnownKey") + @Schema(description = "Metadata property key", example = "country") private String key; - @Schema(description = "Metadata property value", example = "someKnownKeyValue") + @Schema(description = "Metadata property value", example = "DE") private String value; } \ No newline at end of file diff --git a/hawkbit-mgmt/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtTargetRestApi.java b/hawkbit-mgmt/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtTargetRestApi.java index 967008aa0..131549f90 100644 --- a/hawkbit-mgmt/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtTargetRestApi.java +++ b/hawkbit-mgmt/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtTargetRestApi.java @@ -38,6 +38,7 @@ import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTargetAutoConfirmUpdate; import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTargetRequestBody; 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; @@ -47,6 +48,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; /** * API for handling target operations. @@ -727,15 +729,47 @@ public interface MgmtTargetRestApi { produces = { MediaTypes.HAL_JSON_VALUE, MediaType.APPLICATION_JSON_VALUE }) ResponseEntity> getTags(@PathVariable("targetId") String targetId); + /** + * Creates a list of metadata for a specific target. + * + * @param targetId the ID of the targetId to create metadata for + * @param metadataRest the list of metadata entries to create + */ + @Operation(summary = "Create a list of metadata for a specific target", description = "Create a list of metadata 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 = "Target 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.TARGET_V1_REQUEST_MAPPING + "/{targetId}/metadata", + consumes = { MediaType.APPLICATION_JSON_VALUE, MediaTypes.HAL_JSON_VALUE }) + @ResponseStatus(HttpStatus.CREATED) + void createMetadata(@PathVariable("targetId") String targetId, @RequestBody List metadataRest); + /** * Gets a paged list of metadata for a target. * * @param targetId the ID of the target for the metadata - * @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 metadata */ @Operation(summary = "Return metadata for specific target", description = "Get a paged list of metadata for a target. Required permission: READ_REPOSITORY") @@ -758,32 +792,9 @@ public interface MgmtTargetRestApi { "and the client has to wait another second.", content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))) }) - @GetMapping(value = MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/{targetId}/metadata", produces = { - MediaTypes.HAL_JSON_VALUE, MediaType.APPLICATION_JSON_VALUE }) - ResponseEntity> getMetadata( - @PathVariable("targetId") String targetId, - @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); + @GetMapping(value = MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/{targetId}/metadata", + produces = { MediaTypes.HAL_JSON_VALUE, MediaType.APPLICATION_JSON_VALUE }) + ResponseEntity> getMetadata(@PathVariable("targetId") String targetId); /** * Gets a single metadata value for a specific key of a target. @@ -824,7 +835,6 @@ public interface MgmtTargetRestApi { * @param targetId the ID of the target to update the metadata entry * @param metadataKey the key of the metadata to update the value * @param metadata update body - * @return status OK if the update request is successful and the updated metadata result */ @Operation(summary = "Updates a single metadata value of a target", description = "Update a single metadata value for speficic key. Required permission: UPDATE_REPOSITORY") @ApiResponses(value = { @@ -834,8 +844,7 @@ public interface MgmtTargetRestApi { @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.", + 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 = "Target 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.", @@ -852,9 +861,9 @@ public interface MgmtTargetRestApi { "and the client has to wait another second.", content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))) }) - @PutMapping(value = MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/{targetId}/metadata/{metadataKey}", - produces = { MediaTypes.HAL_JSON_VALUE, MediaType.APPLICATION_JSON_VALUE }) - ResponseEntity updateMetadata( + @PutMapping(value = MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/{targetId}/metadata/{metadataKey}") + @ResponseStatus(HttpStatus.OK) + void updateMetadata( @PathVariable("targetId") String targetId, @PathVariable("metadataKey") String metadataKey, @RequestBody MgmtMetadataBodyPut metadata); @@ -887,49 +896,8 @@ public interface MgmtTargetRestApi { content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))) }) @DeleteMapping(value = MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/{targetId}/metadata/{metadataKey}") - ResponseEntity deleteMetadata( - @PathVariable("targetId") String targetId, - @PathVariable("metadataKey") String metadataKey); - - /** - * Creates a list of metadata for a specific target. - * - * @param targetId the ID of the targetId to create metadata for - * @param metadataRest the list of metadata entries to create - * @return status created if post request is successful with the value of the created metadata - */ - @Operation(summary = "Create a list of metadata for a specific target", description = "Create a list of metadata 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 = "Target 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.TARGET_V1_REQUEST_MAPPING + "/{targetId}/metadata", - consumes = { MediaType.APPLICATION_JSON_VALUE, MediaTypes.HAL_JSON_VALUE }, - produces = { MediaTypes.HAL_JSON_VALUE, MediaType.APPLICATION_JSON_VALUE }) - ResponseEntity> createMetadata( - @PathVariable("targetId") String targetId, - @RequestBody List metadataRest); + @ResponseStatus(HttpStatus.OK) + void deleteMetadata(@PathVariable("targetId") String targetId, @PathVariable("metadataKey") String metadataKey); /** * Get the current auto-confirm state for a specific target. @@ -945,8 +913,7 @@ public interface MgmtTargetRestApi { @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.", + 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 = "Target 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.", diff --git a/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetMapper.java b/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetMapper.java index 78ee278d5..a031c279b 100644 --- a/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetMapper.java +++ b/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetMapper.java @@ -18,7 +18,9 @@ import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.List; +import java.util.Map; import java.util.function.Function; +import java.util.stream.Collectors; import lombok.AccessLevel; import lombok.NoArgsConstructor; @@ -46,11 +48,9 @@ import org.eclipse.hawkbit.repository.model.Action.ActionType; import org.eclipse.hawkbit.repository.model.ActionStatus; import org.eclipse.hawkbit.repository.model.AutoConfirmationStatus; import org.eclipse.hawkbit.repository.model.DistributionSet; -import org.eclipse.hawkbit.repository.model.MetaData; import org.eclipse.hawkbit.repository.model.PollStatus; import org.eclipse.hawkbit.repository.model.Rollout; import org.eclipse.hawkbit.repository.model.Target; -import org.eclipse.hawkbit.repository.model.TargetMetadata; import org.eclipse.hawkbit.rest.json.model.ResponseList; import org.eclipse.hawkbit.util.IpUtil; import org.eclipse.hawkbit.utils.TenantConfigHelper; @@ -79,10 +79,8 @@ public final class MgmtTargetMapper { MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT_VALUE, ActionFields.ID.getJpaEntityFieldName() + ":" + SortDirection.DESC, null)) .withRel(MgmtRestConstants.TARGET_V1_ACTIONS).expand()); - response.add(linkTo(methodOn(MgmtTargetRestApi.class).getMetadata(response.getControllerId(), - MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET_VALUE, - MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT_VALUE, null, null)).withRel("metadata") - .expand()); + response.add(linkTo(methodOn(MgmtTargetRestApi.class).getMetadata(response.getControllerId())) + .withRel("metadata").expand()); if (response.getTargetType() != null) { response.add(linkTo(methodOn(MgmtTargetTypeRestApi.class).getTargetType(response.getTargetType())) .withRel(MgmtRestConstants.TARGET_V1_ASSIGNED_TARGET_TYPE).expand()); @@ -196,19 +194,14 @@ public final class MgmtTargetMapper { .toList(); } - static List fromRequestTargetMetadata(final List metadata, - final EntityFactory entityFactory) { - if (metadata == null) { - return Collections.emptyList(); - } - - return metadata.stream().map( - metadataRest -> entityFactory.generateTargetMetadata(metadataRest.getKey(), metadataRest.getValue())) - .toList(); + static Map fromRequestMetadata(final List metadata) { + return metadata == null + ? Collections.emptyMap() + : metadata.stream().collect(Collectors.toMap(MgmtMetadata::getKey, MgmtMetadata::getValue)); } - static List toActionStatusRestResponse(final Collection actionStatus, - final DeploymentManagement deploymentManagement) { + static List toActionStatusRestResponse( + final Collection actionStatus, final DeploymentManagement deploymentManagement) { if (actionStatus == null) { return Collections.emptyList(); } @@ -308,15 +301,15 @@ public final class MgmtTargetMapper { return actions.stream().map(action -> toResponse(targetId, action)).toList(); } - static MgmtMetadata toResponseTargetMetadata(final TargetMetadata metadata) { + static MgmtMetadata toResponseMetadata(final String key, final 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 toResponseTargetMetadata(final List metadata) { - return metadata.stream().map(MgmtTargetMapper::toResponseTargetMetadata).toList(); + static List toResponseMetadata(final Map metadata) { + return metadata.entrySet().stream().map(e -> toResponseMetadata(e.getKey(), e.getValue())).toList(); } private static void addPollStatus(final Target target, final MgmtTarget targetRest, final Function pollStatusResolver) { diff --git a/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResource.java b/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResource.java index 7456ba707..a8a4e9047 100644 --- a/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResource.java +++ b/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResource.java @@ -57,7 +57,6 @@ import org.eclipse.hawkbit.repository.model.ActionStatus; import org.eclipse.hawkbit.repository.model.DeploymentRequest; import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult; import org.eclipse.hawkbit.repository.model.Target; -import org.eclipse.hawkbit.repository.model.TargetMetadata; import org.eclipse.hawkbit.repository.model.TargetTag; import org.eclipse.hawkbit.security.SystemSecurityContext; import org.eclipse.hawkbit.utils.TenantConfigHelper; @@ -421,57 +420,39 @@ public class MgmtTargetResource implements MgmtTargetRestApi { @Override public ResponseEntity> getTags(final String targetId) { - final Set tags = targetManagement.getTagsByControllerId(targetId); + final Set tags = targetManagement.getTags(targetId); return ResponseEntity.ok(MgmtTagMapper.toResponse(tags == null ? Collections.emptyList() : tags.stream().toList())); } @Override - public ResponseEntity> getMetadata( - final String targetId, - 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 String targetId, final List metadataRest) { + targetManagement.createMetadata(targetId, MgmtTargetMapper.fromRequestMetadata(metadataRest)); + } - final Pageable pageable = new OffsetBasedPageRequest(sanitizedOffsetParam, sanitizedLimitParam, sorting); - final Page metaDataPage; - - if (rsqlParam != null) { - metaDataPage = targetManagement.findMetaDataByControllerIdAndRsql(pageable, targetId, rsqlParam); - } else { - metaDataPage = targetManagement.findMetaDataByControllerId(pageable, targetId); - } - - return ResponseEntity.ok(new PagedList<>(MgmtTargetMapper.toResponseTargetMetadata( - metaDataPage.getContent()), metaDataPage.getTotalElements())); + @Override + public ResponseEntity> getMetadata(final String targetId) { + final Map metadata = targetManagement.getMetadata(targetId); + return ResponseEntity.ok(new PagedList<>(MgmtTargetMapper.toResponseMetadata(metadata), metadata.size())); } @Override public ResponseEntity getMetadataValue(final String targetId, final String metadataKey) { - final TargetMetadata findOne = targetManagement.getMetaDataByControllerId(targetId, metadataKey) - .orElseThrow(() -> new EntityNotFoundException(TargetMetadata.class, targetId, metadataKey)); - return ResponseEntity.ok(MgmtTargetMapper.toResponseTargetMetadata(findOne)); + final String metadataValue = targetManagement.getMetadata(targetId).get(metadataKey); + if (metadataValue == null) { + throw new EntityNotFoundException("Target metadata", targetId + ":" + metadataKey); + } + return ResponseEntity.ok(MgmtTargetMapper.toResponseMetadata(metadataKey, metadataValue)); } @Override - public ResponseEntity updateMetadata(final String targetId, final String metadataKey, final MgmtMetadataBodyPut metadata) { - final TargetMetadata updated = targetManagement.updateMetadata(targetId, - entityFactory.generateTargetMetadata(metadataKey, metadata.getValue())); - return ResponseEntity.ok(MgmtTargetMapper.toResponseTargetMetadata(updated)); + public void updateMetadata(final String targetId, final String metadataKey, final MgmtMetadataBodyPut metadata) { + targetManagement.updateMetadata(targetId, metadataKey, metadata.getValue()); } @Override @AuditLog(entity = "Target", type = AuditLog.Type.DELETE, description = "Delete Target Metadata") - public ResponseEntity deleteMetadata(final String targetId, final String metadataKey) { - targetManagement.deleteMetaData(targetId, metadataKey); - return ResponseEntity.ok().build(); - } - - @Override - public ResponseEntity> createMetadata(final String targetId, final List metadataRest) { - final List created = targetManagement.createMetaData(targetId, - MgmtTargetMapper.fromRequestTargetMetadata(metadataRest, entityFactory)); - return new ResponseEntity<>(MgmtTargetMapper.toResponseTargetMetadata(created), HttpStatus.CREATED); + public void deleteMetadata(final String targetId, final String metadataKey) { + targetManagement.deleteMetadata(targetId, metadataKey); } @Override diff --git a/hawkbit-mgmt/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResourceTest.java b/hawkbit-mgmt/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResourceTest.java index 4f463044d..ac58088e6 100644 --- a/hawkbit-mgmt/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResourceTest.java +++ b/hawkbit-mgmt/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResourceTest.java @@ -37,7 +37,6 @@ import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; -import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Optional; @@ -74,12 +73,10 @@ import org.eclipse.hawkbit.repository.model.Action.ActionType; import org.eclipse.hawkbit.repository.model.Action.Status; import org.eclipse.hawkbit.repository.model.ActionStatus; import org.eclipse.hawkbit.repository.model.DistributionSet; -import org.eclipse.hawkbit.repository.model.MetaData; import org.eclipse.hawkbit.repository.model.NamedEntity; import org.eclipse.hawkbit.repository.model.Rollout; import org.eclipse.hawkbit.repository.model.SoftwareModule; import org.eclipse.hawkbit.repository.model.Target; -import org.eclipse.hawkbit.repository.model.TargetMetadata; import org.eclipse.hawkbit.repository.model.TargetTag; import org.eclipse.hawkbit.repository.model.TargetType; import org.eclipse.hawkbit.repository.model.TargetUpdateStatus; @@ -114,8 +111,6 @@ import org.springframework.test.web.servlet.ResultActions; @Story("Target Resource") class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest { - @Autowired - ActionRepository actionRepository; private static final String TARGET_DESCRIPTION_TEST = "created in test"; private static final String JSON_PATH_ROOT = "$"; // fields, attributes @@ -139,23 +134,26 @@ class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest { private static final String JSON_PATH_DESCRIPTION = JSON_PATH_ROOT + JSON_PATH_FIELD_DESCRIPTION; private static final String JSON_PATH_LAST_REQUEST_AT = JSON_PATH_ROOT + JSON_PATH_FIELD_LAST_REQUEST_AT; private static final String JSON_PATH_TYPE = JSON_PATH_ROOT + JSON_PATH_FIELD_TARGET_TYPE; + @Autowired - private ObjectMapper objectMapper; + ActionRepository actionRepository; @Autowired private JpaProperties jpaProperties; + @Autowired + private ObjectMapper objectMapper; @Test @Description("Ensures that when targetType value of -1 is provided the target type is unassigned from the target.") - public void updateTargetAndUnnasignTargetType() throws Exception { + void updateTargetAndUnassignTargetType() throws Exception { final String knownControllerId = "123"; final String knownNewAddress = "amqp://test123/foobar"; final String knownNameNotModify = "controllerName"; - final Long unnasignTargetTypeValue = -1L; + final Long unassignTargetTypeValue = -1L; final TargetType targetType = targetTypeManagement.create( entityFactory.targetType().create().name("targettype1").description("targettypedes1")); - final String body = new JSONObject().put("targetType", unnasignTargetTypeValue).toString(); + final String body = new JSONObject().put("targetType", unassignTargetTypeValue).toString(); // create a target with the created TargetType targetManagement.create(entityFactory.target().create().controllerId(knownControllerId).name(knownNameNotModify) @@ -183,18 +181,18 @@ class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest { @Test @Description("Ensures that when targetType value of -1 is provided the target type is unassigned from the target when updating multiple fields in target object.") - public void updateTargetNameAndUnnasignTargetType() throws Exception { + void updateTargetNameAndUnassignTargetType() throws Exception { final String knownControllerId = "123"; final String knownNewAddress = "amqp://test123/foobar"; final String knownNameNotModify = "controllerName"; - final Long unnasignTargetTypeValue = -1L; + final Long unassignTargetTypeValue = -1L; final String controllerNewName = "controllerNewName"; final TargetType targetType = targetTypeManagement.create( entityFactory.targetType().create().name("targettype1").description("targettypedes1")); final String body = new JSONObject() - .put("targetType", unnasignTargetTypeValue).put("name", "controllerNewName") + .put("targetType", unassignTargetTypeValue).put("name", "controllerNewName") .toString(); // create a target with the created TargetType @@ -223,7 +221,7 @@ class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest { @Test @Description("Handles the GET request of retrieving all targets within SP..") - public void getTargets() throws Exception { + void getTargets() throws Exception { enableConfirmationFlow(); mvc.perform(get(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING)) .andExpect(status().isOk()) @@ -232,33 +230,15 @@ class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest { @Test @Description("Handles the GET request of retrieving all targets within SP based by parameter.") - public void getTargetsWithParameters() throws Exception { + void getTargetsWithParameters() throws Exception { mvc.perform(get(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "?limit=10&sort=name:ASC&offset=0&q=name==a")) .andExpect(status().isOk()) .andDo(MockMvcResultPrinter.print()); } - @Test - @Description("Get a paged list of meta data for a target with standard page size.") - public void getMetadata() throws Exception { - final int totalMetadata = 4; - final String knownKeyPrefix = "knownKey"; - final String knownValuePrefix = "knownValue"; - final Target testTarget = testdataFactory.createTarget("targetId"); - for (int index = 0; index < totalMetadata; index++) { - targetManagement.createMetaData(testTarget.getControllerId(), List.of( - entityFactory.generateTargetMetadata(knownKeyPrefix + index, knownValuePrefix + index))); - } - - mvc.perform(get(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/{targetId}/metadata", testTarget.getControllerId())) - .andDo(MockMvcResultPrinter.print()) - .andExpect(status().isOk()) - .andExpect(content().contentType(MediaTypes.HAL_JSON)); - } - @Test @Description("Handles the POST request to activate auto-confirm on a target. Payload can be provided to specify more details about the operation.") - public void postActivateAutoConfirm() throws Exception { + void postActivateAutoConfirm() throws Exception { final Target testTarget = testdataFactory.createTarget("targetId"); final MgmtTargetAutoConfirmUpdate body = new MgmtTargetAutoConfirmUpdate("custom_initiator_value", @@ -273,7 +253,7 @@ class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest { @Test @Description("Handles the POST request to deactivate auto-confirm on a target.") - public void postDeactivateAutoConfirm() throws Exception { + void postDeactivateAutoConfirm() throws Exception { final Target testTarget = testdataFactory.createTarget("targetId"); confirmationManagement.activateAutoConfirmation(testTarget.getControllerId(), null, null); @@ -285,7 +265,7 @@ class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest { @Test @Description("Test confirmation of single Action with confirm status. Check that Action goes into Running status with appropriate messages and status code") - public void updateActionConfirmationWithConfirm() throws Exception { + void updateActionConfirmationWithConfirm() throws Exception { final int expectedStatusCode = 210; final String expectedStatusMessage1 = "some-custom-message1"; final String expectedStatusMessage2 = "some-custom-message2"; @@ -296,7 +276,7 @@ class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest { @Test @Description("Test confirmation of single Action with deny status. Check that Action stays in WAIT_FOR_CONFIRMATION status with appropriate messages and status code") - public void updateActionConfirmationWithDeny() throws Exception { + void updateActionConfirmationWithDeny() throws Exception { final int expectedStatusCode = 410; final String expectedStatusMessage1 = "some-error-custom-message1"; final String expectedStatusMessage2 = "some-error-custom-message2"; @@ -307,7 +287,7 @@ class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest { @Test @Description("Test confirmation of single Action with wrong ControllerId - e.g. the given Action is not assigned to the given Target - confirmation call must fail.") - public void updateActionConfirmationFailsIfActionNotAssignedToTarget() throws Exception { + void updateActionConfirmationFailsIfActionNotAssignedToTarget() throws Exception { final int payloadCallCode = 200; final String payloadCallMessage1 = "random1"; final String payloadCallMessage2 = "random2"; @@ -2005,17 +1985,10 @@ class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest { .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(content().string("")); - final TargetMetadata metaKey1 = targetManagement.getMetaDataByControllerId(knownControllerId, knownKey1).get(); - final TargetMetadata metaKey2 = targetManagement.getMetaDataByControllerId(knownControllerId, knownKey2).get(); - - assertThat(metaKey1.getValue()).isEqualTo(knownValue1); - assertThat(metaKey2.getValue()).isEqualTo(knownValue2); + assertThat(targetManagement.getMetadata(knownControllerId).get(knownKey1)).isEqualTo(knownValue1); + assertThat(targetManagement.getMetadata(knownControllerId).get(knownKey2)).isEqualTo(knownValue2); // verify quota enforcement final int maxMetaData = quotaManagement.getMaxMetaDataEntriesPerTarget(); @@ -2025,16 +1998,14 @@ class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest { metaData2.put(new JSONObject().put("key", knownKey1 + i).put("value", knownValue1 + i)); } - mvc.perform(post("/rest/v1/targets/{targetId}/metadata", knownControllerId).accept(MediaType.APPLICATION_JSON) + mvc.perform(post("/rest/v1/targets/{targetId}/metadata", knownControllerId) + .accept(MediaType.APPLICATION_JSON) .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(targetManagement.findMetaDataByControllerId(PageRequest.of(0, Integer.MAX_VALUE), knownControllerId) - .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(targetManagement.getMetadata(knownControllerId)).hasSize(metaData1.length()); } @Test @@ -2056,14 +2027,9 @@ class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest { .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 TargetMetadata updatedTargetMetadata = targetManagement - .getMetaDataByControllerId(knownControllerId, knownKey).get(); - assertThat(updatedTargetMetadata.getValue()).isEqualTo(updateValue); + .andExpect(content().string("")); + assertThat(targetManagement.getMetadata(knownControllerId).get(knownKey)).isEqualTo(updateValue); } @Test @@ -2081,7 +2047,12 @@ class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest { .andDo(MockMvcResultPrinter.print()) .andExpect(status().isOk()); - assertThat(targetManagement.getMetaDataByControllerId(knownControllerId, knownKey)).isNotPresent(); + // already deleted + mvc.perform(delete("/rest/v1/targets/{targetId}/metadata/{key}", knownControllerId, knownKey)) + .andDo(MockMvcResultPrinter.print()) + .andExpect(status().isNotFound()); + + assertThat(targetManagement.getMetadata(knownControllerId).get(knownKey)).isNull(); } @Test @@ -2103,7 +2074,7 @@ class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest { .andDo(MockMvcResultPrinter.print()) .andExpect(status().isNotFound()); - assertThat(targetManagement.getMetaDataByControllerId(knownControllerId, knownKey)).isPresent(); + assertThat(targetManagement.getMetadata(knownControllerId).get(knownKey)).isNotNull(); } @Test @@ -2126,53 +2097,27 @@ class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest { @Test @Description("Ensures that a metadata entry paged list selection through API reflectes the repository content.") - void getPagedListOfMetadata() throws Exception { + void getMetadata() throws Exception { final String knownControllerId = "targetIdWithMetadata"; final int totalMetadata = 10; - final int limitParam = 5; - final String offsetParam = "0"; final String knownKeyPrefix = "knownKey"; final String knownValuePrefix = "knownValue"; setupTargetWithMetadata(knownControllerId, knownKeyPrefix, knownValuePrefix, totalMetadata); - mvc.perform(get("/rest/v1/targets/{targetId}/metadata?offset=" + offsetParam + "&limit=" + limitParam, - knownControllerId)) + mvc.perform(get("/rest/v1/targets/{targetId}/metadata", knownControllerId)) .andDo(MockMvcResultPrinter.print()) .andExpect(status().isOk()) - .andExpect(jsonPath("size", equalTo(limitParam))) + .andExpect(jsonPath("size", equalTo(totalMetadata))) .andExpect(jsonPath("total", equalTo(totalMetadata))) .andExpect(jsonPath("content[0].key", equalTo("knownKey0"))) .andExpect(jsonPath("content[0].value", equalTo("knownValue0"))); - - } - - @Test - @Description("Ensures that a target metadata filtered query with value==knownValue1 parameter returns only the metadata entries with that value.") - void searchDistributionSetMetadataRsql() throws Exception { - final String knownControllerId = "targetIdWithMetadata"; - - final int totalMetadata = 10; - final String knownKeyPrefix = "knownKey"; - final String knownValuePrefix = "knownValue"; - - setupTargetWithMetadata(knownControllerId, knownKeyPrefix, knownValuePrefix, totalMetadata); - - final String rsqlSearchValue1 = "value==knownValue1"; - - mvc.perform(get("/rest/v1/targets/{targetId}/metadata?q=" + rsqlSearchValue1, knownControllerId)) - .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("A request for assigning multiple DS to a target results in a Bad Request when multiassignment in disabled.") - void multiassignmentRequestNotAllowedIfDisabled() throws Exception { + void multiAssignmentRequestNotAllowedIfDisabled() throws Exception { final String targetId = testdataFactory.createTarget().getControllerId(); final List dsIds = testdataFactory.createDistributionSets(2).stream().map(DistributionSet::getId) .toList(); @@ -2953,23 +2898,20 @@ class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest { return targetManagement.getByControllerID(tA.getControllerId()).get(); } - private void setupTargetWithMetadata(final String knownControllerId, final String knownKey, - final String knownValue) { + private void setupTargetWithMetadata(final String knownControllerId, final String knownKey, final String knownValue) { testdataFactory.createTarget(knownControllerId); - targetManagement.createMetaData(knownControllerId, - Collections.singletonList(entityFactory.generateTargetMetadata(knownKey, knownValue))); + targetManagement.createMetadata(knownControllerId, Map.of(knownKey, knownValue)); } - private void setupTargetWithMetadata(final String knownControllerId, final String knownKeyPrefix, - final String knownValuePrefix, final int totalMetadata) { + private void setupTargetWithMetadata( + final String knownControllerId, final String knownKeyPrefix, final String knownValuePrefix, final int totalMetadata) { testdataFactory.createTarget(knownControllerId); - final List targetMetadataEntries = new LinkedList<>(); + final Map metadataEntries = new HashMap<>(); for (int index = 0; index < totalMetadata; index++) { - targetMetadataEntries - .add(entityFactory.generateTargetMetadata(knownKeyPrefix + index, knownValuePrefix + index)); + metadataEntries.put(knownKeyPrefix + index, knownValuePrefix + index); } - targetManagement.createMetaData(knownControllerId, targetMetadataEntries); + targetManagement.createMetadata(knownControllerId, metadataEntries); } private Action updateActionStatus(final Action action, final Status status, final Integer statusCode) { 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 1d94cb137..8f12cca14 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 @@ -54,16 +54,6 @@ public interface EntityFactory { MetaData generateDsMetadata(@Size(min = 1, max = MetaData.KEY_MAX_SIZE) @NotNull String key, @Size(max = MetaData.VALUE_MAX_SIZE) String value); - /** - * Generates an {@link MetaData} element for target without persisting it. - * - * @param key {@link MetaData#getKey()} - * @param value {@link MetaData#getValue()} - * @return {@link MetaData} object - */ - MetaData generateTargetMetadata(@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/TargetManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetManagement.java index 6d3bbb39d..2284fb91c 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetManagement.java @@ -35,7 +35,6 @@ import org.eclipse.hawkbit.repository.model.MetaData; import org.eclipse.hawkbit.repository.model.RolloutGroup; import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.TargetFilterQuery; -import org.eclipse.hawkbit.repository.model.TargetMetadata; import org.eclipse.hawkbit.repository.model.TargetTag; import org.eclipse.hawkbit.repository.model.TargetType; import org.eclipse.hawkbit.repository.model.TargetTypeAssignmentResult; @@ -434,8 +433,7 @@ public interface TargetManagement { * @throws EntityNotFoundException if distribution set with given ID does not exist */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY_AND_READ_TARGET) - Page findByInstalledDistributionSetAndRsql(@NotNull Pageable pageReq, long distributionSetId, - @NotNull String rsqlParam); + Page findByInstalledDistributionSetAndRsql(@NotNull Pageable pageReq, long distributionSetId, @NotNull String rsqlParam); /** * Retrieves the {@link Target} which have a certain {@link TargetUpdateStatus}. @@ -509,6 +507,19 @@ public interface TargetManagement { @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) Page findByRsqlAndTag(@NotNull Pageable pageable, @NotNull String rsqlParam, long tagId); + /** + * Verify if a target matches a specific target filter query, does not have a + * specific DS already assigned and is compatible with it. + * + * @param controllerId of the {@link org.eclipse.hawkbit.repository.model.Target} to check + * @param distributionSetId of the {@link org.eclipse.hawkbit.repository.model.DistributionSet} to consider + * @param targetFilterQuery to execute + * @return true if it matches + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY_AND_READ_TARGET) + boolean isTargetMatchingQueryAndDSNotAssignedAndCompatibleAndUpdatable( + @NotNull String controllerId, long distributionSetId, @NotNull String targetFilterQuery); + /** * Initiates {@link TargetType} assignment to given {@link Target}s. If some * targets in the list have the {@link TargetType} not yet assigned, they will @@ -525,12 +536,10 @@ public interface TargetManagement { TargetTypeAssignmentResult assignType(@NotEmpty Collection controllerIds, @NotNull Long typeId); /** - * Initiates {@link TargetType} un-assignment to given {@link Target}s. The type - * of the targets will be set to {@code null} + * Initiates {@link TargetType} un-assignment to given {@link Target}s. The type of the targets will be set to {@code null} * * @param controllerIds to remove the type from - * @return {@link TargetTypeAssignmentResult} with all metadata of the - * assignment outcome. + * @return {@link TargetTypeAssignmentResult} with all metadata of the assignment outcome. */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET) TargetTypeAssignmentResult unassignType(@NotEmpty Collection controllerIds); @@ -631,6 +640,25 @@ public interface TargetManagement { @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) List get(@NotNull Collection ids); + /** + * Verifies that {@link Target} with given controller ID exists in the repository. + * + * @param controllerId of target + * @return {@code true} if target with given ID exists + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) + boolean existsByControllerId(@NotEmpty String controllerId); + + /** + * Finds a single target tags its id. + * + * @param controllerId of the {@link Target} + * @return the found Tag set + * @throws EntityNotFoundException if target with given ID does not exist + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) + Set getTags(@NotEmpty String controllerId); + /** * Get controller attributes of given {@link Target}. * @@ -671,55 +699,37 @@ public interface TargetManagement { Page findByControllerAttributesRequested(@NotNull Pageable pageReq); /** - * Verifies that {@link Target} with given controller ID exists in the - * repository. - * - * @param controllerId of target - * @return {@code true} if target with given ID exists - */ - @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) - boolean existsByControllerId(@NotEmpty String controllerId); - - /** - * Verify if a target matches a specific target filter query, does not have a - * specific DS already assigned and is compatible with it. - * - * @param controllerId of the {@link org.eclipse.hawkbit.repository.model.Target} to - * check - * @param distributionSetId of the - * {@link org.eclipse.hawkbit.repository.model.DistributionSet} to - * consider - * @param targetFilterQuery to execute - * @return true if it matches - */ - @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY_AND_READ_TARGET) - boolean isTargetMatchingQueryAndDSNotAssignedAndCompatibleAndUpdatable(@NotNull String controllerId, - long distributionSetId, @NotNull String targetFilterQuery); - - /** - * Finds a single target tags its id. - * - * @param controllerId of the {@link Target} - * @return the found Tag set - * @throws EntityNotFoundException if target with given ID does not exist - */ - @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) - Set getTagsByControllerId(@NotEmpty String controllerId); - - /** - * Creates a list of target meta data entries. + * Creates a list of target meta-data entries. * * @param controllerId {@link Target} controller id the metadata has to be created for - * @param metadata the meta data entries to create or update - * @return the updated or created target metadata entries + * @param metadata the meta-data entries to create or update * @throws EntityNotFoundException if given target does not exist - * @throws EntityAlreadyExistsException in case one of the metadata entry already exists for the specific - * key - * @throws AssignmentQuotaExceededException if the maximum number of {@link MetaData} entries is exceeded for - * the addressed {@link Target} + * @throws EntityAlreadyExistsException in case one of the metadata entry already exists for the specific key + * @throws AssignmentQuotaExceededException if the maximum number of {@link MetaData} entries is exceeded for the addressed {@link Target} */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY) - List createMetaData(@NotEmpty String controllerId, @NotEmpty Collection metadata); + void createMetadata(@NotEmpty String controllerId, @NotEmpty Map metadata); + + /** + * Finds a single target meta-data by its id. + * + * @param controllerId of the {@link Target} + * @return the found target metadata + * @throws EntityNotFoundException if target with given ID does not exist + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY) + Map getMetadata(@NotEmpty String controllerId); + + /** + * Updates a target meta-data value if corresponding entry exists. + * + * @param controllerId {@link Target} controller id of the metadata entry to be updated + * @param key meta data-entry key to be updated + * @param value meta data-entry to be new value + * @throws EntityNotFoundException in case the metadata entry does not exist and cannot be updated + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY) + void updateMetadata(@NotEmpty String controllerId, @NotNull String key, @NotNull String value); /** * Deletes a target meta data entry. @@ -729,64 +739,5 @@ public interface TargetManagement { * @throws EntityNotFoundException if given target does not exist */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY) - void deleteMetaData(@NotEmpty String controllerId, @NotEmpty String key); - - /** - * Finds all meta data by the given target id. - * - * @param pageable the page request to page the result - * @param controllerId the controller id to retrieve the metadata from - * @return a paged result of all meta data entries for a given target id - * @throws EntityNotFoundException if target with given ID does not exist - */ - @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY) - Page findMetaDataByControllerId(@NotNull Pageable pageable, @NotEmpty String controllerId); - - /** - * Counts all meta data by the given target id. - * - * @param controllerId the controller id to retrieve the meta data from - * @return count of all meta data entries for a given target id - * @throws EntityNotFoundException if target with given ID does not exist - */ - @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY) - long countMetaDataByControllerId(@NotEmpty String controllerId); - - /** - * Finds all metadata by the given target id and query. - * - * @param pageable the page request to page the result - * @param controllerId the controller id to retrieve the metadata from - * @param rsqlParam rsql query string - * @return a paged result of all meta data entries for a given target 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 if target with given ID does not exist - */ - @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY) - Page findMetaDataByControllerIdAndRsql(@NotNull Pageable pageable, @NotEmpty String controllerId, - @NotNull String rsqlParam); - - /** - * Finds a single target meta data by its id. - * - * @param controllerId of the {@link Target} - * @param key of the meta data element - * @return the found TargetMetadata - * @throws EntityNotFoundException if target with given ID does not exist - */ - @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY) - Optional getMetaDataByControllerId(@NotEmpty String controllerId, @NotEmpty String key); - - /** - * Updates a target meta data value if corresponding entry exists. - * - * @param controllerId {@link Target} controller id of the metadata entry to be updated - * @param metadata meta data entry to be updated - * @return the updated meta data entry - * @throws EntityNotFoundException in case the metadata entry does not exist and cannot be updated - */ - @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY) - TargetMetadata updateMetadata(@NotEmpty String controllerId, @NotNull MetaData metadata); + void deleteMetadata(@NotEmpty String controllerId, @NotEmpty String key); } \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/exception/EntityNotFoundException.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/exception/EntityNotFoundException.java index 5c226562c..1e738767c 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/exception/EntityNotFoundException.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/exception/EntityNotFoundException.java @@ -75,10 +75,20 @@ public class EntityNotFoundException extends AbstractServerRtException { * @param type of the entity that was not found * @param entityId of the {@link BaseEntity} */ - public EntityNotFoundException(final Class type, final Object entityId) { - super(type.getSimpleName() + " with given identifier {" + entityId + "} does not exist.", + public EntityNotFoundException(final String type, final Object entityId) { + super(type + " with given identifier {" + entityId + "} does not exist.", THIS_ERROR, - Map.of(TYPE, type.getSimpleName(), ENTITY_ID, entityId)); + Map.of(TYPE, type, ENTITY_ID, entityId)); + } + + /** + * Parameterized constructor for {@link BaseEntity} not found. + * + * @param type of the entity that was not found + * @param entityId of the {@link BaseEntity} + */ + public EntityNotFoundException(final Class type, final Object entityId) { + this(type.getSimpleName(), entityId); } /** diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/Target.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/Target.java index 6c189990a..23638bd50 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/Target.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/Target.java @@ -47,6 +47,16 @@ public interface Target extends NamedEntity { */ int CONTROLLER_ATTRIBUTE_VALUE_SIZE = 128; + /** + * Maximum length of metadata key. + */ + int CONTROLLER_METADATA_KEY_SIZE = 128; + + /** + * Maximum length of metadata value. + */ + int CONTROLLER_METADATA_VALUE_SIZE = 4000; + /** * @return business identifier of the {@link Target} */ diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/TargetMetadata.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/TargetMetadata.java deleted file mode 100644 index d18ecf545..000000000 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/TargetMetadata.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2018 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 Target}. - */ -public interface TargetMetadata extends MetaData { - - /** - * @return {@link Target} of this {@link MetaData} entry. - */ - Target getTarget(); - - @Override - default Long getEntityId() { - return getTarget().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 f03fd41e1..3c7628e38 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 @@ -27,7 +27,6 @@ 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.jpa.model.JpaTargetMetadata; import org.eclipse.hawkbit.repository.model.MetaData; import org.springframework.validation.annotation.Validated; @@ -77,11 +76,6 @@ public class JpaEntityFactory implements EntityFactory { return new JpaDistributionSetMetadata(key, value == null ? null : value.strip()); } - @Override - public MetaData generateTargetMetadata(final String key, final String value) { - return new JpaTargetMetadata(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 305ed64f2..d9c95f98e 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 @@ -132,7 +132,6 @@ import org.eclipse.hawkbit.repository.jpa.repository.SoftwareModuleMetadataRepos import org.eclipse.hawkbit.repository.jpa.repository.SoftwareModuleRepository; import org.eclipse.hawkbit.repository.jpa.repository.SoftwareModuleTypeRepository; import org.eclipse.hawkbit.repository.jpa.repository.TargetFilterQueryRepository; -import org.eclipse.hawkbit.repository.jpa.repository.TargetMetadataRepository; import org.eclipse.hawkbit.repository.jpa.repository.TargetRepository; import org.eclipse.hawkbit.repository.jpa.repository.TargetTagRepository; import org.eclipse.hawkbit.repository.jpa.repository.TargetTypeRepository; @@ -622,7 +621,7 @@ public class RepositoryApplicationConfiguration { @Bean @ConditionalOnMissingBean TargetManagement targetManagement(final EntityManager entityManager, final QuotaManagement quotaManagement, - final TargetRepository targetRepository, final TargetMetadataRepository targetMetadataRepository, + final TargetRepository targetRepository, final RolloutGroupRepository rolloutGroupRepository, final TargetFilterQueryRepository targetFilterQueryRepository, final TargetTypeRepository targetTypeRepository, final TargetTagRepository targetTagRepository, @@ -630,7 +629,7 @@ public class RepositoryApplicationConfiguration { final VirtualPropertyReplacer virtualPropertyReplacer, final JpaProperties properties, final DistributionSetManagement distributionSetManagement) { return new JpaTargetManagement(entityManager, distributionSetManagement, quotaManagement, targetRepository, - targetTypeRepository, targetMetadataRepository, rolloutGroupRepository, targetFilterQueryRepository, + targetTypeRepository, rolloutGroupRepository, targetFilterQueryRepository, targetTagRepository, eventPublisherHolder, tenantAware, virtualPropertyReplacer, properties.getDatabase()); } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/builder/JpaTargetCreate.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/builder/JpaTargetCreate.java index 4e76151b7..8d6cccbd6 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/builder/JpaTargetCreate.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/builder/JpaTargetCreate.java @@ -37,12 +37,7 @@ public class JpaTargetCreate extends AbstractTargetUpdateCreate im @Override public JpaTarget build() { - final JpaTarget target; - if (ObjectUtils.isEmpty(securityToken)) { - target = new JpaTarget(controllerId); - } else { - target = new JpaTarget(controllerId, securityToken); - } + final JpaTarget target = new JpaTarget(controllerId, securityToken); if (!ObjectUtils.isEmpty(name)) { target.setName(name); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaTargetManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaTargetManagement.java index 43b4cd85a..feac7d3d0 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaTargetManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaTargetManagement.java @@ -31,6 +31,7 @@ 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.NotEmpty; import org.apache.commons.collections4.ListUtils; @@ -39,12 +40,10 @@ import org.eclipse.hawkbit.repository.FilterParams; import org.eclipse.hawkbit.repository.QuotaManagement; import org.eclipse.hawkbit.repository.TargetFields; import org.eclipse.hawkbit.repository.TargetManagement; -import org.eclipse.hawkbit.repository.TargetMetadataFields; import org.eclipse.hawkbit.repository.TimestampCalculator; import org.eclipse.hawkbit.repository.builder.TargetCreate; import org.eclipse.hawkbit.repository.builder.TargetUpdate; import org.eclipse.hawkbit.repository.event.remote.TargetAttributesRequestedEvent; -import org.eclipse.hawkbit.repository.event.remote.entity.TargetUpdatedEvent; import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.jpa.JpaManagementHelper; @@ -52,18 +51,13 @@ import org.eclipse.hawkbit.repository.jpa.acm.AccessController; import org.eclipse.hawkbit.repository.jpa.builder.JpaTargetCreate; import org.eclipse.hawkbit.repository.jpa.builder.JpaTargetUpdate; import org.eclipse.hawkbit.repository.jpa.configuration.Constants; -import org.eclipse.hawkbit.repository.jpa.model.AbstractJpaBaseEntity_; import org.eclipse.hawkbit.repository.jpa.model.JpaTarget; -import org.eclipse.hawkbit.repository.jpa.model.JpaTargetMetadata; -import org.eclipse.hawkbit.repository.jpa.model.JpaTargetMetadata_; import org.eclipse.hawkbit.repository.jpa.model.JpaTargetTag; import org.eclipse.hawkbit.repository.jpa.model.JpaTargetType; import org.eclipse.hawkbit.repository.jpa.model.JpaTarget_; -import org.eclipse.hawkbit.repository.jpa.model.TargetMetadataCompositeKey; import org.eclipse.hawkbit.repository.jpa.model.helper.AfterTransactionCommitExecutorHolder; import org.eclipse.hawkbit.repository.jpa.repository.RolloutGroupRepository; import org.eclipse.hawkbit.repository.jpa.repository.TargetFilterQueryRepository; -import org.eclipse.hawkbit.repository.jpa.repository.TargetMetadataRepository; import org.eclipse.hawkbit.repository.jpa.repository.TargetRepository; import org.eclipse.hawkbit.repository.jpa.repository.TargetTagRepository; import org.eclipse.hawkbit.repository.jpa.repository.TargetTypeRepository; @@ -73,11 +67,9 @@ import org.eclipse.hawkbit.repository.jpa.specifications.TargetSpecifications; import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper; import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSetType; -import org.eclipse.hawkbit.repository.model.MetaData; import org.eclipse.hawkbit.repository.model.RolloutGroup; import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.TargetFilterQuery; -import org.eclipse.hawkbit.repository.model.TargetMetadata; import org.eclipse.hawkbit.repository.model.TargetTag; import org.eclipse.hawkbit.repository.model.TargetType; import org.eclipse.hawkbit.repository.model.TargetTypeAssignmentResult; @@ -109,7 +101,6 @@ public class JpaTargetManagement implements TargetManagement { private final QuotaManagement quotaManagement; private final TargetRepository targetRepository; private final TargetTypeRepository targetTypeRepository; - private final TargetMetadataRepository targetMetadataRepository; private final RolloutGroupRepository rolloutGroupRepository; private final TargetFilterQueryRepository targetFilterQueryRepository; private final TargetTagRepository targetTagRepository; @@ -122,7 +113,6 @@ public class JpaTargetManagement implements TargetManagement { public JpaTargetManagement(final EntityManager entityManager, final DistributionSetManagement distributionSetManagement, final QuotaManagement quotaManagement, final TargetRepository targetRepository, final TargetTypeRepository targetTypeRepository, - final TargetMetadataRepository targetMetadataRepository, final RolloutGroupRepository rolloutGroupRepository, final TargetFilterQueryRepository targetFilterQueryRepository, final TargetTagRepository targetTagRepository, final EventPublisherHolder eventPublisherHolder, @@ -133,7 +123,6 @@ public class JpaTargetManagement implements TargetManagement { this.quotaManagement = quotaManagement; this.targetRepository = targetRepository; this.targetTypeRepository = targetTypeRepository; - this.targetMetadataRepository = targetMetadataRepository; this.rolloutGroupRepository = rolloutGroupRepository; this.targetFilterQueryRepository = targetFilterQueryRepository; this.targetTagRepository = targetTagRepository; @@ -597,7 +586,7 @@ public class JpaTargetManagement implements TargetManagement { final JpaTarget target = getByControllerIdAndThrowIfNotFound(controllerId); targetRepository.getAccessController().ifPresent(acm -> - acm.assertOperationAllowed(AccessController.Operation.UPDATE, target)); + acm.assertOperationAllowed(AccessController.Operation.UPDATE, target)); final JpaTargetType targetType = getTargetTypeByIdAndThrowIfNotFound(targetTypeId); target.setTargetType(targetType); @@ -635,57 +624,6 @@ public class JpaTargetManagement implements TargetManagement { return Collections.unmodifiableList(targetRepository.findAll(TargetSpecifications.hasIdIn(ids))); } - @Override - public Map getControllerAttributes(final String controllerId) { - getByControllerIdAndThrowIfNotFound(controllerId); - - final CriteriaBuilder cb = entityManager.getCriteriaBuilder(); - final CriteriaQuery query = cb.createQuery(Object[].class); - - final Root targetRoot = query.from(JpaTarget.class); - query.where(cb.equal(targetRoot.get(JpaTarget_.controllerId), controllerId)); - - final MapJoin attributes = targetRoot.join(JpaTarget_.controllerAttributes); - query.multiselect(attributes.key(), attributes.value()); - query.orderBy(cb.asc(attributes.key())); - - final List attr = entityManager.createQuery(query).getResultList(); - - return attr.stream().collect(Collectors.toMap(entry -> (String) entry[0], entry -> (String) entry[1], - (v1, v2) -> v1, LinkedHashMap::new)); - } - - @Override - @Transactional - @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, - backoff = @Backoff(delay = Constants.TX_RT_DELAY)) - public void requestControllerAttributes(final String controllerId) { - final JpaTarget target = getByControllerIdAndThrowIfNotFound(controllerId); - targetRepository.getAccessController() - .ifPresent(acm -> acm.assertOperationAllowed(AccessController.Operation.UPDATE, target)); - target.setRequestControllerAttributes(true); - AfterTransactionCommitExecutorHolder.getInstance().getAfterCommit().afterCommit(() -> - eventPublisherHolder.getEventPublisher() - .publishEvent(new TargetAttributesRequestedEvent( - tenantAware.getCurrentTenant(), target.getId(), target.getControllerId(), - target.getAddress() != null ? target.getAddress().toString() : null, - JpaTarget.class, eventPublisherHolder.getApplicationId()))); - } - - @Override - public boolean isControllerAttributesRequested(final String controllerId) { - final JpaTarget target = getByControllerIdAndThrowIfNotFound(controllerId); - - return target.isRequestControllerAttributes(); - } - - @Override - public Page findByControllerAttributesRequested(final Pageable pageReq) { - return JpaManagementHelper.findAllWithCountBySpec(targetRepository, List.of(TargetSpecifications.hasRequestControllerAttributesTrue()), - pageReq - ); - } - @Override public boolean existsByControllerId(final String controllerId) { return targetRepository.exists(TargetSpecifications.hasControllerId(controllerId)); @@ -710,115 +648,120 @@ public class JpaTargetManagement implements TargetManagement { } @Override - public Set getTagsByControllerId(@NotEmpty String controllerId) { + public Set getTags(@NotEmpty String controllerId) { // the method has PreAuthorized by itself - return ((JpaTarget)getWithTags(controllerId)).getTags(); + return ((JpaTarget) getWithTags(controllerId)).getTags(); + } + + @Override + public Map getControllerAttributes(final String controllerId) { + return getMap(controllerId, JpaTarget_.controllerAttributes); } @Override @Transactional @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) - public List createMetaData(final String controllerId, final Collection md) { + public void requestControllerAttributes(final String controllerId) { final JpaTarget target = getByControllerIdAndThrowIfNotFound(controllerId); - - md.forEach(meta -> checkAndThrowIfTargetMetadataAlreadyExists( - new TargetMetadataCompositeKey(target.getId(), meta.getKey()))); - - assertMetaDataQuota(target.getId(), md.size()); - - final JpaTarget updatedTarget = JpaManagementHelper.touch(entityManager, targetRepository, target); - - final List createdMetadata = md.stream() - .map(meta -> targetMetadataRepository.save(new JpaTargetMetadata(meta.getKey(), meta.getValue(), updatedTarget))) - .collect(Collectors.toUnmodifiableList()); - - // TargetUpdatedEvent is not sent within the touch() method due to the - // "lastModifiedAt" field being ignored in JpaTarget - eventPublisherHolder.getEventPublisher().publishEvent(new TargetUpdatedEvent(updatedTarget, eventPublisherHolder.getApplicationId())); - - return createdMetadata; - } - - @Override - @Transactional - @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, - backoff = @Backoff(delay = Constants.TX_RT_DELAY)) - public void deleteMetaData(final String controllerId, final String key) { - final JpaTargetMetadata metadata = (JpaTargetMetadata) getMetaDataByControllerId(controllerId, key) - .orElseThrow(() -> new EntityNotFoundException(TargetMetadata.class, controllerId, key)); - - final JpaTarget target = JpaManagementHelper.touch( - entityManager, targetRepository, getByControllerIdAndThrowIfNotFound(controllerId)); - targetRepository.getAccessController() .ifPresent(acm -> acm.assertOperationAllowed(AccessController.Operation.UPDATE, target)); - - targetMetadataRepository.deleteById(metadata.getId()); - // target update event is set to ignore "lastModifiedAt" field, so it is - // not send automatically within the touch() method - eventPublisherHolder.getEventPublisher() - .publishEvent(new TargetUpdatedEvent(target, eventPublisherHolder.getApplicationId())); + target.setRequestControllerAttributes(true); + AfterTransactionCommitExecutorHolder.getInstance().getAfterCommit().afterCommit(() -> + eventPublisherHolder.getEventPublisher() + .publishEvent(new TargetAttributesRequestedEvent( + tenantAware.getCurrentTenant(), target.getId(), target.getControllerId(), + target.getAddress() != null ? target.getAddress().toString() : null, + JpaTarget.class, eventPublisherHolder.getApplicationId()))); } @Override - public Page findMetaDataByControllerId(final Pageable pageable, final String controllerId) { - final Long id = getByControllerIdAndThrowIfNotFound(controllerId).getId(); - - return JpaManagementHelper.findAllWithCountBySpec(targetMetadataRepository, Collections.singletonList(metadataByTargetIdSpec(id)), - pageable - ); + public boolean isControllerAttributesRequested(final String controllerId) { + return getByControllerIdAndThrowIfNotFound(controllerId).isRequestControllerAttributes(); } @Override - public long countMetaDataByControllerId(@NotEmpty final String controllerId) { - final Long targetId = getByControllerIdAndThrowIfNotFound(controllerId).getId(); - - return JpaManagementHelper.countBySpec(targetMetadataRepository, - Collections.singletonList(metadataByTargetIdSpec(targetId))); - } - - @Override - public Page findMetaDataByControllerIdAndRsql(final Pageable pageable, final String controllerId, - final String rsqlParam) { - final Long targetId = getByControllerIdAndThrowIfNotFound(controllerId).getId(); - - final List> specList = Arrays.asList(RSQLUtility - .buildRsqlSpecification(rsqlParam, TargetMetadataFields.class, virtualPropertyReplacer, database), - metadataByTargetIdSpec(targetId)); - - return JpaManagementHelper.findAllWithCountBySpec(targetMetadataRepository, specList, pageable); - } - - @Override - public Optional getMetaDataByControllerId(final String controllerId, final String key) { - final Long targetId = getByControllerIdAndThrowIfNotFound(controllerId).getId(); - - return targetMetadataRepository.findById(new TargetMetadataCompositeKey(targetId, key)).map(t -> t); + public Page findByControllerAttributesRequested(final Pageable pageReq) { + return JpaManagementHelper.findAllWithCountBySpec( + targetRepository, List.of(TargetSpecifications.hasRequestControllerAttributesTrue()), pageReq); } @Override @Transactional @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) - public TargetMetadata updateMetadata(final String controllerId, final MetaData md) { + public void createMetadata(final String controllerId, final Map md) { + final JpaTarget target = getByControllerIdAndThrowIfNotFound(controllerId); - // check if exists otherwise throw entity not found exception - final JpaTargetMetadata updatedMetadata = (JpaTargetMetadata) getMetaDataByControllerId(controllerId, - md.getKey()) - .orElseThrow(() -> new EntityNotFoundException(TargetMetadata.class, controllerId, md.getKey())); - updatedMetadata.setValue(md.getValue()); - // touch it to update the lock revision because we are modifying the - // target indirectly - final JpaTarget target = JpaManagementHelper.touch(entityManager, targetRepository, - getByControllerIdAndThrowIfNotFound(controllerId)); + // get the modifiable metadata map + final Map metadata = target.getMetadata(); + md.keySet().forEach(key -> { + if (metadata.containsKey(key)) { + throw new EntityAlreadyExistsException("Metadata entry with key '" + key + "' already exists"); + } + }); + metadata.putAll(md); - final JpaTargetMetadata metadata = targetMetadataRepository.save(updatedMetadata); - // target update event is set to ignore "lastModifiedAt" field, so it is - // not send automatically within the touch() method - eventPublisherHolder.getEventPublisher() - .publishEvent(new TargetUpdatedEvent(target, eventPublisherHolder.getApplicationId())); - return metadata; + assertMetadataQuota(target.getId(), metadata.size()); + targetRepository.save(target); + } + + @Override + public Map getMetadata(final String controllerId) { + return getMap(controllerId, JpaTarget_.metadata); + } + + @Override + @Transactional + @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, + backoff = @Backoff(delay = Constants.TX_RT_DELAY)) + public void updateMetadata(final String controllerId, final String key, final String value) { + final JpaTarget target = getByControllerIdAndThrowIfNotFound(controllerId); + + // get the modifiable metadata map + final Map metadata = target.getMetadata(); + if (!metadata.containsKey(key)) { + throw new EntityNotFoundException("Target metadata", controllerId + ":" + key); + } + metadata.put(key, value); + + targetRepository.save(target); + } + + @Override + @Transactional + @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, + backoff = @Backoff(delay = Constants.TX_RT_DELAY)) + public void deleteMetadata(final String controllerId, final String key) { + final JpaTarget target = getByControllerIdAndThrowIfNotFound(controllerId); + + // get the modifiable metadata map + final Map metadata = target.getMetadata(); + if (metadata.remove(key) == null) { + throw new EntityNotFoundException("Target metadata", controllerId + ":" + key); + } + + targetRepository.save(target); + } + + private Map getMap(final String controllerId, final MapAttribute mapAttribute) { + getByControllerIdAndThrowIfNotFound(controllerId); + + final CriteriaBuilder cb = entityManager.getCriteriaBuilder(); + final CriteriaQuery query = cb.createQuery(Object[].class); + + final Root targetRoot = query.from(JpaTarget.class); + query.where(cb.equal(targetRoot.get(JpaTarget_.controllerId), controllerId)); + + 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 static boolean hasTagsFilterActive(final FilterParams filterParams) { @@ -851,20 +794,9 @@ public class JpaTargetManagement implements TargetManagement { return targetTypeRepository.findById(id).orElseThrow(() -> new EntityNotFoundException(TargetType.class, id)); } - private void checkAndThrowIfTargetMetadataAlreadyExists(final TargetMetadataCompositeKey metadataId) { - if (targetMetadataRepository.existsById(metadataId)) { - throw new EntityAlreadyExistsException( - "Metadata entry with key '" + metadataId.getKey() + "' already exists"); - } - } - - private void assertMetaDataQuota(final Long targetId, final int requested) { - QuotaHelper.assertAssignmentQuota(targetId, requested, quotaManagement.getMaxMetaDataEntriesPerTarget(), - TargetMetadata.class, Target.class, targetMetadataRepository::countByTargetId); - } - - private Specification metadataByTargetIdSpec(final Long targetId) { - return (root, query, cb) -> cb.equal(root.get(JpaTargetMetadata_.target).get(AbstractJpaBaseEntity_.id), targetId); + private void assertMetadataQuota(final Long targetId, final int requested) { + final int limit = quotaManagement.getMaxMetaDataEntriesPerTarget(); + QuotaHelper.assertAssignmentQuota(targetId, requested, limit, "Metadata", Target.class.getSimpleName(), null); } private List> buildSpecificationList(final FilterParams filterParams) { diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaTarget.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaTarget.java index f862af0a9..c72825abd 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaTarget.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaTarget.java @@ -59,7 +59,6 @@ import org.eclipse.hawkbit.repository.jpa.utils.MapAttributeConverter; import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.PollStatus; import org.eclipse.hawkbit.repository.model.Target; -import org.eclipse.hawkbit.repository.model.TargetMetadata; import org.eclipse.hawkbit.repository.model.TargetTag; import org.eclipse.hawkbit.repository.model.TargetType; import org.eclipse.hawkbit.repository.model.TargetUpdateStatus; @@ -181,7 +180,7 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw }) private Set tags; - // no cascade option on an ElementCollection, the target objects are always persisted, merged, removed with their parent. + // no cascade option on an ElementCollection, the target objects are always persisted, merged, removed with their parent @Getter @ElementCollection @CollectionTable( @@ -192,8 +191,16 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw @Column(name = "attribute_value", length = Target.CONTROLLER_ATTRIBUTE_VALUE_SIZE) private Map controllerAttributes; - @OneToMany(mappedBy = "target", fetch = FetchType.LAZY, cascade = { CascadeType.REMOVE }, targetEntity = JpaTargetMetadata.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_target_metadata", + joinColumns = { @JoinColumn(name = "target", nullable = false) }, + foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_target_metadata_target")) + @MapKeyColumn(name = "meta_key", length = Target.CONTROLLER_METADATA_KEY_SIZE) + @Column(name = "meta_value", length = Target.CONTROLLER_METADATA_VALUE_SIZE) + private Map metadata; @OneToMany(mappedBy = "target", fetch = FetchType.LAZY, cascade = { CascadeType.REMOVE }, targetEntity = JpaAction.class) private List actions; @@ -201,15 +208,11 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw @OneToMany(mappedBy = "target", fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST, CascadeType.REMOVE }) private List rolloutTargetGroup; - public JpaTarget(final String controllerId) { - this(controllerId, SecurityTokenGeneratorHolder.getInstance().generateToken()); - } - public JpaTarget(final String controllerId, final String securityToken) { this.controllerId = controllerId; - // truncate controller ID to max name length (if needed) + // truncate controller ID to max name length (if too big) setName(controllerId != null && controllerId.length() > NAME_MAX_SIZE ? controllerId.substring(0, NAME_MAX_SIZE) : controllerId); - this.securityToken = securityToken; + this.securityToken = ObjectUtils.isEmpty(securityToken) ? SecurityTokenGeneratorHolder.getInstance().generateToken() : securityToken; } @Override @@ -287,10 +290,6 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw return tags == null ? Collections.emptySet() : Collections.unmodifiableSet(tags); } - public List getMetadata() { - return metadata == null ? Collections.emptyList() : Collections.unmodifiableList(metadata); - } - public void addAction(final Action action) { if (actions == null) { actions = new ArrayList<>(); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaTargetMetadata.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaTargetMetadata.java deleted file mode 100644 index 3fd431e19..000000000 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaTargetMetadata.java +++ /dev/null @@ -1,100 +0,0 @@ -/** - * Copyright (c) 2018 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 lombok.Setter; -import org.eclipse.hawkbit.repository.model.Target; -import org.eclipse.hawkbit.repository.model.TargetMetadata; - -/** - * Meta data for {@link Target}. - */ -@NoArgsConstructor // Default constructor for JPA -@Setter -@Getter -@IdClass(TargetMetadataCompositeKey.class) -@Entity -@Table(name = "sp_target_metadata") -public class JpaTargetMetadata extends AbstractJpaMetaData implements TargetMetadata { - - @Serial - private static final long serialVersionUID = 1L; - - @Id - @ManyToOne(fetch = FetchType.LAZY, optional = false) - @JoinColumn( - name = "target", nullable = false, updatable = false, - foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_target_metadata_target")) - private JpaTarget target; - - /** - * Creates a single metadata entry with the given key and value. - * - * @param key of the meta-data entry - * @param value of the meta-data entry - */ - public JpaTargetMetadata(final String key, final String value) { - super(key, value); - } - - /** - * Creates a single metadata entry with the given key and value for the - * given {@link Target}. - * - * @param key of the meta-data entry - * @param value of the meta-data entry - * @param target the meta-data entry is associated with - */ - public JpaTargetMetadata(final String key, final String value, final Target target) { - super(key, value); - this.target = (JpaTarget) target; - } - - public TargetMetadataCompositeKey getId() { - return new TargetMetadataCompositeKey(target.getId(), getKey()); - } - - @Override - public int hashCode() { - final int prime = 31; - int result = super.hashCode(); - result = prime * result + ((target == null) ? 0 : target.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 JpaTargetMetadata other = (JpaTargetMetadata) obj; - if (target == null) { - return other.target == null; - } else { - return target.equals(other.target); - } - } -} diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/TargetMetadataCompositeKey.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/TargetMetadataCompositeKey.java deleted file mode 100644 index f7bf4d3bb..000000000 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/TargetMetadataCompositeKey.java +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright (c) 2018 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.Data; -import lombok.NoArgsConstructor; - -/** - * The Target Metadata composite key which contains the meta-data key and the ID of the Target itself. - */ -@NoArgsConstructor // Default constructor for JPA -@Data -public final class TargetMetadataCompositeKey implements Serializable { - - @Serial - private static final long serialVersionUID = 1L; - - private String key; - private Long target; - - /** - * @param target the target Id for this meta-data - * @param key the key of the meta-data - */ - public TargetMetadataCompositeKey(final Long target, final String key) { - this.target = target; - this.key = key; - } -} \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/TargetMetadataRepository.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/TargetMetadataRepository.java deleted file mode 100644 index d6e0c22b8..000000000 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/TargetMetadataRepository.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.JpaTargetMetadata; -import org.eclipse.hawkbit.repository.jpa.model.TargetMetadataCompositeKey; -import org.eclipse.hawkbit.repository.model.TargetMetadata; -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 TargetMetadata} repository. - */ -@Transactional(readOnly = true) -public interface TargetMetadataRepository - extends PagingAndSortingRepository, - CrudRepository, - JpaSpecificationExecutor { - - /** - * Counts the meta data entries that match the given target ID. - *

- * No access control applied - * - * @param id of the target. - * @return The number of matching meta data entries. - */ - long countByTargetId(@Param("id") Long id); -} diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/utils/QuotaHelper.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/utils/QuotaHelper.java index 1e8594a8f..4785e5bde 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/utils/QuotaHelper.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/utils/QuotaHelper.java @@ -13,35 +13,31 @@ import java.util.function.ToLongFunction; import jakarta.validation.constraints.NotNull; +import lombok.NoArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException; /** * Helper class to check quotas. */ +@NoArgsConstructor @Slf4j public final class QuotaHelper { private static final String MAX_ASSIGNMENT_QUOTA_EXCEEDED = "Quota exceeded: Cannot assign %s entities at once. The maximum is %s."; - private QuotaHelper() { - // no need to instantiate this class - } - /** * Asserts the specified assignment quota. * - * @param requested The number of entities that shall be assigned to the parent - * entity. - * @param limit The maximum number of entities that may be assigned to the - * parent entity. + * @param requested The number of entities that shall be assigned to the parent entity. + * @param limit The maximum number of entities that may be assigned to the parent entity. * @param type The type of the entities that shall be assigned. * @param parentType The type of the parent entity. - * @throws AssignmentQuotaExceededException if the assignment operation would cause the quota to be - * exceeded + * @throws AssignmentQuotaExceededException if the assignment operation would cause the quota to be exceeded */ - public static void assertAssignmentQuota(final long requested, final long limit, @NotNull final Class type, - @NotNull final Class parentType) { + public static void assertAssignmentQuota( + final long requested, final long limit, + @NotNull final Class type, @NotNull final Class parentType) { assertAssignmentQuota(null, requested, limit, type.getSimpleName(), parentType.getSimpleName(), null); } @@ -49,19 +45,18 @@ public final class QuotaHelper { * Asserts the specified assignment quota. * * @param parentId The ID of the parent entity. - * @param requested The number of entities that shall be assigned to the parent - * entity. - * @param limit The maximum number of entities that may be assigned to the - * parent entity. + * @param requested The number of entities that shall be assigned to the parent entity. + * @param limit The maximum number of entities that may be assigned to the parent entity. * @param type The type of the entities that shall be assigned. * @param parentType The type of the parent entity. - * @param countFct Function to count the entities that are currently assigned to - * the parent entity. - * @throws AssignmentQuotaExceededException if the assignment operation would cause the quota to be - * exceeded + * @param countFct Function to count the entities that are currently assigned to the parent entity. + * @throws AssignmentQuotaExceededException if the assignment operation would cause the quota to be exceeded */ - public static void assertAssignmentQuota(final T parentId, final long requested, final long limit, - @NotNull final Class type, @NotNull final Class parentType, final ToLongFunction countFct) { + public static void assertAssignmentQuota( + final T parentId, + final long requested, final long limit, + @NotNull final Class type, @NotNull final Class parentType, + final ToLongFunction countFct) { assertAssignmentQuota(parentId, requested, limit, type.getSimpleName(), parentType.getSimpleName(), countFct); } @@ -69,20 +64,18 @@ public final class QuotaHelper { * Asserts the specified assignment quota. * * @param parentId The ID of the parent entity. - * @param requested The number of entities that shall be assigned to the parent - * entity. - * @param limit The maximum number of entities that may be assigned to the - * parent entity. + * @param requested The number of entities that shall be assigned to the parent entity. + * @param limit The maximum number of entities that may be assigned to the parent entity. * @param type The type of the entities that shall be assigned. * @param parentType The type of the parent entity. - * @param countFct Function to count the entities that are currently assigned to - * the parent entity. - * @throws AssignmentQuotaExceededException if the assignment operation would cause the quota to be - * exceeded + * @param countFct Function to count the entities that are currently assigned to the parent entity. + * @throws AssignmentQuotaExceededException if the assignment operation would cause the quota to be exceeded */ - public static void assertAssignmentQuota(final T parentId, final long requested, final long limit, - @NotNull final String type, @NotNull final String parentType, final ToLongFunction countFct) { - + public static void assertAssignmentQuota( + final T parentId, + final long requested, final long limit, + @NotNull final String type, @NotNull final String parentType, + final ToLongFunction countFct) { // check if the quota is unlimited if (limit <= 0) { log.debug("Quota 'Max {} entities per {}' is unlimited.", type, parentType); @@ -91,8 +84,9 @@ public final class QuotaHelper { if (requested > limit) { final String parentIdStr = parentId != null ? String.valueOf(parentId) : ""; - log.warn("Cannot assign {} {} entities to {} '{}' because of the configured quota limit {}.", requested, - type, parentType, parentIdStr, limit); + log.warn( + "Cannot assign {} {} entities to {} '{}' because of the configured quota limit {}.", + requested, type, parentType, parentIdStr, limit); throw new AssignmentQuotaExceededException(type, parentType, parentId, requested, limit); } @@ -100,7 +94,8 @@ public final class QuotaHelper { final long currentCount = countFct.applyAsLong(parentId); if (currentCount + requested > limit) { log.warn( - "Cannot assign {} {} entities to {} '{}' because of the configured quota limit {}. Currently, there are {} {} entities assigned.", + "Cannot assign {} {} entities to {} '{}' because of the configured quota limit {}. " + + "Currently, there are {} {} entities assigned.", requested, type, parentType, parentId, limit, currentCount, type); throw new AssignmentQuotaExceededException(type, parentType, parentId, requested, limit); } @@ -108,8 +103,7 @@ public final class QuotaHelper { } /** - * Assert that the number of assignments in a request does not exceed the - * limit. + * Assert that the number of assignments in a request does not exceed the limit. * * @param requested the number of assignments that are to be made * @param limit the maximum number of assignments per request @@ -121,4 +115,4 @@ public final class QuotaHelper { throw new AssignmentQuotaExceededException(message); } } -} +} \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/AbstractJpaIntegrationTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/AbstractJpaIntegrationTest.java index ddaa6e6d5..c16a96235 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/AbstractJpaIntegrationTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/AbstractJpaIntegrationTest.java @@ -303,7 +303,7 @@ public abstract class AbstractJpaIntegrationTest extends AbstractIntegrationTest } protected Set getTargetTags(final String controllerId) { - return targetManagement.getTagsByControllerId(controllerId); + return targetManagement.getTags(controllerId); } protected JpaRolloutGroup refresh(final RolloutGroup group) { 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 1301c0441..3b90bb6aa 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 @@ -68,7 +68,7 @@ class DistributionSetManagementSecurityTest @Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.") void createMetaDataPermissionsCheck() { assertPermissions( - () -> distributionSetManagement.putMetaData(1L, List.of(entityFactory.generateTargetMetadata("key", "value"))), + () -> distributionSetManagement.putMetaData(1L, List.of(entityFactory.generateDsMetadata("key", "value"))), List.of(SpPermission.UPDATE_REPOSITORY)); } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/TargetManagementSecurityTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/TargetManagementSecurityTest.java index e826997f3..3f9c5ef89 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/TargetManagementSecurityTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/TargetManagementSecurityTest.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; @@ -339,6 +340,26 @@ class TargetManagementSecurityTest extends AbstractJpaIntegrationTest { assertPermissions(() -> targetManagement.get(List.of(1L)), List.of(SpPermission.READ_TARGET)); } + @Test + @Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.") + void existsByControllerIdPermissionsCheck() { + assertPermissions(() -> targetManagement.existsByControllerId("controllerId"), List.of(SpPermission.READ_TARGET)); + } + + @Test + @Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.") + void isTargetMatchingQueryAndDSNotAssignedAndCompatibleAndUpdatablePermissionsCheck() { + assertPermissions( + () -> targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatibleAndUpdatable("controllerId", 1L, "controllerId==id"), + List.of(SpPermission.READ_TARGET, SpPermission.READ_REPOSITORY)); + } + + @Test + @Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.") + void getTagsPermissionsCheck() { + assertPermissions(() -> targetManagement.getTags("controllerId"), List.of(SpPermission.READ_TARGET)); + } + @Test @Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.") void getControllerAttributesPermissionsCheck() { @@ -368,72 +389,38 @@ class TargetManagementSecurityTest extends AbstractJpaIntegrationTest { @Test @Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.") - void existsByControllerIdPermissionsCheck() { - assertPermissions(() -> targetManagement.existsByControllerId("controllerId"), List.of(SpPermission.READ_TARGET)); - } - - @Test - @Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.") - void isTargetMatchingQueryAndDSNotAssignedAndCompatibleAndUpdatablePermissionsCheck() { + void createMetadataPermissionsCheck() { assertPermissions( - () -> targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatibleAndUpdatable("controllerId", 1L, "controllerId==id"), - List.of(SpPermission.READ_TARGET, SpPermission.READ_REPOSITORY)); - } - - @Test - @Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.") - void getTagsByControllerIdPermissionsCheck() { - assertPermissions(() -> targetManagement.getTagsByControllerId("controllerId"), List.of(SpPermission.READ_TARGET)); - } - - @Test - @Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.") - void createMetaDataPermissionsCheck() { - assertPermissions( - () -> targetManagement.createMetaData("controllerId", List.of(entityFactory.generateTargetMetadata("key", "value"))), + () -> { + targetManagement.createMetadata("controllerId", Map.of("key", "value")); + return null; + }, List.of(SpPermission.UPDATE_REPOSITORY)); } @Test @Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.") - void deleteMetaDataPermissionsCheck() { - assertPermissions(() -> { - targetManagement.deleteMetaData("controllerId", "key"); - return null; - }, List.of(SpPermission.UPDATE_REPOSITORY)); - } - - @Test - @Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.") - void countMetaDataByControllerIdPermissionsCheck() { - assertPermissions(() -> targetManagement.countMetaDataByControllerId("controllerId"), List.of(SpPermission.READ_REPOSITORY)); - } - - @Test - @Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.") - void findMetaDataByControllerIdAndRsqlPermissionsCheck() { - assertPermissions(() -> targetManagement.findMetaDataByControllerIdAndRsql(PAGE, "controllerId", "controllerId==id"), - List.of(SpPermission.READ_REPOSITORY)); - } - - @Test - @Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.") - void getMetaDataByControllerIdPermissionsCheck() { - assertPermissions(() -> targetManagement.getMetaDataByControllerId("controllerId", "key"), List.of(SpPermission.READ_REPOSITORY)); - } - - @Test - @Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.") - void findMetaDataByControllerIdPermissionsCheck() { - assertPermissions(() -> targetManagement.findMetaDataByControllerId(PAGE, "controllerId"), List.of(SpPermission.READ_REPOSITORY)); + void getMetadataPermissionsCheck() { + assertPermissions(() -> targetManagement.getMetadata("controllerId"), List.of(SpPermission.READ_REPOSITORY)); } @Test @Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.") @WithUser(principal = "user", authorities = { SpPermission.UPDATE_REPOSITORY }) void updateMetadataPermissionsCheck() { - assertPermissions(() -> targetManagement.updateMetadata("controllerId", entityFactory.generateTargetMetadata("key", "value")), + assertPermissions(() -> { + targetManagement.updateMetadata("controllerId", "key", "value"); + return null; + }, List.of(SpPermission.UPDATE_REPOSITORY)); } -} + @Test + @Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.") + void deleteMetadataPermissionsCheck() { + assertPermissions(() -> { + targetManagement.deleteMetadata("controllerId", "key"); + return null; + }, List.of(SpPermission.UPDATE_REPOSITORY)); + } +} \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/TargetManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/TargetManagementTest.java index 0596b5868..92227db5c 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/TargetManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/TargetManagementTest.java @@ -60,17 +60,14 @@ import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldExc import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest; import org.eclipse.hawkbit.repository.jpa.model.JpaAction; import org.eclipse.hawkbit.repository.jpa.model.JpaTarget; -import org.eclipse.hawkbit.repository.jpa.model.JpaTargetMetadata; 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.DistributionSetAssignmentResult; -import org.eclipse.hawkbit.repository.model.MetaData; import org.eclipse.hawkbit.repository.model.NamedEntity; import org.eclipse.hawkbit.repository.model.Rollout; import org.eclipse.hawkbit.repository.model.Tag; import org.eclipse.hawkbit.repository.model.Target; -import org.eclipse.hawkbit.repository.model.TargetMetadata; import org.eclipse.hawkbit.repository.model.TargetTag; import org.eclipse.hawkbit.repository.model.TargetType; import org.eclipse.hawkbit.repository.model.TargetTypeAssignmentResult; @@ -81,7 +78,6 @@ import org.eclipse.hawkbit.repository.test.matcher.ExpectEvents; import org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch; import org.eclipse.hawkbit.repository.test.util.WithUser; import org.junit.jupiter.api.Test; -import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Slice; @@ -99,7 +95,7 @@ class TargetManagementTest extends AbstractJpaIntegrationTest { final Target target = testdataFactory.createTarget(); assertThat(targetManagement.getByControllerID(NOT_EXIST_ID)).isNotPresent(); assertThat(targetManagement.get(NOT_EXIST_IDL)).isNotPresent(); - assertThat(targetManagement.getMetaDataByControllerId(target.getControllerId(), NOT_EXIST_ID)).isNotPresent(); + assertThat(targetManagement.getMetadata(target.getControllerId()).get(NOT_EXIST_ID)).isNull(); } @Test @@ -113,20 +109,15 @@ class TargetManagementTest extends AbstractJpaIntegrationTest { final Target target = testdataFactory.createTarget(); verifyThrownExceptionBy( - () -> targetManagement.assignTag(Collections.singletonList(target.getControllerId()), NOT_EXIST_IDL), - "TargetTag"); - verifyThrownExceptionBy(() -> targetManagement.assignTag(Collections.singletonList(NOT_EXIST_ID), tag.getId()), - "Target"); + () -> targetManagement.assignTag(Collections.singletonList(target.getControllerId()), NOT_EXIST_IDL), "TargetTag"); + verifyThrownExceptionBy(() -> targetManagement.assignTag(Collections.singletonList(NOT_EXIST_ID), tag.getId()), "Target"); verifyThrownExceptionBy(() -> targetManagement.findByTag(PAGE, NOT_EXIST_IDL), "TargetTag"); verifyThrownExceptionBy(() -> targetManagement.findByRsqlAndTag(PAGE, "name==*", NOT_EXIST_IDL), "TargetTag"); - verifyThrownExceptionBy(() -> targetManagement.countByAssignedDistributionSet(NOT_EXIST_IDL), - "DistributionSet"); - verifyThrownExceptionBy(() -> targetManagement.countByInstalledDistributionSet(NOT_EXIST_IDL), - "DistributionSet"); - verifyThrownExceptionBy(() -> targetManagement.existsByInstalledOrAssignedDistributionSet(NOT_EXIST_IDL), - "DistributionSet"); + verifyThrownExceptionBy(() -> targetManagement.countByAssignedDistributionSet(NOT_EXIST_IDL), "DistributionSet"); + verifyThrownExceptionBy(() -> targetManagement.countByInstalledDistributionSet(NOT_EXIST_IDL), "DistributionSet"); + verifyThrownExceptionBy(() -> targetManagement.existsByInstalledOrAssignedDistributionSet(NOT_EXIST_IDL), "DistributionSet"); verifyThrownExceptionBy(() -> targetManagement.countByTargetFilterQuery(NOT_EXIST_IDL), "TargetFilterQuery"); verifyThrownExceptionBy(() -> targetManagement.countByRsqlAndNonDSAndCompatibleAndUpdatable(NOT_EXIST_IDL, "name==*"), @@ -138,45 +129,29 @@ class TargetManagementTest extends AbstractJpaIntegrationTest { verifyThrownExceptionBy( () -> targetManagement.findByTargetFilterQueryAndNonDSAndCompatibleAndUpdatable(PAGE, NOT_EXIST_IDL, "name==*"), "DistributionSet"); - verifyThrownExceptionBy(() -> targetManagement.findByInRolloutGroupWithoutAction(PAGE, NOT_EXIST_IDL), - "RolloutGroup"); - verifyThrownExceptionBy(() -> targetManagement.findByAssignedDistributionSet(PAGE, NOT_EXIST_IDL), - "DistributionSet"); + verifyThrownExceptionBy(() -> targetManagement.findByInRolloutGroupWithoutAction(PAGE, NOT_EXIST_IDL), "RolloutGroup"); + verifyThrownExceptionBy(() -> targetManagement.findByAssignedDistributionSet(PAGE, NOT_EXIST_IDL), "DistributionSet"); verifyThrownExceptionBy( - () -> targetManagement.findByAssignedDistributionSetAndRsql(PAGE, NOT_EXIST_IDL, "name==*"), - "DistributionSet"); + () -> targetManagement.findByAssignedDistributionSetAndRsql(PAGE, NOT_EXIST_IDL, "name==*"), "DistributionSet"); - verifyThrownExceptionBy(() -> targetManagement.findByInstalledDistributionSet(PAGE, NOT_EXIST_IDL), - "DistributionSet"); + verifyThrownExceptionBy(() -> targetManagement.findByInstalledDistributionSet(PAGE, NOT_EXIST_IDL), "DistributionSet"); verifyThrownExceptionBy( - () -> targetManagement.findByInstalledDistributionSetAndRsql(PAGE, NOT_EXIST_IDL, "name==*"), - "DistributionSet"); + () -> targetManagement.findByInstalledDistributionSetAndRsql(PAGE, NOT_EXIST_IDL, "name==*"), "DistributionSet"); verifyThrownExceptionBy(() -> targetManagement .assignTag(Collections.singletonList(target.getControllerId()), Long.parseLong(NOT_EXIST_ID)), "TargetTag"); verifyThrownExceptionBy( - () -> targetManagement.assignTag(Collections.singletonList(NOT_EXIST_ID), tag.getId()), - "Target"); + () -> targetManagement.assignTag(Collections.singletonList(NOT_EXIST_ID), tag.getId()), "Target"); verifyThrownExceptionBy(() -> targetManagement.unassignTag(List.of(NOT_EXIST_ID), tag.getId()), "Target"); - verifyThrownExceptionBy(() -> targetManagement.unassignTag(List.of(target.getControllerId()), NOT_EXIST_IDL), - "TargetTag"); + verifyThrownExceptionBy(() -> targetManagement.unassignTag(List.of(target.getControllerId()), NOT_EXIST_IDL), "TargetTag"); verifyThrownExceptionBy(() -> targetManagement.update(entityFactory.target().update(NOT_EXIST_ID)), "Target"); - verifyThrownExceptionBy(() -> targetManagement.createMetaData(NOT_EXIST_ID, - Collections.singletonList(entityFactory.generateTargetMetadata("123", "123"))), "Target"); - verifyThrownExceptionBy(() -> targetManagement.deleteMetaData(NOT_EXIST_ID, "xxx"), "Target"); - verifyThrownExceptionBy(() -> targetManagement.deleteMetaData(target.getControllerId(), NOT_EXIST_ID), - "TargetMetadata"); - verifyThrownExceptionBy(() -> targetManagement.getMetaDataByControllerId(NOT_EXIST_ID, "xxx"), "Target"); - verifyThrownExceptionBy(() -> targetManagement.findMetaDataByControllerId(PAGE, NOT_EXIST_ID), "Target"); - verifyThrownExceptionBy(() -> targetManagement.findMetaDataByControllerIdAndRsql(PAGE, NOT_EXIST_ID, "key==*"), - "Target"); - verifyThrownExceptionBy( - () -> targetManagement.updateMetadata(NOT_EXIST_ID, entityFactory.generateTargetMetadata("xxx", "xxx")), - "Target"); - verifyThrownExceptionBy(() -> targetManagement.updateMetadata(target.getControllerId(), - entityFactory.generateTargetMetadata(NOT_EXIST_ID, "xxx")), "TargetMetadata"); + verifyThrownExceptionBy(() -> targetManagement.createMetadata(NOT_EXIST_ID, Map.of("123", "123")), "Target"); + verifyThrownExceptionBy(() -> targetManagement.deleteMetadata(NOT_EXIST_ID, "xxx"), "Target"); + verifyThrownExceptionBy(() -> targetManagement.deleteMetadata(target.getControllerId(), NOT_EXIST_ID), "Target"); + verifyThrownExceptionBy(() -> targetManagement.getMetadata(NOT_EXIST_ID).get("xxx"), "Target"); + verifyThrownExceptionBy(() -> targetManagement.updateMetadata(NOT_EXIST_ID, "xxx", "xxx"), "Target"); } @Test @@ -764,68 +739,75 @@ class TargetManagementTest extends AbstractJpaIntegrationTest { @Test @Description("Checks that metadata for a target can be created.") - void createTargetMetadata() { - final String knownKey = "targetMetaKnownKey"; - final String knownValue = "targetMetaKnownValue"; - - final Target target = testdataFactory.createTarget("targetIdWithMetadata"); - final JpaTargetMetadata createdMetadata = insertTargetMetadata(knownKey, knownValue, target); - - assertThat(createdMetadata).isNotNull(); - assertThat(createdMetadata.getId().getKey()).isEqualTo(knownKey); - assertThat(createdMetadata.getTarget().getControllerId()).isEqualTo(target.getControllerId()); - assertThat(createdMetadata.getTarget().getId()).isEqualTo(target.getId()); - assertThat(createdMetadata.getValue()).isEqualTo(knownValue); + void createMetadata() { + insertMetadata( + "targetMetaKnownKey", "targetMetaKnownValue", + testdataFactory.createTarget("targetIdWithMetadata")); // and validate } @Test @Description("Verifies the enforcement of the metadata quota per target.") - void createTargetMetadataUntilQuotaIsExceeded() { + void createMetadataUntilQuotaIsExceeded() { // add meta-data one by one final Target target1 = testdataFactory.createTarget("target1"); final int maxMetaData = quotaManagement.getMaxMetaDataEntriesPerTarget(); for (int i = 0; i < maxMetaData; ++i) { - assertThat(insertTargetMetadata("k" + i, "v" + i, target1)).isNotNull(); + insertMetadata("k" + i, "v" + i, target1); } // quota exceeded assertThatExceptionOfType(AssignmentQuotaExceededException.class) - .isThrownBy(() -> insertTargetMetadata("k" + maxMetaData, "v" + maxMetaData, target1)); + .isThrownBy(() -> insertMetadata("k" + maxMetaData, "v" + maxMetaData, target1)); // add multiple meta data entries at once final Target target2 = testdataFactory.createTarget("target2"); - final List metaData2 = new ArrayList<>(); - for (int i = 0; i < maxMetaData + 1; ++i) { - metaData2.add(new JpaTargetMetadata("k" + i, "v" + i, target2)); + final Map metaData2 = new HashMap<>(); + for (int i = 0; i < maxMetaData + 1; i++) { + metaData2.put("k" + i, "v" + i); } // verify quota is exceeded final String target2ControllerId = target2.getControllerId(); assertThatExceptionOfType(AssignmentQuotaExceededException.class) - .isThrownBy(() -> targetManagement.createMetaData(target2ControllerId, metaData2)); + .isThrownBy(() -> targetManagement.createMetadata(target2ControllerId, metaData2)); // add some meta data entries final Target target3 = testdataFactory.createTarget("target3"); final int firstHalf = maxMetaData / 2; - for (int i = 0; i < firstHalf; ++i) { - insertTargetMetadata("k" + i, "v" + i, target3); + for (int i = 0; i < firstHalf; i++) { + insertMetadata("k" + i, "v" + i, target3); } // add too many data entries final int secondHalf = maxMetaData - firstHalf; - final List metaData3 = new ArrayList<>(); - for (int i = 0; i < secondHalf + 1; ++i) { - metaData3.add(new JpaTargetMetadata("kk" + i, "vv" + i, target3)); + final Map metaData3 = new HashMap<>(); + for (int i = 0; i < secondHalf + 1; i++) { + metaData3.put("kk" + i, "vv" + i); } // verify quota is exceeded final String target3ControllerId = target3.getControllerId(); assertThatExceptionOfType(AssignmentQuotaExceededException.class) - .isThrownBy(() -> targetManagement.createMetaData(target3ControllerId, metaData3)); + .isThrownBy(() -> targetManagement.createMetadata(target3ControllerId, metaData3)); + } + + + @Test + @Description("Queries and loads the metadata related to a given target.") + void getMetadata() { + // create targets + final Target target1 = createTargetWithMetadata("target1", 10); + final Target target2 = createTargetWithMetadata("target2", 8); + + final Map metadataOfTarget1 = targetManagement.getMetadata(target1.getControllerId()); + final Map metadataOfTarget2 = targetManagement.getMetadata(target2.getControllerId()); + + assertThat(metadataOfTarget1).hasSize(10); + assertThat(metadataOfTarget2).hasSize(8); } @Test @WithUser(allSpPermissions = true) @Description("Checks that metadata for a target can be updated.") - void updateTargetMetadata() { + void updateMetadata() { final String knownKey = "myKnownKey"; final String knownValue = "myKnownValue"; final String knownUpdateValue = "myNewUpdatedValue"; @@ -836,26 +818,36 @@ class TargetManagementTest extends AbstractJpaIntegrationTest { assertThat(target.getOptLockRevision()).isEqualTo(1); // create target meta data entry - insertTargetMetadata(knownKey, knownValue, target); + insertMetadata(knownKey, knownValue, target); - Target changedLockRevisionTarget = targetManagement.get(target.getId()).orElseThrow(NoSuchElementException::new); + final Target changedLockRevisionTarget = targetManagement.get(target.getId()).orElseThrow(NoSuchElementException::new); assertThat(changedLockRevisionTarget.getOptLockRevision()).isEqualTo(2); // update the target metadata - final JpaTargetMetadata updated = (JpaTargetMetadata) targetManagement.updateMetadata( - target.getControllerId(), entityFactory.generateTargetMetadata(knownKey, knownUpdateValue)); - // we are updating the target meta-data so also modifying the base - // software module so opt lock revision must be three - changedLockRevisionTarget = targetManagement.get(target.getId()).orElseThrow(NoSuchElementException::new); - assertThat(changedLockRevisionTarget.getOptLockRevision()).isEqualTo(3); - assertThat(changedLockRevisionTarget.getLastModifiedAt()).isPositive(); + targetManagement.updateMetadata(target.getControllerId(), knownKey, knownUpdateValue); + // we are updating the target meta-data so also modifying the target so opt lock revision must be three + final Target changedLockRevisionTarget2 = targetManagement.get(target.getId()).orElseThrow(NoSuchElementException::new); + assertThat(changedLockRevisionTarget2.getOptLockRevision()).isEqualTo(3); + assertThat(changedLockRevisionTarget2.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.getTarget().getControllerId()).isEqualTo(target.getControllerId()); - assertThat(updated.getTarget().getId()).isEqualTo(target.getId()); + assertThat(targetManagement.getMetadata(target.getControllerId()).get(knownKey)).isEqualTo(knownUpdateValue); + } + + @Test + @Description("Queries and loads the metadata related to a given target.") + void deleteMetadata() { + final String knownKey = "myKnownKey"; + final String knownValue = "myKnownValue"; + + // create target + final String controllerId = createTargetWithMetadata("target1", 10).getControllerId(); + targetManagement.createMetadata(controllerId, Map.of(knownKey, knownValue)); + + targetManagement.deleteMetadata(controllerId, knownKey); + // already removed + assertThatExceptionOfType(EntityNotFoundException.class) + .isThrownBy(() -> targetManagement.deleteMetadata(controllerId, knownKey)); } @Test @@ -963,26 +955,6 @@ class TargetManagementTest extends AbstractJpaIntegrationTest { checkTargetsHaveType(typeATargets, typeB); } - @Test - @Description("Queries and loads the metadata related to a given target.") - void findAllTargetMetadataByControllerId() { - // create targets - final Target target1 = createTargetWithMetadata("target1", 10); - final Target target2 = createTargetWithMetadata("target2", 8); - - final Page metadataOfTarget1 = targetManagement - .findMetaDataByControllerId(PageRequest.of(0, 100), target1.getControllerId()); - - final Page metadataOfTarget2 = targetManagement - .findMetaDataByControllerId(PageRequest.of(0, 100), target2.getControllerId()); - - assertThat(metadataOfTarget1.getNumberOfElements()).isEqualTo(10); - assertThat(metadataOfTarget1.getTotalElements()).isEqualTo(10); - - assertThat(metadataOfTarget2.getNumberOfElements()).isEqualTo(8); - assertThat(metadataOfTarget2.getTotalElements()).isEqualTo(8); - } - @Test @WithUser(allSpPermissions = true) @Description("Checks that target type is not assigned to target if invalid.") @@ -1419,11 +1391,9 @@ class TargetManagementTest extends AbstractJpaIntegrationTest { } } - private JpaTargetMetadata insertTargetMetadata(final String knownKey, final String knownValue, - final Target target) { - final JpaTargetMetadata metadata = new JpaTargetMetadata(knownKey, knownValue, target); - return (JpaTargetMetadata) targetManagement - .createMetaData(target.getControllerId(), Collections.singletonList(metadata)).get(0); + private void insertMetadata(final String knownKey, final String knownValue, final Target target) { + targetManagement.createMetadata(target.getControllerId(), Map.of(knownKey, knownValue)); + assertThat(targetManagement.getMetadata(target.getControllerId()).get(knownKey)).isEqualTo(knownValue); } private void checkTargetsHaveType(final List targets, final TargetType type) { @@ -1440,7 +1410,7 @@ class TargetManagementTest extends AbstractJpaIntegrationTest { final Target target = testdataFactory.createTarget(controllerId); for (int index = 1; index <= count; index++) { - insertTargetMetadata("key" + index, controllerId + "-value" + index, target); + insertMetadata("key" + index, controllerId + "-value" + index, target); } return target; @@ -1450,7 +1420,7 @@ class TargetManagementTest extends AbstractJpaIntegrationTest { final Target target = testdataFactory.createTarget(controllerId, controllerId, targetTypeId); for (int index = 1; index <= count; index++) { - insertTargetMetadata("key" + index, controllerId + "-value" + index, target); + insertMetadata("key" + index, controllerId + "-value" + index, target); } return target; diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLTargetFieldTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLTargetFieldTest.java index 5a08820d6..686e8201d 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLTargetFieldTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLTargetFieldTest.java @@ -79,7 +79,7 @@ class RSQLTargetFieldTest extends AbstractJpaIntegrationTest { .description("targetDesc123")); target = controllerManagement.updateControllerAttributes(target.getControllerId(), Map.of("revision", "1.1"), null); target = controllerManagement.findOrRegisterTargetIfItDoesNotExist(target.getControllerId(), LOCALHOST); - createTargetMetadata(target.getControllerId(), entityFactory.generateTargetMetadata("metaKey", "metaValue")); + targetManagement.createMetadata(target.getControllerId(), Map.of("metaKey", "metaValue")); assignDistributionSet(ds.getId(), target.getControllerId()); targetManagement.assignType(target.getControllerId(), targetType1.getId()); @@ -89,7 +89,7 @@ class RSQLTargetFieldTest extends AbstractJpaIntegrationTest { .description("targetId1234")); target2 = controllerManagement.updateControllerAttributes(target2.getControllerId(), Map.of("revision", "1.2"), null); targetManagement.assignType(target2.getControllerId(), targetType2.getId()); - createTargetMetadata(target2.getControllerId(), entityFactory.generateTargetMetadata("metaKey", "value")); + targetManagement.createMetadata(target2.getControllerId(), Map.of("metaKey", "value")); target2 = controllerManagement.findOrRegisterTargetIfItDoesNotExist(target2.getControllerId(), LOCALHOST); targetManagement.assignTag(Arrays.asList(target.getControllerId(), target2.getControllerId()), targetTag.getId()); @@ -270,7 +270,7 @@ class RSQLTargetFieldTest extends AbstractJpaIntegrationTest { @Test @Description("Test filter target by metadata") void testFilterByMetadata() { - createTargetMetadata(testdataFactory.createTarget().getControllerId(), entityFactory.generateTargetMetadata("key.dot", "value.dot")); + targetManagement.createMetadata(testdataFactory.createTarget().getControllerId(), Map.of("key.dot", "value.dot")); assertRSQLQuery(TargetFields.METADATA.name() + ".metaKey==metaValue", 1); assertRSQLQuery(TargetFields.METADATA.name() + ".metaKey==null", 0); // "null" check diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLTargetMetadataFieldsTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLTargetMetadataFieldsTest.java deleted file mode 100644 index b4545fef6..000000000 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLTargetMetadataFieldsTest.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.TargetMetadataFields; -import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest; -import org.eclipse.hawkbit.repository.model.MetaData; -import org.eclipse.hawkbit.repository.model.Target; -import org.eclipse.hawkbit.repository.model.TargetMetadata; -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 target metadata") -class RSQLTargetMetadataFieldsTest extends AbstractJpaIntegrationTest { - - private String controllerId; - - @BeforeEach - void setupBeforeTest() { - final Target target = testdataFactory.createTarget("target"); - controllerId = target.getControllerId(); - - final List metadata = new ArrayList<>(5); - for (int i = 0; i < 5; i++) { - metadata.add(entityFactory.generateTargetMetadata("" + i, "" + i)); - } - - targetManagement.createMetaData(controllerId, metadata); - - targetManagement.createMetaData(controllerId, - Arrays.asList(entityFactory.generateTargetMetadata("emptyValueTest", null))); - } - - @Test - @Description("Test filter target metadata by key") - void testFilterByParameterKey() { - assertRSQLQuery(TargetMetadataFields.KEY.name() + "==1", 1); - assertRSQLQuery(TargetMetadataFields.KEY.name() + "!=1", 5); - assertRSQLQuery(TargetMetadataFields.KEY.name() + "=in=(1,2)", 2); - assertRSQLQuery(TargetMetadataFields.KEY.name() + "=out=(1,2)", 4); - } - - @Test - @Description("Test filter target metadata by value") - void testFilterByParameterValue() { - assertRSQLQuery(TargetMetadataFields.VALUE.name() + "==''", 1); - assertRSQLQuery(TargetMetadataFields.VALUE.name() + "!=''", 5); - assertRSQLQuery(TargetMetadataFields.VALUE.name() + "==1", 1); - assertRSQLQuery(TargetMetadataFields.VALUE.name() + "!=1", 5); - assertRSQLQuery(TargetMetadataFields.VALUE.name() + "=in=(1,2)", 2); - assertRSQLQuery(TargetMetadataFields.VALUE.name() + "=out=(1,2)", 4); - } - - private void assertRSQLQuery(final String rsqlParam, final long expectedEntities) { - - final Page findEnitity = targetManagement - .findMetaDataByControllerIdAndRsql(PageRequest.of(0, 100), controllerId, rsqlParam); - final long countAllEntities = findEnitity.getTotalElements(); - assertThat(findEnitity).isNotNull(); - assertThat(countAllEntities).isEqualTo(expectedEntities); - } -} diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/resources/jpa-test.properties b/hawkbit-repository/hawkbit-repository-jpa/src/test/resources/jpa-test.properties index d5bdb00cf..176de3681 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/resources/jpa-test.properties +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/resources/jpa-test.properties @@ -12,10 +12,10 @@ logging.level.org.eclipse.persistence=ERROR ## Uncomment to see the debug of persistence, e.g. to see the generated SQLs -logging.level.org.eclipse.persistence=DEBUG -spring.jpa.properties.eclipselink.logging.level=FINE -spring.jpa.properties.eclipselink.logging.level.sql=FINE -spring.jpa.properties.eclipselink.logging.parameters=true +#logging.level.org.eclipse.persistence=DEBUG +#spring.jpa.properties.eclipselink.logging.level=FINE +#spring.jpa.properties.eclipselink.logging.level.sql=FINE +#spring.jpa.properties.eclipselink.logging.parameters=true ## Enable EclipseLink performance monitor (monitoring and profile) #spring.jpa.properties.eclipselink.profiler=PerformanceMonitor 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 bdb1049af..b7f8906f7 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 @@ -424,10 +424,6 @@ public abstract class AbstractIntegrationTest { return distributionSetManagement.putMetaData(dsId, md); } - protected void createTargetMetadata(final String controllerId, final MetaData md) { - createTargetMetadata(controllerId, Collections.singletonList(md)); - } - protected Long getOsModule(final DistributionSet ds) { return ds.findFirstModuleByType(osType).orElseThrow(NoSuchElementException::new).getId(); } @@ -504,8 +500,4 @@ public abstract class AbstractIntegrationTest { throw new ArtifactStoreException("Cannot create temp file", e); } } - - private void createTargetMetadata(final String controllerId, final List md) { - targetManagement.createMetaData(controllerId, md); - } -} +} \ No newline at end of file