Soft Module metadata as complex map value (#2568)

---------

Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
Avgustin Marinov
2025-07-30 17:29:02 +03:00
committed by GitHub
parent 4a8e60764f
commit 08cacf9034
38 changed files with 392 additions and 962 deletions

View File

@@ -32,7 +32,6 @@ import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.ActionStatus;
import org.eclipse.hawkbit.repository.model.AutoConfirmationStatus;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.SoftwareModuleMetadata;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
import org.eclipse.hawkbit.tenancy.configuration.ControllerPollProperties;
@@ -69,14 +68,13 @@ public interface ControllerManagement {
Optional<SoftwareModule> getSoftwareModule(long moduleId);
/**
* Retrieves {@link SoftwareModuleMetadata} where {@link SoftwareModuleMetadata#isTargetVisible()}.
* Retrieves software module metadata where isTargetVisible.
*
* @param moduleId of the {@link SoftwareModule}
* @return the map of software module id to {@link SoftwareModuleMetadata} with maximum size of
* {@link RepositoryConstants#MAX_META_DATA_COUNT}
* @return the map of software module id to software module metadata with maximum size of {@link RepositoryConstants#MAX_META_DATA_COUNT}
*/
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
Map<Long, List<SoftwareModuleMetadata>> findTargetVisibleMetaDataBySoftwareModuleId(@NotNull Collection<Long> moduleId);
Map<Long, Map<String, String>> findTargetVisibleMetaDataBySoftwareModuleId(@NotNull Collection<Long> moduleId);
/**
* Simple addition of a new {@link ActionStatus} entry to the {@link Action}. No state changes.

View File

@@ -12,7 +12,6 @@ package org.eclipse.hawkbit.repository;
import org.eclipse.hawkbit.repository.builder.ActionStatusBuilder;
import org.eclipse.hawkbit.repository.builder.RolloutBuilder;
import org.eclipse.hawkbit.repository.builder.RolloutGroupBuilder;
import org.eclipse.hawkbit.repository.builder.SoftwareModuleMetadataBuilder;
import org.eclipse.hawkbit.repository.builder.TagBuilder;
import org.eclipse.hawkbit.repository.builder.TargetBuilder;
import org.eclipse.hawkbit.repository.builder.TargetFilterQueryBuilder;
@@ -31,11 +30,6 @@ public interface EntityFactory {
*/
ActionStatusBuilder actionStatus();
/**
* @return {@link SoftwareModuleMetadataBuilder} object
*/
SoftwareModuleMetadataBuilder softwareModuleMetadata();
/**
* @return {@link TagBuilder} object
*/

View File

@@ -9,14 +9,17 @@
*/
package org.eclipse.hawkbit.repository;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.ActionStatus;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.SoftwareModuleMetadata;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
/**
* Repository constants.
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class RepositoryConstants {
/**
@@ -46,12 +49,7 @@ public final class RepositoryConstants {
/**
* Maximum number of metadata entries provided to controllers.
*
* @see SoftwareModuleMetadata#isTargetVisible()
* @see SoftwareModule.MetadataValueCreate#isTargetVisible()
*/
public static final int MAX_META_DATA_COUNT = 50;
private RepositoryConstants() {
// Utility class.
}
}
}

View File

@@ -10,7 +10,6 @@
package org.eclipse.hawkbit.repository;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -25,8 +24,6 @@ import lombok.Getter;
import lombok.ToString;
import lombok.experimental.SuperBuilder;
import org.eclipse.hawkbit.im.authentication.SpringEvalExpressions;
import org.eclipse.hawkbit.repository.builder.SoftwareModuleMetadataCreate;
import org.eclipse.hawkbit.repository.builder.SoftwareModuleMetadataUpdate;
import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException;
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
@@ -34,9 +31,8 @@ import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.NamedEntity;
import org.eclipse.hawkbit.repository.model.NamedVersionedEntity;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.SoftwareModuleMetadata;
import org.eclipse.hawkbit.repository.model.SoftwareModule.MetadataValue;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
import org.eclipse.hawkbit.repository.model.Type;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
@@ -53,27 +49,27 @@ public interface SoftwareModuleManagement<T extends SoftwareModule>
return "SOFTWARE_MODULE";
}
/**
* Creates or updates a distribution set meta-data value.
*
* @param id the software module id which meta-data has to be updated
* @param key the key of the meta-data entry to be updated
* @param value the meta-data value to be updated
* @throws EntityNotFoundException in case the meta-data entry does not exist and cannot be updated
*/
@PreAuthorize(SpringEvalExpressions.HAS_UPDATE_REPOSITORY)
void createMetadata(@NotNull Long id, @NotEmpty String key, @NotNull @Valid MetadataValue value);
/**
* Creates a list of software module meta-data entries.
*
* @param metadata the meta-data entries to create
* @throws EntityAlreadyExistsException in case one of the meta-data entry already exists for the specific key
* @throws EntityNotFoundException if software module with given ID does not exist
* @throws AssignmentQuotaExceededException if the maximum number of {@link SoftwareModuleMetadata} entries is exceeded for the addressed
* {@link SoftwareModule}
* @throws AssignmentQuotaExceededException if the maximum number of meta-data entries is exceeded for the addressed {@link SoftwareModule}
*/
@PreAuthorize(SpringEvalExpressions.HAS_UPDATE_REPOSITORY)
void createMetadata(@NotNull @Valid Collection<SoftwareModuleMetadataCreate> metadata);
/**
* Finds all meta-data by the given software module id.
*
* @param id the software module id to retrieve the meta-data from
* @return a paged result of all meta-data entries for a given software module id
* @throws EntityNotFoundException if software module with given ID does not exist
*/
@PreAuthorize(SpringEvalExpressions.HAS_READ_REPOSITORY)
List<SoftwareModuleMetadata> getMetadata(long id);
void createMetadata(@NotNull Long id, @NotEmpty @Valid Map<String, ? extends MetadataValue> metadata);
/**
* Finds all meta-data by the given software module id and key.
@@ -84,52 +80,30 @@ public interface SoftwareModuleManagement<T extends SoftwareModule>
* @throws EntityNotFoundException if software module with given ID does not exist ot the
*/
@PreAuthorize(SpringEvalExpressions.HAS_READ_REPOSITORY)
SoftwareModuleMetadata getMetadata(long id, String key);
MetadataValue getMetadata(@NotNull Long id, @NotEmpty String key);
/**
* Finds all meta-data by the given software module id where {@link SoftwareModuleMetadata#isTargetVisible()}.
* Finds all meta-data by the given software module id.
*
* @param id the software module 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 software module id
* @throws EntityNotFoundException if software module with given ID does not exist
*/
@PreAuthorize(SpringEvalExpressions.HAS_READ_REPOSITORY)
Page<SoftwareModuleMetadata> findMetaDataBySoftwareModuleIdAndTargetVisible(long id, @NotNull Pageable pageable);
/**
* Creates or updates a single software module meta-data entry.
*
* @param metadata the meta-data entry to create
* @return the updated or created software module meta-data entry
* @throws EntityAlreadyExistsException in case the meta-data entry already exists for the specific key
* @throws EntityNotFoundException if software module with given ID does not exist
* @throws AssignmentQuotaExceededException if the maximum number of {@link SoftwareModuleMetadata}
* entries is exceeded for the addressed {@link SoftwareModule}
*/
@PreAuthorize(SpringEvalExpressions.HAS_UPDATE_REPOSITORY)
SoftwareModuleMetadata updateMetadata(@NotNull @Valid SoftwareModuleMetadataCreate metadata);
/**
* Updates a distribution set meta-data value if corresponding entry exists.
*
* @param update the meta-data entry to be updated
* @return the updated meta-data entry
* @throws EntityNotFoundException in case the meta-data entry does not exist and cannot be updated
*/
@PreAuthorize(SpringEvalExpressions.HAS_UPDATE_REPOSITORY)
SoftwareModuleMetadata updateMetadata(@NotNull @Valid SoftwareModuleMetadataUpdate update);
Map<String, MetadataValue> getMetadata(@NotNull Long id);
/**
* Deletes a software module meta-data entry.
*
* @param id where meta-data has to be deleted
* @param key of the meta-data element
* @return true if really deleted, false if not
* @throws EntityNotFoundException if software module with given ID does not exist or the key is not found
*/
@PreAuthorize(SpringEvalExpressions.HAS_UPDATE_REPOSITORY)
void deleteMetadata(long id, @NotEmpty String key);
void deleteMetadata(@NotNull Long id, @NotEmpty String key);
@PreAuthorize(SpringEvalExpressions.IS_SYSTEM_CODE)
Map<Long, Map<String, String>> findMetaDataBySoftwareModuleIdsAndTargetVisible(Collection<Long> ids);
/**
* Locks a software module.
@@ -198,9 +172,6 @@ public interface SoftwareModuleManagement<T extends SoftwareModule>
@PreAuthorize(SpringEvalExpressions.HAS_READ_REPOSITORY)
Slice<T> findByType(long typeId, @NotNull Pageable pageable);
@PreAuthorize(SpringEvalExpressions.HAS_READ_REPOSITORY)
Map<Long, List<SoftwareModuleMetadata>> findMetaDataBySoftwareModuleIdsAndTargetVisible(Collection<Long> moduleIds);
/**
* Returns count of all modules assigned to given {@link DistributionSet}.
*

View File

@@ -1,32 +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.builder;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.SoftwareModuleMetadata;
/**
* Builder for {@link SoftwareModuleMetadata}.
*/
public interface SoftwareModuleMetadataBuilder {
/**
* @param softwareModuleId of the {@link SoftwareModule} the {@link SoftwareModuleMetadata} belongs to
* @param key of {@link SoftwareModuleMetadata#getKey()}
* @return builder instance
*/
SoftwareModuleMetadataUpdate update(long softwareModuleId, String key);
/**
* @param softwareModuleId of the {@link SoftwareModule} the {@link SoftwareModuleMetadata} belongs to
* @return builder instance
*/
SoftwareModuleMetadataCreate create(long softwareModuleId);
}

View File

