JpaDistributionSet#metadata made Map (#2411)

Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
Avgustin Marinov
2025-05-21 13:45:18 +03:00
committed by GitHub
parent ceba4f5cfb
commit 452d8618d7
26 changed files with 382 additions and 1216 deletions

View File

@@ -1,33 +0,0 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.repository;
import lombok.Getter;
/**
* Sort fields for DistributionSetMetadata.
*/
@Getter
public enum DistributionSetMetadataFields implements RsqlQueryField {
KEY("key"),
VALUE("value");
private final String jpaEntityFieldName;
DistributionSetMetadataFields(final String jpaEntityFieldName) {
this.jpaEntityFieldName = jpaEntityFieldName;
}
@Override
public String identifierFieldName() {
return KEY.getJpaEntityFieldName();
}
}

View File

@@ -35,6 +35,7 @@ import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTarget;
import org.eclipse.hawkbit.mgmt.json.model.targetfilter.MgmtTargetFilterQuery; import org.eclipse.hawkbit.mgmt.json.model.targetfilter.MgmtTargetFilterQuery;
import org.eclipse.hawkbit.rest.json.model.ExceptionInfo; import org.eclipse.hawkbit.rest.json.model.ExceptionInfo;
import org.springframework.hateoas.MediaTypes; import org.springframework.hateoas.MediaTypes;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.DeleteMapping;
@@ -44,6 +45,7 @@ import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
/** /**
* REST Resource handling for DistributionSet CRUD operations. * REST Resource handling for DistributionSet CRUD operations.
@@ -473,15 +475,50 @@ public interface MgmtDistributionSetRestApi {
@RequestBody List<MgmtTargetAssignmentRequestBody> assignments, @RequestBody List<MgmtTargetAssignmentRequestBody> assignments,
@RequestParam(value = "offline", required = false) Boolean offline); @RequestParam(value = "offline", required = false) Boolean offline);
/**
* Creates a list of meta-data for a specific distribution set.
*
* @param distributionSetId the ID of the distribution set to create meta data for
* @param metadataRest the list of meta-data entries to create
*/
@Operation(summary = "Create a list of meta data for a specific distribution set",
description = "Create a list of meta data entries Required permissions: READ_REPOSITORY and UPDATE_TARGET")
@ApiResponses(value = {
@ApiResponse(responseCode = "201", description = "Successfully created"),
@ApiResponse(responseCode = "400", description = "Bad Request - e.g. invalid parameters",
content = @Content(mediaType = "application/json", schema = @Schema(implementation = ExceptionInfo.class))),
@ApiResponse(responseCode = "401", description = "The request requires user authentication.",
content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))),
@ApiResponse(responseCode = "403", description = "Insufficient permissions, entity is not allowed to be " +
"changed (i.e. read-only) or data volume restriction applies.",
content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))),
@ApiResponse(responseCode = "404", description = "Distribution Set not found.",
content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))),
@ApiResponse(responseCode = "405", description = "The http request method is not allowed on the resource.",
content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))),
@ApiResponse(responseCode = "406", description = "In case accept header is specified and not application/json.",
content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))),
@ApiResponse(responseCode = "409", description = "E.g. in case an entity is created or modified by another " +
"user in another request at the same time. You may retry your modification request.",
content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))),
@ApiResponse(responseCode = "415", description = "The request was attempt with a media-type which is not " +
"supported by the server for this resource.",
content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))),
@ApiResponse(responseCode = "429", description = "Too many requests. The server will refuse further attempts " +
"and the client has to wait another second.",
content = @Content(mediaType = "application/json", schema = @Schema(hidden = true)))
})
@PostMapping(value = MgmtRestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING + "/{distributionSetId}/metadata",
consumes = { MediaType.APPLICATION_JSON_VALUE, MediaTypes.HAL_JSON_VALUE })
@ResponseStatus(HttpStatus.CREATED)
void createMetadata(
@PathVariable("distributionSetId") Long distributionSetId,
@RequestBody List<MgmtMetadata> metadataRest);
/** /**
* Gets a paged list of meta-data for a distribution set. * Gets a paged list of meta-data for a distribution set.
* *
* @param distributionSetId the ID of the distribution set for the meta-data * @param distributionSetId the ID of the distribution set for the meta-data
* @param pagingOffsetParam the offset of list of targets for pagination, might not be present in the rest request then default value will
* be applied
* @param pagingLimitParam the limit of the paged request, might not be present in the rest request then default value will be applied
* @param sortParam the sorting parameter in the request URL, syntax {@code field:direction, field:direction}
* @param rsqlParam the search parameter in the request URL, syntax {@code q=key==abc}
* @return status OK if get request is successful with the paged list of meta data * @return status OK if get request is successful with the paged list of meta data
*/ */
@Operation(summary = "Return meta data for Distribution Set", description = "Get a paged list of meta data for a " + @Operation(summary = "Return meta data for Distribution Set", description = "Get a paged list of meta data for a " +
@@ -507,37 +544,14 @@ public interface MgmtDistributionSetRestApi {
}) })
@GetMapping(value = MgmtRestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING + "/{distributionSetId}/metadata", @GetMapping(value = MgmtRestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING + "/{distributionSetId}/metadata",
produces = { MediaTypes.HAL_JSON_VALUE, MediaType.APPLICATION_JSON_VALUE }) produces = { MediaTypes.HAL_JSON_VALUE, MediaType.APPLICATION_JSON_VALUE })
ResponseEntity<PagedList<MgmtMetadata>> getMetadata( ResponseEntity<PagedList<MgmtMetadata>> getMetadata(@PathVariable("distributionSetId") Long distributionSetId);
@PathVariable("distributionSetId") Long distributionSetId,
@RequestParam(
value = MgmtRestConstants.REQUEST_PARAMETER_PAGING_OFFSET,
defaultValue = MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET)
@Schema(description = "The paging offset (default is 0)")
int pagingOffsetParam,
@RequestParam(
value = MgmtRestConstants.REQUEST_PARAMETER_PAGING_LIMIT,
defaultValue = MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT)
@Schema(description = "The maximum number of entries in a page (default is 50)")
int pagingLimitParam,
@RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_SORTING, required = false)
@Schema(description = """
The query parameter sort allows to define the sort order for the result of a query. A sort criteria
consists of the name of a field and the sort direction (ASC for ascending and DESC descending).
The sequence of the sort criteria (multiple can be used) defines the sort order of the entities
in the result.""")
String sortParam,
@RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_SEARCH, required = false)
@Schema(description = """
Query fields based on the Feed Item Query Language (FIQL). See Entity Definitions for
available fields.""")
String rsqlParam);
/** /**
* Gets a single meta data value for a specific key of a distribution set. * Gets a single meta-data value for a specific key of a distribution set.
* *
* @param distributionSetId the ID of the distribution set to get the meta data from * @param distributionSetId the ID of the distribution set to get the meta-data from
* @param metadataKey the key of the meta data entry to retrieve the value from * @param metadataKey the key of the meta-data entry to retrieve the value from
* @return status OK if get request is successful with the value of the meta data * @return status OK with the value of the meta-data, if the request is successful
*/ */
@Operation(summary = "Return single meta data value for a specific key of a Distribution Set", @Operation(summary = "Return single meta data value for a specific key of a Distribution Set",
description = "Get a single meta data value for a meta data key. Required permission: READ_REPOSITORY") description = "Get a single meta data value for a meta data key. Required permission: READ_REPOSITORY")
@@ -567,12 +581,11 @@ public interface MgmtDistributionSetRestApi {
@PathVariable("metadataKey") String metadataKey); @PathVariable("metadataKey") String metadataKey);
/** /**
* Updates a single meta data value of a distribution set. * Updates a single meta-data value of a distribution set.
* *
* @param distributionSetId the ID of the distribution set to update the meta data entry * @param distributionSetId the ID of the distribution set to update the meta data entry
* @param metadataKey the key of the meta data to update the value * @param metadataKey the key of the meta-data to update the value
* @param metadata update body * @param metadata update body
* @return status OK if the update request is successful and the updated meta data result
*/ */
@Operation(summary = "Update single meta data value of a distribution set", description = "Update a single meta " + @Operation(summary = "Update single meta data value of a distribution set", description = "Update a single meta " +
"data value for speficic key. Required permission: UPDATE_REPOSITORY") "data value for speficic key. Required permission: UPDATE_REPOSITORY")
@@ -601,9 +614,9 @@ public interface MgmtDistributionSetRestApi {
"and the client has to wait another second.", "and the client has to wait another second.",
content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))) content = @Content(mediaType = "application/json", schema = @Schema(hidden = true)))
}) })
@PutMapping(value = MgmtRestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING + "/{distributionSetId}/metadata/{metadataKey}", @PutMapping(value = MgmtRestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING + "/{distributionSetId}/metadata/{metadataKey}")
produces = { MediaTypes.HAL_JSON_VALUE, MediaType.APPLICATION_JSON_VALUE }) @ResponseStatus(HttpStatus.OK)
ResponseEntity<MgmtMetadata> updateMetadata( void updateMetadata(
@PathVariable("distributionSetId") Long distributionSetId, @PathVariable("distributionSetId") Long distributionSetId,
@PathVariable("metadataKey") String metadataKey, @PathVariable("metadataKey") String metadataKey,
@RequestBody MgmtMetadataBodyPut metadata); @RequestBody MgmtMetadataBodyPut metadata);
@@ -613,7 +626,6 @@ public interface MgmtDistributionSetRestApi {
* *
* @param distributionSetId the ID of the distribution set to delete the meta data entry * @param distributionSetId the ID of the distribution set to delete the meta data entry
* @param metadataKey the key of the meta data to delete * @param metadataKey the key of the meta data to delete
* @return status OK if the delete request is successful
*/ */
@Operation(summary = "Delete a single meta data entry from the distribution set", description = "Delete a single " + @Operation(summary = "Delete a single meta data entry from the distribution set", description = "Delete a single " +
"meta data. Required permission: UPDATE_REPOSITORY") "meta data. Required permission: UPDATE_REPOSITORY")
@@ -637,51 +649,11 @@ public interface MgmtDistributionSetRestApi {
content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))) content = @Content(mediaType = "application/json", schema = @Schema(hidden = true)))
}) })
@DeleteMapping(value = MgmtRestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING + "/{distributionSetId}/metadata/{metadataKey}") @DeleteMapping(value = MgmtRestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING + "/{distributionSetId}/metadata/{metadataKey}")
ResponseEntity<Void> deleteMetadata( @ResponseStatus(HttpStatus.OK)
void deleteMetadata(
@PathVariable("distributionSetId") Long distributionSetId, @PathVariable("distributionSetId") Long distributionSetId,
@PathVariable("metadataKey") String metadataKey); @PathVariable("metadataKey") String metadataKey);
/**
* Creates a list of meta data for a specific distribution set.
*
* @param distributionSetId the ID of the distribution set to create meta data for
* @param metadataRest the list of meta data entries to create
* @return status created if post request is successful with the value of the created meta data
*/
@Operation(summary = "Create a list of meta data for a specific distribution set", description = "Create a list " +
"of meta data entries Required permissions: READ_REPOSITORY and UPDATE_TARGET")
@ApiResponses(value = {
@ApiResponse(responseCode = "201", description = "Successfully created"),
@ApiResponse(responseCode = "400", description = "Bad Request - e.g. invalid parameters",
content = @Content(mediaType = "application/json", schema = @Schema(implementation = ExceptionInfo.class))),
@ApiResponse(responseCode = "401", description = "The request requires user authentication.",
content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))),
@ApiResponse(responseCode = "403", description = "Insufficient permissions, entity is not allowed to be " +
"changed (i.e. read-only) or data volume restriction applies.",
content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))),
@ApiResponse(responseCode = "404", description = "Distribution Set not found.",
content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))),
@ApiResponse(responseCode = "405", description = "The http request method is not allowed on the resource.",
content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))),
@ApiResponse(responseCode = "406", description = "In case accept header is specified and not application/json.",
content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))),
@ApiResponse(responseCode = "409", description = "E.g. in case an entity is created or modified by another " +
"user in another request at the same time. You may retry your modification request.",
content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))),
@ApiResponse(responseCode = "415", description = "The request was attempt with a media-type which is not " +
"supported by the server for this resource.",
content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))),
@ApiResponse(responseCode = "429", description = "Too many requests. The server will refuse further attempts " +
"and the client has to wait another second.",
content = @Content(mediaType = "application/json", schema = @Schema(hidden = true)))
})
@PostMapping(value = MgmtRestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING + "/{distributionSetId}/metadata",
consumes = { MediaType.APPLICATION_JSON_VALUE, MediaTypes.HAL_JSON_VALUE },
produces = { MediaTypes.HAL_JSON_VALUE, MediaType.APPLICATION_JSON_VALUE })
ResponseEntity<List<MgmtMetadata>> createMetadata(
@PathVariable("distributionSetId") Long distributionSetId,
@RequestBody List<MgmtMetadata> metadataRest);
/** /**
* Assigns a list of software modules to a distribution set. * Assigns a list of software modules to a distribution set.
* *

View File

@@ -16,6 +16,8 @@ import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import lombok.AccessLevel; import lombok.AccessLevel;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
@@ -31,7 +33,6 @@ import org.eclipse.hawkbit.repository.EntityFactory;
import org.eclipse.hawkbit.repository.builder.DistributionSetCreate; import org.eclipse.hawkbit.repository.builder.DistributionSetCreate;
import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult; import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult;
import org.eclipse.hawkbit.repository.model.DistributionSetMetadata;
import org.eclipse.hawkbit.repository.model.MetaData; import org.eclipse.hawkbit.repository.model.MetaData;
import org.eclipse.hawkbit.rest.json.model.ResponseList; import org.eclipse.hawkbit.rest.json.model.ResponseList;
@@ -53,16 +54,6 @@ public final class MgmtDistributionSetMapper {
return sets.stream().map(dsRest -> fromRequest(dsRest, entityFactory)).toList(); return sets.stream().map(dsRest -> fromRequest(dsRest, entityFactory)).toList();
} }
static List<MetaData> fromRequestDsMetadata(final List<MgmtMetadata> metadata, final EntityFactory entityFactory) {
if (metadata == null) {
return Collections.emptyList();
}
return metadata.stream()
.map(metadataRest -> entityFactory.generateDsMetadata(metadataRest.getKey(), metadataRest.getValue()))
.toList();
}
static MgmtDistributionSet toResponse(final DistributionSet distributionSet) { static MgmtDistributionSet toResponse(final DistributionSet distributionSet) {
if (distributionSet == null) { if (distributionSet == null) {
return null; return null;
@@ -100,10 +91,8 @@ public final class MgmtDistributionSetMapper {
response.add(linkTo(methodOn(MgmtDistributionSetTypeRestApi.class) response.add(linkTo(methodOn(MgmtDistributionSetTypeRestApi.class)
.getDistributionSetType(distributionSet.getType().getId())).withRel("type").expand()); .getDistributionSetType(distributionSet.getType().getId())).withRel("type").expand());
response.add(linkTo(methodOn(MgmtDistributionSetRestApi.class).getMetadata(response.getId(), response.add(linkTo(methodOn(MgmtDistributionSetRestApi.class).getMetadata(response.getId()))
MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET_VALUE, .withRel("metadata").expand());
MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT_VALUE, null, null)).withRel("metadata")
.expand());
} }
static MgmtTargetAssignmentResponseBody toResponse(final DistributionSetAssignmentResult dsAssignmentResult) { static MgmtTargetAssignmentResponseBody toResponse(final DistributionSetAssignmentResult dsAssignmentResult) {
@@ -138,19 +127,21 @@ public final class MgmtDistributionSetMapper {
sets.stream().map(MgmtDistributionSetMapper::toResponse).toList()); sets.stream().map(MgmtDistributionSetMapper::toResponse).toList());
} }
static MgmtMetadata toResponseDsMetadata(final DistributionSetMetadata metadata) { static MgmtMetadata toResponseDsMetadata(final String key, String value) {
final MgmtMetadata metadataRest = new MgmtMetadata(); final MgmtMetadata metadataRest = new MgmtMetadata();
metadataRest.setKey(metadata.getKey()); metadataRest.setKey(key);
metadataRest.setValue(metadata.getValue()); metadataRest.setValue(value);
return metadataRest; return metadataRest;
} }
static List<MgmtMetadata> toResponseDsMetadata(final List<DistributionSetMetadata> metadata) { static Map<String, String> fromRequestDsMetadata(final List<MgmtMetadata> metadata) {
final List<MgmtMetadata> mappedList = new ArrayList<>(metadata.size()); return metadata == null
for (final DistributionSetMetadata distributionSetMetadata : metadata) { ? Collections.emptyMap()
mappedList.add(toResponseDsMetadata(distributionSetMetadata)); : metadata.stream().collect(Collectors.toMap(MgmtMetadata::getKey, MgmtMetadata::getValue));
} }
return mappedList;
static List<MgmtMetadata> toResponseDsMetadata(final Map<String, String> metadata) {
return metadata.entrySet().stream().map(e -> toResponseDsMetadata(e.getKey(), e.getValue())).toList();
} }
static List<MgmtDistributionSet> toResponseFromDsList(final List<DistributionSet> sets) { static List<MgmtDistributionSet> toResponseFromDsList(final List<DistributionSet> sets) {

View File

@@ -14,6 +14,7 @@ import java.util.AbstractMap.SimpleEntry;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -54,7 +55,6 @@ import org.eclipse.hawkbit.repository.model.DeploymentRequest;
import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult; import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult;
import org.eclipse.hawkbit.repository.model.DistributionSetInvalidation; import org.eclipse.hawkbit.repository.model.DistributionSetInvalidation;
import org.eclipse.hawkbit.repository.model.DistributionSetMetadata;
import org.eclipse.hawkbit.repository.model.DistributionSetType; import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.SoftwareModule; import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.Target;
@@ -279,64 +279,37 @@ public class MgmtDistributionSetResource implements MgmtDistributionSetRestApi {
} }
@Override @Override
public ResponseEntity<PagedList<MgmtMetadata>> getMetadata( public void createMetadata(final Long distributionSetId, final List<MgmtMetadata> metadataRest) {
final Long distributionSetId, distributionSetManagement.createMetadata(distributionSetId, MgmtDistributionSetMapper.fromRequestDsMetadata(metadataRest));
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);
final Pageable pageable = new OffsetBasedPageRequest(sanitizedOffsetParam, sanitizedLimitParam, sorting); @Override
final Page<DistributionSetMetadata> metaDataPage; public ResponseEntity<PagedList<MgmtMetadata>> getMetadata(final Long distributionSetId) {
final Map<String, String> metadata = distributionSetManagement.getMetadata(distributionSetId);
if (rsqlParam != null) { return ResponseEntity.ok(new PagedList<>(MgmtDistributionSetMapper.toResponseDsMetadata(metadata), metadata.size()));
metaDataPage = distributionSetManagement.findMetaDataByDistributionSetIdAndRsql(distributionSetId, rsqlParam, pageable
);
} else {
metaDataPage = distributionSetManagement.findMetaDataByDistributionSetId(distributionSetId, pageable);
}
return ResponseEntity
.ok(new PagedList<>(MgmtDistributionSetMapper.toResponseDsMetadata(metaDataPage.getContent()), metaDataPage.getTotalElements()));
} }
@Override @Override
public ResponseEntity<MgmtMetadata> getMetadataValue(final Long distributionSetId, final String metadataKey) { public ResponseEntity<MgmtMetadata> getMetadataValue(final Long distributionSetId, final String metadataKey) {
// check if distribution set exists otherwise throw exception immediately final String metadataValue = distributionSetManagement.getMetadata(distributionSetId).get(metadataKey);
final DistributionSetMetadata findOne = distributionSetManagement if (metadataValue == null) {
.findMetaDataByDistributionSetId(distributionSetId, metadataKey) throw new EntityNotFoundException("Target metadata", distributionSetId + ":" + metadataKey);
.orElseThrow(() -> new EntityNotFoundException(DistributionSetMetadata.class, distributionSetId, metadataKey)); }
return ResponseEntity.ok(MgmtDistributionSetMapper.toResponseDsMetadata(findOne)); return ResponseEntity.ok(MgmtDistributionSetMapper.toResponseDsMetadata(metadataKey, metadataValue));
} }
@Override @Override
public ResponseEntity<MgmtMetadata> updateMetadata( public void updateMetadata(final Long distributionSetId, final String metadataKey, final MgmtMetadataBodyPut metadata) {
final Long distributionSetId, final String metadataKey, final MgmtMetadataBodyPut metadata) { distributionSetManagement.updateMetadata(distributionSetId, metadataKey, metadata.getValue());
// check if distribution set exists otherwise throw exception immediately
final DistributionSetMetadata updated = distributionSetManagement.updateMetaData(distributionSetId,
entityFactory.generateDsMetadata(metadataKey, metadata.getValue()));
return ResponseEntity.ok(MgmtDistributionSetMapper.toResponseDsMetadata(updated));
} }
@Override @Override
public ResponseEntity<Void> deleteMetadata(final Long distributionSetId, final String metadataKey) { public void deleteMetadata(final Long distributionSetId, final String metadataKey) {
// check if distribution set exists otherwise throw exception immediately distributionSetManagement.deleteMetadata(distributionSetId, metadataKey);
distributionSetManagement.deleteMetaData(distributionSetId, metadataKey);
return ResponseEntity.ok().build();
} }
@Override @Override
public ResponseEntity<List<MgmtMetadata>> createMetadata(final Long distributionSetId, final List<MgmtMetadata> metadataRest) { public ResponseEntity<Void> assignSoftwareModules(final Long distributionSetId, final List<MgmtSoftwareModuleAssignment> softwareModuleIDs) {
// check if distribution set exists otherwise throw exception immediately
final List<DistributionSetMetadata> created = distributionSetManagement.putMetaData(distributionSetId,
MgmtDistributionSetMapper.fromRequestDsMetadata(metadataRest, entityFactory));
return new ResponseEntity<>(MgmtDistributionSetMapper.toResponseDsMetadata(created), HttpStatus.CREATED);
}
@Override
public ResponseEntity<Void> assignSoftwareModules(
final Long distributionSetId, final List<MgmtSoftwareModuleAssignment> softwareModuleIDs) {
distributionSetManagement.assignSoftwareModules( distributionSetManagement.assignSoftwareModules(
distributionSetId, distributionSetId,
softwareModuleIDs.stream() softwareModuleIDs.stream()

View File

@@ -15,7 +15,6 @@ import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
import org.eclipse.hawkbit.repository.ActionFields; import org.eclipse.hawkbit.repository.ActionFields;
import org.eclipse.hawkbit.repository.ActionStatusFields; import org.eclipse.hawkbit.repository.ActionStatusFields;
import org.eclipse.hawkbit.repository.DistributionSetFields; import org.eclipse.hawkbit.repository.DistributionSetFields;
import org.eclipse.hawkbit.repository.DistributionSetMetadataFields;
import org.eclipse.hawkbit.repository.DistributionSetTypeFields; import org.eclipse.hawkbit.repository.DistributionSetTypeFields;
import org.eclipse.hawkbit.repository.RolloutFields; import org.eclipse.hawkbit.repository.RolloutFields;
import org.eclipse.hawkbit.repository.RolloutGroupFields; import org.eclipse.hawkbit.repository.RolloutGroupFields;
@@ -133,12 +132,12 @@ public final class PagingUtility {
return Sort.by(SortUtility.parse(ActionStatusFields.class, sortParam)); return Sort.by(SortUtility.parse(ActionStatusFields.class, sortParam));
} }
public static Sort sanitizeDistributionSetMetadataSortParam(final String sortParam) { public static Sort sanitizeMetadataSortParam(final String sortParam) {
if (sortParam == null) { if (sortParam == null) {
// default // default
return Sort.by(Direction.ASC, DistributionSetMetadataFields.KEY.getJpaEntityFieldName()); return Sort.by(Direction.ASC, SoftwareModuleMetadataFields.KEY.getJpaEntityFieldName());
} }
return Sort.by(SortUtility.parse(DistributionSetMetadataFields.class, sortParam)); return Sort.by(SortUtility.parse(SoftwareModuleMetadataFields.class, sortParam));
} }
public static Sort sanitizeSoftwareModuleMetadataSortParam(final String sortParam) { public static Sort sanitizeSoftwareModuleMetadataSortParam(final String sortParam) {

View File

@@ -30,6 +30,7 @@ import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.stream.IntStream; import java.util.stream.IntStream;
@@ -51,7 +52,6 @@ import org.eclipse.hawkbit.repository.jpa.specifications.ActionSpecifications;
import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.Status; import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetMetadata;
import org.eclipse.hawkbit.repository.model.DistributionSetType; import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.NamedEntity; import org.eclipse.hawkbit.repository.model.NamedEntity;
import org.eclipse.hawkbit.repository.model.Rollout; import org.eclipse.hawkbit.repository.model.Rollout;
@@ -890,20 +890,20 @@ class MgmtDistributionSetResourceTest extends AbstractManagementApiIntegrationTe
.getWithDetails(distributionSetManagement.findByRsql("name==three", PAGE).getContent().get(0).getId()) .getWithDetails(distributionSetManagement.findByRsql("name==three", PAGE).getContent().get(0).getId())
.get(); .get();
assertThat((Object)JsonPath.compile("[0]_links.self.href").read(mvcResult.getResponse().getContentAsString())) assertThat((Object) JsonPath.compile("[0]_links.self.href").read(mvcResult.getResponse().getContentAsString()))
.hasToString("http://localhost/rest/v1/distributionsets/" + one.getId()); .hasToString("http://localhost/rest/v1/distributionsets/" + one.getId());
assertThat((Object)JsonPath.compile("[0]id").read(mvcResult.getResponse().getContentAsString())) assertThat((Object) JsonPath.compile("[0]id").read(mvcResult.getResponse().getContentAsString()))
.hasToString(String.valueOf(one.getId())); .hasToString(String.valueOf(one.getId()));
assertThat((Object)JsonPath.compile("[1]_links.self.href").read(mvcResult.getResponse().getContentAsString())) assertThat((Object) JsonPath.compile("[1]_links.self.href").read(mvcResult.getResponse().getContentAsString()))
.hasToString("http://localhost/rest/v1/distributionsets/" + two.getId()); .hasToString("http://localhost/rest/v1/distributionsets/" + two.getId());
assertThat((Object)JsonPath.compile("[1]id").read(mvcResult.getResponse().getContentAsString())) assertThat((Object) JsonPath.compile("[1]id").read(mvcResult.getResponse().getContentAsString()))
.hasToString(String.valueOf(two.getId())); .hasToString(String.valueOf(two.getId()));
assertThat((Object)JsonPath.compile("[2]_links.self.href").read(mvcResult.getResponse().getContentAsString())) assertThat((Object) JsonPath.compile("[2]_links.self.href").read(mvcResult.getResponse().getContentAsString()))
.hasToString("http://localhost/rest/v1/distributionsets/" + three.getId()); .hasToString("http://localhost/rest/v1/distributionsets/" + three.getId());
assertThat((Object)JsonPath.compile("[2]id").read(mvcResult.getResponse().getContentAsString())) assertThat((Object) JsonPath.compile("[2]id").read(mvcResult.getResponse().getContentAsString()))
.hasToString(String.valueOf(three.getId())); .hasToString(String.valueOf(three.getId()));
// check in database // check in database
@@ -1105,23 +1105,14 @@ class MgmtDistributionSetResourceTest extends AbstractManagementApiIntegrationTe
metaData1.put(new JSONObject().put("key", knownKey1).put("value", knownValue1)); metaData1.put(new JSONObject().put("key", knownKey1).put("value", knownValue1));
metaData1.put(new JSONObject().put("key", knownKey2).put("value", knownValue2)); metaData1.put(new JSONObject().put("key", knownKey2).put("value", knownValue2));
mvc.perform(post("/rest/v1/distributionsets/{dsId}/metadata", testDS.getId()).accept(MediaType.APPLICATION_JSON) mvc.perform(post("/rest/v1/distributionsets/{dsId}/metadata", testDS.getId())
.contentType(MediaType.APPLICATION_JSON).content(metaData1.toString())) .contentType(MediaType.APPLICATION_JSON)
.content(metaData1.toString()))
.andDo(MockMvcResultPrinter.print()) .andDo(MockMvcResultPrinter.print())
.andExpect(status().isCreated()) .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)));
final DistributionSetMetadata metaKey1 = distributionSetManagement assertThat(distributionSetManagement.getMetadata(testDS.getId()).get(knownKey1)).isEqualTo(knownValue1);
.findMetaDataByDistributionSetId(testDS.getId(), knownKey1).get(); assertThat(distributionSetManagement.getMetadata(testDS.getId()).get(knownKey2)).isEqualTo(knownValue2);
final DistributionSetMetadata metaKey2 = distributionSetManagement
.findMetaDataByDistributionSetId(testDS.getId(), knownKey2).get();
assertThat(metaKey1.getValue()).isEqualTo(knownValue1);
assertThat(metaKey2.getValue()).isEqualTo(knownValue2);
// verify quota enforcement // verify quota enforcement
final int maxMetaData = quotaManagement.getMaxMetaDataEntriesPerDistributionSet(); final int maxMetaData = quotaManagement.getMaxMetaDataEntriesPerDistributionSet();
@@ -1131,17 +1122,14 @@ class MgmtDistributionSetResourceTest extends AbstractManagementApiIntegrationTe
metaData2.put(new JSONObject().put("key", knownKey1 + i).put("value", knownValue1 + i)); metaData2.put(new JSONObject().put("key", knownKey1 + i).put("value", knownValue1 + i));
} }
mvc.perform(post("/rest/v1/distributionsets/{dsId}/metadata", testDS.getId()).accept(MediaType.APPLICATION_JSON) mvc.perform(post("/rest/v1/distributionsets/{dsId}/metadata", testDS.getId())
.contentType(MediaType.APPLICATION_JSON).content(metaData2.toString())) .contentType(MediaType.APPLICATION_JSON)
.content(metaData2.toString()))
.andDo(MockMvcResultPrinter.print()) .andDo(MockMvcResultPrinter.print())
.andExpect(status().isForbidden()); .andExpect(status().isForbidden());
// verify that the number of meta data entries has not changed // 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)
// (we cannot use the PAGE constant here as it tries to sort by ID) assertThat(distributionSetManagement.getMetadata(testDS.getId())).hasSize(metaData1.length());
assertThat(distributionSetManagement
.findMetaDataByDistributionSetId(testDS.getId(), PageRequest.of(0, Integer.MAX_VALUE))
.getTotalElements()).isEqualTo(metaData1.length());
} }
@Test @Test
@@ -1151,25 +1139,18 @@ class MgmtDistributionSetResourceTest extends AbstractManagementApiIntegrationTe
final String knownKey = "knownKey"; final String knownKey = "knownKey";
final String knownValue = "knownValue"; final String knownValue = "knownValue";
final String updateValue = "valueForUpdate"; final String updateValue = "valueForUpdate";
final DistributionSet testDS = testdataFactory.createDistributionSet("one"); final DistributionSet testDS = testdataFactory.createDistributionSet("one");
createDistributionSetMetadata(testDS.getId(), entityFactory.generateDsMetadata(knownKey, knownValue)); distributionSetManagement.createMetadata(testDS.getId(), Map.of(knownKey, knownValue));
final JSONObject jsonObject = new JSONObject().put("key", knownKey).put("value", updateValue); final JSONObject jsonObject = new JSONObject().put("key", knownKey).put("value", updateValue);
mvc.perform(put("/rest/v1/distributionsets/{dsId}/metadata/{key}", testDS.getId(), knownKey) mvc.perform(put("/rest/v1/distributionsets/{dsId}/metadata/{key}", testDS.getId(), knownKey)
.accept(MediaType.APPLICATION_JSON).contentType(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON)
.content(jsonObject.toString())) .content(jsonObject.toString()))
.andDo(MockMvcResultPrinter.print()) .andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk()) .andExpect(status().isOk());
.andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE))
.andExpect(jsonPath("key", equalTo(knownKey)))
.andExpect(jsonPath("value", equalTo(updateValue)));
final DistributionSetMetadata assertDS = distributionSetManagement
.findMetaDataByDistributionSetId(testDS.getId(), knownKey).get();
assertThat(assertDS.getValue()).isEqualTo(updateValue);
assertThat(distributionSetManagement.getMetadata(testDS.getId()).get(knownKey)).isEqualTo(updateValue);
} }
@Test @Test
@@ -1180,13 +1161,18 @@ class MgmtDistributionSetResourceTest extends AbstractManagementApiIntegrationTe
final String knownValue = "knownValue"; final String knownValue = "knownValue";
final DistributionSet testDS = testdataFactory.createDistributionSet("one"); final DistributionSet testDS = testdataFactory.createDistributionSet("one");
createDistributionSetMetadata(testDS.getId(), entityFactory.generateDsMetadata(knownKey, knownValue)); distributionSetManagement.createMetadata(testDS.getId(), Map.of(knownKey, knownValue));
mvc.perform(delete("/rest/v1/distributionsets/{dsId}/metadata/{key}", testDS.getId(), knownKey)) mvc.perform(delete("/rest/v1/distributionsets/{dsId}/metadata/{key}", testDS.getId(), knownKey))
.andDo(MockMvcResultPrinter.print()) .andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk()); .andExpect(status().isOk());
assertThat(distributionSetManagement.findMetaDataByDistributionSetId(testDS.getId(), knownKey)).isNotPresent(); // already deleted
mvc.perform(delete("/rest/v1/distributionsets/{dsId}/metadata/{key}", testDS.getId(), knownKey))
.andDo(MockMvcResultPrinter.print())
.andExpect(status().isNotFound());
assertThat(distributionSetManagement.getMetadata(testDS.getId()).get(knownKey)).isNull();
} }
@Test @Test
@@ -1195,9 +1181,8 @@ class MgmtDistributionSetResourceTest extends AbstractManagementApiIntegrationTe
// prepare and create metadata for deletion // prepare and create metadata for deletion
final String knownKey = "knownKey"; final String knownKey = "knownKey";
final String knownValue = "knownValue"; final String knownValue = "knownValue";
final DistributionSet testDS = testdataFactory.createDistributionSet("one"); final DistributionSet testDS = testdataFactory.createDistributionSet("one");
createDistributionSetMetadata(testDS.getId(), entityFactory.generateDsMetadata(knownKey, knownValue)); distributionSetManagement.createMetadata(testDS.getId(), Map.of(knownKey, knownValue));
mvc.perform(delete("/rest/v1/distributionsets/{dsId}/metadata/XXX", testDS.getId(), knownKey)) mvc.perform(delete("/rest/v1/distributionsets/{dsId}/metadata/XXX", testDS.getId(), knownKey))
.andDo(MockMvcResultPrinter.print()) .andDo(MockMvcResultPrinter.print())
@@ -1207,17 +1192,17 @@ class MgmtDistributionSetResourceTest extends AbstractManagementApiIntegrationTe
.andDo(MockMvcResultPrinter.print()) .andDo(MockMvcResultPrinter.print())
.andExpect(status().isNotFound()); .andExpect(status().isNotFound());
assertThat(distributionSetManagement.findMetaDataByDistributionSetId(testDS.getId(), knownKey)).isPresent(); assertThat(distributionSetManagement.getMetadata(testDS.getId()).get(knownKey)).isNotNull();
} }
@Test @Test
@Description("Ensures that a metadata entry selection through API reflects the repository content.") @Description("Ensures that a metadata entry selection through API reflects the repository content.")
void geteMetadataKey() throws Exception { void getMetadataKey() throws Exception {
// prepare and create metadata // prepare and create metadata
final String knownKey = "knownKey"; final String knownKey = "knownKey";
final String knownValue = "knownValue"; final String knownValue = "knownValue";
final DistributionSet testDS = testdataFactory.createDistributionSet("one"); final DistributionSet testDS = testdataFactory.createDistributionSet("one");
createDistributionSetMetadata(testDS.getId(), entityFactory.generateDsMetadata(knownKey, knownValue)); distributionSetManagement.createMetadata(testDS.getId(), Map.of(knownKey, knownValue));
mvc.perform(get("/rest/v1/distributionsets/{dsId}/metadata/{key}", testDS.getId(), knownKey)) mvc.perform(get("/rest/v1/distributionsets/{dsId}/metadata/{key}", testDS.getId(), knownKey))
.andDo(MockMvcResultPrinter.print()) .andDo(MockMvcResultPrinter.print())
@@ -1234,43 +1219,15 @@ class MgmtDistributionSetResourceTest extends AbstractManagementApiIntegrationTe
final String knownValuePrefix = "knownValue"; final String knownValuePrefix = "knownValue";
final DistributionSet testDS = testdataFactory.createDistributionSet("one"); final DistributionSet testDS = testdataFactory.createDistributionSet("one");
for (int index = 0; index < totalMetadata; index++) { for (int index = 0; index < totalMetadata; index++) {
distributionSetManagement.putMetaData(testDS.getId(), distributionSetManagement.createMetadata(testDS.getId(), Map.of(knownKeyPrefix + index, knownValuePrefix + index));
List.of(entityFactory.generateDsMetadata(knownKeyPrefix + index, knownValuePrefix + index)));
} }
mvc.perform(get(MgmtRestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING + "/{distributionSetId}/metadata", mvc.perform(get(MgmtRestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING + "/{distributionSetId}/metadata", testDS.getId()))
testDS.getId()))
.andDo(MockMvcResultPrinter.print()) .andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(content().contentType(MediaTypes.HAL_JSON)); .andExpect(content().contentType(MediaTypes.HAL_JSON));
} }
@Test
@Description("Ensures that a metadata entry paged list selection through API reflectes the repository content.")
void getPagedListOfMetadata() throws Exception {
final int totalMetadata = 10;
final int limitParam = 5;
final String offsetParam = "0";
final String knownKeyPrefix = "knownKey";
final String knownValuePrefix = "knownValue";
final DistributionSet testDS = testdataFactory.createDistributionSet("one");
for (int index = 0; index < totalMetadata; index++) {
createDistributionSetMetadata(testDS.getId(),
entityFactory.generateDsMetadata(knownKeyPrefix + index, knownValuePrefix + index));
}
mvc.perform(get("/rest/v1/distributionsets/{dsId}/metadata?offset=" + offsetParam + "&limit=" + limitParam,
testDS.getId()))
.andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk())
.andExpect(jsonPath("size", equalTo(limitParam)))
.andExpect(jsonPath("total", equalTo(totalMetadata)))
.andExpect(jsonPath("content[0].key", equalTo("knownKey0")))
.andExpect(jsonPath("content[0].value", equalTo("knownValue0")));
}
@Test @Test
@Description("Ensures that a DS search with query parameters returns the expected result.") @Description("Ensures that a DS search with query parameters returns the expected result.")
void searchDistributionSetRsql() throws Exception { void searchDistributionSetRsql() throws Exception {
@@ -1334,29 +1291,6 @@ class MgmtDistributionSetResourceTest extends AbstractManagementApiIntegrationTe
.andExpect(jsonPath("content[0].controllerId", equalTo("1"))); .andExpect(jsonPath("content[0].controllerId", equalTo("1")));
} }
@Test
@Description("Ensures that a DS metadata filtered query with value==knownValue1 parameter returns only the metadata entries with that value.")
void searchDistributionSetMetadataRsql() throws Exception {
final int totalMetadata = 10;
final String knownKeyPrefix = "knownKey";
final String knownValuePrefix = "knownValue";
final DistributionSet testDS = testdataFactory.createDistributionSet("one");
for (int index = 0; index < totalMetadata; index++) {
createDistributionSetMetadata(testDS.getId(),
entityFactory.generateDsMetadata(knownKeyPrefix + index, knownValuePrefix + index));
}
final String rsqlSearchValue1 = "value==knownValue1";
mvc.perform(get("/rest/v1/distributionsets/{dsId}/metadata?q=" + rsqlSearchValue1, testDS.getId()))
.andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk())
.andExpect(jsonPath("size", equalTo(1)))
.andExpect(jsonPath("total", equalTo(1)))
.andExpect(jsonPath("content[0].key", equalTo("knownKey1")))
.andExpect(jsonPath("content[0].value", equalTo("knownValue1")));
}
@Test @Test
@Description("Ensures that multi target assignment through API is reflected by the repository in the case of " @Description("Ensures that multi target assignment through API is reflected by the repository in the case of "
+ "DOWNLOAD_ONLY.") + "DOWNLOAD_ONLY.")

View File

@@ -11,6 +11,7 @@ package org.eclipse.hawkbit.repository;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Optional; import java.util.Optional;
import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotEmpty;
@@ -30,7 +31,6 @@ import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldExc
import org.eclipse.hawkbit.repository.exception.UnsupportedSoftwareModuleForThisDistributionSetException; import org.eclipse.hawkbit.repository.exception.UnsupportedSoftwareModuleForThisDistributionSetException;
import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetFilter; import org.eclipse.hawkbit.repository.model.DistributionSetFilter;
import org.eclipse.hawkbit.repository.model.DistributionSetMetadata;
import org.eclipse.hawkbit.repository.model.DistributionSetTag; import org.eclipse.hawkbit.repository.model.DistributionSetTag;
import org.eclipse.hawkbit.repository.model.MetaData; import org.eclipse.hawkbit.repository.model.MetaData;
import org.eclipse.hawkbit.repository.model.SoftwareModule; import org.eclipse.hawkbit.repository.model.SoftwareModule;
@@ -124,29 +124,38 @@ public interface DistributionSetManagement extends RepositoryManagement<Distribu
List<DistributionSet> unassignTag(@NotEmpty Collection<Long> ids, long tagId); List<DistributionSet> unassignTag(@NotEmpty Collection<Long> ids, long tagId);
/** /**
* Creates a list of distribution set meta-data entries. * Creates a map of distribution set meta-data entries.
* *
* @param id if the {@link DistributionSet} the metadata has to be created for * @param id if the {@link DistributionSet} the metadata has to be created for
* @param metadata the meta-data entries to create or update * @param metadata the meta-data entries to create or update
* @return the updated or created distribution set meta-data entries
* @throws EntityNotFoundException if given set does not exist * @throws EntityNotFoundException if given set does not exist
* @throws EntityAlreadyExistsException in case one of the meta-data entry already exists for the specific key * @throws EntityAlreadyExistsException in case one of the meta-data entry already exists for the specific key
* @throws AssignmentQuotaExceededException if the maximum number of {@link MetaData} entries is exceeded for the addressed * @throws AssignmentQuotaExceededException if the maximum number of {@link MetaData} entries is exceeded for the addressed
* {@link DistributionSet} * {@link DistributionSet}
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY)
List<DistributionSetMetadata> putMetaData(long id, @NotEmpty Collection<MetaData> metadata); void createMetadata(long id, @NotEmpty Map<String, String> metadata);
/** /**
* Updates a distribution set meta-data value if corresponding entry exists. * Finds all meta-data by the given distribution set id.
*
* @param id the distribution set id to retrieve the meta-data from
* @return a paged result of all meta-data entries for a given distribution set id
* @throws EntityNotFoundException if distribution set with given ID does not exist
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY)
Map<String, String> getMetadata(long id);
/**
* Updates a distribution set meta-data values by adding them.
* *
* @param id {@link DistributionSet} of the meta-data entry to be updated * @param id {@link DistributionSet} of the meta-data entry to be updated
* @param metadata meta-data entry to be updated * @param key meta data-entry key to be updated
* @return the updated meta-data entry * @param value meta data-entry to be new value
* @throws EntityNotFoundException in case the meta-data entry does not exist and cannot be updated * @throws EntityNotFoundException in case the meta-data entry does not exist and cannot be updated
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY)
DistributionSetMetadata updateMetaData(long id, @NotNull MetaData metadata); void updateMetadata(long id, @NotNull String key, @NotNull String value);
/** /**
* Deletes a distribution set meta-data entry. * Deletes a distribution set meta-data entry.
@@ -156,7 +165,7 @@ public interface DistributionSetManagement extends RepositoryManagement<Distribu
* @throws EntityNotFoundException if given set does not exist * @throws EntityNotFoundException if given set does not exist
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY)
void deleteMetaData(long id, @NotEmpty String key); void deleteMetadata(long id, @NotEmpty String key);
/** /**
* Locks a distribution set. From then on its functional properties could not be changed, and it could be assigned to targets * Locks a distribution set. From then on its functional properties could not be changed, and it could be assigned to targets
@@ -221,33 +230,6 @@ public interface DistributionSetManagement extends RepositoryManagement<Distribu
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY)
Optional<DistributionSet> findByNameAndVersion(@NotEmpty String distributionName, @NotEmpty String version); Optional<DistributionSet> findByNameAndVersion(@NotEmpty String distributionName, @NotEmpty String version);
/**
* Finds all meta-data by the given distribution set id.
*
* @param id the distribution set id to retrieve the meta-data from
* @param pageable the page request to page the result
* @return a paged result of all meta-data entries for a given distribution
* set id
* @throws EntityNotFoundException if distribution set with given ID does not exist
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY)
Page<DistributionSetMetadata> findMetaDataByDistributionSetId(long id, @NotNull Pageable pageable);
/**
* Finds all meta-data by the given distribution set id.
*
* @param id the distribution set id to retrieve the meta-data from
* @param rsqlParam rsql query string
* @param pageable the page request to page the result
* @return a paged result of all meta-data entries for a given distribution set id
* @throws RSQLParameterUnsupportedFieldException if a field in the RSQL string is used but not provided by the given
* {@code fieldNameProvider}
* @throws RSQLParameterSyntaxException if the RSQL syntax is wrong
* @throws EntityNotFoundException of distribution set with given ID does not exist
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY)
Page<DistributionSetMetadata> findMetaDataByDistributionSetIdAndRsql(long id, @NotNull String rsqlParam, @NotNull Pageable pageable);
/** /**
* Finds all {@link DistributionSet}s based on completeness. * Finds all {@link DistributionSet}s based on completeness.
* *
@@ -295,25 +277,6 @@ public interface DistributionSetManagement extends RepositoryManagement<Distribu
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY)
Page<DistributionSet> findByRsqlAndTag(@NotNull String rsqlParam, long tagId, @NotNull Pageable pageable); Page<DistributionSet> findByRsqlAndTag(@NotNull String rsqlParam, long tagId, @NotNull Pageable pageable);
/**
* Finds a single distribution set meta-data by its id.
*
* @param id of the {@link DistributionSet}
* @param key of the meta-data element
* @return the found DistributionSetMetadata
* @throws EntityNotFoundException is set with given ID does not exist
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY)
Optional<DistributionSetMetadata> findMetaDataByDistributionSetId(long id, @NotEmpty String key);/**
* Counts all meta-data by the given distribution set id.
*
* @param id the distribution set id to retrieve the meta-data count from
* @return count of ds metadata
* @throws EntityNotFoundException if distribution set with given ID does not exist
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY)
long countMetaDataByDistributionSetId(long id);
/** /**
* Counts all {@link DistributionSet}s based on completeness. * Counts all {@link DistributionSet}s based on completeness.
* *

View File

@@ -9,9 +9,6 @@
*/ */
package org.eclipse.hawkbit.repository; package org.eclipse.hawkbit.repository;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import org.eclipse.hawkbit.repository.builder.ActionStatusBuilder; import org.eclipse.hawkbit.repository.builder.ActionStatusBuilder;
import org.eclipse.hawkbit.repository.builder.DistributionSetBuilder; import org.eclipse.hawkbit.repository.builder.DistributionSetBuilder;
import org.eclipse.hawkbit.repository.builder.DistributionSetTypeBuilder; import org.eclipse.hawkbit.repository.builder.DistributionSetTypeBuilder;
@@ -43,17 +40,6 @@ public interface EntityFactory {
*/ */
DistributionSetBuilder distributionSet(); DistributionSetBuilder distributionSet();
/**
* Generates an {@link MetaData} element for distribution set without
* persisting it.
*
* @param key {@link MetaData#getKey()}
* @param value {@link MetaData#getValue()}
* @return {@link MetaData} object
*/
MetaData generateDsMetadata(@Size(min = 1, max = MetaData.KEY_MAX_SIZE) @NotNull String key,
@Size(max = MetaData.VALUE_MAX_SIZE) String value);
/** /**
* @return {@link SoftwareModuleMetadataBuilder} object * @return {@link SoftwareModuleMetadataBuilder} object
*/ */

View File

@@ -23,6 +23,16 @@ import java.util.Set;
*/ */
public interface DistributionSet extends NamedVersionedEntity { public interface DistributionSet extends NamedVersionedEntity {
/**
* Maximum length of metadata key.
*/
int METADATA_KEY_SIZE = 128;
/**
* Maximum length of metadata value.
*/
int METADATA_VALUE_SIZE = 4000;
/** /**
* @return type of the {@link DistributionSet}. * @return type of the {@link DistributionSet}.
*/ */

View File

@@ -1,26 +0,0 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.repository.model;
/**
* {@link MetaData} of a {@link DistributionSet}.
*/
public interface DistributionSetMetadata extends MetaData {
/**
* @return {@link DistributionSet} of this {@link MetaData} entry.
*/
DistributionSet getDistributionSet();
@Override
default Long getEntityId() {
return getDistributionSet().getId();
}
}

View File

@@ -26,7 +26,6 @@ import org.eclipse.hawkbit.repository.jpa.builder.JpaActionStatusBuilder;
import org.eclipse.hawkbit.repository.jpa.builder.JpaRolloutGroupBuilder; import org.eclipse.hawkbit.repository.jpa.builder.JpaRolloutGroupBuilder;
import org.eclipse.hawkbit.repository.jpa.builder.JpaSoftwareModuleTypeBuilder; import org.eclipse.hawkbit.repository.jpa.builder.JpaSoftwareModuleTypeBuilder;
import org.eclipse.hawkbit.repository.jpa.builder.JpaTagBuilder; import org.eclipse.hawkbit.repository.jpa.builder.JpaTagBuilder;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetMetadata;
import org.eclipse.hawkbit.repository.model.MetaData; import org.eclipse.hawkbit.repository.model.MetaData;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
@@ -71,11 +70,6 @@ public class JpaEntityFactory implements EntityFactory {
return distributionSetBuilder; return distributionSetBuilder;
} }
@Override
public MetaData generateDsMetadata(final String key, final String value) {
return new JpaDistributionSetMetadata(key, value == null ? null : value.strip());
}
@Override @Override
public SoftwareModuleMetadataBuilder softwareModuleMetadata() { public SoftwareModuleMetadataBuilder softwareModuleMetadata() {
return softwareModuleMetadataBuilder; return softwareModuleMetadataBuilder;

View File

@@ -119,7 +119,6 @@ import org.eclipse.hawkbit.repository.jpa.model.helper.SecurityTokenGeneratorHol
import org.eclipse.hawkbit.repository.jpa.model.helper.TenantAwareHolder; import org.eclipse.hawkbit.repository.jpa.model.helper.TenantAwareHolder;
import org.eclipse.hawkbit.repository.jpa.repository.ActionRepository; import org.eclipse.hawkbit.repository.jpa.repository.ActionRepository;
import org.eclipse.hawkbit.repository.jpa.repository.ActionStatusRepository; import org.eclipse.hawkbit.repository.jpa.repository.ActionStatusRepository;
import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetMetadataRepository;
import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetRepository; import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetRepository;
import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetTagRepository; import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetTagRepository;
import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetTypeRepository; import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetTypeRepository;
@@ -536,7 +535,6 @@ public class RepositoryApplicationConfiguration {
final DistributionSetRepository distributionSetRepository, final DistributionSetRepository distributionSetRepository,
final DistributionSetTagManagement distributionSetTagManagement, final SystemManagement systemManagement, final DistributionSetTagManagement distributionSetTagManagement, final SystemManagement systemManagement,
final DistributionSetTypeManagement distributionSetTypeManagement, final QuotaManagement quotaManagement, final DistributionSetTypeManagement distributionSetTypeManagement, final QuotaManagement quotaManagement,
final DistributionSetMetadataRepository distributionSetMetadataRepository,
final TargetRepository targetRepository, final TargetRepository targetRepository,
final TargetFilterQueryRepository targetFilterQueryRepository, final ActionRepository actionRepository, final TargetFilterQueryRepository targetFilterQueryRepository, final ActionRepository actionRepository,
final SystemSecurityContext systemSecurityContext, final TenantConfigurationManagement tenantConfigurationManagement, final SystemSecurityContext systemSecurityContext, final TenantConfigurationManagement tenantConfigurationManagement,
@@ -544,7 +542,7 @@ public class RepositoryApplicationConfiguration {
final DistributionSetTagRepository distributionSetTagRepository, final DistributionSetTagRepository distributionSetTagRepository,
final JpaProperties properties, final RepositoryProperties repositoryProperties) { final JpaProperties properties, final RepositoryProperties repositoryProperties) {
return new JpaDistributionSetManagement(entityManager, distributionSetRepository, distributionSetTagManagement, return new JpaDistributionSetManagement(entityManager, distributionSetRepository, distributionSetTagManagement,
systemManagement, distributionSetTypeManagement, quotaManagement, distributionSetMetadataRepository, systemManagement, distributionSetTypeManagement, quotaManagement,
targetRepository, targetFilterQueryRepository, actionRepository, targetRepository, targetFilterQueryRepository, actionRepository,
TenantConfigHelper.usingContext(systemSecurityContext, tenantConfigurationManagement), TenantConfigHelper.usingContext(systemSecurityContext, tenantConfigurationManagement),
virtualPropertyReplacer, softwareModuleRepository, distributionSetTagRepository, virtualPropertyReplacer, softwareModuleRepository, distributionSetTagRepository,

View File

@@ -12,9 +12,9 @@ package org.eclipse.hawkbit.repository.jpa.management;
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.IMPLICIT_LOCK_ENABLED; import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.IMPLICIT_LOCK_ENABLED;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
@@ -24,11 +24,15 @@ import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import jakarta.persistence.EntityManager; import jakarta.persistence.EntityManager;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.MapJoin;
import jakarta.persistence.criteria.Root;
import jakarta.persistence.metamodel.MapAttribute;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import org.eclipse.hawkbit.repository.DistributionSetFields; import org.eclipse.hawkbit.repository.DistributionSetFields;
import org.eclipse.hawkbit.repository.DistributionSetManagement; import org.eclipse.hawkbit.repository.DistributionSetManagement;
import org.eclipse.hawkbit.repository.DistributionSetMetadataFields;
import org.eclipse.hawkbit.repository.DistributionSetTagManagement; import org.eclipse.hawkbit.repository.DistributionSetTagManagement;
import org.eclipse.hawkbit.repository.DistributionSetTypeManagement; import org.eclipse.hawkbit.repository.DistributionSetTypeManagement;
import org.eclipse.hawkbit.repository.QuotaManagement; import org.eclipse.hawkbit.repository.QuotaManagement;
@@ -49,14 +53,10 @@ import org.eclipse.hawkbit.repository.jpa.acm.AccessController;
import org.eclipse.hawkbit.repository.jpa.builder.JpaDistributionSetCreate; import org.eclipse.hawkbit.repository.jpa.builder.JpaDistributionSetCreate;
import org.eclipse.hawkbit.repository.jpa.configuration.Constants; import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
import org.eclipse.hawkbit.repository.jpa.model.AbstractJpaBaseEntity_; import org.eclipse.hawkbit.repository.jpa.model.AbstractJpaBaseEntity_;
import org.eclipse.hawkbit.repository.jpa.model.DsMetadataCompositeKey;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet; import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetMetadata;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetMetadata_;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet_; import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet_;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule; import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule;
import org.eclipse.hawkbit.repository.jpa.repository.ActionRepository; import org.eclipse.hawkbit.repository.jpa.repository.ActionRepository;
import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetMetadataRepository;
import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetRepository; import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetRepository;
import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetTagRepository; import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetTagRepository;
import org.eclipse.hawkbit.repository.jpa.repository.SoftwareModuleRepository; import org.eclipse.hawkbit.repository.jpa.repository.SoftwareModuleRepository;
@@ -69,10 +69,8 @@ import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper;
import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetFilter; import org.eclipse.hawkbit.repository.model.DistributionSetFilter;
import org.eclipse.hawkbit.repository.model.DistributionSetMetadata;
import org.eclipse.hawkbit.repository.model.DistributionSetTag; import org.eclipse.hawkbit.repository.model.DistributionSetTag;
import org.eclipse.hawkbit.repository.model.DistributionSetType; import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.MetaData;
import org.eclipse.hawkbit.repository.model.SoftwareModule; import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.Statistic; import org.eclipse.hawkbit.repository.model.Statistic;
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer; import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer;
@@ -103,7 +101,6 @@ public class JpaDistributionSetManagement implements DistributionSetManagement {
private final SystemManagement systemManagement; private final SystemManagement systemManagement;
private final DistributionSetTypeManagement distributionSetTypeManagement; private final DistributionSetTypeManagement distributionSetTypeManagement;
private final QuotaManagement quotaManagement; private final QuotaManagement quotaManagement;
private final DistributionSetMetadataRepository distributionSetMetadataRepository;
private final TargetRepository targetRepository; private final TargetRepository targetRepository;
private final TargetFilterQueryRepository targetFilterQueryRepository; private final TargetFilterQueryRepository targetFilterQueryRepository;
private final ActionRepository actionRepository; private final ActionRepository actionRepository;
@@ -120,7 +117,6 @@ public class JpaDistributionSetManagement implements DistributionSetManagement {
final DistributionSetRepository distributionSetRepository, final DistributionSetRepository distributionSetRepository,
final DistributionSetTagManagement distributionSetTagManagement, final SystemManagement systemManagement, final DistributionSetTagManagement distributionSetTagManagement, final SystemManagement systemManagement,
final DistributionSetTypeManagement distributionSetTypeManagement, final QuotaManagement quotaManagement, final DistributionSetTypeManagement distributionSetTypeManagement, final QuotaManagement quotaManagement,
final DistributionSetMetadataRepository distributionSetMetadataRepository,
final TargetRepository targetRepository, final TargetRepository targetRepository,
final TargetFilterQueryRepository targetFilterQueryRepository, final ActionRepository actionRepository, final TargetFilterQueryRepository targetFilterQueryRepository, final ActionRepository actionRepository,
final TenantConfigHelper tenantConfigHelper, final TenantConfigHelper tenantConfigHelper,
@@ -135,7 +131,6 @@ public class JpaDistributionSetManagement implements DistributionSetManagement {
this.systemManagement = systemManagement; this.systemManagement = systemManagement;
this.distributionSetTypeManagement = distributionSetTypeManagement; this.distributionSetTypeManagement = distributionSetTypeManagement;
this.quotaManagement = quotaManagement; this.quotaManagement = quotaManagement;
this.distributionSetMetadataRepository = distributionSetMetadataRepository;
this.targetRepository = targetRepository; this.targetRepository = targetRepository;
this.targetFilterQueryRepository = targetFilterQueryRepository; this.targetFilterQueryRepository = targetFilterQueryRepository;
this.actionRepository = actionRepository; this.actionRepository = actionRepository;
@@ -360,21 +355,6 @@ public class JpaDistributionSetManagement implements DistributionSetManagement {
}); });
} }
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public DistributionSetMetadata updateMetaData(final long id, final MetaData md) {
// check if exists otherwise throw entity not found exception
final JpaDistributionSetMetadata toUpdate = (JpaDistributionSetMetadata) findMetaDataByDistributionSetId(id, md.getKey())
.orElseThrow(() -> new EntityNotFoundException(DistributionSetMetadata.class, id, md.getKey()));
toUpdate.setValue(md.getValue());
// touch it to update the lock revision because we are modifying the DS indirectly, it will, also check UPDATE access
JpaManagementHelper.touch(entityManager, distributionSetRepository, (JpaDistributionSet) getValid(id));
return distributionSetMetadataRepository.save(toUpdate);
}
@Override @Override
@Transactional @Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
@@ -394,34 +374,60 @@ public class JpaDistributionSetManagement implements DistributionSetManagement {
@Transactional @Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY)) backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public List<DistributionSetMetadata> putMetaData(final long id, final Collection<MetaData> md) { public void createMetadata(final long id, final Map<String, String> md) {
final JpaDistributionSet distributionSet = (JpaDistributionSet) getValid(id); final JpaDistributionSet distributionSet = (JpaDistributionSet) getValid(id);
assertMetaDataQuota(id, md.size());
md.forEach(meta -> checkAndThrowIfDistributionSetMetadataAlreadyExists( // get the modifiable metadata map
new DsMetadataCompositeKey(id, meta.getKey()))); final Map<String, String> metadata = distributionSet.getMetadata();
md.keySet().forEach(key -> {
if (metadata.containsKey(key)) {
throw new EntityAlreadyExistsException("Metadata entry with key '" + key + "' already exists");
}
});
metadata.putAll(md);
JpaManagementHelper.touch(entityManager, distributionSetRepository, distributionSet); assertMetaDataQuota(id, metadata.size());
return md.stream() distributionSetRepository.save(distributionSet);
.map(meta -> distributionSetMetadataRepository }
.save(new JpaDistributionSetMetadata(meta.getKey(), distributionSet, meta.getValue())))
.collect(Collectors.toUnmodifiableList()); @Override
public Map<String, String> getMetadata(final long id) {
assertDistributionSetExists(id);
return getMap(id, JpaDistributionSet_.metadata);
} }
@Override @Override
@Transactional @Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY)) backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public void deleteMetaData(final long id, final String key) { public void updateMetadata(final long id, final String key, final String value) {
final JpaDistributionSetMetadata metadata = (JpaDistributionSetMetadata) findMetaDataByDistributionSetId( final JpaDistributionSet distributionSet = (JpaDistributionSet) getValid(id);
id, key)
.orElseThrow(() -> new EntityNotFoundException(DistributionSetMetadata.class, id, key));
// touch it to update the lock revision because we are modifying the // get the modifiable metadata map
// DS indirectly, it will, also check UPDATE access final Map<String, String> metadata = distributionSet.getMetadata();
JpaManagementHelper.touch(entityManager, distributionSetRepository, metadata.getDistributionSet()); if (!metadata.containsKey(key)) {
distributionSetMetadataRepository.deleteById(metadata.getId()); throw new EntityNotFoundException("DistributionSet metadata", id + ":" + key);
}
metadata.put(key, value);
distributionSetRepository.save(distributionSet);
}
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public void deleteMetadata(final long id, final String key) {
final JpaDistributionSet distributionSet = (JpaDistributionSet) getValid(id);
// get the modifiable metadata map
final Map<String, String> metadata = distributionSet.getMetadata();
if (metadata.remove(key) == null) {
throw new EntityNotFoundException("DistributionSet metadata", id + ":" + key);
}
distributionSetRepository.save(distributionSet);
} }
@Override @Override
@@ -504,33 +510,6 @@ public class JpaDistributionSetManagement implements DistributionSetManagement {
return distributionSet; return distributionSet;
} }
@Override
public Page<DistributionSetMetadata> findMetaDataByDistributionSetId(final long id, final Pageable pageable) {
assertDistributionSetExists(id);
return JpaManagementHelper.findAllWithCountBySpec(distributionSetMetadataRepository, Collections.singletonList(byDsIdSpec(id)), pageable
);
}
@Override
public long countMetaDataByDistributionSetId(final long id) {
assertDistributionSetExists(id);
return distributionSetMetadataRepository.countByDistributionSetId(id);
}
@Override
public Page<DistributionSetMetadata> findMetaDataByDistributionSetIdAndRsql(final long id, final String rsqlParam,
final Pageable pageable) {
assertDistributionSetExists(id);
final List<Specification<JpaDistributionSetMetadata>> specList = Arrays
.asList(RSQLUtility.buildRsqlSpecification(rsqlParam, DistributionSetMetadataFields.class,
virtualPropertyReplacer, database), byDsIdSpec(id));
return JpaManagementHelper.findAllWithCountBySpec(distributionSetMetadataRepository, specList, pageable);
}
@Override @Override
public Slice<DistributionSet> findByCompleted(final Pageable pageReq, final Boolean complete) { public Slice<DistributionSet> findByCompleted(final Pageable pageReq, final Boolean complete) {
final List<Specification<JpaDistributionSet>> specifications = buildSpecsByComplete(complete); final List<Specification<JpaDistributionSet>> specifications = buildSpecsByComplete(complete);
@@ -577,15 +556,6 @@ public class JpaDistributionSetManagement implements DistributionSetManagement {
DistributionSetSpecification.hasTag(tagId), DistributionSetSpecification.isNotDeleted()), pageable); DistributionSetSpecification.hasTag(tagId), DistributionSetSpecification.isNotDeleted()), pageable);
} }
@Override
public Optional<DistributionSetMetadata> findMetaDataByDistributionSetId(final long id, final String key) {
assertDistributionSetExists(id);
return distributionSetMetadataRepository
.findById(new DsMetadataCompositeKey(id, key))
.map(DistributionSetMetadata.class::cast);
}
@Override @Override
public long countByTypeId(final long typeId) { public long countByTypeId(final long typeId) {
if (!distributionSetTypeManagement.exists(typeId)) { if (!distributionSetTypeManagement.exists(typeId)) {
@@ -732,6 +702,24 @@ public class JpaDistributionSetManagement implements DistributionSetManagement {
} }
} }
private Map<String, String> getMap(final long id, final MapAttribute<JpaDistributionSet, String, String> mapAttribute) {
final CriteriaBuilder cb = entityManager.getCriteriaBuilder();
final CriteriaQuery<Object[]> query = cb.createQuery(Object[].class);
final Root<JpaDistributionSet> targetRoot = query.from(JpaDistributionSet.class);
query.where(cb.equal(targetRoot.get(AbstractJpaBaseEntity_.ID), id));
final MapJoin<JpaDistributionSet, String, String> mapJoin = targetRoot.join(mapAttribute);
query.multiselect(mapJoin.key(), mapJoin.value());
query.orderBy(cb.asc(mapJoin.key()));
return entityManager
.createQuery(query)
.getResultList()
.stream()
.collect(Collectors.toMap(entry -> (String) entry[0], entry -> (String) entry[1], (v1, v2) -> v1, LinkedHashMap::new));
}
private JpaSoftwareModule findSoftwareModuleAndThrowExceptionIfNotFound(final Long softwareModuleId) { private JpaSoftwareModule findSoftwareModuleAndThrowExceptionIfNotFound(final Long softwareModuleId) {
return softwareModuleRepository.findById(softwareModuleId) return softwareModuleRepository.findById(softwareModuleId)
.orElseThrow(() -> new EntityNotFoundException(SoftwareModule.class, softwareModuleId)); .orElseThrow(() -> new EntityNotFoundException(SoftwareModule.class, softwareModuleId));
@@ -754,9 +742,8 @@ public class JpaDistributionSetManagement implements DistributionSetManagement {
} }
private void assertMetaDataQuota(final Long dsId, final int requested) { private void assertMetaDataQuota(final Long dsId, final int requested) {
QuotaHelper.assertAssignmentQuota(dsId, requested, quotaManagement.getMaxMetaDataEntriesPerDistributionSet(), final int limit = quotaManagement.getMaxMetaDataEntriesPerDistributionSet();
DistributionSetMetadata.class, DistributionSet.class, QuotaHelper.assertAssignmentQuota(dsId, requested, limit, "Metadata", DistributionSet.class.getSimpleName(), null);
distributionSetMetadataRepository::countByDistributionSetId);
} }
private void assertSoftwareModuleQuota(final Long id, final int requested) { private void assertSoftwareModuleQuota(final Long id, final int requested) {
@@ -764,11 +751,6 @@ public class JpaDistributionSetManagement implements DistributionSetManagement {
SoftwareModule.class, DistributionSet.class, softwareModuleRepository::countByAssignedToId); SoftwareModule.class, DistributionSet.class, softwareModuleRepository::countByAssignedToId);
} }
private Specification<JpaDistributionSetMetadata> byDsIdSpec(final long dsId) {
return (root, query, cb) -> cb
.equal(root.get(JpaDistributionSetMetadata_.distributionSet).get(AbstractJpaBaseEntity_.id), dsId);
}
private void assertDistributionSetIsNotAssignedToTargets(final Long distributionSet) { private void assertDistributionSetIsNotAssignedToTargets(final Long distributionSet) {
if (actionRepository.countByDistributionSetId(distributionSet) > 0) { if (actionRepository.countByDistributionSetId(distributionSet) > 0) {
throw new EntityReadOnlyException(String.format( throw new EntityReadOnlyException(String.format(
@@ -776,13 +758,6 @@ public class JpaDistributionSetManagement implements DistributionSetManagement {
} }
} }
private void checkAndThrowIfDistributionSetMetadataAlreadyExists(final DsMetadataCompositeKey metadataId) {
if (distributionSetMetadataRepository.existsById(metadataId)) {
throw new EntityAlreadyExistsException(
"Metadata entry with key '" + metadataId.getKey() + "' already exists");
}
}
private List<JpaDistributionSet> getDistributionSets(final Collection<Long> ids) { private List<JpaDistributionSet> getDistributionSets(final Collection<Long> ids) {
final List<JpaDistributionSet> foundDs = distributionSetRepository.findAllById(ids); final List<JpaDistributionSet> foundDs = distributionSetRepository.findAllById(ids);
if (foundDs.size() != ids.size()) { if (foundDs.size() != ids.size()) {

View File

@@ -1,76 +0,0 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.repository.jpa.model;
import java.io.Serial;
import java.io.Serializable;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
/**
* The DistributionSet Metadata composite key which contains the meta-data key and the ID of the DistributionSet itself.
*/
@NoArgsConstructor // Default constructor for JPA
@Setter
@Getter
public final class DsMetadataCompositeKey implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private String key;
private Long distributionSet;
/**
* @param distributionSet the distribution set for this meta data
* @param key the key of the meta data
*/
public DsMetadataCompositeKey(final Long distributionSet, final String key) {
this.distributionSet = distributionSet;
this.key = key;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (distributionSet == null ? 0 : distributionSet.hashCode());
result = prime * result + (key == null ? 0 : key.hashCode());
return result;
}
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final DsMetadataCompositeKey other = (DsMetadataCompositeKey) obj;
if (distributionSet == null) {
if (other.distributionSet != null) {
return false;
}
} else if (!distributionSet.equals(other.distributionSet)) {
return false;
}
if (key == null) {
return other.key == null;
} else {
return key.equals(other.key);
}
}
}

View File

@@ -13,13 +13,14 @@ import java.io.Serial;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import jakarta.persistence.CascadeType; import jakarta.persistence.CollectionTable;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.persistence.ConstraintMode; import jakarta.persistence.ConstraintMode;
import jakarta.persistence.ElementCollection;
import jakarta.persistence.Entity; import jakarta.persistence.Entity;
import jakarta.persistence.FetchType; import jakarta.persistence.FetchType;
import jakarta.persistence.ForeignKey; import jakarta.persistence.ForeignKey;
@@ -28,9 +29,9 @@ import jakarta.persistence.JoinColumn;
import jakarta.persistence.JoinTable; import jakarta.persistence.JoinTable;
import jakarta.persistence.ManyToMany; import jakarta.persistence.ManyToMany;
import jakarta.persistence.ManyToOne; import jakarta.persistence.ManyToOne;
import jakarta.persistence.MapKeyColumn;
import jakarta.persistence.NamedAttributeNode; import jakarta.persistence.NamedAttributeNode;
import jakarta.persistence.NamedEntityGraph; import jakarta.persistence.NamedEntityGraph;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table; import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint; import jakarta.persistence.UniqueConstraint;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
@@ -47,7 +48,6 @@ import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetExcepti
import org.eclipse.hawkbit.repository.exception.LockedException; import org.eclipse.hawkbit.repository.exception.LockedException;
import org.eclipse.hawkbit.repository.exception.UnsupportedSoftwareModuleForThisDistributionSetException; import org.eclipse.hawkbit.repository.exception.UnsupportedSoftwareModuleForThisDistributionSetException;
import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetMetadata;
import org.eclipse.hawkbit.repository.model.DistributionSetTag; import org.eclipse.hawkbit.repository.model.DistributionSetTag;
import org.eclipse.hawkbit.repository.model.DistributionSetType; import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.SoftwareModule; import org.eclipse.hawkbit.repository.model.SoftwareModule;
@@ -109,11 +109,16 @@ public class JpaDistributionSet extends AbstractJpaNamedVersionedEntity implemen
foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_ds_tag_tag")) }) foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_ds_tag_tag")) })
private Set<DistributionSetTag> tags = new HashSet<>(); private Set<DistributionSetTag> tags = new HashSet<>();
@ToString.Exclude // no cascade option on an ElementCollection, the target objects are always persisted, merged, removed with their parent
@OneToMany(mappedBy = "distributionSet", fetch = FetchType.LAZY, @Getter
cascade = { CascadeType.REMOVE }, @ElementCollection
targetEntity = JpaDistributionSetMetadata.class) @CollectionTable(
private List<DistributionSetMetadata> metadata; name = "sp_ds_metadata",
joinColumns = { @JoinColumn(name = "ds", nullable = false) },
foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_ds_metadata_ds"))
@MapKeyColumn(name = "meta_key", length = DistributionSet.METADATA_KEY_SIZE)
@Column(name = "meta_value", length = DistributionSet.METADATA_VALUE_SIZE)
private Map<String, String> metadata;
@Column(name = "complete") @Column(name = "complete")
private boolean complete; private boolean complete;
@@ -217,14 +222,6 @@ public class JpaDistributionSet extends AbstractJpaNamedVersionedEntity implemen
return tags.remove(tag); return tags.remove(tag);
} }
public List<DistributionSetMetadata> getMetadata() {
if (metadata == null) {
return Collections.emptyList();
}
return Collections.unmodifiableList(metadata);
}
public void lock() { public void lock() {
if (!isComplete()) { if (!isComplete()) {
throw new IncompleteDistributionSetException("Could not be locked while incomplete!"); throw new IncompleteDistributionSetException("Could not be locked while incomplete!");

View File

@@ -1,86 +0,0 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.repository.jpa.model;
import java.io.Serial;
import jakarta.persistence.ConstraintMode;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.ForeignKey;
import jakarta.persistence.Id;
import jakarta.persistence.IdClass;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetMetadata;
/**
* Meta data for {@link DistributionSet}.
*/
@NoArgsConstructor // Default constructor needed for JPA entities.
@Getter
@IdClass(DsMetadataCompositeKey.class)
@Entity
@Table(name = "sp_ds_metadata")
public class JpaDistributionSetMetadata extends AbstractJpaMetaData implements DistributionSetMetadata {
@Serial
private static final long serialVersionUID = 1L;
@Id
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "ds", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_ds_metadata_ds"))
private JpaDistributionSet distributionSet;
public JpaDistributionSetMetadata(final String key, final String value) {
super(key, value);
}
public JpaDistributionSetMetadata(final String key, final DistributionSet distributionSet, final String value) {
super(key, value);
this.distributionSet = (JpaDistributionSet) distributionSet;
}
public DsMetadataCompositeKey getId() {
return new DsMetadataCompositeKey(distributionSet.getId(), getKey());
}
public void setDistributionSet(final DistributionSet distributionSet) {
this.distributionSet = (JpaDistributionSet) distributionSet;
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + ((distributionSet == null) ? 0 : distributionSet.hashCode());
return result;
}
@Override
// exception squid:S2259 - obj is checked for null in super
@SuppressWarnings("squid:S2259")
public boolean equals(final Object obj) {
if (!super.equals(obj)) {
return false;
}
final JpaDistributionSetMetadata other = (JpaDistributionSetMetadata) obj;
if (distributionSet == null) {
return other.distributionSet == null;
} else {
return distributionSet.equals(other.distributionSet);
}
}
}

View File

@@ -1,39 +0,0 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.repository.jpa.repository;
import org.eclipse.hawkbit.repository.jpa.model.DsMetadataCompositeKey;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetMetadata;
import org.eclipse.hawkbit.repository.model.DistributionSetMetadata;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.transaction.annotation.Transactional;
/**
* {@link DistributionSetMetadata} repository.
*/
@Transactional(readOnly = true)
public interface DistributionSetMetadataRepository
extends PagingAndSortingRepository<JpaDistributionSetMetadata, DsMetadataCompositeKey>,
CrudRepository<JpaDistributionSetMetadata, DsMetadataCompositeKey>,
JpaSpecificationExecutor<JpaDistributionSetMetadata> {
/**
* Counts the meta data entries that match the given distribution set ID.
* <p/>
* No access control applied
*
* @param id of the distribution set.
* @return The number of matching meta data entries.
*/
long countByDistributionSetId(@Param("id") Long id);
}

View File

@@ -16,6 +16,7 @@ import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map;
import jakarta.persistence.criteria.Predicate; import jakarta.persistence.criteria.Predicate;
@@ -28,14 +29,12 @@ import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException; import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException;
import org.eclipse.hawkbit.repository.jpa.acm.AccessController; import org.eclipse.hawkbit.repository.jpa.acm.AccessController;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet; import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetMetadata;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet_; import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet_;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget; import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
import org.eclipse.hawkbit.repository.jpa.specifications.TargetSpecifications; import org.eclipse.hawkbit.repository.jpa.specifications.TargetSpecifications;
import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetFilter; import org.eclipse.hawkbit.repository.model.DistributionSetFilter;
import org.eclipse.hawkbit.repository.model.MetaData;
import org.eclipse.hawkbit.repository.model.SoftwareModule; import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.TargetFilterQuery; import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@@ -121,12 +120,18 @@ class DistributionSetAccessControllerTest extends AbstractAccessControllerTest {
@Description("Verifies read access rules for distribution sets") @Description("Verifies read access rules for distribution sets")
void verifyDistributionSetUpdates() { void verifyDistributionSetUpdates() {
// permit all operations first to prepare test setup // permit all operations first to prepare test setup
permitAllOperations(AccessController.Operation.READ);
permitAllOperations(AccessController.Operation.CREATE); permitAllOperations(AccessController.Operation.CREATE);
permitAllOperations(AccessController.Operation.UPDATE); permitAllOperations(AccessController.Operation.UPDATE);
final DistributionSet permitted = testdataFactory.createDistributionSet(); final DistributionSet permitted = testdataFactory.createDistributionSet();
final String mdPresetKey = "metadata.preset";
final String mdPresetValue = "presetValue";
distributionSetManagement.createMetadata(permitted.getId(), Map.of(mdPresetKey, mdPresetValue));
final DistributionSet readOnly = testdataFactory.createDistributionSet(); final DistributionSet readOnly = testdataFactory.createDistributionSet();
distributionSetManagement.createMetadata(readOnly.getId(), Map.of(mdPresetKey, mdPresetValue));
final DistributionSet hidden = testdataFactory.createDistributionSet(); final DistributionSet hidden = testdataFactory.createDistributionSet();
distributionSetManagement.createMetadata(hidden.getId(), Map.of(mdPresetKey, mdPresetValue));
final SoftwareModule swModule = testdataFactory.createSoftwareModuleOs(); final SoftwareModule swModule = testdataFactory.createSoftwareModuleOs();
@@ -134,52 +139,49 @@ class DistributionSetAccessControllerTest extends AbstractAccessControllerTest {
testAccessControlManger.deleteAllRules(); testAccessControlManger.deleteAllRules();
// define access controlling rule // define access controlling rule
defineAccess(AccessController.Operation.READ, permitted, readOnly); defineAccess(AccessController.Operation.READ, permitted, readOnly);
// allow updating the permitted distributionSet
defineAccess(AccessController.Operation.READ, permitted);
defineAccess(AccessController.Operation.UPDATE, permitted); defineAccess(AccessController.Operation.UPDATE, permitted);
// verify distributionSetManagement#assignSoftwareModules // verify distributionSetManagement#assignSoftwareModules
final var singleModuleIdList = Collections.singletonList(swModule.getId()); final List<Long> singleModuleIdList = Collections.singletonList(swModule.getId());
assertThat(distributionSetManagement.assignSoftwareModules(permitted.getId(), singleModuleIdList)) assertThat(distributionSetManagement.assignSoftwareModules(permitted.getId(), singleModuleIdList))
.satisfies(ds -> assertThat(ds.getModules().stream().map(Identifiable::getId).toList()).contains(swModule.getId())); .satisfies(ds -> assertThat(ds.getModules().stream().map(Identifiable::getId).toList()).contains(swModule.getId()));
final Long readOnlyId = readOnly.getId(); final Long readOnlyId = readOnly.getId();
assertThatThrownBy(() -> distributionSetManagement.assignSoftwareModules(readOnlyId, singleModuleIdList)) assertThatThrownBy(() -> distributionSetManagement.assignSoftwareModules(readOnlyId, singleModuleIdList))
.as("Distribution set not allowed to me modified.") .as("Distribution set not allowed to me modified.")
.isInstanceOf(EntityNotFoundException.class); .isInstanceOf(InsufficientPermissionException.class);
final Long hiddenId = hidden.getId(); final Long hiddenId = hidden.getId();
assertThatThrownBy(() -> distributionSetManagement.assignSoftwareModules(hiddenId, singleModuleIdList)) assertThatThrownBy(() -> distributionSetManagement.assignSoftwareModules(hiddenId, singleModuleIdList))
.as("Distribution set should not be visible.") .as("Distribution set should not be visible.")
.isInstanceOf(EntityNotFoundException.class); .isInstanceOf(EntityNotFoundException.class);
final JpaDistributionSetMetadata metadata = new JpaDistributionSetMetadata("test", "test"); final Map<String, String> metadata = Map.of("test.create", mdPresetValue);
// verify distributionSetManagement#createMetaData // verify distributionSetManagement#createMetaData
final List<MetaData> metadataList = Collections.singletonList(metadata); distributionSetManagement.createMetadata(permitted.getId(), metadata);
distributionSetManagement.putMetaData(permitted.getId(), metadataList); assertThatThrownBy(() -> distributionSetManagement.createMetadata(readOnlyId, metadata))
assertThatThrownBy(() -> distributionSetManagement.putMetaData(readOnlyId, metadataList)) .as("Distribution set not allowed to be modified.")
.as("Distribution set not allowed to me modified.") .isInstanceOf(InsufficientPermissionException.class);
.isInstanceOf(EntityNotFoundException.class); assertThatThrownBy(() -> distributionSetManagement.createMetadata(hiddenId, metadata))
assertThatThrownBy(() -> distributionSetManagement.putMetaData(hiddenId, metadataList))
.as("Distribution set should not be visible.") .as("Distribution set should not be visible.")
.isInstanceOf(EntityNotFoundException.class); .isInstanceOf(EntityNotFoundException.class);
// verify distributionSetManagement#updateMetaData // verify distributionSetManagement#updateMetaData
distributionSetManagement.updateMetaData(permitted.getId(), metadata); final String newValue = "newValue";
assertThatThrownBy(() -> distributionSetManagement.updateMetaData(readOnlyId, metadata)) distributionSetManagement.updateMetadata(permitted.getId(), mdPresetKey, newValue);
assertThatThrownBy(() -> distributionSetManagement.updateMetadata(readOnlyId, mdPresetKey, newValue))
.as("Distribution set not allowed to me modified.") .as("Distribution set not allowed to me modified.")
.isInstanceOf(EntityNotFoundException.class); .isInstanceOf(InsufficientPermissionException.class);
assertThatThrownBy(() -> distributionSetManagement.updateMetaData(hiddenId, metadata)) assertThatThrownBy(() -> distributionSetManagement.updateMetadata(hiddenId, mdPresetKey, newValue))
.as("Distribution set should not be visible.") .as("Distribution set should not be visible.")
.isInstanceOf(EntityNotFoundException.class); .isInstanceOf(EntityNotFoundException.class);
// verify distributionSetManagement#deleteMetaData // verify distributionSetManagement#deleteMetaData
final String metadataKey = metadata.getKey(); final String metadataKey = metadata.entrySet().stream().findAny().get().getKey();
distributionSetManagement.deleteMetaData(permitted.getId(), metadataKey); distributionSetManagement.deleteMetadata(permitted.getId(), metadataKey);
assertThatThrownBy(() -> distributionSetManagement.deleteMetaData(readOnlyId, metadataKey)) assertThatThrownBy(() -> distributionSetManagement.deleteMetadata(readOnlyId, mdPresetKey))
.as("Distribution set not allowed to me modified.") .as("Distribution set not allowed to me modified.")
.isInstanceOf(EntityNotFoundException.class); .isInstanceOf(InsufficientPermissionException.class);
assertThatThrownBy(() -> distributionSetManagement.deleteMetaData(hiddenId, metadataKey)) assertThatThrownBy(() -> distributionSetManagement.deleteMetadata(hiddenId, mdPresetKey))
.as("Distribution set should not be visible.") .as("Distribution set should not be visible.")
.isInstanceOf(EntityNotFoundException.class); .isInstanceOf(EntityNotFoundException.class);
} }

View File

@@ -220,13 +220,12 @@ class TargetAccessControllerTest extends AbstractAccessControllerTest {
final Target permittedTarget = targetManagement final Target permittedTarget = targetManagement
.create(entityFactory.target().create().controllerId("device01").status(TargetUpdateStatus.REGISTERED)); .create(entityFactory.target().create().controllerId("device01").status(TargetUpdateStatus.REGISTERED));
final String hiddenTargetControllerId = targetManagement final String hiddenTargetControllerId = targetManagement
.create(entityFactory.target().create().controllerId("device02").status(TargetUpdateStatus.REGISTERED)) .create(entityFactory.target().create().controllerId("device02").status(TargetUpdateStatus.REGISTERED))
.getControllerId(); .getControllerId();
// define access controlling rule // define access controlling rule
defineAccess(AccessController.Operation.READ, permittedTarget); overwriteAccess(AccessController.Operation.READ, permittedTarget);
// verify targetManagement#findByUpdateStatus before assignment // verify targetManagement#findByUpdateStatus before assignment
assertThat(targetManagement.findByUpdateStatus(Pageable.unpaged(), TargetUpdateStatus.REGISTERED).get() assertThat(targetManagement.findByUpdateStatus(Pageable.unpaged(), TargetUpdateStatus.REGISTERED).get()
@@ -269,18 +268,15 @@ class TargetAccessControllerTest extends AbstractAccessControllerTest {
final Target manageableTarget = targetManagement final Target manageableTarget = targetManagement
.create(entityFactory.target().create().controllerId("device01").status(TargetUpdateStatus.REGISTERED)); .create(entityFactory.target().create().controllerId("device01").status(TargetUpdateStatus.REGISTERED));
final Target readOnlyTarget = targetManagement final Target readOnlyTarget = targetManagement
.create(entityFactory.target().create().controllerId("device02").status(TargetUpdateStatus.REGISTERED)); .create(entityFactory.target().create().controllerId("device02").status(TargetUpdateStatus.REGISTERED));
// define access controlling rule // overwriting full access controlling rule
defineAccess(AccessController.Operation.READ, manageableTarget, readOnlyTarget); overwriteAccess(AccessController.Operation.READ, manageableTarget, readOnlyTarget);
overwriteAccess(AccessController.Operation.UPDATE, manageableTarget);
defineAccess(AccessController.Operation.UPDATE, manageableTarget);
// assignment is permitted for manageableTarget // assignment is permitted for manageableTarget
assertThat(assignDistributionSet(firstDsId, manageableTarget.getControllerId()).getAssigned()) assertThat(assignDistributionSet(firstDsId, manageableTarget.getControllerId()).getAssigned()).isEqualTo(1);
.isEqualTo(1);
// assignment is denied for readOnlyTarget (read, but no update permissions) // assignment is denied for readOnlyTarget (read, but no update permissions)
final var readOnlyTargetControllerId = readOnlyTarget.getControllerId(); final var readOnlyTargetControllerId = readOnlyTarget.getControllerId();
@@ -309,19 +305,17 @@ class TargetAccessControllerTest extends AbstractAccessControllerTest {
final List<Target> updateTargets = testdataFactory.createTargets("update1", "update2", "update3"); final List<Target> updateTargets = testdataFactory.createTargets("update1", "update2", "update3");
final List<Target> readTargets = testdataFactory.createTargets("read1", "read2", "read3", "read4"); final List<Target> readTargets = testdataFactory.createTargets("read1", "read2", "read3", "read4");
final List<Target> hiddenTargets = testdataFactory.createTargets( final List<Target> hiddenTargets = testdataFactory.createTargets("hidden1", "hidden2", "hidden3", "hidden4", "hidden5");
"hidden1", "hidden2", "hidden3", "hidden4", "hidden5");
defineAccess(AccessController.Operation.UPDATE, updateTargets); defineAccess(AccessController.Operation.UPDATE, updateTargets);
defineAccess(AccessController.Operation.READ, merge(readTargets, updateTargets)); overwriteAccess(AccessController.Operation.READ, merge(readTargets, updateTargets));
final Rollout rollout = testdataFactory.createRolloutByVariables("testRollout", "description", final Rollout rollout = testdataFactory.createRolloutByVariables(
updateTargets.size(), "id==*", ds, "50", "5"); "testRollout", "description", updateTargets.size(), "id==*", ds, "50", "5");
assertThat(rollout.getTotalTargets()).isEqualTo(updateTargets.size()); assertThat(rollout.getTotalTargets()).isEqualTo(updateTargets.size());
final List<RolloutGroup> content = rolloutGroupManagement.findByRollout(rollout.getId(), Pageable.unpaged()) final List<RolloutGroup> content = rolloutGroupManagement.findByRollout(rollout.getId(), Pageable.unpaged()).getContent();
.getContent();
assertThat(content).hasSize(updateTargets.size()); assertThat(content).hasSize(updateTargets.size());
final List<Target> rolloutTargets = content.stream().flatMap( final List<Target> rolloutTargets = content.stream().flatMap(
@@ -393,6 +387,18 @@ class TargetAccessControllerTest extends AbstractAccessControllerTest {
target -> ids.contains(target.getId())); target -> ids.contains(target.getId()));
} }
private void overwriteAccess(final AccessController.Operation operation, final Target... target) {
overwriteAccess(operation, List.of(target));
}
private void overwriteAccess(final AccessController.Operation operation, final List<Target> targets) {
final List<Long> ids = targets.stream().map(Target::getId).toList();
testAccessControlManger.overwriteAccessRule(
JpaTarget.class, operation,
TargetSpecifications.hasIdIn(ids),
target -> ids.contains(target.getId()));
}
private static Specification<JpaDistributionSet> dsById(final Long distid) { private static Specification<JpaDistributionSet> dsById(final Long distid) {
return (dsRoot, query, cb) -> { return (dsRoot, query, cb) -> {
final Predicate predicate = cb.equal(dsRoot.get(JpaDistributionSet_.id), distid); final Predicate predicate = cb.equal(dsRoot.get(JpaDistributionSet_.id), distid);

View File

@@ -14,6 +14,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.stream.Stream;
import io.qameta.allure.Description; import io.qameta.allure.Description;
import io.qameta.allure.Feature; import io.qameta.allure.Feature;
@@ -37,14 +38,9 @@ class TargetTypeAccessControllerTest extends AbstractAccessControllerTest {
@Test @Test
@Description("Verifies read access rules for target types") @Description("Verifies read access rules for target types")
void verifyTargetTypeReadOperations() { void verifyTargetTypeReadOperations() {
permitAllOperations(AccessController.Operation.READ);
permitAllOperations(AccessController.Operation.CREATE); permitAllOperations(AccessController.Operation.CREATE);
final TargetType permittedTargetType = targetTypeManagement.create(entityFactory.targetType().create().name("type1"));
final TargetType permittedTargetType = targetTypeManagement final TargetType hiddenTargetType = targetTypeManagement.create(entityFactory.targetType().create().name("type2"));
.create(entityFactory.targetType().create().name("type1"));
final TargetType hiddenTargetType = targetTypeManagement
.create(entityFactory.targetType().create().name("type2"));
// define access controlling rule // define access controlling rule
defineAccess(AccessController.Operation.READ, permittedTargetType); defineAccess(AccessController.Operation.READ, permittedTargetType);
@@ -67,12 +63,12 @@ class TargetTypeAccessControllerTest extends AbstractAccessControllerTest {
assertThat(targetTypeManagement.count()).isEqualTo(1); assertThat(targetTypeManagement.count()).isEqualTo(1);
// verify targetTypeManagement#countByName // verify targetTypeManagement#countByName
// assertThat(targetTypeManagement.countByName(permittedTargetType.getName())).isEqualTo(1); assertThat(targetTypeManagement.countByName(permittedTargetType.getName())).isEqualTo(1);
// assertThat(targetTypeManagement.countByName(hiddenTargetType.getName())).isZero(); assertThat(targetTypeManagement.countByName(hiddenTargetType.getName())).isZero();
// verify targetTypeManagement#countByName // verify targetTypeManagement#countByName
// assertThat(targetTypeManagement.countByName(permittedTargetType.getName())).isEqualTo(1); assertThat(targetTypeManagement.countByName(permittedTargetType.getName())).isEqualTo(1);
// assertThat(targetTypeManagement.countByName(hiddenTargetType.getName())).isZero(); assertThat(targetTypeManagement.countByName(hiddenTargetType.getName())).isZero();
// verify targetTypeManagement#get by id // verify targetTypeManagement#get by id
assertThat(targetTypeManagement.get(permittedTargetType.getId())).isPresent(); assertThat(targetTypeManagement.get(permittedTargetType.getId())).isPresent();
@@ -161,15 +157,11 @@ class TargetTypeAccessControllerTest extends AbstractAccessControllerTest {
.isInstanceOf(InsufficientPermissionException.class); .isInstanceOf(InsufficientPermissionException.class);
} }
private void defineAccess(final AccessController.Operation operation, final TargetType... targetType) { private void defineAccess(final AccessController.Operation operation, final TargetType... targetTypes) {
defineAccess(operation, List.of(targetType)); final List<Long> ids = Stream.of(targetTypes).map(TargetType::getId).toList();
}
private void defineAccess(final AccessController.Operation operation, final List<TargetType> targetTypes) {
final List<Long> ids = targetTypes.stream().map(TargetType::getId).toList();
testAccessControlManger.defineAccessRule( testAccessControlManger.defineAccessRule(
JpaTargetType.class, operation, JpaTargetType.class, operation,
TargetTypeSpecification.hasIdIn(ids), TargetTypeSpecification.hasIdIn(ids),
targetType -> ids.contains(targetType.getId())); targetType -> ids.contains(targetType.getId()));
} }
} }

View File

@@ -31,7 +31,23 @@ public class TestAccessControlManger {
public <T> void defineAccessRule( public <T> void defineAccessRule(
final Class<T> ruleClass, final AccessController.Operation operation, final Class<T> ruleClass, final AccessController.Operation operation,
final Specification<T> specification, final Predicate<T> check) { final Specification<T> specification, final Predicate<T> check) {
accessRules.put(new AccessRuleId<>(ruleClass, operation), new AccessRule<>(specification, check)); defineAccessRule(ruleClass, operation, specification, check, false);
}
public <T> void overwriteAccessRule(
final Class<T> ruleClass, final AccessController.Operation operation,
final Specification<T> specification, final Predicate<T> check) {
defineAccessRule(ruleClass, operation, specification, check, true);
}
private <T> void defineAccessRule(
final Class<T> ruleClass, final AccessController.Operation operation,
final Specification<T> specification, final Predicate<T> check, final boolean overwrite) {
final AccessRuleId<T> ruleId = new AccessRuleId<>(ruleClass, operation);
if (!overwrite && accessRules.containsKey(ruleId)) {
throw new IllegalStateException("Access rule already defined for " + ruleId + "! You should explicitly set overwrite to true.");
}
accessRules.put(ruleId, new AccessRule<>(specification, check));
} }
public <T extends AbstractJpaBaseEntity> Specification<T> getAccessRule(final Class<T> ruleClass, public <T extends AbstractJpaBaseEntity> Specification<T> getAccessRule(final Class<T> ruleClass,
@@ -53,8 +69,7 @@ public class TestAccessControlManger {
} else { } else {
for (final T entity : entities) { for (final T entity : entities) {
if (!accessRule.checker.test(entity)) { if (!accessRule.checker.test(entity)) {
throw new InsufficientPermissionException( throw new InsufficientPermissionException("Access to " + ruleClass.getName() + "/" + entity + " not allowed by checker!");
"Access to " + ruleClass.getName() + "/" + entity + " not allowed by checker!");
} }
} }
} }
@@ -67,4 +82,4 @@ public class TestAccessControlManger {
private record AccessRuleId<T>(Class<T> ruleClass, AccessController.Operation operation) {} private record AccessRuleId<T>(Class<T> ruleClass, AccessController.Operation operation) {}
private record AccessRule<T>(Specification<T> specification, Predicate<T> checker) {} private record AccessRule<T>(Specification<T> specification, Predicate<T> checker) {}
} }

View File

@@ -10,6 +10,7 @@
package org.eclipse.hawkbit.repository.jpa.management; package org.eclipse.hawkbit.repository.jpa.management;
import java.util.List; import java.util.List;
import java.util.Map;
import io.qameta.allure.Description; import io.qameta.allure.Description;
import io.qameta.allure.Feature; import io.qameta.allure.Feature;
@@ -66,17 +67,36 @@ class DistributionSetManagementSecurityTest
@Test @Test
@Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.") @Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.")
void createMetaDataPermissionsCheck() { void createMetadataPermissionsCheck() {
assertPermissions( assertPermissions(
() -> distributionSetManagement.putMetaData(1L, List.of(entityFactory.generateDsMetadata("key", "value"))), () -> {
distributionSetManagement.createMetadata(1L, Map.of("key", "value"));
return null;
},
List.of(SpPermission.UPDATE_REPOSITORY)); List.of(SpPermission.UPDATE_REPOSITORY));
} }
@Test @Test
@Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.") @Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.")
void deleteMetaDataPermissionsCheck() { void getMetadataPermissiosCheck() {
assertPermissions(() -> distributionSetManagement.getMetadata(1L), List.of(SpPermission.READ_REPOSITORY));
}
@Test
@Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.")
void updateMetadataPermissionsCheck() {
assertPermissions(() -> { assertPermissions(() -> {
distributionSetManagement.deleteMetaData(1L, "key"); distributionSetManagement.updateMetadata(1L,"key", "value");
return null;
},
List.of(SpPermission.UPDATE_REPOSITORY));
}
@Test
@Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.")
void deleteMetadataPermissionsCheck() {
assertPermissions(() -> {
distributionSetManagement.deleteMetadata(1L, "key");
return null; return null;
}, List.of(SpPermission.UPDATE_REPOSITORY)); }, List.of(SpPermission.UPDATE_REPOSITORY));
} }
@@ -135,25 +155,6 @@ class DistributionSetManagementSecurityTest
assertPermissions(() -> distributionSetManagement.getOrElseThrowException(1L), List.of(SpPermission.READ_REPOSITORY)); assertPermissions(() -> distributionSetManagement.getOrElseThrowException(1L), List.of(SpPermission.READ_REPOSITORY));
} }
@Test
@Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.")
void findMetaDataByDistributionSetIdPermissionsCheck() {
assertPermissions(() -> distributionSetManagement.findMetaDataByDistributionSetId(1L, PAGE), List.of(SpPermission.READ_REPOSITORY));
}
@Test
@Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.")
void countMetaDataByDistributionSetIdPermissionsCheck() {
assertPermissions(() -> distributionSetManagement.countMetaDataByDistributionSetId(1L), List.of(SpPermission.READ_REPOSITORY));
}
@Test
@Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.")
void findMetaDataByDistributionSetIdAndRsqlPermissionsCheck() {
assertPermissions(() -> distributionSetManagement.findMetaDataByDistributionSetIdAndRsql(1L, "rsql", PAGE),
List.of(SpPermission.READ_REPOSITORY));
}
@Test @Test
@Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.") @Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.")
void findByCompletedPermissionsCheck() { void findByCompletedPermissionsCheck() {
@@ -192,12 +193,6 @@ class DistributionSetManagementSecurityTest
assertPermissions(() -> distributionSetManagement.findByRsqlAndTag("rsql", 1L, PAGE), List.of(SpPermission.READ_REPOSITORY)); assertPermissions(() -> distributionSetManagement.findByRsqlAndTag("rsql", 1L, PAGE), List.of(SpPermission.READ_REPOSITORY));
} }
@Test
@Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.")
void getMetaDataByDistributionSetIdPermissionsCheck() {
assertPermissions(() -> distributionSetManagement.findMetaDataByDistributionSetId(1L, "key"), List.of(SpPermission.READ_REPOSITORY));
}
@Test @Test
@Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.") @Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.")
void isInUsePermissionsCheck() { void isInUsePermissionsCheck() {
@@ -210,13 +205,6 @@ class DistributionSetManagementSecurityTest
assertPermissions(() -> distributionSetManagement.unassignSoftwareModule(1L, 1L), List.of(SpPermission.UPDATE_REPOSITORY)); assertPermissions(() -> distributionSetManagement.unassignSoftwareModule(1L, 1L), List.of(SpPermission.UPDATE_REPOSITORY));
} }
@Test
@Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.")
void updateMetaDataPermissionsCheck() {
assertPermissions(() -> distributionSetManagement.updateMetaData(1L, entityFactory.generateDsMetadata("key", "value")),
List.of(SpPermission.UPDATE_REPOSITORY));
}
@Test @Test
@Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.") @Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.")
void countByTypeIdPermissionsCheck() { void countByTypeIdPermissionsCheck() {

View File

@@ -18,7 +18,9 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
@@ -49,17 +51,14 @@ import org.eclipse.hawkbit.repository.exception.LockedException;
import org.eclipse.hawkbit.repository.exception.UnsupportedSoftwareModuleForThisDistributionSetException; import org.eclipse.hawkbit.repository.exception.UnsupportedSoftwareModuleForThisDistributionSetException;
import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest; import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet; import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetMetadata;
import org.eclipse.hawkbit.repository.model.Action.Status; import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetFilter; import org.eclipse.hawkbit.repository.model.DistributionSetFilter;
import org.eclipse.hawkbit.repository.model.DistributionSetFilter.DistributionSetFilterBuilder; import org.eclipse.hawkbit.repository.model.DistributionSetFilter.DistributionSetFilterBuilder;
import org.eclipse.hawkbit.repository.model.DistributionSetInvalidation; import org.eclipse.hawkbit.repository.model.DistributionSetInvalidation;
import org.eclipse.hawkbit.repository.model.DistributionSetInvalidation.CancelationType; import org.eclipse.hawkbit.repository.model.DistributionSetInvalidation.CancelationType;
import org.eclipse.hawkbit.repository.model.DistributionSetMetadata;
import org.eclipse.hawkbit.repository.model.DistributionSetTag; import org.eclipse.hawkbit.repository.model.DistributionSetTag;
import org.eclipse.hawkbit.repository.model.DistributionSetType; import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.MetaData;
import org.eclipse.hawkbit.repository.model.NamedEntity; import org.eclipse.hawkbit.repository.model.NamedEntity;
import org.eclipse.hawkbit.repository.model.NamedVersionedEntity; import org.eclipse.hawkbit.repository.model.NamedVersionedEntity;
import org.eclipse.hawkbit.repository.model.Rollout; import org.eclipse.hawkbit.repository.model.Rollout;
@@ -71,8 +70,6 @@ import org.eclipse.hawkbit.repository.test.matcher.ExpectEvents;
import org.eclipse.hawkbit.repository.test.util.WithUser; import org.eclipse.hawkbit.repository.test.util.WithUser;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
/** /**
* {@link DistributionSetManagement} tests. * {@link DistributionSetManagement} tests.
@@ -96,7 +93,7 @@ class DistributionSetManagementTest extends AbstractJpaIntegrationTest {
assertThat(distributionSetManagement.get(NOT_EXIST_IDL)).isNotPresent(); assertThat(distributionSetManagement.get(NOT_EXIST_IDL)).isNotPresent();
assertThat(distributionSetManagement.getWithDetails(NOT_EXIST_IDL)).isNotPresent(); assertThat(distributionSetManagement.getWithDetails(NOT_EXIST_IDL)).isNotPresent();
assertThat(distributionSetManagement.findByNameAndVersion(NOT_EXIST_ID, NOT_EXIST_ID)).isNotPresent(); assertThat(distributionSetManagement.findByNameAndVersion(NOT_EXIST_ID, NOT_EXIST_ID)).isNotPresent();
assertThat(distributionSetManagement.findMetaDataByDistributionSetId(set.getId(), NOT_EXIST_ID)).isNotPresent(); assertThat(distributionSetManagement.getMetadata(set.getId()).get(NOT_EXIST_ID)).isNull();
} }
@Test @Test
@@ -138,44 +135,30 @@ class DistributionSetManagementTest extends AbstractJpaIntegrationTest {
verifyThrownExceptionBy(() -> distributionSetManagement.create( verifyThrownExceptionBy(() -> distributionSetManagement.create(
entityFactory.distributionSet().create().name("xxx").type(NOT_EXIST_ID)), "DistributionSetType"); entityFactory.distributionSet().create().name("xxx").type(NOT_EXIST_ID)), "DistributionSetType");
verifyThrownExceptionBy(() -> distributionSetManagement.putMetaData( verifyThrownExceptionBy(() -> distributionSetManagement.createMetadata(NOT_EXIST_IDL, Map.of("123", "123")), "DistributionSet");
NOT_EXIST_IDL, singletonList(entityFactory.generateDsMetadata("123", "123"))), "DistributionSet");
verifyThrownExceptionBy(() -> distributionSetManagement.delete(singletonList(NOT_EXIST_IDL)), "DistributionSet"); verifyThrownExceptionBy(() -> distributionSetManagement.delete(singletonList(NOT_EXIST_IDL)), "DistributionSet");
verifyThrownExceptionBy(() -> distributionSetManagement.delete(NOT_EXIST_IDL), "DistributionSet"); verifyThrownExceptionBy(() -> distributionSetManagement.delete(NOT_EXIST_IDL), "DistributionSet");
verifyThrownExceptionBy(() -> distributionSetManagement.deleteMetaData(NOT_EXIST_IDL, "xxx"), verifyThrownExceptionBy(() -> distributionSetManagement.deleteMetadata(NOT_EXIST_IDL, "xxx"), "DistributionSet");
"DistributionSet"); verifyThrownExceptionBy(() -> distributionSetManagement.deleteMetadata(set.getId(), NOT_EXIST_ID), "DistributionSet");
verifyThrownExceptionBy(() -> distributionSetManagement.deleteMetaData(set.getId(), NOT_EXIST_ID),
"DistributionSetMetadata");
verifyThrownExceptionBy(() -> distributionSetManagement.findByAction(NOT_EXIST_IDL), "Action"); verifyThrownExceptionBy(() -> distributionSetManagement.findByAction(NOT_EXIST_IDL), "Action");
verifyThrownExceptionBy(() -> distributionSetManagement.findMetaDataByDistributionSetId(NOT_EXIST_IDL, "xxx"), verifyThrownExceptionBy(() -> distributionSetManagement.getMetadata(NOT_EXIST_IDL).get("xxx"), "DistributionSet");
"DistributionSet");
verifyThrownExceptionBy(() -> distributionSetManagement.findMetaDataByDistributionSetId(NOT_EXIST_IDL, PAGE), verifyThrownExceptionBy(() -> distributionSetManagement.getMetadata(NOT_EXIST_IDL).get(PAGE), "DistributionSet");
"DistributionSet");
verifyThrownExceptionBy(
() -> distributionSetManagement.findMetaDataByDistributionSetIdAndRsql(NOT_EXIST_IDL, "name==*", PAGE),
"DistributionSet");
assertThatThrownBy(() -> distributionSetManagement.isInUse(NOT_EXIST_IDL)) assertThatThrownBy(() -> distributionSetManagement.isInUse(NOT_EXIST_IDL))
.isInstanceOf(EntityNotFoundException.class).hasMessageContaining(NOT_EXIST_ID) .isInstanceOf(EntityNotFoundException.class).hasMessageContaining(NOT_EXIST_ID)
.hasMessageContaining("DistributionSet"); .hasMessageContaining("DistributionSet");
verifyThrownExceptionBy( verifyThrownExceptionBy(
() -> distributionSetManagement.update(entityFactory.distributionSet().update(NOT_EXIST_IDL)), () -> distributionSetManagement.update(entityFactory.distributionSet().update(NOT_EXIST_IDL)), "DistributionSet");
"DistributionSet");
verifyThrownExceptionBy(() -> distributionSetManagement.updateMetaData(NOT_EXIST_IDL, verifyThrownExceptionBy(() -> distributionSetManagement.updateMetadata(NOT_EXIST_IDL,"xxx", "xxx"), "DistributionSet");
entityFactory.generateDsMetadata("xxx", "xxx")), "DistributionSet"); verifyThrownExceptionBy(() -> distributionSetManagement.updateMetadata(set.getId(),NOT_EXIST_ID, "xxx"), "DistributionSet");
verifyThrownExceptionBy(() -> distributionSetManagement.updateMetaData(set.getId(), verifyThrownExceptionBy(() -> distributionSetManagement.getOrElseThrowException(NOT_EXIST_IDL), "DistributionSet");
entityFactory.generateDsMetadata(NOT_EXIST_ID, "xxx")), "DistributionSetMetadata");
verifyThrownExceptionBy(() -> distributionSetManagement.getOrElseThrowException(NOT_EXIST_IDL),
"DistributionSet");
verifyThrownExceptionBy(() -> distributionSetManagement.getValidAndComplete(NOT_EXIST_IDL), "DistributionSet"); verifyThrownExceptionBy(() -> distributionSetManagement.getValidAndComplete(NOT_EXIST_IDL), "DistributionSet");
@@ -247,68 +230,57 @@ class DistributionSetManagementTest extends AbstractJpaIntegrationTest {
@Test @Test
@Description("Checks that metadata for a distribution set can be created.") @Description("Checks that metadata for a distribution set can be created.")
void createDistributionSetMetadata() { void createMetadata() {
final String knownKey = "dsMetaKnownKey"; final String knownKey = "dsMetaKnownKey";
final String knownValue = "dsMetaKnownValue"; final String knownValue = "dsMetaKnownValue";
final DistributionSet ds = testdataFactory.createDistributionSet("testDs"); final DistributionSet ds = testdataFactory.createDistributionSet("testDs");
final DistributionSetMetadata metadata = new JpaDistributionSetMetadata(knownKey, ds, knownValue); insertMetadata(knownKey, knownValue, ds); // and validate
final JpaDistributionSetMetadata createdMetadata = (JpaDistributionSetMetadata) createDistributionSetMetadata(
ds.getId(), metadata);
assertThat(createdMetadata).isNotNull();
assertThat(createdMetadata.getId().getKey()).isEqualTo(knownKey);
assertThat(createdMetadata.getDistributionSet().getId()).isEqualTo(ds.getId());
assertThat(createdMetadata.getValue()).isEqualTo(knownValue);
} }
@Test @Test
@Description("Verifies the enforcement of the metadata quota per distribution set.") @Description("Verifies the enforcement of the metadata quota per distribution set.")
void createDistributionSetMetadataUntilQuotaIsExceeded() { void createMetadataUntilQuotaIsExceeded() {
// add meta data one by one // add meta data one by one
final DistributionSet ds1 = testdataFactory.createDistributionSet("ds1"); final DistributionSet ds1 = testdataFactory.createDistributionSet("ds1");
final int maxMetaData = quotaManagement.getMaxMetaDataEntriesPerDistributionSet(); final int maxMetaData = quotaManagement.getMaxMetaDataEntriesPerDistributionSet();
for (int i = 0; i < maxMetaData; ++i) { for (int i = 0; i < maxMetaData; ++i) {
assertThat((JpaDistributionSetMetadata) createDistributionSetMetadata(ds1.getId(), insertMetadata("k" + i, "v" + i, ds1);
new JpaDistributionSetMetadata("k" + i, ds1, "v" + i))).isNotNull();
} }
// quota exceeded // quota exceeded
final Long ds1Id = ds1.getId();
final JpaDistributionSetMetadata jpaMaxMetaData = new JpaDistributionSetMetadata("k" + maxMetaData, ds1, "v" + maxMetaData);
assertThatExceptionOfType(AssignmentQuotaExceededException.class) assertThatExceptionOfType(AssignmentQuotaExceededException.class)
.isThrownBy(() -> createDistributionSetMetadata(ds1Id, jpaMaxMetaData)); .isThrownBy(() -> insertMetadata("k" + maxMetaData, "v" + maxMetaData, ds1));
// add multiple meta data entries at once // add multiple meta data entries at once
final DistributionSet ds2 = testdataFactory.createDistributionSet("ds2"); final DistributionSet ds2 = testdataFactory.createDistributionSet("ds2");
final List<MetaData> metaData2 = new ArrayList<>(); final Map<String, String> metaData2 = new HashMap<>();
for (int i = 0; i < maxMetaData + 1; ++i) { for (int i = 0; i < maxMetaData + 1; ++i) {
metaData2.add(new JpaDistributionSetMetadata("k" + i, ds2, "v" + i)); metaData2.put("k" + i, "v" + i);
} }
// verify quota is exceeded // verify quota is exceeded
final Long ds2Id = ds2.getId(); final Long ds2Id = ds2.getId();
assertThatExceptionOfType(AssignmentQuotaExceededException.class) assertThatExceptionOfType(AssignmentQuotaExceededException.class)
.isThrownBy(() -> createDistributionSetMetadata(ds2Id, metaData2)); .isThrownBy(() -> distributionSetManagement.createMetadata(ds2Id, metaData2));
// add some meta data entries // add some meta data entries
final DistributionSet ds3 = testdataFactory.createDistributionSet("ds3"); final DistributionSet ds3 = testdataFactory.createDistributionSet("ds3");
final int firstHalf = Math.round((maxMetaData) / 2.f); final int firstHalf = Math.round((maxMetaData) / 2.f);
final Long ds3Id = ds3.getId(); final Long ds3Id = ds3.getId();
for (int i = 0; i < firstHalf; ++i) { for (int i = 0; i < firstHalf; ++i) {
createDistributionSetMetadata(ds3Id, new JpaDistributionSetMetadata("k" + i, ds3, "v" + i)); insertMetadata("k" + i, "v" + i, ds3);
} }
// add too many data entries // add too many data entries
final int secondHalf = maxMetaData - firstHalf; final int secondHalf = maxMetaData - firstHalf;
final List<MetaData> metaData3 = new ArrayList<>(); final Map<String, String> metaData3 = new HashMap<>();
for (int i = 0; i < secondHalf + 1; ++i) { for (int i = 0; i < secondHalf + 1; ++i) {
metaData3.add(new JpaDistributionSetMetadata("kk" + i, ds3, "vv" + i)); metaData3.put("kk" + i, "vv" + i);
} }
// verify quota is exceeded // verify quota is exceeded
assertThatExceptionOfType(AssignmentQuotaExceededException.class) assertThatExceptionOfType(AssignmentQuotaExceededException.class)
.isThrownBy(() -> createDistributionSetMetadata(ds3Id, metaData3)); .isThrownBy(() -> distributionSetManagement.createMetadata(ds3Id, metaData3));
} }
@Test @Test
@@ -507,7 +479,7 @@ class DistributionSetManagementTest extends AbstractJpaIntegrationTest {
@Test @Test
@WithUser(allSpPermissions = true) @WithUser(allSpPermissions = true)
@Description("Checks that metadata for a distribution set can be updated.") @Description("Checks that metadata for a distribution set can be updated.")
void updateDistributionSetMetadata() { void updateMetadata() {
final String knownKey = "myKnownKey"; final String knownKey = "myKnownKey";
final String knownValue = "myKnownValue"; final String knownValue = "myKnownValue";
final String knownUpdateValue = "myNewUpdatedValue"; final String knownUpdateValue = "myNewUpdatedValue";
@@ -519,26 +491,22 @@ class DistributionSetManagementTest extends AbstractJpaIntegrationTest {
waitNextMillis(); waitNextMillis();
// create an DS meta data entry // create an DS meta data entry
createDistributionSetMetadata(ds.getId(), new JpaDistributionSetMetadata(knownKey, ds, knownValue)); insertMetadata(knownKey, knownValue, ds);
final DistributionSet changedLockRevisionDS = getOrThrow(distributionSetManagement.get(ds.getId())); final DistributionSet changedLockRevisionDS = getOrThrow(distributionSetManagement.get(ds.getId()));
assertThat(changedLockRevisionDS.getOptLockRevision()).isEqualTo(2); assertThat(changedLockRevisionDS.getOptLockRevision()).isEqualTo(2);
waitNextMillis(); waitNextMillis();
// update the DS metadata // update the DS metadata
final JpaDistributionSetMetadata updated = (JpaDistributionSetMetadata) distributionSetManagement distributionSetManagement.updateMetadata(ds.getId(), knownKey, knownUpdateValue);
.updateMetaData(ds.getId(), entityFactory.generateDsMetadata(knownKey, knownUpdateValue));
// we are updating the sw metadata so also modifying the base software // we are updating the sw metadata so also modifying the base software
// module so opt lock revision must be three // module so opt lock revision must be three
final DistributionSet reloadedDS = getOrThrow(distributionSetManagement.get(ds.getId())); final DistributionSet reloadedDS = getOrThrow(distributionSetManagement.get(ds.getId()));
assertThat(reloadedDS.getOptLockRevision()).isEqualTo(3); assertThat(reloadedDS.getOptLockRevision()).isEqualTo(3);
assertThat(reloadedDS.getLastModifiedAt()).isPositive(); assertThat(reloadedDS.getLastModifiedAt()).isPositive();
// verify updated meta data contains the updated value // verify updated meta data is the updated value
assertThat(updated).isNotNull(); assertThat(distributionSetManagement.getMetadata(ds.getId()).get(knownKey)).isEqualTo(knownUpdateValue);
assertThat(updated.getValue()).isEqualTo(knownUpdateValue);
assertThat(updated.getId().getKey()).isEqualTo(knownKey);
assertThat(updated.getDistributionSet().getId()).isEqualTo(ds.getId());
} }
@Test @Test
@@ -783,37 +751,22 @@ class DistributionSetManagementTest extends AbstractJpaIntegrationTest {
} }
@Test @Test
@Description("Queries and loads the metadata related to a given software module.") @Description("Queries and loads the metadata related to a given distribution set.")
void findAllDistributionSetMetadataByDsId() { void getMetadata() {
// create a DS // create a DS
final DistributionSet ds1 = testdataFactory.createDistributionSet("testDs1"); final DistributionSet ds1 = testdataFactory.createDistributionSet("testDs1");
final DistributionSet ds2 = testdataFactory.createDistributionSet("testDs2"); final DistributionSet ds2 = testdataFactory.createDistributionSet("testDs2");
for (int index = 0; index < quotaManagement.getMaxMetaDataEntriesPerDistributionSet(); index++) { for (int index = 0; index < quotaManagement.getMaxMetaDataEntriesPerDistributionSet(); index++) {
createDistributionSetMetadata(ds1.getId(), insertMetadata("key" + index, "value" + index, ds1);
new JpaDistributionSetMetadata("key" + index, ds1, "value" + index));
} }
for (int index = 0; index <= quotaManagement.getMaxMetaDataEntriesPerDistributionSet() - 2; index++) { for (int index = 0; index <= quotaManagement.getMaxMetaDataEntriesPerDistributionSet() - 2; index++) {
createDistributionSetMetadata(ds2.getId(), insertMetadata("key" + index, "value" + index, ds2);
new JpaDistributionSetMetadata("key" + index, ds2, "value" + index));
} }
final Page<DistributionSetMetadata> metadataOfDs1 = distributionSetManagement assertThat(distributionSetManagement.getMetadata(ds1.getId())).hasSize(quotaManagement.getMaxMetaDataEntriesPerDistributionSet());
.findMetaDataByDistributionSetId(ds1.getId(), PageRequest.of(0, 100)); assertThat(distributionSetManagement.getMetadata(ds2.getId())).hasSize(quotaManagement.getMaxMetaDataEntriesPerDistributionSet() - 1);
final Page<DistributionSetMetadata> metadataOfDs2 = distributionSetManagement
.findMetaDataByDistributionSetId(ds2.getId(), PageRequest.of(0, 100));
assertThat(metadataOfDs1.getNumberOfElements())
.isEqualTo(quotaManagement.getMaxMetaDataEntriesPerDistributionSet());
assertThat(metadataOfDs1.getTotalElements())
.isEqualTo(quotaManagement.getMaxMetaDataEntriesPerDistributionSet());
assertThat(metadataOfDs2.getNumberOfElements())
.isEqualTo(quotaManagement.getMaxMetaDataEntriesPerDistributionSet() - 1);
assertThat(metadataOfDs2.getTotalElements())
.isEqualTo(quotaManagement.getMaxMetaDataEntriesPerDistributionSet() - 1);
} }
@Test @Test
@@ -897,23 +850,20 @@ class DistributionSetManagementTest extends AbstractJpaIntegrationTest {
final String knownUpdateValue = "knownUpdateValue"; final String knownUpdateValue = "knownUpdateValue";
final Long dsId = testdataFactory.createDistributionSet().getId(); final Long dsId = testdataFactory.createDistributionSet().getId();
distributionSetManagement.putMetaData(dsId, distributionSetManagement.createMetadata(dsId, Map.of(knownKey1, knownValue));
singletonList(entityFactory.generateDsMetadata(knownKey1, knownValue)));
distributionSetInvalidationManagement.invalidateDistributionSet( distributionSetInvalidationManagement.invalidateDistributionSet(
new DistributionSetInvalidation(singletonList(dsId), CancelationType.NONE, false)); new DistributionSetInvalidation(singletonList(dsId), CancelationType.NONE, false));
// assert that no new metadata can be created // assert that no new metadata can be created
final List<MetaData> metadata = singletonList(entityFactory.generateDsMetadata(knownKey2, knownValue));
assertThatExceptionOfType(InvalidDistributionSetException.class) assertThatExceptionOfType(InvalidDistributionSetException.class)
.as("Invalid distributionSet should throw an exception") .as("Invalid distributionSet should throw an exception")
.isThrownBy(() -> distributionSetManagement.putMetaData(dsId, metadata)); .isThrownBy(() -> distributionSetManagement.createMetadata(dsId, Map.of(knownKey2, knownValue)));
// assert that an existing metadata can not be updated // assert that an existing metadata can not be updated
final MetaData metadata2 = entityFactory.generateDsMetadata(knownKey1, knownUpdateValue);
assertThatExceptionOfType(InvalidDistributionSetException.class) assertThatExceptionOfType(InvalidDistributionSetException.class)
.as("Invalid distributionSet should throw an exception") .as("Invalid distributionSet should throw an exception")
.isThrownBy(() -> distributionSetManagement.updateMetaData(dsId, metadata2)); .isThrownBy(() -> distributionSetManagement.updateMetadata(dsId, knownKey1, knownUpdateValue));
} }
@Test @Test
@@ -1265,6 +1215,11 @@ class DistributionSetManagementTest extends AbstractJpaIntegrationTest {
.isComplete(Boolean.FALSE).isDeleted(Boolean.FALSE)); .isComplete(Boolean.FALSE).isDeleted(Boolean.FALSE));
} }
private void insertMetadata(final String knownKey, final String knownValue, final DistributionSet distributionSet) {
distributionSetManagement.createMetadata(distributionSet.getId(), Map.of(knownKey, knownValue));
assertThat(distributionSetManagement.getMetadata(distributionSet.getId()).get(knownKey)).isEqualTo(knownValue);
}
private void assertThatFilterContainsOnlyGivenDistributionSets(final DistributionSetFilterBuilder filterBuilder, private void assertThatFilterContainsOnlyGivenDistributionSets(final DistributionSetFilterBuilder filterBuilder,
final List<DistributionSet> distributionSets) { final List<DistributionSet> distributionSets) {
final int expectedDsSize = distributionSets.size(); final int expectedDsSize = distributionSets.size();

View File

@@ -1,234 +0,0 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.repository.jpa.rsql;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import java.util.Arrays;
import java.util.Collections;
import jakarta.persistence.EntityManager;
import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import io.qameta.allure.Story;
import org.eclipse.hawkbit.repository.DistributionSetFields;
import org.eclipse.hawkbit.repository.SoftwareModuleFields;
import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException;
import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException;
import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetTag;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer;
import org.eclipse.hawkbit.repository.test.util.TestdataFactory;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.orm.jpa.vendor.Database;
@Feature("Component Tests - Repository")
@Story("RSQL filter distribution set")
@SuppressWarnings("java:S6813") // constructor injects are not possible for test classes
class RSQLDistributionSetFieldTest extends AbstractJpaIntegrationTest {
@Autowired
protected VirtualPropertyReplacer virtualPropertyReplacer;
@Autowired
protected EntityManager entityManager;
private DistributionSet ds;
private SoftwareModule sm;
@BeforeEach
void setupBeforeTest() {
sm = testdataFactory.createSoftwareModuleApp("SM");
ds = testdataFactory.createDistributionSet(Collections.singletonList(sm), "DS");
ds = distributionSetManagement.update(entityFactory.distributionSet().update(ds.getId()).description("DS"));
createDistributionSetMetadata(ds.getId(), entityFactory.generateDsMetadata("metaKey", "metaValue"));
DistributionSet ds2 = testdataFactory.createDistributionSets("NewDS", 3).get(0);
ds2 = distributionSetManagement.update(entityFactory.distributionSet().update(ds2.getId()).description("DS%"));
createDistributionSetMetadata(ds2.getId(), entityFactory.generateDsMetadata("metaKey", "value"));
final DistributionSetTag distSetTag = distributionSetTagManagement
.create(entityFactory.tag().create().name("Tag1"));
distributionSetTagManagement.create(entityFactory.tag().create().name("Tag2"));
distributionSetTagManagement.create(entityFactory.tag().create().name("Tag3"));
distributionSetTagManagement.create(entityFactory.tag().create().name("Tag4"));
distributionSetManagement.assignTag(Arrays.asList(ds.getId(), ds2.getId()), distSetTag.getId());
final DistributionSet ds3 = distributionSetManagement
.create(entityFactory.distributionSet().create().name("test123").version("noDescription"));
distributionSetManagement.invalidate(ds3);
}
@Test
@Description("Test filter distribution set by id")
void testFilterByParameterId() {
assertRSQLQuery(DistributionSetFields.ID.name() + "==" + ds.getId(), 1);
assertRSQLQuery(DistributionSetFields.ID.name() + "!=" + ds.getId(), 4);
assertRSQLQuery(DistributionSetFields.ID.name() + "==" + -1, 0);
assertRSQLQuery(DistributionSetFields.ID.name() + "!=" + -1, 5);
// Not supported for numbers
if (Database.POSTGRESQL.equals(getDatabase())) {
return;
}
assertRSQLQuery(DistributionSetFields.ID.name() + "=in=(" + ds.getId() + ",10000000)", 1);
assertRSQLQuery(DistributionSetFields.ID.name() + "=out=(" + ds.getId() + ",10000000)", 4);
}
@Test
@Description("Test filter distribution set by name")
void testFilterByParameterName() {
assertRSQLQuery(DistributionSetFields.NAME.name() + "==DS", 1);
assertRSQLQuery(DistributionSetFields.NAME.name() + "!=DS", 4);
assertRSQLQuery(DistributionSetFields.NAME.name() + "==*DS", 4);
assertRSQLQuery(DistributionSetFields.NAME.name() + "==noExist*", 0);
assertRSQLQuery(DistributionSetFields.NAME.name() + "=in=(DS,notexist)", 1);
assertRSQLQuery(DistributionSetFields.NAME.name() + "=out=(DS,notexist)", 4);
}
@Test
@Description("Test filter distribution set by assigned software module")
void testFilterBySoftwareModule() {
assertRSQLQuery(DistributionSetFields.MODULE.name() + "." + SoftwareModuleFields.NAME.name() + "==" + sm.getName(), 1);
assertRSQLQuery(DistributionSetFields.MODULE.name() + "." + SoftwareModuleFields.ID.name() + "==" + sm.getId(), 1);
assertRSQLQuery(DistributionSetFields.MODULE.name() + "." + SoftwareModuleFields.NAME.name() + "==noExist", 0);
assertRSQLQuery(DistributionSetFields.MODULE.name() + "." + SoftwareModuleFields.ID.name() + "=in=(" + sm.getId() + ", -1)", 1);
assertRSQLQuery(DistributionSetFields.MODULE.name() + "." + SoftwareModuleFields.ID.name() + "=out=(" + sm.getId() + ", -1)", 4);
}
@Test
@Description("Test filter distribution set by description")
void testFilterByParameterDescription() {
assertRSQLQuery(DistributionSetFields.DESCRIPTION.name() + "==''", 1);
assertRSQLQuery(DistributionSetFields.DESCRIPTION.name() + "!=''", 4);
assertRSQLQuery(DistributionSetFields.DESCRIPTION.name() + "==DS", 1);
assertRSQLQuery(DistributionSetFields.DESCRIPTION.name() + "!=DS*", 3);
assertRSQLQuery(DistributionSetFields.DESCRIPTION.name() + "!=DS", 4);
assertRSQLQuery(DistributionSetFields.DESCRIPTION.name() + "==DS*", 2);
assertRSQLQuery(DistributionSetFields.DESCRIPTION.name() + "==DS%", 1);
assertRSQLQuery(DistributionSetFields.DESCRIPTION.name() + "==noExist*", 0);
assertRSQLQuery(DistributionSetFields.DESCRIPTION.name() + "=in=(DS,notexist)", 1);
assertRSQLQuery(DistributionSetFields.DESCRIPTION.name() + "=out=(DS,notexist)", 4);
}
@Test
@Description("Test filter distribution set by version")
void testFilterByParameterVersion() {
assertRSQLQuery(DistributionSetFields.VERSION.name() + "==" + TestdataFactory.DEFAULT_VERSION, 1);
assertRSQLQuery(DistributionSetFields.VERSION.name() + "!=" + TestdataFactory.DEFAULT_VERSION, 4);
assertRSQLQuery(DistributionSetFields.VERSION.name() + "=in=(" + TestdataFactory.DEFAULT_VERSION + ",1.0.0,1.0.1)", 3);
assertRSQLQuery(DistributionSetFields.VERSION.name() + "=out=(" + TestdataFactory.DEFAULT_VERSION + ",error)", 4);
}
@Test
@Description("Test filter distribution set by complete property")
void testFilterByAttributeComplete() {
assertRSQLQuery(DistributionSetFields.COMPLETE.name() + "==true", 3);
final String noExistStar = DistributionSetFields.COMPLETE.name() + "==noExist*";
assertThatExceptionOfType(RSQLParameterSyntaxException.class)
.isThrownBy(() -> assertRSQLQuery(noExistStar, 0));
assertRSQLQuery(DistributionSetFields.COMPLETE.name() + "=in=(true)", 3);
assertRSQLQuery(DistributionSetFields.COMPLETE.name() + "=out=(true)", 2);
}
@Test
@Description("Test filter distribution set by valid property")
void testFilterByAttributeValid() {
assertRSQLQuery(DistributionSetFields.VALID.name() + "==true", 4);
assertRSQLQuery(DistributionSetFields.VALID.name() + "==false", 1);
final String rsqlNoExistStar = DistributionSetFields.VALID.name() + "==noExist*";
assertThatExceptionOfType(RSQLParameterSyntaxException.class)
.isThrownBy(() -> assertRSQLQuery(rsqlNoExistStar, 0));
assertRSQLQuery(DistributionSetFields.VALID.name() + "=in=(true)", 4);
assertRSQLQuery(DistributionSetFields.VALID.name() + "=out=(true)", 1);
}
@Test
@Description("Test filter distribution set by tag name")
void testFilterByTag() {
assertRSQLQuery(DistributionSetFields.TAG.name() + "==Tag1", 2);
assertRSQLQuery(DistributionSetFields.TAG.name() + "!=Tag1", 3);
assertRSQLQuery(DistributionSetFields.TAG.name() + "==T*", 2);
assertRSQLQuery(DistributionSetFields.TAG.name() + "==noExist*", 0);
assertRSQLQuery(DistributionSetFields.TAG.name() + "=in=(Tag1,notexist)", 2);
assertRSQLQuery(DistributionSetFields.TAG.name() + "=out=(null)", 5);
}
@Test
@Description("Test filter distribution set by type key")
void testFilterByType() {
assertRSQLQuery(DistributionSetFields.TYPE.name() + "==" + TestdataFactory.DS_TYPE_DEFAULT, 4);
assertRSQLQuery(DistributionSetFields.TYPE.name() + "==noExist*", 0);
assertRSQLQuery(DistributionSetFields.TYPE.name() + "=in=(" + TestdataFactory.DS_TYPE_DEFAULT + ",ecl)", 4);
assertRSQLQuery(DistributionSetFields.TYPE.name() + "=out=(" + TestdataFactory.DS_TYPE_DEFAULT + ")", 1);
}
@Test
@Description("Test filter distribution set by metadata")
void testFilterByMetadata() {
createDistributionSetWithMetadata("key.dot", "value.dot");
assertRSQLQuery(DistributionSetFields.METADATA.name() + ".metaKey==metaValue", 1);
assertRSQLQuery(DistributionSetFields.METADATA.name() + ".metaKey==*v*", 2);
assertRSQLQuery(DistributionSetFields.METADATA.name() + ".metaKey==noExist*", 0);
assertRSQLQuery(DistributionSetFields.METADATA.name() + ".metaKey=in=(metaValue,notexist)", 1);
assertRSQLQuery(DistributionSetFields.METADATA.name() + ".metaKey=out=(metaValue,notexist)", 1);
assertRSQLQuery(DistributionSetFields.METADATA.name() + ".notExist==metaValue", 0);
assertRSQLQuery(DistributionSetFields.METADATA.name() + ".key.dot==value.dot", 1);
assertRSQLQuery(DistributionSetFields.METADATA.name() + ".key.dot*==value.dot", 0);
assertRSQLQuery(DistributionSetFields.METADATA.name() + ".key.*==value.dot", 0);
assertRSQLQuery(DistributionSetFields.METADATA.name() + ".key.==value.dot", 0);
assertRSQLQuery(DistributionSetFields.METADATA.name() + ".key*==value.dot", 0);
assertRSQLQuery(DistributionSetFields.METADATA.name() + ".*==value.dot", 0);
assertRSQLQuery(DistributionSetFields.METADATA.name() + "..==value.dot", 0);
assertRSQLQueryThrowsException(
DistributionSetFields.METADATA.name() + ".==value.dot",
RSQLParameterUnsupportedFieldException.class);
assertRSQLQueryThrowsException(
DistributionSetFields.METADATA.name() + "*==value.dot",
RSQLParameterUnsupportedFieldException.class);
assertRSQLQueryThrowsException(
DistributionSetFields.METADATA.name() + "==value.dot",
RSQLParameterUnsupportedFieldException.class);
}
private void assertRSQLQuery(final String rsqlParam, final long expectedEntity) {
final Page<DistributionSet> find = distributionSetManagement.findByRsql(rsqlParam, PageRequest.of(0, 100));
final long countAll = find.getTotalElements();
assertThat(find).as("Found entity is should not be null").isNotNull();
assertThat(countAll).as("Found entity size is wrong").isEqualTo(expectedEntity);
}
private <T extends Throwable> void assertRSQLQueryThrowsException(final String rsqlParam, final Class<T> expectedException) {
assertThatExceptionOfType(expectedException)
.isThrownBy(() -> RSQLUtility.validateRsqlFor(
rsqlParam, DistributionSetFields.class, JpaDistributionSet.class, virtualPropertyReplacer, entityManager));
}
private DistributionSet createDistributionSetWithMetadata(final String metadataKeyName, final String metadataValue) {
final DistributionSet distributionSet = testdataFactory.createDistributionSet();
createDistributionSetMetadata(distributionSet.getId(), entityFactory.generateDsMetadata(metadataKeyName, metadataValue));
return distributionSet;
}
}

View File

@@ -1,81 +0,0 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.repository.jpa.rsql;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import io.qameta.allure.Story;
import org.eclipse.hawkbit.repository.DistributionSetMetadataFields;
import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetMetadata;
import org.eclipse.hawkbit.repository.model.MetaData;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
@Feature("Component Tests - Repository")
@Story("RSQL filter distribution set metadata")
class RSQLDistributionSetMetadataFieldsTest extends AbstractJpaIntegrationTest {
private Long distributionSetId;
@BeforeEach
void setupBeforeTest() {
final DistributionSet distributionSet = testdataFactory.createDistributionSet("DS");
distributionSetId = distributionSet.getId();
final List<MetaData> metadata = new ArrayList<>(5);
for (int i = 0; i < 5; i++) {
metadata.add(entityFactory.generateDsMetadata("" + i, "" + i));
}
distributionSetManagement.putMetaData(distributionSetId, metadata);
distributionSetManagement.putMetaData(distributionSetId,
Arrays.asList(entityFactory.generateDsMetadata("emptyValueTest", null)));
}
@Test
@Description("Test filter distribution set metadata by key")
void testFilterByParameterKey() {
assertRSQLQuery(DistributionSetMetadataFields.KEY.name() + "==1", 1);
assertRSQLQuery(DistributionSetMetadataFields.KEY.name() + "!=1", 5);
assertRSQLQuery(DistributionSetMetadataFields.KEY.name() + "=in=(1,2)", 2);
assertRSQLQuery(DistributionSetMetadataFields.KEY.name() + "=out=(1,2)", 4);
}
@Test
@Description("Test filter distribution set metadata by value")
void testFilterByParameterValue() {
assertRSQLQuery(DistributionSetMetadataFields.VALUE.name() + "==''", 1);
assertRSQLQuery(DistributionSetMetadataFields.VALUE.name() + "!=''", 5);
assertRSQLQuery(DistributionSetMetadataFields.VALUE.name() + "==1", 1);
assertRSQLQuery(DistributionSetMetadataFields.VALUE.name() + "!=1", 5);
assertRSQLQuery(DistributionSetMetadataFields.VALUE.name() + "=in=(1,2)", 2);
assertRSQLQuery(DistributionSetMetadataFields.VALUE.name() + "=out=(1,2)", 4);
}
private void assertRSQLQuery(final String rsqlParam, final long expectedEntities) {
final Page<DistributionSetMetadata> findEnitity = distributionSetManagement
.findMetaDataByDistributionSetIdAndRsql(distributionSetId, rsqlParam, PageRequest.of(0, 100));
final long countAllEntities = findEnitity.getTotalElements();
assertThat(findEnitity).isNotNull();
assertThat(countAllEntities).isEqualTo(expectedEntities);
}
}

View File

@@ -21,6 +21,7 @@ import java.time.ZonedDateTime;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@@ -56,7 +57,6 @@ import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.DeploymentRequest; import org.eclipse.hawkbit.repository.model.DeploymentRequest;
import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult; import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult;
import org.eclipse.hawkbit.repository.model.DistributionSetMetadata;
import org.eclipse.hawkbit.repository.model.DistributionSetType; import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.MetaData; import org.eclipse.hawkbit.repository.model.MetaData;
import org.eclipse.hawkbit.repository.model.RepositoryModelConstants; import org.eclipse.hawkbit.repository.model.RepositoryModelConstants;
@@ -412,16 +412,7 @@ public abstract class AbstractIntegrationTest {
} }
protected boolean isConfirmationFlowActive() { protected boolean isConfirmationFlowActive() {
return tenantConfigurationManagement.getConfigurationValue(TenantConfigurationKey.USER_CONFIRMATION_ENABLED, return tenantConfigurationManagement.getConfigurationValue(TenantConfigurationKey.USER_CONFIRMATION_ENABLED, Boolean.class).getValue();
Boolean.class).getValue();
}
protected DistributionSetMetadata createDistributionSetMetadata(final long dsId, final MetaData md) {
return createDistributionSetMetadata(dsId, Collections.singletonList(md)).get(0);
}
protected List<DistributionSetMetadata> createDistributionSetMetadata(final long dsId, final List<MetaData> md) {
return distributionSetManagement.putMetaData(dsId, md);
} }
protected Long getOsModule(final DistributionSet ds) { protected Long getOsModule(final DistributionSet ds) {