@@ -1,49 +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.builder;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import org.eclipse.hawkbit.repository.model.BaseEntity;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.SoftwareModuleMetadata;
/**
* Builder to create a new {@link SoftwareModuleMetadata} entry. Defines all
* fields that can be set at creation time. Other fields are set by the
* repository automatically, e.g. {@link BaseEntity#getCreatedAt()}.
*/
public interface SoftwareModuleMetadataCreate {
/**
* @param key for {@link SoftwareModuleMetadata#getKey()}
* @return updated builder instance
*/
SoftwareModuleMetadataCreate key(@Size(min = 1, max =SoftwareModule.METADATA_KEY_MAX_SIZE) @NotNull String key);
/**
* @param value for {@link SoftwareModuleMetadata#getValue()}
* @return updated builder instance
*/
SoftwareModuleMetadataCreate value(@Size(max = SoftwareModule.METADATA_VALUE_MAX_SIZE) String value);
/**
* @param visible for {@link SoftwareModuleMetadata#isTargetVisible()}
* @return updated builder instance
*/
SoftwareModuleMetadataCreate targetVisible(Boolean visible);
/**
* @return peek on current state of {@link SoftwareModuleMetadata} in the
* builder
*/
SoftwareModuleMetadata build();
}

View File

@@ -1,35 +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.builder;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.SoftwareModuleMetadata;
/**
* Builder to update an existing {@link SoftwareModuleMetadata} entry. Defines
* all fields that can be updated.
*/
public interface SoftwareModuleMetadataUpdate {
/**
* @param value for {@link SoftwareModuleMetadata#getValue()}
* @return updated builder instance
*/
SoftwareModuleMetadataUpdate value(@Size(min = 1, max = SoftwareModule.METADATA_VALUE_MAX_SIZE) @NotNull String value);
/**
* @param visible for {@link SoftwareModuleMetadata#isTargetVisible()}
* @return updated builder instance
*/
SoftwareModuleMetadataUpdate targetVisible(Boolean visible);
}

View File

@@ -12,6 +12,11 @@ package org.eclipse.hawkbit.repository.model;
import java.util.List;
import java.util.Optional;
import jakarta.validation.constraints.Size;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* Software package as sub element of a {@link DistributionSet}.
*/
@@ -81,4 +86,28 @@ public interface SoftwareModule extends NamedVersionedEntity {
return getArtifacts().stream().filter(artifact -> artifact.getFilename().equalsIgnoreCase(fileName.trim()))
.findAny();
}
interface MetadataValue {
String getValue();
boolean isTargetVisible();
}
@NoArgsConstructor
@Data
class MetadataValueCreate implements MetadataValue {
@Size(max = METADATA_VALUE_MAX_SIZE)
private String value;
private boolean targetVisible;
public MetadataValueCreate(final String value) {
this(value, false);
}
public MetadataValueCreate(final String value, final boolean targetVisible) {
this.value = value;
this.targetVisible = targetVisible;
}
}
}

View File

@@ -1,34 +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;
import java.io.Serializable;
/**
* Metadata element of a {@link SoftwareModule}. The software module metadata is not only (key, value) pair (like the metadata of
* targets and distribution sets), but also contains the information if the metadata is visible for targets as part of {@link Action}.
*/
public interface SoftwareModuleMetadata extends Serializable {
/**
* @return the key
*/
String getKey();
/**
* @return the value
*/
String getValue();
/**
* @return <code>true</code> if element is visible for targets as part of {@link Action}.
*/
boolean isTargetVisible();
}

View File

@@ -1,24 +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.builder;
/**
* Update implementation.
*/
public class GenericSoftwareModuleMetadataUpdate
extends AbstractSoftwareModuleMetadataUpdateCreate<SoftwareModuleMetadataUpdate>
implements SoftwareModuleMetadataUpdate {
public GenericSoftwareModuleMetadataUpdate(final long softwareModuleId, final String key) {
super.softwareModuleId = softwareModuleId;
this.key = key;
}
}

View File

@@ -149,17 +149,16 @@ public class SpecificationBuilder<T> {
if (comparison.getValue() == null) {
// map entry with key is null (doesn't exist) / is not null (exists) - use left join with on
return switch (op) {
case EQ -> cb.isNull(pathResolver.getJoinOn(attribute, split[1]));
case NE -> cb.isNotNull(pathResolver.getJoinOn(attribute, split[1]));
case EQ -> cb.isNull(toMapValuePath(pathResolver.getJoinOn(attribute, split[1])));
case NE -> cb.isNotNull(toMapValuePath(pathResolver.getJoinOn(attribute, split[1])));
default -> throw new RSQLParameterSyntaxException(
String.format("Operator %s is not supported for map fields with value null", op));
};
} else {
final MapJoin<?, ?, ?> mapPath = (MapJoin<?, ?, ?>) pathResolver.getPath(attribute);
final Path<String> valuePath = (Path<String>) mapPath.value();
return isNot(op)
? compare(comparison, pathResolver.getJoinOnInner(attribute, split[1]))
: cb.and(equal(mapPath.key(), split[1]), compare(comparison, valuePath));
? compare(comparison, toMapValuePath(pathResolver.getJoinOnInner(attribute, split[1])))
: cb.and(equal(mapPath.key(), split[1]), compare(comparison, toMapValuePath(mapPath)));
}
} else if (attribute instanceof SetAttribute<?, ?> setAttribute) {
if (split.length < 2 || ObjectUtils.isEmpty(split[1])) {
@@ -185,6 +184,12 @@ public class SpecificationBuilder<T> {
}
}
@SuppressWarnings("unchecked")
private static Path<String> toMapValuePath(final Path<?> mapJoin) {
final Path<?> valuePath = ((MapJoin<?, ?, ?>) mapJoin).value();
return valuePath.getJavaType() == String.class ? (Path<String>) valuePath : valuePath.get("value");
}
private Predicate compare(final Comparison comparison, final Path<?> fieldPath) {
final List<Object> values = getValues(comparison, fieldPath.getJavaType());
final Object firstValue = values.get(0);

View File

@@ -343,8 +343,7 @@ public class JpaQueryRsqlVisitor<A extends Enum<A> & RsqlQueryField, T> extends
final Predicate mapPredicate = mapToMapPredicate(fieldPath, queryPath);
final Predicate valuePredicate = addOperatorPredicate(node, fieldPath, transformedValues, value, queryPath);
final Predicate valuePredicate = addOperatorPredicate(node, queryPath.getEnumValue().isMap() ? (Path)toMapValuePath(fieldPath) : fieldPath, transformedValues, value, queryPath);
return toSingleList(mapPredicate != null ? cb.and(mapPredicate, valuePredicate) : valuePredicate);
}
@@ -380,6 +379,16 @@ public class JpaQueryRsqlVisitor<A extends Enum<A> & RsqlQueryField, T> extends
}
}
@SuppressWarnings("unchecked")
private static Path<String> toMapValuePath(final Path<?> fieldPath) {
final Path<?> mapValuePath = ((MapJoin<?, ?, ?>) pathOfString(fieldPath)).value();
if (mapValuePath.getJavaType() == String.class) {
return (Path<String>) mapValuePath;
} else {
return mapValuePath.get("value");
}
}
private Predicate getOutPredicate(
final List<Object> transformedValues,
final QueryPath queryPath, final Path<Object> fieldPath) {
@@ -496,8 +505,7 @@ public class JpaQueryRsqlVisitor<A extends Enum<A> & RsqlQueryField, T> extends
@SuppressWarnings({ "rawtypes", "unchecked" })
private Expression<String> getExpressionToCompare(final Path innerFieldPath, final A enumField) {
if (enumField.isMap()){
// Currently we support only string key. So below cast is safe.
return (Expression<String>) (((MapJoin<?, ?, ?>) pathOfString(innerFieldPath)).value());
return toMapValuePath(innerFieldPath);
} else {
return pathOfString(innerFieldPath);
}

View File

@@ -129,7 +129,7 @@ public class JpaQueryRsqlVisitorG2<A extends Enum<A> & RsqlQueryField, T>
if (values.get(0) == null) {
// IS operator for maps and null value is treated as doesn't exist correspondingly
((PluralJoin<?, ?, ?>) fieldPath).on(toMapEntryKeyPredicate(queryPath, fieldPath));
return cb.isNull(fieldPath);
return cb.isNull(toMapValuePath(fieldPath));
}
} else if (node.getOperator() == NOT) {
if (values.size() != 1) {
@@ -139,10 +139,10 @@ public class JpaQueryRsqlVisitorG2<A extends Enum<A> & RsqlQueryField, T>
((PluralJoin<?, ?, ?>) fieldPath).on(toMapEntryKeyPredicate(queryPath, fieldPath));
if (values.get(0) == null) {
// special handling of "exists"
return cb.isNotNull(fieldPath);
return cb.isNotNull(toMapValuePath(fieldPath));
} else {
// special handling or "not equal" or null (same as != but with possible optimized join - no subquery)
return toNotEqualToPredicate(queryPath, fieldPath, values.get(0));
return toNotEqualToPredicate(queryPath, toMapValuePath(fieldPath), values.get(0));
}
}
mapEntryKeyPredicate = toMapEntryKeyPredicate(queryPath, fieldPath);
@@ -150,11 +150,21 @@ public class JpaQueryRsqlVisitorG2<A extends Enum<A> & RsqlQueryField, T>
mapEntryKeyPredicate = null;
}
final Predicate valuePredicate = toOperatorAndValuePredicate(node, queryPath, fieldPath, values);
final Predicate valuePredicate = toOperatorAndValuePredicate(node, queryPath, queryPath.getEnumValue().isMap() ? toMapValuePath(fieldPath) : fieldPath, values);
return mapEntryKeyPredicate == null ? valuePredicate : cb.and(mapEntryKeyPredicate, valuePredicate);
}
@SuppressWarnings("unchecked")
private static Path<String> toMapValuePath(final Path<?> fieldPath) {
final Path<?> mapValuePath = ((MapJoin<?, ?, ?>) pathOfString(fieldPath)).value();
if (mapValuePath.getJavaType() == String.class) {
return (Path<String>) mapValuePath;
} else {
return mapValuePath.get("value");
}
}
@SuppressWarnings("unchecked")
private Predicate toMapEntryKeyPredicate(final QueryPath queryPath, final Path<?> fieldPath) {
final String[] graph = queryPath.getJpaPath();
@@ -341,7 +351,7 @@ public class JpaQueryRsqlVisitorG2<A extends Enum<A> & RsqlQueryField, T>
private Path<String> getExpressionToCompare(final A enumField, final Path fieldPath) {
if (enumField.isMap()) {
// Currently we support only string key. So below cast is safe.
return (Path<String>) (((MapJoin<?, ?, ?>) fieldPath).value());
return toMapValuePath(fieldPath);
} else {
return pathOfString(fieldPath);
}

View File

@@ -13,7 +13,6 @@ import org.eclipse.hawkbit.repository.EntityFactory;
import org.eclipse.hawkbit.repository.builder.ActionStatusBuilder;
import org.eclipse.hawkbit.repository.builder.RolloutBuilder;
import org.eclipse.hawkbit.repository.builder.RolloutGroupBuilder;
import org.eclipse.hawkbit.repository.builder.SoftwareModuleMetadataBuilder;
import org.eclipse.hawkbit.repository.builder.TagBuilder;
import org.eclipse.hawkbit.repository.builder.TargetBuilder;
import org.eclipse.hawkbit.repository.builder.TargetFilterQueryBuilder;
@@ -33,19 +32,16 @@ public class JpaEntityFactory implements EntityFactory {
private final TargetBuilder targetBuilder;
private final TargetTypeBuilder targetTypeBuilder;
private final TargetFilterQueryBuilder targetFilterQueryBuilder;
private final SoftwareModuleMetadataBuilder softwareModuleMetadataBuilder;
private final RolloutBuilder rolloutBuilder;
@SuppressWarnings("java:S107")
public JpaEntityFactory(
final TargetBuilder targetBuilder, final TargetTypeBuilder targetTypeBuilder,
final TargetFilterQueryBuilder targetFilterQueryBuilder,
final SoftwareModuleMetadataBuilder softwareModuleMetadataBuilder,
final RolloutBuilder rolloutBuilder) {
this.targetBuilder = targetBuilder;
this.targetTypeBuilder = targetTypeBuilder;
this.targetFilterQueryBuilder = targetFilterQueryBuilder;
this.softwareModuleMetadataBuilder = softwareModuleMetadataBuilder;
this.rolloutBuilder = rolloutBuilder;
}
@Override
@@ -53,11 +49,6 @@ public class JpaEntityFactory implements EntityFactory {
return new JpaActionStatusBuilder();
}
@Override
public SoftwareModuleMetadataBuilder softwareModuleMetadata() {
return softwareModuleMetadataBuilder;
}
@Override
public TagBuilder<Tag> tag() {
return (TagBuilder)new JpaTagBuilder();

View File

@@ -44,7 +44,6 @@ import org.eclipse.hawkbit.repository.artifact.encryption.ArtifactEncryptionSecr
import org.eclipse.hawkbit.repository.artifact.encryption.ArtifactEncryptionService;
import org.eclipse.hawkbit.repository.autoassign.AutoAssignExecutor;
import org.eclipse.hawkbit.repository.builder.RolloutBuilder;
import org.eclipse.hawkbit.repository.builder.SoftwareModuleMetadataBuilder;
import org.eclipse.hawkbit.repository.builder.TargetBuilder;
import org.eclipse.hawkbit.repository.builder.TargetFilterQueryBuilder;
import org.eclipse.hawkbit.repository.builder.TargetTypeBuilder;
@@ -60,7 +59,6 @@ import org.eclipse.hawkbit.repository.jpa.autocleanup.AutoActionCleanup;
import org.eclipse.hawkbit.repository.jpa.autocleanup.AutoCleanupScheduler;
import org.eclipse.hawkbit.repository.jpa.autocleanup.CleanupTask;
import org.eclipse.hawkbit.repository.jpa.builder.JpaRolloutBuilder;
import org.eclipse.hawkbit.repository.jpa.builder.JpaSoftwareModuleMetadataBuilder;
import org.eclipse.hawkbit.repository.jpa.builder.JpaTargetBuilder;
import org.eclipse.hawkbit.repository.jpa.builder.JpaTargetFilterQueryBuilder;
import org.eclipse.hawkbit.repository.jpa.builder.JpaTargetTypeBuilder;
@@ -70,7 +68,6 @@ import org.eclipse.hawkbit.repository.jpa.event.JpaEventEntityManager;
import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitDefaultServiceExecutor;
import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor;
import org.eclipse.hawkbit.repository.jpa.utils.ExceptionMapper;
import org.eclipse.hawkbit.repository.jpa.management.JpaSoftwareModuleManagement;
import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
import org.eclipse.hawkbit.repository.jpa.model.JpaArtifact;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
@@ -323,12 +320,6 @@ public class JpaRepositoryConfiguration {
return new JpaTargetTypeBuilder(dsTypeManagement);
}
@Bean
SoftwareModuleMetadataBuilder softwareModuleMetadataBuilder(
final JpaSoftwareModuleManagement softwareModuleManagement) {
return new JpaSoftwareModuleMetadataBuilder(softwareModuleManagement);
}
/**
* @param distributionSetManagement for loading {@link Rollout#getDistributionSet()}
* @return RolloutBuilder bean
@@ -461,11 +452,9 @@ public class JpaRepositoryConfiguration {
EntityFactory entityFactory(
final TargetBuilder targetBuilder, final TargetTypeBuilder targetTypeBuilder,
final TargetFilterQueryBuilder targetFilterQueryBuilder,
final SoftwareModuleMetadataBuilder softwareModuleMetadataBuilder,
final RolloutBuilder rolloutBuilder) {
return new JpaEntityFactory(
targetBuilder, targetTypeBuilder, targetFilterQueryBuilder,
softwareModuleMetadataBuilder, rolloutBuilder);
targetBuilder, targetTypeBuilder, targetFilterQueryBuilder, rolloutBuilder);
}
/**

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.builder;
import org.eclipse.hawkbit.repository.builder.GenericSoftwareModuleMetadataUpdate;
import org.eclipse.hawkbit.repository.builder.SoftwareModuleMetadataBuilder;
import org.eclipse.hawkbit.repository.builder.SoftwareModuleMetadataCreate;
import org.eclipse.hawkbit.repository.builder.SoftwareModuleMetadataUpdate;
import org.eclipse.hawkbit.repository.jpa.management.JpaSoftwareModuleManagement;
import org.eclipse.hawkbit.repository.model.SoftwareModuleMetadata;
/**
* Builder implementation for {@link SoftwareModuleMetadata}.
*/
public class JpaSoftwareModuleMetadataBuilder implements SoftwareModuleMetadataBuilder {
private final JpaSoftwareModuleManagement softwareModuleManagement;
public JpaSoftwareModuleMetadataBuilder(final JpaSoftwareModuleManagement softwareModuleManagement) {
this.softwareModuleManagement = softwareModuleManagement;
}
@Override
public SoftwareModuleMetadataUpdate update(final long softwareModuleId, final String key) {
return new GenericSoftwareModuleMetadataUpdate(softwareModuleId, key);
}
@Override
public SoftwareModuleMetadataCreate create(final long softwareModuleId) {
return new JpaSoftwareModuleMetadataCreate(softwareModuleId, softwareModuleManagement);
}
}

View File

@@ -1,44 +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.builder;
import org.eclipse.hawkbit.repository.builder.AbstractSoftwareModuleMetadataUpdateCreate;
import org.eclipse.hawkbit.repository.builder.SoftwareModuleMetadataCreate;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.jpa.management.JpaSoftwareModuleManagement;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModuleMetadata;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
/**
* Create/build implementation.
*/
public class JpaSoftwareModuleMetadataCreate
extends AbstractSoftwareModuleMetadataUpdateCreate<SoftwareModuleMetadataCreate> implements SoftwareModuleMetadataCreate {
private final JpaSoftwareModuleManagement softwareModuleManagement;
JpaSoftwareModuleMetadataCreate(final long softwareModuleId, final JpaSoftwareModuleManagement softwareModuleManagement) {
this.softwareModuleManagement = softwareModuleManagement;
this.softwareModuleId = softwareModuleId;
}
@Override
public JpaSoftwareModuleMetadata build() {
final JpaSoftwareModule module = softwareModuleManagement.get(softwareModuleId)
.orElseThrow(() -> new EntityNotFoundException(SoftwareModule.class, softwareModuleId));
if (key == null) {
new JpaSoftwareModuleMetadata(key, module, value, isTargetVisible().orElse(false));
}
return new JpaSoftwareModuleMetadata(key, module, value, isTargetVisible().orElse(false));
}
}

View File

@@ -54,6 +54,7 @@ import org.eclipse.hawkbit.repository.MaintenanceScheduleHelper;
import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.RepositoryConstants;
import org.eclipse.hawkbit.repository.RepositoryProperties;
import org.eclipse.hawkbit.repository.SoftwareModuleManagement;
import org.eclipse.hawkbit.repository.TargetTypeManagement;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.repository.UpdateMode;
@@ -82,7 +83,6 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaTarget_;
import org.eclipse.hawkbit.repository.jpa.ql.EntityMatcher;
import org.eclipse.hawkbit.repository.jpa.repository.ActionRepository;
import org.eclipse.hawkbit.repository.jpa.repository.ActionStatusRepository;
import org.eclipse.hawkbit.repository.jpa.repository.SoftwareModuleMetadataRepository;
import org.eclipse.hawkbit.repository.jpa.repository.SoftwareModuleRepository;
import org.eclipse.hawkbit.repository.jpa.repository.TargetRepository;
import org.eclipse.hawkbit.repository.jpa.specifications.ActionSpecifications;
@@ -96,7 +96,6 @@ import org.eclipse.hawkbit.repository.model.AutoConfirmationStatus;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.SoftwareModuleMetadata;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetType;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
@@ -143,7 +142,7 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont
private final DeploymentManagement deploymentManagement;
private final ConfirmationManagement confirmationManagement;
private final SoftwareModuleRepository softwareModuleRepository;
private final SoftwareModuleMetadataRepository softwareModuleMetadataRepository;
private final SoftwareModuleManagement<? extends SoftwareModule> softwareModuleManagement;
private final DistributionSetManagement<? extends DistributionSet> distributionSetManagement;
private final TenantConfigurationManagement tenantConfigurationManagement;
private final ControllerPollProperties controllerPollProperties;
@@ -162,7 +161,8 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont
final RepositoryProperties repositoryProperties,
final TargetRepository targetRepository, final TargetTypeManagement targetTypeManagement,
final DeploymentManagement deploymentManagement, final ConfirmationManagement confirmationManagement,
final SoftwareModuleRepository softwareModuleRepository, final SoftwareModuleMetadataRepository softwareModuleMetadataRepository,
final SoftwareModuleRepository softwareModuleRepository,
final SoftwareModuleManagement<? extends SoftwareModule> softwareModuleManagement,
final DistributionSetManagement<? extends DistributionSet> distributionSetManagement,
final TenantConfigurationManagement tenantConfigurationManagement, final ControllerPollProperties controllerPollProperties,
final PlatformTransactionManager txManager, final EntityFactory entityFactory, final EntityManager entityManager,
@@ -176,7 +176,7 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont
this.deploymentManagement = deploymentManagement;
this.confirmationManagement = confirmationManagement;
this.softwareModuleRepository = softwareModuleRepository;
this.softwareModuleMetadataRepository = softwareModuleMetadataRepository;
this.softwareModuleManagement = softwareModuleManagement;
this.distributionSetManagement = distributionSetManagement;
this.tenantConfigurationManagement = tenantConfigurationManagement;
this.controllerPollProperties = controllerPollProperties;
@@ -281,11 +281,8 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont
}
@Override
public Map<Long, List<SoftwareModuleMetadata>> findTargetVisibleMetaDataBySoftwareModuleId(final Collection<Long> moduleId) {
return softwareModuleMetadataRepository
.findBySoftwareModuleIdInAndTargetVisible(moduleId, true, PageRequest.of(0, RepositoryConstants.MAX_META_DATA_COUNT))
.getContent().stream()
.collect(Collectors.groupingBy(o -> (Long) o[0], Collectors.mapping(o -> (SoftwareModuleMetadata) o[1], Collectors.toList())));
public Map<Long, Map<String, String>> findTargetVisibleMetaDataBySoftwareModuleId(final Collection<Long> moduleId) {
return systemSecurityContext.runAsSystem(() -> softwareModuleManagement.findMetaDataBySoftwareModuleIdsAndTargetVisible(moduleId));
}
@Override

View File

@@ -15,37 +15,30 @@ import static org.eclipse.hawkbit.repository.jpa.configuration.Constants.TX_RT_M
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
import jakarta.persistence.EntityManager;
import org.eclipse.hawkbit.repository.ArtifactManagement;
import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.RepositoryConstants;
import org.eclipse.hawkbit.repository.SoftwareModuleFields;
import org.eclipse.hawkbit.repository.SoftwareModuleManagement;
import org.eclipse.hawkbit.repository.artifact.encryption.ArtifactEncryptionService;
import org.eclipse.hawkbit.repository.builder.GenericSoftwareModuleMetadataUpdate;
import org.eclipse.hawkbit.repository.builder.SoftwareModuleMetadataCreate;
import org.eclipse.hawkbit.repository.builder.SoftwareModuleMetadataUpdate;
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.LockedException;
import org.eclipse.hawkbit.repository.jpa.JpaManagementHelper;
import org.eclipse.hawkbit.repository.jpa.acm.AccessController;
import org.eclipse.hawkbit.repository.jpa.builder.JpaSoftwareModuleMetadataCreate;
import org.eclipse.hawkbit.repository.jpa.model.AbstractJpaBaseEntity_;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModuleMetadata;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModuleMetadata_;
import org.eclipse.hawkbit.repository.jpa.model.SwMetadataCompositeKey;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule.JpaMetadataValue;
import org.eclipse.hawkbit.repository.jpa.model.helper.AfterTransactionCommitExecutorHolder;
import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetRepository;
import org.eclipse.hawkbit.repository.jpa.repository.SoftwareModuleMetadataRepository;
import org.eclipse.hawkbit.repository.jpa.repository.SoftwareModuleRepository;
import org.eclipse.hawkbit.repository.jpa.repository.SoftwareModuleTypeRepository;
import org.eclipse.hawkbit.repository.jpa.specifications.SoftwareModuleSpecification;
@@ -53,12 +46,12 @@ import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper;
import org.eclipse.hawkbit.repository.model.Artifact;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.SoftwareModuleMetadata;
import org.eclipse.hawkbit.repository.model.SoftwareModule.MetadataValue;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
import org.eclipse.hawkbit.utils.ObjectCopyUtil;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
import org.springframework.dao.ConcurrencyFailureException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.jpa.domain.Specification;
@@ -71,13 +64,13 @@ import org.springframework.util.ObjectUtils;
@Service
@ConditionalOnBooleanProperty(prefix = "hawkbit.jpa", name = { "enabled", "software-module-management" }, matchIfMissing = true)
public class JpaSoftwareModuleManagement
extends AbstractJpaRepositoryManagement<JpaSoftwareModule, SoftwareModuleManagement.Create, SoftwareModuleManagement.Update, SoftwareModuleRepository, SoftwareModuleFields>
extends
AbstractJpaRepositoryManagement<JpaSoftwareModule, SoftwareModuleManagement.Create, SoftwareModuleManagement.Update, SoftwareModuleRepository, SoftwareModuleFields>
implements SoftwareModuleManagement<JpaSoftwareModule> {
protected static final String SOFTWARE_MODULE_METADATA = "SoftwareModuleMetadata";
private final DistributionSetRepository distributionSetRepository;
private final SoftwareModuleMetadataRepository softwareModuleMetadataRepository;
private final SoftwareModuleTypeRepository softwareModuleTypeRepository;
private final ArtifactManagement artifactManagement;
private final QuotaManagement quotaManagement;
@@ -86,12 +79,10 @@ public class JpaSoftwareModuleManagement
final SoftwareModuleRepository softwareModuleRepository,
final EntityManager entityManager,
final DistributionSetRepository distributionSetRepository,
final SoftwareModuleMetadataRepository softwareModuleMetadataRepository,
final SoftwareModuleTypeRepository softwareModuleTypeRepository,
final ArtifactManagement artifactManagement, final QuotaManagement quotaManagement) {
super(softwareModuleRepository, entityManager);
this.distributionSetRepository = distributionSetRepository;
this.softwareModuleMetadataRepository = softwareModuleMetadataRepository;
this.softwareModuleTypeRepository = softwareModuleTypeRepository;
this.artifactManagement = artifactManagement;
this.quotaManagement = quotaManagement;
@@ -176,86 +167,97 @@ public class JpaSoftwareModuleManagement
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY))
public void createMetadata(final Collection<SoftwareModuleMetadataCreate> create) {
// group by software module id to minimize database access
create.stream()
.map(JpaSoftwareModuleMetadataCreate.class::cast)
.collect(Collectors.groupingBy(JpaSoftwareModuleMetadataCreate::getSoftwareModuleId))
.forEach((id, createsForSoftwareModule) -> {
assertSoftwareModuleExists(id);
assertMetadataQuota(id, createsForSoftwareModule.size());
// touch to update revision and last modified timestamp
JpaManagementHelper.touch(
entityManager, jpaRepository, jpaRepository.findById(id).orElseThrow(() -> new EntityNotFoundException(SoftwareModule.class, id)));
createsForSoftwareModule.forEach(this::saveMetadata);
});
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
public List<SoftwareModuleMetadata> getMetadata(final long id) {
assertSoftwareModuleExists(id);
return (List) softwareModuleMetadataRepository.findAll(metadataBySoftwareModuleIdSpec(id));
}
@Override
public SoftwareModuleMetadata getMetadata(final long id, final String key) {
assertSoftwareModuleExists(id);
return findMetadata(id, key).orElseThrow(() -> new EntityNotFoundException(SOFTWARE_MODULE_METADATA, id + ":" + key));
}
@Override
public Page<SoftwareModuleMetadata> findMetaDataBySoftwareModuleIdAndTargetVisible(final long id, final Pageable pageable) {
assertSoftwareModuleExists(id);
return JpaManagementHelper.convertPage(softwareModuleMetadataRepository.findBySoftwareModuleIdAndTargetVisible(
id, true, PageRequest.of(0, RepositoryConstants.MAX_META_DATA_COUNT)), pageable);
public void createMetadata(final Long id, final String key, final MetadataValue value) {
final JpaSoftwareModule softwareModule = jpaRepository
.findById(id)
.orElseThrow(() -> new EntityNotFoundException(SoftwareModule.class, id));
final Map<String, JpaMetadataValue> metadataValueMap = softwareModule.getMetadata();
final JpaMetadataValue existingValue = metadataValueMap.get(key);
if (existingValue == null) {
assertMetadataQuota(metadataValueMap.size() + 1L);
}
final JpaMetadataValue jpaMetadataValue = existingValue == null ?
new JpaMetadataValue() : existingValue;
if (ObjectCopyUtil.copy(value, jpaMetadataValue, true, UnaryOperator.identity())) {
metadataValueMap.put(key, jpaMetadataValue);
jpaRepository.save(softwareModule);
}
}
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY))
public SoftwareModuleMetadata updateMetadata(final SoftwareModuleMetadataCreate c) {
final JpaSoftwareModuleMetadataCreate create = (JpaSoftwareModuleMetadataCreate) c;
final Long id = create.getSoftwareModuleId();
public void createMetadata(final Long id, final Map<String, ? extends MetadataValue> metadata) {
final JpaSoftwareModule softwareModule = jpaRepository
.findById(id)
.orElseThrow(() -> new EntityNotFoundException(SoftwareModule.class, id));
final Map<String, JpaMetadataValue> metadataValueMap = softwareModule.getMetadata();
assertMetadataQuota(metadata.keySet().stream().filter(key -> !metadataValueMap.containsKey(key)).count() + metadataValueMap.size());
final AtomicBoolean changed = new AtomicBoolean(false);
metadata.forEach((key, value) -> {
final JpaMetadataValue jpaMetadataValue = metadataValueMap.getOrDefault(key, new JpaMetadataValue());
if (ObjectCopyUtil.copy(value, jpaMetadataValue, true, UnaryOperator.identity())) {
metadataValueMap.put(key, jpaMetadataValue);
changed.set(true);
}
});
if (changed.get()) {
jpaRepository.save(softwareModule);
}
}
assertSoftwareModuleExists(id);
assertMetadataQuota(id, 1);
@Override
public MetadataValue getMetadata(final Long id, final String key) {
final JpaSoftwareModule softwareModule = jpaRepository
.findById(id)
.orElseThrow(() -> new EntityNotFoundException(SoftwareModule.class, id));
final MetadataValue metadataValue = softwareModule.getMetadata().get(key);
if (metadataValue == null) {
throw new EntityNotFoundException(SOFTWARE_MODULE_METADATA, id + ":" + key);
} else {
return metadataValue;
}
}
// touch to update revision and last modified timestamp
JpaManagementHelper.touch(
entityManager, jpaRepository, jpaRepository.findById(id).orElseThrow(() -> new EntityNotFoundException(SoftwareModule.class, id)));
return saveMetadata(create);
@Override
public Map<String, MetadataValue> getMetadata(final Long id) {
return jpaRepository
.findById(id)
.map(JpaSoftwareModule::getMetadata)
.map(metadata -> metadata.entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
e -> (MetadataValue)e.getValue())))
.orElseThrow(() -> new EntityNotFoundException(SoftwareModule.class, id));
}
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY))
public SoftwareModuleMetadata updateMetadata(final SoftwareModuleMetadataUpdate u) {
final GenericSoftwareModuleMetadataUpdate update = (GenericSoftwareModuleMetadataUpdate) u;
// check if exists otherwise throw entity not found exception
final JpaSoftwareModuleMetadata metadata = (JpaSoftwareModuleMetadata) findMetadata(
update.getSoftwareModuleId(), update.getKey())
.orElseThrow(() -> new EntityNotFoundException(SOFTWARE_MODULE_METADATA, update.getSoftwareModuleId() + ":" + update.getKey()));
update.getValue().ifPresent(metadata::setValue);
update.isTargetVisible().ifPresent(metadata::setTargetVisible);
JpaManagementHelper.touch(entityManager, jpaRepository, metadata.getSoftwareModule());
return softwareModuleMetadataRepository.save(metadata);
public void deleteMetadata(final Long id, final String key) {
final JpaSoftwareModule softwareModule = jpaRepository
.findById(id)
.orElseThrow(() -> new EntityNotFoundException(SoftwareModule.class, id));
final Map<String, JpaMetadataValue> metadataValueMap = softwareModule.getMetadata();
if (!metadataValueMap.containsKey(key)) {
throw new EntityNotFoundException(SOFTWARE_MODULE_METADATA, id + ":" + key);
}
metadataValueMap.remove(key);
jpaRepository.save(softwareModule);
}
// called only with 'system code' access, so no need to check access control
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY))
public void deleteMetadata(final long id, final String key) {
final JpaSoftwareModuleMetadata metadata = (JpaSoftwareModuleMetadata) findMetadata(id, key)
.orElseThrow(() -> new EntityNotFoundException(SOFTWARE_MODULE_METADATA, id + ":" + key));
JpaManagementHelper.touch(entityManager, jpaRepository, metadata.getSoftwareModule());
softwareModuleMetadataRepository.deleteById(metadata.getId());
public Map<Long, Map<String, String>> findMetaDataBySoftwareModuleIdsAndTargetVisible(final Collection<Long> ids) {
return jpaRepository.findVisibleMetadataByModuleIds(ids)
.stream()
.collect(Collectors.groupingBy(
entry -> (Long) entry[0],
Collectors.toMap(
entry -> (String) entry[1],
entry -> (String) entry[2],
(existing, replacement) -> existing, // in case of duplicates, keep the first one
HashMap::new)));
}
@Override
@@ -340,14 +342,6 @@ public class JpaSoftwareModuleManagement
);
}
@Override
public Map<Long, List<SoftwareModuleMetadata>> findMetaDataBySoftwareModuleIdsAndTargetVisible(final Collection<Long> moduleIds) {
return softwareModuleMetadataRepository
.findBySoftwareModuleIdInAndTargetVisible(moduleIds, true, PageRequest.of(0, RepositoryConstants.MAX_META_DATA_COUNT))
.getContent().stream()
.collect(Collectors.groupingBy(o -> (Long) o[0], Collectors.mapping(o -> (SoftwareModuleMetadata) o[1], Collectors.toList())));
}
@Override
public long countByAssignedTo(final long distributionSetId) {
assertDistributionSetExists(distributionSetId);
@@ -355,10 +349,6 @@ public class JpaSoftwareModuleManagement
return jpaRepository.count(SoftwareModuleSpecification.byAssignedToDs(distributionSetId));
}
private static Specification<JpaSoftwareModuleMetadata> metadataBySoftwareModuleIdSpec(final long id) {
return (root, query, cb) -> cb.equal(root.get(JpaSoftwareModuleMetadata_.softwareModule).get(AbstractJpaBaseEntity_.id), id);
}
private void deleteGridFsArtifacts(final JpaSoftwareModule swModule) {
jpaRepository.getAccessController().ifPresent(accessController ->
accessController.assertOperationAllowed(AccessController.Operation.DELETE, swModule));
@@ -374,41 +364,14 @@ public class JpaSoftwareModuleManagement
smFilterNameAndVersionEntries[1]);
}
private SoftwareModuleMetadata saveMetadata(final JpaSoftwareModuleMetadataCreate create) {
assertSoftwareModuleMetadataDoesNotExist(create.getSoftwareModuleId(), create);
return softwareModuleMetadataRepository.save(create.build());
}
private Optional<SoftwareModuleMetadata> findMetadata(final long id, final String key) {
assertSoftwareModuleExists(id);
return softwareModuleMetadataRepository.findById(new SwMetadataCompositeKey(id, key))
.map(SoftwareModuleMetadata.class::cast);
}
private void assertSoftwareModuleMetadataDoesNotExist(final Long id, final JpaSoftwareModuleMetadataCreate md) {
if (softwareModuleMetadataRepository.existsById(new SwMetadataCompositeKey(id, md.getKey()))) {
throw new EntityAlreadyExistsException("Metadata entry with key '" + md.getKey() + "' already exists!");
}
}
/**
* Asserts the meta-data quota for the software module with the given ID.
*
* @param id The software module ID.
* @param requested Number of meta-data entries to be created.
*/
private void assertMetadataQuota(final Long id, final int requested) {
private void assertMetadataQuota(final long requested) {
final int maxMetaData = quotaManagement.getMaxMetaDataEntriesPerSoftwareModule();
QuotaHelper.assertAssignmentQuota(
id, requested, maxMetaData, SoftwareModuleMetadata.class, SoftwareModule.class,
softwareModuleMetadataRepository::countBySoftwareModuleId);
}
private void assertSoftwareModuleExists(final Long id) {
if (!jpaRepository.existsById(id)) {
throw new EntityNotFoundException(SoftwareModule.class, id);
}
QuotaHelper.assertAssignmentQuota(requested, maxMetaData, SoftwareModule.MetadataValueCreate.class, SoftwareModule.class);
}
private void assertSoftwareModuleTypeExists(final Long typeId) {

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
* Copyright (c) 2025 Contributors to the Eclipse Foundation
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
@@ -10,6 +10,7 @@
package org.eclipse.hawkbit.repository.jpa.model;
import java.io.Serial;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -20,6 +21,7 @@ import jakarta.persistence.CollectionTable;
import jakarta.persistence.Column;
import jakarta.persistence.ConstraintMode;
import jakarta.persistence.ElementCollection;
import jakarta.persistence.Embeddable;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.ForeignKey;
@@ -36,7 +38,7 @@ import jakarta.persistence.UniqueConstraint;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.AccessLevel;
import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@@ -102,8 +104,7 @@ public class JpaSoftwareModule extends AbstractJpaNamedVersionedEntity implement
joinColumns = { @JoinColumn(name = "sm", nullable = false) },
foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_sm_metadata_sm"))
@MapKeyColumn(name = "meta_key", length = SoftwareModule.METADATA_KEY_MAX_SIZE)
@Column(name = "meta_value", length = SoftwareModule.METADATA_VALUE_MAX_SIZE)
private Map<String, String> metadata;
private Map<String, JpaMetadataValue> metadata;
@Column(name = "locked")
private boolean locked;
@@ -192,4 +193,18 @@ public class JpaSoftwareModule extends AbstractJpaNamedVersionedEntity implement
public void fireDeleteEvent() {
EventPublisherHolder.getInstance().getEventPublisher().publishEvent(new SoftwareModuleDeletedEvent(getTenant(), getId(), getClass()));
}
@Data
@Embeddable
public static class JpaMetadataValue implements MetadataValue, Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Column(name = "meta_value", length = SoftwareModule.METADATA_VALUE_MAX_SIZE)
@Size(max = METADATA_VALUE_MAX_SIZE)
private String value;
@Column(name = "target_visible")
private boolean targetVisible;
}
}

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.model;
import java.io.Serial;
import jakarta.persistence.Basic;
import jakarta.persistence.Column;
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 jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.SoftwareModuleMetadata;
/**
* Metadata for {@link SoftwareModule}.
*/
@NoArgsConstructor // Default constructor for JPA
@Data
@IdClass(SwMetadataCompositeKey.class)
@Entity
@Table(name = "sp_sm_metadata")
public class JpaSoftwareModuleMetadata implements SoftwareModuleMetadata {
@Serial
private static final long serialVersionUID = 1L;
@Id
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(
name = "sm", nullable = false, updatable = false,
foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_sm_metadata_sm"))
private JpaSoftwareModule softwareModule;
@Id
@Column(name = "meta_key", nullable = false, length = SoftwareModule.METADATA_KEY_MAX_SIZE, updatable = false)
@Size(min = 1, max = SoftwareModule.METADATA_KEY_MAX_SIZE)
@NotNull
private String key;
@Column(name = "meta_value", length = SoftwareModule.METADATA_VALUE_MAX_SIZE)
@Size(max = SoftwareModule.METADATA_VALUE_MAX_SIZE)
@Basic
private String value;
@Column(name = "target_visible")
private boolean targetVisible;
public JpaSoftwareModuleMetadata(final String key, final SoftwareModule softwareModule, final String value) {
this(key, softwareModule, value, false);
}
public JpaSoftwareModuleMetadata(final String key, final SoftwareModule softwareModule, final String value, final boolean targetVisible) {
this.key = key;
this.value = value;
this.softwareModule = (JpaSoftwareModule) softwareModule;
this.targetVisible = targetVisible;
}
public SwMetadataCompositeKey getId() {
return new SwMetadataCompositeKey(softwareModule.getId(), getKey());
}
}

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.model;
import java.io.Serial;
import java.io.Serializable;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* The Software Module meta-data composite key which contains the meta-data key and the ID of the software module itself.
*/
@NoArgsConstructor // Default constructor for JPA
@Data
public final class SwMetadataCompositeKey implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private String key;
private Long softwareModule;
/**
* @param moduleId the software module for this meta-data
* @param key the key of the meta-data
*/
public SwMetadataCompositeKey(final Long moduleId, final String key) {
this.softwareModule = moduleId;
this.key = key;
}
}

View File

@@ -1,69 +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 java.util.Collection;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModuleMetadata;
import org.eclipse.hawkbit.repository.jpa.model.SwMetadataCompositeKey;
import org.eclipse.hawkbit.repository.model.SoftwareModuleMetadata;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;
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 SoftwareModuleMetadata} repository.
*/
@Transactional(readOnly = true)
public interface SoftwareModuleMetadataRepository
extends PagingAndSortingRepository<JpaSoftwareModuleMetadata, SwMetadataCompositeKey>,
CrudRepository<JpaSoftwareModuleMetadata, SwMetadataCompositeKey>,
JpaSpecificationExecutor<JpaSoftwareModuleMetadata> {
/**
* Locates the meta-data entries that match the given software module ID and target visibility flag.
*
* @param moduleId The ID of the software module.
* @param targetVisible The target visibility flag.
* @param page The pagination parameters.
* @return A {@link Page} with the matching meta-data entries.
*/
Page<JpaSoftwareModuleMetadata> findBySoftwareModuleIdAndTargetVisible(
@Param("moduleId") Long moduleId, @Param("targetVisible") boolean targetVisible, Pageable page);
/**
* Locates the meta-data entries that match the given software module IDs and target visibility flag.
* <p/>
* No access control applied
*
* @param moduleId List of software module IDs.
* @param targetVisible The target visibility flag.
* @param page The pagination parameters.
* @return A {@link Page} with the matching meta-data entries.
*/
@Query("SELECT smd.softwareModule.id, smd FROM JpaSoftwareModuleMetadata smd WHERE smd.softwareModule.id IN :moduleId AND smd.targetVisible = :targetVisible")
Page<Object[]> findBySoftwareModuleIdInAndTargetVisible(
@Param("moduleId") Collection<Long> moduleId, @Param("targetVisible") boolean targetVisible, Pageable page);
/**
* Counts the meta-data entries that are associated with the addressed software module.
* <p/>
* No access control applied
*
* @param moduleId The ID of the software module.
* @return The number of meta-data entries associated with the software module.
*/
long countBySoftwareModuleId(@Param("moduleId") Long moduleId);
}

View File

@@ -9,6 +9,9 @@
*/
package org.eclipse.hawkbit.repository.jpa.repository;
import java.util.Collection;
import java.util.List;
import jakarta.persistence.EntityManager;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule;
@@ -47,6 +50,9 @@ public interface SoftwareModuleRepository extends BaseEntityRepository<JpaSoftwa
*/
long countByAssignedToId(Long distributionSetId);
@Query("SELECT sm.id, KEY(m), m.value FROM JpaSoftwareModule sm JOIN sm.metadata m WHERE sm.id IN :ids AND m.targetVisible = true")
List<Object[]> findVisibleMetadataByModuleIds(@Param("ids") Collection<Long> ids);
/**
* Deletes all {@link TenantAwareBaseEntity} of a given tenant. For safety
* reasons (this is a "delete everything" query after all) we add the tenant

View File

@@ -44,7 +44,6 @@ import org.eclipse.hawkbit.repository.jpa.repository.LocalArtifactRepository;
import org.eclipse.hawkbit.repository.jpa.repository.RolloutGroupRepository;
import org.eclipse.hawkbit.repository.jpa.repository.RolloutRepository;
import org.eclipse.hawkbit.repository.jpa.repository.RolloutTargetGroupRepository;
import org.eclipse.hawkbit.repository.jpa.repository.SoftwareModuleMetadataRepository;
import org.eclipse.hawkbit.repository.jpa.repository.SoftwareModuleRepository;
import org.eclipse.hawkbit.repository.jpa.repository.SoftwareModuleTypeRepository;
import org.eclipse.hawkbit.repository.jpa.repository.TargetRepository;
@@ -111,8 +110,6 @@ public abstract class AbstractJpaIntegrationTest extends AbstractIntegrationTest
@Autowired
protected DistributionSetTagRepository distributionSetTagRepository;
@Autowired
protected SoftwareModuleMetadataRepository softwareModuleMetadataRepository;
@Autowired
protected ActionStatusRepository actionStatusRepository;
@Autowired
protected LocalArtifactRepository artifactRepository;

View File

@@ -73,7 +73,6 @@ import org.eclipse.hawkbit.repository.model.ArtifactUpload;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.SoftwareModuleMetadata;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
import org.eclipse.hawkbit.repository.test.matcher.Expect;
@@ -989,7 +988,7 @@ class ControllerManagementTest extends AbstractJpaIntegrationTest {
final DistributionSet set = testdataFactory.createDistributionSet();
testdataFactory.addSoftwareModuleMetadata(set);
final Map<Long, List<SoftwareModuleMetadata>> result = controllerManagement
final Map<Long, Map<String, String>> result = controllerManagement
.findTargetVisibleMetaDataBySoftwareModuleId(set.getModules().stream().map(SoftwareModule::getId).toList());
assertThat(result).hasSize(3);

View File

@@ -10,12 +10,15 @@
package org.eclipse.hawkbit.repository.jpa.management;
import java.util.List;
import java.util.Map;
import org.eclipse.hawkbit.im.authentication.SpPermission;
import org.eclipse.hawkbit.im.authentication.SpRole;
import org.eclipse.hawkbit.repository.RepositoryManagement;
import org.eclipse.hawkbit.repository.SoftwareModuleManagement;
import org.eclipse.hawkbit.repository.jpa.AbstractRepositoryManagementSecurityTest;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.SoftwareModule.MetadataValueCreate;
import org.junit.jupiter.api.Test;
/**
@@ -46,11 +49,15 @@ class SoftwareManagementSecurityTest
@Test
void createMetaDataPermissionsCheck() {
assertPermissions(
() -> softwareModuleManagement.updateMetadata(entityFactory.softwareModuleMetadata().create(1L).key("key").value("value")),
() -> {
softwareModuleManagement.createMetadata(1L, "key", new MetadataValueCreate("value"));
return null;
},
List.of(SpPermission.UPDATE_REPOSITORY));
assertPermissions(() -> {
softwareModuleManagement.createMetadata(
List.of(entityFactory.softwareModuleMetadata().create(1L).key("key").value("value")));
1L,
Map.of("key", new MetadataValueCreate("value")));
return null;
}, List.of(SpPermission.UPDATE_REPOSITORY));
}
@@ -112,15 +119,6 @@ class SoftwareManagementSecurityTest
assertPermissions(() -> softwareModuleManagement.getMetadata(1L), List.of(SpPermission.READ_REPOSITORY));
}
/**
* Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.
*/
@Test
void findMetaDataBySoftwareModuleIdAndTargetVisiblePermissionsCheck() {
assertPermissions(() -> softwareModuleManagement.findMetaDataBySoftwareModuleIdAndTargetVisible(1L, PAGE),
List.of(SpPermission.READ_REPOSITORY));
}
/**
* Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.
*/
@@ -157,7 +155,10 @@ class SoftwareManagementSecurityTest
@Test
void updateMetaDataPermissionsCheck() {
assertPermissions(
() -> softwareModuleManagement.updateMetadata(entityFactory.softwareModuleMetadata().update(1L, "key").value("value")),
() -> {
softwareModuleManagement.createMetadata(1L, "key", new MetadataValueCreate("value"));
return null;
},
List.of(SpPermission.UPDATE_REPOSITORY));
}
@@ -167,6 +168,6 @@ class SoftwareManagementSecurityTest
@Test
void findMetaDataBySoftwareModuleIdsAndTargetVisiblePermissionsCheck() {
assertPermissions(() -> softwareModuleManagement.findMetaDataBySoftwareModuleIdsAndTargetVisible(List.of(1L)),
List.of(SpPermission.READ_REPOSITORY));
List.of(SpRole.SYSTEM_ROLE));
}
}

View File

@@ -14,11 +14,11 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.ByteArrayInputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
@@ -26,7 +26,6 @@ import jakarta.validation.ConstraintViolationException;
import org.eclipse.hawkbit.repository.DistributionSetManagement;
import org.eclipse.hawkbit.repository.SoftwareModuleManagement;
import org.eclipse.hawkbit.repository.builder.SoftwareModuleMetadataCreate;
import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleCreatedEvent;
import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException;
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
@@ -37,7 +36,6 @@ import org.eclipse.hawkbit.repository.jpa.RandomGeneratedInputStream;
import org.eclipse.hawkbit.repository.jpa.model.JpaAction_;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet_;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModuleMetadata;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget_;
import org.eclipse.hawkbit.repository.model.Action;
@@ -45,7 +43,8 @@ import org.eclipse.hawkbit.repository.model.Artifact;
import org.eclipse.hawkbit.repository.model.ArtifactUpload;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.SoftwareModuleMetadata;
import org.eclipse.hawkbit.repository.model.SoftwareModule.MetadataValue;
import org.eclipse.hawkbit.repository.model.SoftwareModule.MetadataValueCreate;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
@@ -53,8 +52,6 @@ import org.eclipse.hawkbit.repository.test.matcher.Expect;
import org.eclipse.hawkbit.repository.test.matcher.ExpectEvents;
import org.eclipse.hawkbit.repository.test.util.WithUser;
import org.junit.jupiter.api.Test;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
/**
* Feature: Component Tests - Repository<br/>
@@ -62,8 +59,6 @@ import org.springframework.data.domain.PageRequest;
*/
class SoftwareModuleManagementTest extends AbstractJpaIntegrationTest {
private static final PageRequest PAGE_REQUEST_100 = PageRequest.of(0, 100);
/**
* Verifies that management get access reacts as specified on calls for non existing entities by means
* of Optional not present.
@@ -72,14 +67,10 @@ class SoftwareModuleManagementTest extends AbstractJpaIntegrationTest {
@ExpectEvents({ @Expect(type = SoftwareModuleCreatedEvent.class, count = 1) })
void nonExistingEntityAccessReturnsNotPresent() {
final SoftwareModule module = testdataFactory.createSoftwareModuleApp();
assertThat(softwareModuleManagement.get(1234L)).isNotPresent();
assertThat(softwareModuleManagement.findByNameAndVersionAndType(NOT_EXIST_ID, NOT_EXIST_ID, osType.getId()))
.isNotPresent();
assertThatExceptionOfType(EntityNotFoundException.class)
.isThrownBy(() -> softwareModuleManagement.getMetadata(module.getId(), NOT_EXIST_ID));
assertThat(softwareModuleManagement.findByNameAndVersionAndType(NOT_EXIST_ID, NOT_EXIST_ID, osType.getId())).isNotPresent();
final Long moduleId = module.getId();
assertThatExceptionOfType(EntityNotFoundException.class).isThrownBy(() -> softwareModuleManagement.getMetadata(moduleId, NOT_EXIST_ID));
}
/**
@@ -89,7 +80,7 @@ class SoftwareModuleManagementTest extends AbstractJpaIntegrationTest {
@Test
@ExpectEvents({ @Expect(type = SoftwareModuleCreatedEvent.class, count = 1) })
void entityQueriesReferringToNotExistingEntitiesThrowsException() {
final SoftwareModule module = testdataFactory.createSoftwareModuleApp();
testdataFactory.createSoftwareModuleApp();
final SoftwareModuleManagement.Create noType = SoftwareModuleManagement.Create.builder().name("xxx").type(null).build();
final List<SoftwareModuleManagement.Create> noTypeList = List.of(noType);
@@ -98,22 +89,20 @@ class SoftwareModuleManagementTest extends AbstractJpaIntegrationTest {
assertThatExceptionOfType(ConstraintViolationException.class).isThrownBy(() -> softwareModuleManagement.create(noType));
verifyThrownExceptionBy(
() -> softwareModuleManagement.updateMetadata(
entityFactory.softwareModuleMetadata().create(NOT_EXIST_IDL).key("xxx").value("xxx")), "SoftwareModule");
() -> softwareModuleManagement.createMetadata(
NOT_EXIST_IDL, "xxx", new MetadataValueCreate("xxx")), "SoftwareModule");
verifyThrownExceptionBy(
() -> softwareModuleManagement.createMetadata(Collections.singletonList(
entityFactory.softwareModuleMetadata().create(NOT_EXIST_IDL).key("xxx").value("xxx"))), "SoftwareModule");
() -> softwareModuleManagement.createMetadata(
NOT_EXIST_IDL,
Map.of("xxx", new MetadataValueCreate("xxx"))), "SoftwareModule");
verifyThrownExceptionBy(() -> softwareModuleManagement.delete(NOT_EXIST_IDL), "SoftwareModule");
verifyThrownExceptionBy(() -> softwareModuleManagement.delete(Collections.singletonList(NOT_EXIST_IDL)), "SoftwareModule");
verifyThrownExceptionBy(() -> softwareModuleManagement.deleteMetadata(NOT_EXIST_IDL, "xxx"), "SoftwareModule");
verifyThrownExceptionBy(
() -> softwareModuleManagement.updateMetadata(
entityFactory.softwareModuleMetadata().update(NOT_EXIST_IDL, "xxx").value("xxx")), "SoftwareModule");
verifyThrownExceptionBy(
() -> softwareModuleManagement.updateMetadata(
entityFactory.softwareModuleMetadata().update(module.getId(), NOT_EXIST_ID).value("xxx")), "SoftwareModuleMetadata");
() -> softwareModuleManagement.createMetadata(
NOT_EXIST_IDL, "xxx", new MetadataValueCreate("xxx")), "SoftwareModule");
verifyThrownExceptionBy(() -> softwareModuleManagement.findByAssignedTo(NOT_EXIST_IDL, PAGE), "DistributionSet");
@@ -241,8 +230,8 @@ class SoftwareModuleManagementTest extends AbstractJpaIntegrationTest {
testdataFactory.createSoftwareModuleApp();
assertThat((List) softwareModuleManagement.findByType(osType.getId(), PAGE).getContent())
.as("Expected to find the following number of modules:").hasSize(2).as("with the following elements")
.contains(two, one);
.as("Expected to find the following number of modules:").hasSize(2)
.as("with the following elements").contains(two, one);
}
/**
@@ -525,30 +514,21 @@ class SoftwareModuleManagementTest extends AbstractJpaIntegrationTest {
assertThat(softwareModule.getOptLockRevision()).isEqualTo(1);
final SoftwareModuleMetadataCreate swMetadata1 = entityFactory.softwareModuleMetadata()
.create(softwareModule.getId())
.key(knownKey1)
.value(knownValue1)
.targetVisible(true);
final SoftwareModuleMetadataCreate swMetadata2 = entityFactory.softwareModuleMetadata()
.create(softwareModule.getId())
.key(knownKey2)
.value(knownValue2);
softwareModuleManagement.createMetadata(Arrays.asList(swMetadata1, swMetadata2));
softwareModuleManagement.createMetadata(
softwareModule.getId(),
Map.of(
knownKey1, new MetadataValueCreate(knownValue1, true),
knownKey2, new MetadataValueCreate(knownValue2)));
final SoftwareModule changedLockRevisionModule = softwareModuleManagement.get(softwareModule.getId()).get();
assertThat(changedLockRevisionModule.getOptLockRevision()).isEqualTo(2);
assertThat(softwareModuleManagement.getMetadata(softwareModule.getId(), knownKey1)).satisfies(metadata -> {
assertThat(metadata.getKey()).isEqualTo(knownKey1);
assertThat(metadata.getValue()).isEqualTo(knownValue1);
assertThat(metadata.isTargetVisible()).isTrue();
});
assertThat(softwareModuleManagement.getMetadata(softwareModule.getId(), knownKey2)).satisfies(metadata -> {
assertThat(metadata.getKey()).isEqualTo(knownKey2);
assertThat(metadata.getValue()).isEqualTo(knownValue2);
assertThat(metadata.isTargetVisible()).isFalse();
});
@@ -559,80 +539,49 @@ class SoftwareModuleManagementTest extends AbstractJpaIntegrationTest {
*/
@Test
void createSoftwareModuleMetadataUntilQuotaIsExceeded() {
// add meta data one by one
// add meta-data one by one
final SoftwareModule module = testdataFactory.createSoftwareModuleApp("m1");
final int maxMetaData = quotaManagement.getMaxMetaDataEntriesPerSoftwareModule();
for (int i = 0; i < maxMetaData; ++i) {
softwareModuleManagement.updateMetadata(
entityFactory.softwareModuleMetadata().create(module.getId()).key("k" + i).value("v" + i));
softwareModuleManagement.createMetadata(module.getId(), "k" + i, new MetadataValueCreate("v" + i));
}
// quota exceeded
final SoftwareModuleMetadataCreate metadata = entityFactory.softwareModuleMetadata().create(module.getId())
.key("k" + maxMetaData).value("v" + maxMetaData);
final Long moduleId = module.getId();
final MetadataValueCreate metadata = new MetadataValueCreate("v" + maxMetaData);
assertThatExceptionOfType(AssignmentQuotaExceededException.class)
.isThrownBy(() -> softwareModuleManagement.updateMetadata(metadata));
.isThrownBy(() -> softwareModuleManagement.createMetadata(moduleId, "k" + maxMetaData, metadata));
// add multiple meta data entries at once
final SoftwareModule module2 = testdataFactory.createSoftwareModuleApp("m2");
final List<SoftwareModuleMetadataCreate> create = new ArrayList<>();
final Long moduleId2 = module2.getId();
final Map<String, MetadataValueCreate> create = new HashMap<>();
for (int i = 0; i < maxMetaData + 1; ++i) {
create.add(entityFactory.softwareModuleMetadata().create(module2.getId()).key("k" + i).value("v" + i));
create.put("k" + i, new MetadataValueCreate("v" + i));
}
// quota exceeded
assertThatExceptionOfType(AssignmentQuotaExceededException.class)
.isThrownBy(() -> softwareModuleManagement.createMetadata(create));
.isThrownBy(() -> softwareModuleManagement.createMetadata(moduleId2, create));
// add some meta data entries
final SoftwareModule module3 = testdataFactory.createSoftwareModuleApp("m3");
final int firstHalf = Math.round((maxMetaData) / 2.f);
for (int i = 0; i < firstHalf; ++i) {
softwareModuleManagement.updateMetadata(
entityFactory.softwareModuleMetadata().create(module3.getId()).key("k" + i).value("v" + i));
softwareModuleManagement.createMetadata(module3.getId(), "k" + i, new MetadataValueCreate("v" + i));
}
// add too many data entries
final Long moduleId3 = module3.getId();
final int secondHalf = maxMetaData - firstHalf;
final List<SoftwareModuleMetadataCreate> create2 = new ArrayList<>();
final Map<String, MetadataValueCreate> create2 = new HashMap<>();
for (int i = 0; i < secondHalf + 1; ++i) {
create2.add(entityFactory.softwareModuleMetadata().create(module3.getId()).key("kk" + i).value("vv" + i));
create2.put("kk" + i, new MetadataValueCreate("vv" + i));
}
// quota exceeded
assertThatExceptionOfType(AssignmentQuotaExceededException.class)
.isThrownBy(() -> softwareModuleManagement.createMetadata(create2));
.isThrownBy(() -> softwareModuleManagement.createMetadata(moduleId3, create2));
}
/**
* Checks that metadata for a software module cannot be created for an existing key.
*/
@Test
void createSoftwareModuleMetadataFailsIfKeyExists() {
final String knownKey1 = "myKnownKey1";
final String knownValue1 = "myKnownValue1";
final SoftwareModule ah = testdataFactory.createSoftwareModuleApp();
final SoftwareModuleMetadataCreate metadata = entityFactory.softwareModuleMetadata()
.create(ah.getId()).key(knownKey1).value(knownValue1).targetVisible(true);
softwareModuleManagement.updateMetadata(metadata);
assertThatExceptionOfType(EntityAlreadyExistsException.class)
.isThrownBy(() -> softwareModuleManagement.updateMetadata(metadata))
.withMessageContaining("Metadata").withMessageContaining(knownKey1);
final String knownKey2 = "myKnownKey2";
softwareModuleManagement.updateMetadata(entityFactory.softwareModuleMetadata().create(ah.getId()).key(knownKey2)
.value(knownValue1).targetVisible(false));
final SoftwareModuleMetadataCreate metadata2 = entityFactory.softwareModuleMetadata().create(ah.getId())
.key(knownKey2).value(knownValue1).targetVisible(true);
assertThatExceptionOfType(EntityAlreadyExistsException.class)
.isThrownBy(() -> softwareModuleManagement.updateMetadata(metadata2))
.withMessageContaining("Metadata").withMessageContaining(knownKey2);
}
/**
* Checks that metadata for a software module can be updated.
*/
@@ -649,8 +598,8 @@ class SoftwareModuleManagementTest extends AbstractJpaIntegrationTest {
assertThat(ah.getOptLockRevision()).isEqualTo(1);
// create an software module meta data entry
final SoftwareModuleMetadata softwareModuleMetadata = softwareModuleManagement.updateMetadata(
entityFactory.softwareModuleMetadata().create(ah.getId()).key(knownKey).value(knownValue));
softwareModuleManagement.createMetadata(ah.getId(), knownKey, new MetadataValueCreate(knownValue));
final MetadataValue softwareModuleMetadata = softwareModuleManagement.getMetadata(ah.getId(), knownKey);
assertThat(softwareModuleMetadata.isTargetVisible()).isFalse();
assertThat(softwareModuleMetadata.getValue()).isEqualTo(knownValue);
@@ -660,8 +609,8 @@ class SoftwareModuleManagementTest extends AbstractJpaIntegrationTest {
assertThat(changedLockRevisionModule.getOptLockRevision()).isEqualTo(2);
// update the software module metadata
final SoftwareModuleMetadata updated = softwareModuleManagement.updateMetadata(entityFactory
.softwareModuleMetadata().update(ah.getId(), knownKey).value(knownUpdateValue).targetVisible(true));
softwareModuleManagement.createMetadata(ah.getId(), knownKey, new MetadataValueCreate(knownUpdateValue, true));
final MetadataValue updated = softwareModuleManagement.getMetadata(ah.getId(), knownKey);
// we are updating the sw metadata so also modifying the base software
// module so opt lock revision must be two
@@ -672,8 +621,6 @@ class SoftwareModuleManagementTest extends AbstractJpaIntegrationTest {
assertThat(updated).isNotNull();
assertThat(updated.getValue()).isEqualTo(knownUpdateValue);
assertThat(updated.isTargetVisible()).isTrue();
assertThat(((JpaSoftwareModuleMetadata) updated).getId().getKey()).isEqualTo(knownKey);
assertThat(((JpaSoftwareModuleMetadata) updated).getSoftwareModule().getId()).isEqualTo(ah.getId());
}
/**
@@ -686,37 +633,18 @@ class SoftwareModuleManagementTest extends AbstractJpaIntegrationTest {
final SoftwareModule swModule = testdataFactory.createSoftwareModuleApp();
softwareModuleManagement.updateMetadata(
entityFactory.softwareModuleMetadata().create(swModule.getId()).key(knownKey1).value(knownValue1));
softwareModuleManagement.createMetadata(swModule.getId(), knownKey1, new MetadataValueCreate(knownValue1));
assertThat(softwareModuleManagement.getMetadata(swModule.getId()))
assertThat(softwareModuleManagement.getMetadata(swModule.getId()).entrySet())
.as("Contains the created metadata element").allSatisfy(metadata -> {
assertThat(((JpaSoftwareModuleMetadata) metadata).getSoftwareModule().getId()).isEqualTo(swModule.getId());
assertThat(metadata.getKey()).isEqualTo(knownKey1);
assertThat(metadata.getValue()).isEqualTo(knownValue1);
assertThat(metadata.getValue().getValue()).isEqualTo(knownValue1);
});
softwareModuleManagement.deleteMetadata(swModule.getId(), knownKey1);
assertThat(softwareModuleManagement.getMetadata(swModule.getId())).as("Metadata elements are").isEmpty();
}
/**
* Verifies that non existing metadata find results in exception.
*/
@Test
void findSoftwareModuleMetadataFailsIfEntryDoesNotExist() {
final String knownKey1 = "myKnownKey1";
final String knownValue1 = "myKnownValue1";
final SoftwareModule ah = testdataFactory.createSoftwareModuleApp();
softwareModuleManagement.updateMetadata(
entityFactory.softwareModuleMetadata().create(ah.getId()).key(knownKey1).value(knownValue1));
assertThatExceptionOfType(EntityNotFoundException.class)
.isThrownBy(() -> softwareModuleManagement.getMetadata(ah.getId(), "doesnotexist"));
}
/**
* Queries and loads the metadata related to a given software module.
*/
@@ -729,28 +657,24 @@ class SoftwareModuleManagementTest extends AbstractJpaIntegrationTest {
final int metadataCountSw2 = 10;
for (int index = 0; index < metadataCountSw1; index++) {
softwareModuleManagement.updateMetadata(entityFactory.softwareModuleMetadata().create(sw1.getId())
.key("key" + index).value("value" + index).targetVisible(true));
softwareModuleManagement.createMetadata(sw1.getId(), "key" + index, new MetadataValueCreate("value" + index, true));
}
for (int index = 0; index < metadataCountSw2; index++) {
softwareModuleManagement.updateMetadata(entityFactory.softwareModuleMetadata().create(sw2.getId())
.key("key" + index).value("value" + index).targetVisible(false));
softwareModuleManagement.createMetadata(sw2.getId(), "key" + index, new MetadataValueCreate("value" + index, false));
}
final List<SoftwareModuleMetadata> metadataSw1 = softwareModuleManagement.getMetadata(sw1.getId());
final List<SoftwareModuleMetadata> metadataSw2 = softwareModuleManagement.getMetadata(sw2.getId());
final Map<String, MetadataValue> metadataSw1 = softwareModuleManagement.getMetadata(sw1.getId());
final Map<String, MetadataValue> metadataSw2 = softwareModuleManagement.getMetadata(sw2.getId());
assertThat(metadataSw1).hasSize(metadataCountSw1);
assertThat(metadataSw2).hasSize(metadataCountSw2);
final Page<SoftwareModuleMetadata> metadataSw1V = softwareModuleManagement.findMetaDataBySoftwareModuleIdAndTargetVisible(
sw1.getId(), PAGE_REQUEST_100);
final Page<SoftwareModuleMetadata> metadataSw2V = softwareModuleManagement.findMetaDataBySoftwareModuleIdAndTargetVisible(
sw2.getId(), PAGE_REQUEST_100);
assertThat(metadataSw1V.getNumberOfElements()).isEqualTo(metadataCountSw1);
assertThat(metadataSw1V.getTotalElements()).isEqualTo(metadataCountSw1);
assertThat(metadataSw2V.getNumberOfElements()).isZero();
assertThat(metadataSw2V.getTotalElements()).isZero();
final Map<String, String> metadataSw1V = softwareModuleManagement
.findMetaDataBySoftwareModuleIdsAndTargetVisible(List.of(sw1.getId())).get(sw1.getId());
final Map<String, String> metadataSw2V = softwareModuleManagement
.findMetaDataBySoftwareModuleIdsAndTargetVisible(List.of(sw2.getId())).get(sw2.getId());
assertThat(metadataSw1V).hasSize(metadataCountSw1);
assertThat(metadataSw2V).isNull();
}
/**

View File

@@ -13,10 +13,10 @@ import static org.assertj.core.api.Assertions.assertThat;
import org.eclipse.hawkbit.repository.SoftwareModuleFields;
import org.eclipse.hawkbit.repository.SoftwareModuleManagement;
import org.eclipse.hawkbit.repository.builder.SoftwareModuleMetadataCreate;
import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.SoftwareModule.MetadataValueCreate;
import org.eclipse.hawkbit.repository.test.util.TestdataFactory;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -49,13 +49,8 @@ class RsqlSoftwareModuleFieldTest extends AbstractJpaIntegrationTest {
final JpaSoftwareModule ah2 = (JpaSoftwareModule) softwareModuleManagement.create(
SoftwareModuleManagement.Create.builder().type(appType).name("agent-hub2").version("1.0.1").description("agent-hub2").build());
final SoftwareModuleMetadataCreate softwareModuleMetadata = entityFactory.softwareModuleMetadata()
.create(ah.getId()).key("metaKey").value("metaValue");
softwareModuleManagement.updateMetadata(softwareModuleMetadata);
final SoftwareModuleMetadataCreate softwareModuleMetadata2 = entityFactory.softwareModuleMetadata()
.create(ah2.getId()).key("metaKey").value("value");
softwareModuleManagement.updateMetadata(softwareModuleMetadata2);
softwareModuleManagement.createMetadata(ah.getId(), "metaKey", new MetadataValueCreate("metaValue"));
softwareModuleManagement.createMetadata(ah2.getId(), "metaKey", new MetadataValueCreate("value"));
}
/**
@@ -160,7 +155,6 @@ class RsqlSoftwareModuleFieldTest extends AbstractJpaIntegrationTest {
assertRSQLQuery(SoftwareModuleFields.METADATA.name() + ".metaKey=in=(metaValue,value)", 2);
assertRSQLQuery(SoftwareModuleFields.METADATA.name() + ".metaKey=out=(metaValue,notexist)", 1);
assertRSQLQuery(SoftwareModuleFields.METADATA.name() + ".notExist==metaValue", 0);
}
private void assertRSQLQuery(final String rsql, final long expectedEntity) {

View File

@@ -18,8 +18,10 @@ import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import org.eclipse.hawkbit.repository.RsqlQueryField;
import org.eclipse.hawkbit.repository.SoftwareModuleFields;
import org.eclipse.hawkbit.repository.TargetFields;
import org.eclipse.hawkbit.repository.jpa.JpaRepositoryConfiguration;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
import org.eclipse.hawkbit.repository.jpa.ql.utils.HawkbitQlToSql;
import org.eclipse.hawkbit.repository.test.TestConfiguration;
@@ -45,6 +47,8 @@ class RsqlToSqlTest {
@Test
void printPG() {
printFrom(JpaSoftwareModule.class, SoftwareModuleFields.class, "metadata.x!=y");
printFrom(JpaSoftwareModule.class, SoftwareModuleFields.class, "metadata.x==y");
printFrom(JpaTarget.class, TargetFields.class, "metadata.x==y");
printFrom(JpaTarget.class, TargetFields.class, "tag!=TAG1 and tag==TAG2");
printFrom(JpaTarget.class, TargetFields.class, "tag==TAG1 and tag!=TAG2");

View File

@@ -74,7 +74,7 @@ import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessCond
import org.eclipse.hawkbit.repository.model.RolloutGroupConditionBuilder;
import org.eclipse.hawkbit.repository.model.RolloutGroupConditions;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.SoftwareModuleMetadata;
import org.eclipse.hawkbit.repository.model.SoftwareModule.MetadataValueCreate;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
import org.eclipse.hawkbit.repository.model.Tag;
import org.eclipse.hawkbit.repository.model.Target;
@@ -341,12 +341,12 @@ public class TestdataFactory {
}
/**
* Adds {@link SoftwareModuleMetadata} to every module of given {@link DistributionSet}.
* Adds software module metadata to every module of given {@link DistributionSet}.
* <p/>
* {@link #VISIBLE_SM_MD_VALUE}, {@link #VISIBLE_SM_MD_KEY} with
* {@link SoftwareModuleMetadata#isTargetVisible()} and
* {@link MetadataValueCreate#isTargetVisible()} and
* {@link #INVISIBLE_SM_MD_KEY}, {@link #INVISIBLE_SM_MD_VALUE} without
* {@link SoftwareModuleMetadata#isTargetVisible()}
* {@link MetadataValueCreate#isTargetVisible()}
*
* @param set to add metadata to
*/
@@ -1256,10 +1256,14 @@ public class TestdataFactory {
}
private void addTestModuleMetadata(final SoftwareModule module) {
softwareModuleManagement.updateMetadata(entityFactory.softwareModuleMetadata().create(module.getId())
.key(VISIBLE_SM_MD_KEY).value(VISIBLE_SM_MD_VALUE).targetVisible(true));
softwareModuleManagement.updateMetadata(entityFactory.softwareModuleMetadata().create(module.getId())
.key(INVISIBLE_SM_MD_KEY).value(INVISIBLE_SM_MD_VALUE).targetVisible(false));
softwareModuleManagement.createMetadata(
module.getId(),
VISIBLE_SM_MD_KEY,
new MetadataValueCreate(VISIBLE_SM_MD_VALUE, true));
softwareModuleManagement.createMetadata(
module.getId(),
INVISIBLE_SM_MD_KEY,
new MetadataValueCreate(INVISIBLE_SM_MD_VALUE, false));
}
private void assertTargetProperlyCreated(final Target target) {