Absract metatype impl (#2575)

* Add common "interface" for metadata supporting entities
* Add common metadata implementation for distribution set and software module
* Extract PermissionSupport + extend by TargetManagement
* TargetManagement tags are now protected by Target permissions - as it should be

---------

Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
Avgustin Marinov
2025-08-01 12:41:53 +03:00
committed by GitHub
parent b40de54d83
commit 5d75c9b0cc
22 changed files with 449 additions and 373 deletions

View File

@@ -54,14 +54,13 @@ import org.springframework.security.access.prepost.PreAuthorize;
* Management service for {@link DistributionSet}s.
*/
public interface DistributionSetManagement<T extends DistributionSet>
extends RepositoryManagement<T, DistributionSetManagement.Create, DistributionSetManagement.Update> {
extends RepositoryManagement<T, DistributionSetManagement.Create, DistributionSetManagement.Update>, MetadataSupport<String> {
@Override
default String permissionGroup() {
return "DISTRIBUTION_SET";
}
/**
* Find {@link DistributionSet} based on given ID including (lazy loaded) details, e.g. {@link DistributionSet#getModules()}. <br/>
* For performance reasons it is recommended to use {@link #get(long)} if the details are not required.
@@ -140,49 +139,6 @@ public interface DistributionSetManagement<T extends DistributionSet>
@PreAuthorize(HAS_UPDATE_REPOSITORY)
List<T> unassignTag(@NotEmpty Collection<Long> ids, long tagId);
/**
* Creates a map of distribution set meta-data entries.
*
* @param id if the {@link DistributionSet} the meta-data has to be created for
* @param metadata the meta-data entries to create or update
* @throws EntityNotFoundException if given set does not exist
* @throws EntityAlreadyExistsException in case one of the meta-data entry already exists for the specific key
* @throws AssignmentQuotaExceededException if the maximum number of meta-data entries is exceeded for the addressed {@link DistributionSet}
*/
@PreAuthorize(HAS_UPDATE_REPOSITORY)
void createMetadata(long id, @NotEmpty Map<String, String> metadata);
/**
* 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(HAS_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 key meta data-entry key to be updated
* @param value meta data-entry to be new value
* @throws EntityNotFoundException in case the meta-data entry does not exist and cannot be updated
*/
@PreAuthorize(HAS_UPDATE_REPOSITORY)
void updateMetadata(long id, @NotNull String key, @NotNull String value);
/**
* Deletes a distribution set meta-data entry.
*
* @param id where meta-data has to be deleted
* @param key of the meta-data element
* @throws EntityNotFoundException if given set does not exist
*/
@PreAuthorize(HAS_UPDATE_REPOSITORY)
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
*

View File

@@ -0,0 +1,85 @@
/**
* 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
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.repository;
import java.util.Map;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import org.eclipse.hawkbit.im.authentication.SpringEvalExpressions;
import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException;
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.springframework.security.access.prepost.PreAuthorize;
/**
* Represents interface for managing meta-data of entities. The keys are always Strings, while the values are generic
*
* @param <MV> the type of the meta-data value
*/
@SuppressWarnings("java:S119") // java:S119 - better self explainable
public interface MetadataSupport<MV> {
/**
* Creates or updates a meta-data value.
*
* @param id the entity 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 MV value);
/**
* Creates a list of entity meta-data entries.
*
* @param id the entity id which meta-data has to be created
* @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 entity with given ID does not exist
* @throws AssignmentQuotaExceededException if the maximum number of meta-data entries is exceeded for the addressed entity
*/
@PreAuthorize(SpringEvalExpressions.HAS_UPDATE_REPOSITORY)
void createMetadata(@NotNull Long id, @NotEmpty @Valid Map<String, ? extends MV> metadata);
/**
* Finds all meta-data by the given entity id and key.
*
* @param id the entity id to retrieve the meta-data from
* @param key the meta-data key to retrieve
* @return a paged result of all meta-data entries for a given entity id
* @throws EntityNotFoundException if entity with given ID does not exist ot the
*/
@PreAuthorize(SpringEvalExpressions.HAS_READ_REPOSITORY)
MV getMetadata(@NotNull Long id, @NotEmpty String key);
/**
* Finds all meta-data by the given entity id.
*
* @param id the entity id to retrieve the meta-data from
* @return a paged result of all meta-data entries for a given entity id
* @throws EntityNotFoundException if entity with given ID does not exist
*/
@PreAuthorize(SpringEvalExpressions.HAS_READ_REPOSITORY)
Map<String, MV> getMetadata(@NotNull Long id);
/**
* Deletes a entity meta-data entry.
*
* @param id where meta-data has to be deleted
* @param key of the meta-data element
* @throws EntityNotFoundException if entity with given ID does not exist or the key is not found
*/
@PreAuthorize(SpringEvalExpressions.HAS_UPDATE_REPOSITORY)
void deleteMetadata(@NotNull Long id, @NotEmpty String key);
}

View File

@@ -0,0 +1,15 @@
/**
* 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
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.repository;
public interface PermissionSupport {
String permissionGroup();
}

View File

@@ -36,7 +36,7 @@ import org.springframework.security.access.prepost.PreAuthorize;
* @param <C> type of the create request
* @param <U> type of the update request
*/
public interface RepositoryManagement<T extends BaseEntity, C, U extends Identifiable<Long>> {
public interface RepositoryManagement<T extends BaseEntity, C, U extends Identifiable<Long>> extends PermissionSupport {
/**
* Creates new {@link BaseEntity}.
@@ -153,6 +153,7 @@ public interface RepositoryManagement<T extends BaseEntity, C, U extends Identif
@PreAuthorize(SpringEvalExpressions.HAS_DELETE_REPOSITORY)
void delete(@NotEmpty Collection<Long> ids);
@Override
default String permissionGroup() {
return "REPOSITORY";
}

View File

@@ -42,66 +42,13 @@ import org.springframework.security.access.prepost.PreAuthorize;
* Service for managing {@link SoftwareModule}s.
*/
public interface SoftwareModuleManagement<T extends SoftwareModule>
extends RepositoryManagement<T, SoftwareModuleManagement.Create, SoftwareModuleManagement.Update> {
extends RepositoryManagement<T, SoftwareModuleManagement.Create, SoftwareModuleManagement.Update>, MetadataSupport<MetadataValue> {
@Override
default String permissionGroup() {
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 meta-data entries is exceeded for the addressed {@link SoftwareModule}
*/
@PreAuthorize(SpringEvalExpressions.HAS_UPDATE_REPOSITORY)
void createMetadata(@NotNull Long id, @NotEmpty @Valid Map<String, ? extends MetadataValue> metadata);
/**
* Finds all meta-data by the given software module id and key.
*
* @param id the software module id to retrieve the meta-data from
* @param key the meta-data key to retrieve
* @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 ot the
*/
@PreAuthorize(SpringEvalExpressions.HAS_READ_REPOSITORY)
MetadataValue getMetadata(@NotNull Long id, @NotEmpty String key);
/**
* 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)
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
* @throws EntityNotFoundException if software module with given ID does not exist or the key is not found
*/
@PreAuthorize(SpringEvalExpressions.HAS_UPDATE_REPOSITORY)
void deleteMetadata(@NotNull Long id, @NotEmpty String key);
@PreAuthorize(SpringEvalExpressions.IS_SYSTEM_CODE)
Map<Long, Map<String, String>> findMetaDataBySoftwareModuleIdsAndTargetVisible(Collection<Long> ids);

View File

@@ -15,12 +15,10 @@ import static org.eclipse.hawkbit.im.authentication.SpringEvalExpressions.HAS_AU
import static org.eclipse.hawkbit.im.authentication.SpringEvalExpressions.HAS_AUTH_CREATE_TARGET;
import static org.eclipse.hawkbit.im.authentication.SpringEvalExpressions.HAS_AUTH_DELETE_TARGET;
import static org.eclipse.hawkbit.im.authentication.SpringEvalExpressions.HAS_AUTH_PREFIX;
import static org.eclipse.hawkbit.im.authentication.SpringEvalExpressions.HAS_READ_REPOSITORY;
import static org.eclipse.hawkbit.im.authentication.SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY_AND_UPDATE_TARGET;
import static org.eclipse.hawkbit.im.authentication.SpringEvalExpressions.HAS_AUTH_READ_TARGET;
import static org.eclipse.hawkbit.im.authentication.SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ_AND_TARGET_READ;
import static org.eclipse.hawkbit.im.authentication.SpringEvalExpressions.HAS_AUTH_SUFFIX;
import static org.eclipse.hawkbit.im.authentication.SpringEvalExpressions.HAS_UPDATE_REPOSITORY;
import static org.eclipse.hawkbit.im.authentication.SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET;
import java.util.Collection;
@@ -36,6 +34,7 @@ import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import org.eclipse.hawkbit.im.authentication.SpPermission;
import org.eclipse.hawkbit.im.authentication.SpringEvalExpressions;
import org.eclipse.hawkbit.repository.builder.TargetCreate;
import org.eclipse.hawkbit.repository.builder.TargetUpdate;
import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException;
@@ -60,7 +59,7 @@ import org.springframework.security.access.prepost.PreAuthorize;
/**
* Management service for {@link Target}s.
*/
public interface TargetManagement {
public interface TargetManagement extends PermissionSupport {
String DETAILS_BASE = "base";
String DETAILS_AUTO_CONFIRMATION_STATUS = "autoConfirmationStatus";
@@ -72,6 +71,12 @@ public interface TargetManagement {
HAS_AUTH_AND +
HAS_AUTH_PREFIX + SpPermission.READ_TARGET + HAS_AUTH_SUFFIX +
BRACKET_CLOSE;
@Override
default String permissionGroup() {
return "TARGET";
}
/**
* Counts number of targets with the given distribution set assigned.
*
@@ -744,45 +749,56 @@ public interface TargetManagement {
Page<Target> findByControllerAttributesRequested(@NotNull Pageable pageable);
/**
* Creates a list of target meta-data entries.
* Creates or updates a meta-data value.
*
* @param controllerId {@link Target} controller id the meta-data has to be created for
* @param metadata the meta-data entries to create or update
* @throws EntityNotFoundException if given target does not exist
* @throws EntityAlreadyExistsException in case one of the metad-ata entry already exists for the specific key
* @throws AssignmentQuotaExceededException if the maximum number of meta-data entries is exceeded for the addressed {@link Target}
*/
@PreAuthorize(HAS_UPDATE_REPOSITORY)
void createMetadata(@NotEmpty String controllerId, @NotEmpty Map<String, String> metadata);
/**
* Finds a single target meta-data by its id.
*
* @param controllerId of the {@link Target}
* @return the found target meta-data
* @throws EntityNotFoundException if target with given ID does not exist
*/
@PreAuthorize(HAS_READ_REPOSITORY)
Map<String, String> getMetadata(@NotEmpty String controllerId);
/**
* Updates a target meta-data value if corresponding entry exists.
*
* @param controllerId {@link Target} controller id of the meta-data entry to be updated
* @param key meta data-entry key to be updated
* @param value meta data-entry to be new value
* @param controllerId the entity 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(HAS_UPDATE_REPOSITORY)
void updateMetadata(@NotEmpty String controllerId, @NotNull String key, @NotNull String value);
@PreAuthorize(SpringEvalExpressions.HAS_UPDATE_REPOSITORY)
void createMetadata(@NotNull String controllerId, @NotEmpty String key, @NotNull @Valid String value);
/**
* Deletes a target meta data entry.
* Creates a list of entity meta-data entries.
*
* @param controllerId the entity id which meta-data has to be created
* @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 entity with given ID does not exist
* @throws AssignmentQuotaExceededException if the maximum number of meta-data entries is exceeded for the addressed entity
*/
@PreAuthorize(SpringEvalExpressions.HAS_UPDATE_REPOSITORY)
void createMetadata(@NotNull String controllerId, @NotEmpty @Valid Map<String, String> metadata);
/**
* Finds all meta-data by the given entity id and key.
*
* @param controllerId the entity id to retrieve the meta-data from
* @param key the meta-data key to retrieve
* @return a paged result of all meta-data entries for a given entity id
* @throws EntityNotFoundException if entity with given ID does not exist ot the
*/
@PreAuthorize(SpringEvalExpressions.HAS_READ_REPOSITORY)
String getMetadata(@NotNull String controllerId, @NotEmpty String key);
/**
* Finds all meta-data by the given entity id.
*
* @param controllerId the entity id to retrieve the meta-data from
* @return a paged result of all meta-data entries for a given entity id
* @throws EntityNotFoundException if entity with given ID does not exist
*/
@PreAuthorize(SpringEvalExpressions.HAS_READ_REPOSITORY)
Map<String, String> getMetadata(@NotNull String controllerId);
/**
* Deletes a entity meta-data entry.
*
* @param controllerId where meta-data has to be deleted
* @param key of the meta data element
* @throws EntityNotFoundException if given target does not exist
* @param key of the meta-data element
* @throws EntityNotFoundException if entity with given ID does not exist or the key is not found
*/
@PreAuthorize(HAS_UPDATE_REPOSITORY)
void deleteMetadata(@NotEmpty String controllerId, @NotEmpty String key);
@PreAuthorize(SpringEvalExpressions.HAS_UPDATE_REPOSITORY)
void deleteMetadata(@NotNull String controllerId, @NotEmpty String key);
}

View File

@@ -64,9 +64,9 @@ public class RepositoryConfiguration {
public boolean hasPermission(final Authentication authentication, final Object targetDomainObject, final Object permission) {
if (targetDomainObject instanceof MethodSecurityExpressionOperations root) {
final String neededPermission =
permission + "_" + (root.getThis() instanceof RepositoryManagement<?, ?, ?> repositoryManagement
? repositoryManagement.permissionGroup()
: "REPOSITORY"); // TODO - should not fall back here - all using parmissions should extend repository management interface
permission + "_" + (root.getThis() instanceof PermissionSupport permissionSupport
? permissionSupport.permissionGroup()
: "REPOSITORY"); // TODO - should not fall back here - all using permissions should extend repository management interface
final boolean hasPermission = roleHierarchy.getReachableGrantedAuthorities(authentication.getAuthorities()).stream()
.map(GrantedAuthority::getAuthority)
.anyMatch(authority -> authority.equals(neededPermission));

View File

@@ -80,11 +80,12 @@ abstract class AbstractJpaRepositoryManagement<T extends AbstractJpaBaseEntity,
protected final R jpaRepository;
protected final EntityManager entityManager;
private final Constructor<T> entityConstructor;
private final Supplier<T> jpaEntityCreator;
protected AbstractJpaRepositoryManagement(final R jpaRepository, final EntityManager entityManager) {
this.jpaRepository = jpaRepository;
this.entityManager = entityManager;
final Constructor<T> entityConstructor;
try {
entityConstructor = jpaRepository.getDomainClass().getConstructor();
// test if method works, if fine - it shall never fail when called later
@@ -92,6 +93,13 @@ abstract class AbstractJpaRepositoryManagement<T extends AbstractJpaBaseEntity,
} catch (final NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
throw new IllegalStateException("JPA domain class " + jpaRepository.getDomainClass() + " shall have public no-args constructor", e);
}
jpaEntityCreator = () -> {
try {
return entityConstructor.newInstance();
} catch (final InstantiationException | IllegalAccessException | InvocationTargetException e) {
throw new IllegalStateException("Must NEVER happen!", e);
}
};
}
@Override
@@ -156,7 +164,7 @@ abstract class AbstractJpaRepositoryManagement<T extends AbstractJpaBaseEntity,
}
protected T update(final Identifiable<Long> update, final T entity) {
// update getId has not setter in target JPA entity but shall have getter and the value shall be the same
// update getId has no setter in target JPA entity but shall have getter and the value shall be the same
// otherwise the Utils will throw an exception that there is no counterpart setter for getId
if (ObjectCopyUtil.copy(update, entity, false, this::attach)) {
return jpaRepository.save(entity);
@@ -289,12 +297,7 @@ abstract class AbstractJpaRepositoryManagement<T extends AbstractJpaBaseEntity,
}
private T jpaEntity(final Object create) {
final T jpaEntity;
try {
jpaEntity = entityConstructor.newInstance();
} catch (final InstantiationException | IllegalAccessException | InvocationTargetException e) {
throw new IllegalStateException("Must NEVER happen!", e);
}
final T jpaEntity = jpaEntityCreator.get();
ObjectCopyUtil.copy(create, jpaEntity, false, this::attach);
return jpaEntity;
}

View File

@@ -0,0 +1,178 @@
/**
* 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
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.repository.jpa.management;
import static org.eclipse.hawkbit.repository.jpa.configuration.Constants.TX_RT_DELAY;
import static org.eclipse.hawkbit.repository.jpa.configuration.Constants.TX_RT_MAX;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.ParameterizedType;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
import jakarta.persistence.EntityManager;
import org.eclipse.hawkbit.repository.Identifiable;
import org.eclipse.hawkbit.repository.MetadataSupport;
import org.eclipse.hawkbit.repository.RsqlQueryField;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.InvalidDistributionSetException;
import org.eclipse.hawkbit.repository.jpa.model.AbstractJpaBaseEntity;
import org.eclipse.hawkbit.repository.jpa.model.WithMetadata;
import org.eclipse.hawkbit.repository.jpa.repository.BaseEntityRepository;
import org.eclipse.hawkbit.utils.ObjectCopyUtil;
import org.springframework.dao.ConcurrencyFailureException;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
import org.springframework.transaction.annotation.Transactional;
@SuppressWarnings("java:S119") // java:S119 - better self explainable
abstract class AbstractJpaRepositoryWithMetadataManagement<T extends AbstractJpaBaseEntity & WithMetadata<MV, MVI>, C, U extends Identifiable<Long>, R extends BaseEntityRepository<T>, A extends Enum<A> & RsqlQueryField, MV, MVI extends MV>
extends AbstractJpaRepositoryManagement<T, C, U, R, A> implements MetadataSupport<MV> {
private final Supplier<MVI> metadataValueCreator;
private final boolean useCopy;
// java:S3011 - intentionally to provide option to forbid constructors
// java:S1141 - better visible this way
@SuppressWarnings({"unchecked", "java:S3011", "java:S1141"})
protected AbstractJpaRepositoryWithMetadataManagement(final R repository, final EntityManager entityManager) {
super(repository, entityManager);
try {
final Class<MVI> metadataValueType = (Class<MVI>) ((ParameterizedType) jpaRepository.getDomainClass().getMethod("getMetadata")
.getGenericReturnType())
.getActualTypeArguments()[1];
final Constructor<MVI> constructor;
try {
constructor = metadataValueType.getDeclaredConstructor();
constructor.setAccessible(true);
constructor.newInstance();
} catch (final NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
throw new IllegalStateException("Metadata class " + jpaRepository.getDomainClass() + " shall have no-args constructor", e);
}
metadataValueCreator = () -> {
try {
return constructor.newInstance();
} catch (final InstantiationException | IllegalAccessException | InvocationTargetException e) {
throw new IllegalStateException("Must NEVER happen!", e);
}
};
useCopy = !String.class.equals(metadataValueType) && !metadataValueType.isPrimitive();
} catch (final NoSuchMethodException | SecurityException e) {
throw new IllegalStateException(e);
}
}
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY))
public void createMetadata(final Long id, final String key, final MV value) {
final T jpaEntity = getValid(id);
final Map<String, MVI> metadataValueMap = jpaEntity.getMetadata();
final MVI existingValue = metadataValueMap.get(key);
if (existingValue == null) {
assertMetadataQuota(metadataValueMap.size() + 1L);
}
if (setMetadataValue(key, value, existingValue, metadataValueMap)) {
jpaRepository.save(jpaEntity);
}
}
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY))
public void createMetadata(final Long id, final Map<String, ? extends MV> metadata) {
final T jpaEntity = getValid(id);
final Map<String, MVI> metadataValueMap = jpaEntity.getMetadata();
assertMetadataQuota(metadata.keySet().stream().filter(key -> !metadataValueMap.containsKey(key)).count() + metadataValueMap.size());
final AtomicBoolean changed = new AtomicBoolean(false);
metadata.forEach((key, value) -> {
if (setMetadataValue(key, value, metadataValueMap.get(key), metadataValueMap)) {
changed.set(true);
}
});
if (changed.get()) {
jpaRepository.save(jpaEntity);
}
}
@Override
public MV getMetadata(final Long id, final String key) {
final T jpaEntity = getValid(id);
final MV metadataValue = jpaEntity.getMetadata().get(key);
if (metadataValue == null) {
throw new EntityNotFoundException("Metadata", jpaRepository.getManagementClass().getSimpleName() + ":" + id + ":" + key);
} else {
return metadataValue;
}
}
@Override
public Map<String, MV> getMetadata(final Long id) {
return jpaRepository
.findById(id)
.map(T::getMetadata)
.map(metadata -> metadata.entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
e -> (MV) e.getValue())))
.orElseThrow(() -> new EntityNotFoundException(jpaRepository.getManagementClass(), id));
}
@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 T jpaEntity = getValid(id);
final Map<String, MVI> metadataValueMap = jpaEntity.getMetadata();
if (!metadataValueMap.containsKey(key)) {
throw new EntityNotFoundException("Metadata", jpaRepository.getManagementClass().getSimpleName() + ":" + id + ":" + key);
}
metadataValueMap.remove(key);
jpaRepository.save(jpaEntity);
}
protected abstract void assertMetadataQuota(final long requested);
private T getValid(final Long id) {
final T jpaEntity = jpaRepository
.findById(id)
.orElseThrow(() -> new EntityNotFoundException(jpaRepository.getManagementClass(), id));
if (!jpaEntity.isValid()) {
throw new InvalidDistributionSetException(jpaRepository.getManagementClass().getSimpleName() + " " + id + " is invalid");
}
return jpaEntity;
}
@SuppressWarnings("unchecked")
private boolean setMetadataValue(final String key, final MV newValue, final MVI existingValue, final Map<String, MVI> metadataValueMap) {
if (useCopy) {
final MVI jpaMetadataValue = existingValue == null ? metadataValueCreator.get() : existingValue;
if (ObjectCopyUtil.copy(newValue, jpaMetadataValue, true, UnaryOperator.identity())) {
metadataValueMap.put(key, jpaMetadataValue);
return true;
} else {
return false;
}
} else {
if (Objects.equals(newValue, existingValue)) {
return false;
} else {
metadataValueMap.put(key, (MVI)newValue);
return true;
}
}
}
}

View File

@@ -90,7 +90,7 @@ import org.springframework.util.ObjectUtils;
@Service
@ConditionalOnBooleanProperty(prefix = "hawkbit.jpa", name = { "enabled", "distribution-set-management" }, matchIfMissing = true)
public class JpaDistributionSetManagement
extends AbstractJpaRepositoryManagement<JpaDistributionSet, DistributionSetManagement.Create, DistributionSetManagement.Update, DistributionSetRepository, DistributionSetFields>
extends AbstractJpaRepositoryWithMetadataManagement<JpaDistributionSet, DistributionSetManagement.Create, DistributionSetManagement.Update, DistributionSetRepository, DistributionSetFields, String, String>
implements DistributionSetManagement<JpaDistributionSet> {
private final DistributionSetTagManagement<JpaDistributionSetTag> distributionSetTagManagement;
@@ -253,63 +253,6 @@ public class JpaDistributionSetManagement
});
}
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY))
public void createMetadata(final long id, final Map<String, String> md) {
final JpaDistributionSet distributionSet = getValid0(id);
// get the modifiable metadata map
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);
assertMetaDataQuota(id, metadata.size());
jpaRepository.save(distributionSet);
}
@Override
public Map<String, String> getMetadata(final long id) {
assertDistributionSetExists(id);
return getMap(id, JpaDistributionSet_.metadata);
}
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY))
public void updateMetadata(final long id, final String key, final String value) {
final JpaDistributionSet distributionSet = getValid0(id);
// get the modifiable metadata map
final Map<String, String> metadata = distributionSet.getMetadata();
if (!metadata.containsKey(key)) {
throw new EntityNotFoundException("DistributionSet metadata", id + ":" + key);
}
metadata.put(key, value);
jpaRepository.save(distributionSet);
}
@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 JpaDistributionSet distributionSet = getValid0(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);
}
jpaRepository.save(distributionSet);
}
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY))
@@ -450,6 +393,17 @@ public class JpaDistributionSetManagement
return jpaRepository.countAutoAssignmentsForDistributionSet(id);
}
/**
* Asserts the meta-data quota for the software module with the given ID.
*
* @param requested Number of meta-data entries to be created.
*/
@Override
protected void assertMetadataQuota(final long requested) {
final int maxMetaData = quotaManagement.getMaxMetaDataEntriesPerDistributionSet();
QuotaHelper.assertAssignmentQuota(requested, maxMetaData, String.class, DistributionSet.class);
}
// check if it shall implicitly lock a distribution set
boolean isImplicitLockApplicable(final DistributionSet distributionSet) {
final JpaDistributionSet jpaDistributionSet = (JpaDistributionSet) distributionSet;
@@ -584,11 +538,6 @@ public class JpaDistributionSetManagement
return specifications;
}
private void assertMetaDataQuota(final Long dsId, final int requested) {
final int limit = quotaManagement.getMaxMetaDataEntriesPerDistributionSet();
QuotaHelper.assertAssignmentQuota(dsId, requested, limit, "Metadata", DistributionSet.class.getSimpleName(), null);
}
private void assertSoftwareModuleQuota(final Long id, final int requested) {
QuotaHelper.assertAssignmentQuota(id, requested, quotaManagement.getMaxSoftwareModulesPerDistributionSet(),
SoftwareModule.class, DistributionSet.class, softwareModuleRepository::countByAssignedToId);

View File

@@ -20,8 +20,6 @@ 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;
@@ -48,7 +46,6 @@ import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
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;
@@ -64,12 +61,9 @@ 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 AbstractJpaRepositoryWithMetadataManagement<JpaSoftwareModule, SoftwareModuleManagement.Create, SoftwareModuleManagement.Update, SoftwareModuleRepository, SoftwareModuleFields, MetadataValue, JpaSoftwareModule.JpaMetadataValue>
implements SoftwareModuleManagement<JpaSoftwareModule> {
protected static final String SOFTWARE_MODULE_METADATA = "SoftwareModuleMetadata";
private final DistributionSetRepository distributionSetRepository;
private final SoftwareModuleTypeRepository softwareModuleTypeRepository;
private final ArtifactManagement artifactManagement;
@@ -164,88 +158,6 @@ public class JpaSoftwareModuleManagement
.toList();
}
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY))
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 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);
}
}
@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;
}
}
@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 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
public Map<Long, Map<String, String>> findMetaDataBySoftwareModuleIdsAndTargetVisible(final Collection<Long> ids) {
@@ -349,6 +261,17 @@ public class JpaSoftwareModuleManagement
return jpaRepository.count(SoftwareModuleSpecification.byAssignedToDs(distributionSetId));
}
/**
* Asserts the meta-data quota for the software module with the given ID.
*
* @param requested Number of meta-data entries to be created.
*/
@Override
protected void assertMetadataQuota(final long requested) {
final int maxMetaData = quotaManagement.getMaxMetaDataEntriesPerSoftwareModule();
QuotaHelper.assertAssignmentQuota(requested, maxMetaData, SoftwareModule.MetadataValueCreate.class, SoftwareModule.class);
}
private void deleteGridFsArtifacts(final JpaSoftwareModule swModule) {
jpaRepository.getAccessController().ifPresent(accessController ->
accessController.assertOperationAllowed(AccessController.Operation.DELETE, swModule));
@@ -364,16 +287,6 @@ public class JpaSoftwareModuleManagement
smFilterNameAndVersionEntries[1]);
}
/**
* Asserts the meta-data quota for the software module with the given ID.
*
* @param requested Number of meta-data entries to be created.
*/
private void assertMetadataQuota(final long requested) {
final int maxMetaData = quotaManagement.getMaxMetaDataEntriesPerSoftwareModule();
QuotaHelper.assertAssignmentQuota(requested, maxMetaData, SoftwareModule.MetadataValueCreate.class, SoftwareModule.class);
}
private void assertSoftwareModuleTypeExists(final Long typeId) {
if (!softwareModuleTypeRepository.existsById(typeId)) {
throw new EntityNotFoundException(SoftwareModuleType.class, typeId);

View File

@@ -711,6 +711,23 @@ public class JpaTargetManagement implements TargetManagement {
targetRepository, List.of(TargetSpecifications.hasRequestControllerAttributesTrue()), pageable);
}
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public void createMetadata(final String controllerId, final String key, final String value) {
final JpaTarget target = getByControllerIdAndThrowIfNotFound(controllerId);
// get the modifiable metadata map
final Map<String, String> metadata = target.getMetadata();
if (!metadata.containsKey(key)) {
throw new EntityNotFoundException("Target metadata", controllerId + ":" + key);
}
metadata.put(key, value);
targetRepository.save(target);
}
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
@@ -737,20 +754,8 @@ public class JpaTargetManagement implements TargetManagement {
}
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public void updateMetadata(final String controllerId, final String key, final String value) {
final JpaTarget target = getByControllerIdAndThrowIfNotFound(controllerId);
// get the modifiable metadata map
final Map<String, String> metadata = target.getMetadata();
if (!metadata.containsKey(key)) {
throw new EntityNotFoundException("Target metadata", controllerId + ":" + key);
}
metadata.put(key, value);
targetRepository.save(target);
public String getMetadata(final String controllerId, final String key) {
return getMap(controllerId, JpaTarget_.metadata).get(key);
}
@Override

View File

@@ -70,7 +70,9 @@ import org.springframework.core.annotation.Order;
attributeNodes = { @NamedAttributeNode("modules"), @NamedAttributeNode("tags"), @NamedAttributeNode("type") })
// exception squid:S2160 - BaseEntity equals/hashcode is handling correctly for sub entities
@SuppressWarnings("squid:S2160")
public class JpaDistributionSet extends AbstractJpaNamedVersionedEntity implements DistributionSet, EventAwareEntity {
public class JpaDistributionSet
extends AbstractJpaNamedVersionedEntity
implements DistributionSet, WithMetadata<String, String>, EventAwareEntity {
@Serial
private static final long serialVersionUID = 1L;

View File

@@ -51,6 +51,7 @@ import org.eclipse.hawkbit.repository.exception.LockedException;
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.SoftwareModule.MetadataValue;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
/**
@@ -70,7 +71,9 @@ import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
@NamedEntityGraph(name = "SoftwareModule.artifacts", attributeNodes = { @NamedAttributeNode("artifacts") })
// exception squid:S2160 - BaseEntity equals/hashcode is handling correctly for sub entities
@SuppressWarnings("squid:S2160")
public class JpaSoftwareModule extends AbstractJpaNamedVersionedEntity implements SoftwareModule, EventAwareEntity {
public class JpaSoftwareModule
extends AbstractJpaNamedVersionedEntity
implements SoftwareModule, WithMetadata<MetadataValue, JpaSoftwareModule.JpaMetadataValue>, EventAwareEntity {
@Serial
private static final long serialVersionUID = 1L;

View File

@@ -0,0 +1,28 @@
/**
* 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
* 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.util.Map;
/**
* Interface for entities that support metadata.
* @param <MV> metadata value type
* @param <MVI> metadata value implementation type
*/
@SuppressWarnings("java:S119") // java:S119 - better self explainable
public interface WithMetadata<MV, MVI extends MV> {
Map<String, MVI> getMetadata();
// return if the entity is valid for update metadata
default boolean isValid() {
return true;
}
}

View File

@@ -160,11 +160,11 @@ class DistributionSetAccessControllerTest extends AbstractJpaIntegrationTest {
// verify distributionSetManagement#updateMetaData
final String newValue = "newValue";
distributionSetManagement.updateMetadata(permitted.getId(), mdPresetKey, newValue);
assertThatThrownBy(() -> distributionSetManagement.updateMetadata(readOnlyId, mdPresetKey, newValue))
distributionSetManagement.createMetadata(permitted.getId(), mdPresetKey, newValue);
assertThatThrownBy(() -> distributionSetManagement.createMetadata(readOnlyId, mdPresetKey, newValue))
.as("Distribution set not allowed to me modified.")
.isInstanceOf(InsufficientPermissionException.class);
assertThatThrownBy(() -> distributionSetManagement.updateMetadata(hiddenId, mdPresetKey, newValue))
assertThatThrownBy(() -> distributionSetManagement.createMetadata(hiddenId, mdPresetKey, newValue))
.as("Distribution set should not be visible.")
.isInstanceOf(EntityNotFoundException.class);

View File

@@ -74,7 +74,7 @@ class DistributionSetManagementSecurityTest
* Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.
*/
@Test
void createMetadataPermissionsCheck() {
void createMetadataMapPermissionsCheck() {
assertPermissions(
() -> {
distributionSetManagement.createMetadata(1L, Map.of("key", "value"));
@@ -95,9 +95,9 @@ class DistributionSetManagementSecurityTest
* Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.
*/
@Test
void updateMetadataPermissionsCheck() {
void createMetadataPermissionsCheck() {
assertPermissions(() -> {
distributionSetManagement.updateMetadata(1L, "key", "value");
distributionSetManagement.createMetadata(1L, "key", "value");
return null;
},
List.of(SpPermission.UPDATE_REPOSITORY));

View File

@@ -156,8 +156,7 @@ class DistributionSetManagementTest extends AbstractJpaIntegrationTest {
() -> distributionSetManagement.update(DistributionSetManagement.Update.builder().id(NOT_EXIST_IDL).build()),
"DistributionSet");
verifyThrownExceptionBy(() -> distributionSetManagement.updateMetadata(NOT_EXIST_IDL, "xxx", "xxx"), "DistributionSet");
verifyThrownExceptionBy(() -> distributionSetManagement.updateMetadata(set.getId(), NOT_EXIST_ID, "xxx"), "DistributionSet");
verifyThrownExceptionBy(() -> distributionSetManagement.createMetadata(NOT_EXIST_IDL, "xxx", "xxx"), "DistributionSet");
verifyThrownExceptionBy(() -> distributionSetManagement.getOrElseThrowException(NOT_EXIST_IDL), "DistributionSet");
@@ -240,19 +239,6 @@ class DistributionSetManagementTest extends AbstractJpaIntegrationTest {
});
}
/**
* Checks that metadata for a distribution set can be created.
*/
@Test
void createMetadata() {
final String knownKey = "dsMetaKnownKey";
final String knownValue = "dsMetaKnownValue";
final DistributionSet ds = testdataFactory.createDistributionSet("testDs");
insertMetadata(knownKey, knownValue, ds); // and validate
}
/**
* Verifies the enforcement of the metadata quota per distribution set.
*/
@@ -520,7 +506,7 @@ class DistributionSetManagementTest extends AbstractJpaIntegrationTest {
*/
@Test
@WithUser(allSpPermissions = true)
void updateMetadata() {
void createMetadata() {
final String knownKey = "myKnownKey";
final String knownValue = "myKnownValue";
final String knownUpdateValue = "myNewUpdatedValue";
@@ -539,7 +525,7 @@ class DistributionSetManagementTest extends AbstractJpaIntegrationTest {
waitNextMillis();
// update the DS metadata
distributionSetManagement.updateMetadata(ds.getId(), knownKey, knownUpdateValue);
distributionSetManagement.createMetadata(ds.getId(), knownKey, knownUpdateValue);
// we are updating the sw metadata so also modifying the base software
// module so opt lock revision must be three
final DistributionSet reloadedDS = getOrThrow(distributionSetManagement.get(ds.getId()));
@@ -944,7 +930,7 @@ class DistributionSetManagementTest extends AbstractJpaIntegrationTest {
// assert that an existing metadata can not be updated
assertThatExceptionOfType(InvalidDistributionSetException.class)
.as("Invalid distributionSet should throw an exception")
.isThrownBy(() -> distributionSetManagement.updateMetadata(dsId, knownKey1, knownUpdateValue));
.isThrownBy(() -> distributionSetManagement.createMetadata(dsId, knownKey1, knownUpdateValue));
}
/**

View File

@@ -500,13 +500,13 @@ class TargetManagementSecurityTest extends AbstractJpaIntegrationTest {
* Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.
*/
@Test
void createMetadataPermissionsCheck() {
void createMetadataMapPermissionsCheck() {
assertPermissions(
() -> {
targetManagement.createMetadata("controllerId", Map.of("key", "value"));
return null;
},
List.of(SpPermission.UPDATE_REPOSITORY));
List.of(SpPermission.UPDATE_TARGET));
}
/**
@@ -514,20 +514,20 @@ class TargetManagementSecurityTest extends AbstractJpaIntegrationTest {
*/
@Test
void getMetadataPermissionsCheck() {
assertPermissions(() -> targetManagement.getMetadata("controllerId"), List.of(SpPermission.READ_REPOSITORY));
assertPermissions(() -> targetManagement.getMetadata("controllerId"), List.of(SpPermission.READ_TARGET));
}
/**
* Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.
*/
@Test
@WithUser(principal = "user", authorities = { SpPermission.UPDATE_REPOSITORY })
void updateMetadataPermissionsCheck() {
@WithUser(principal = "user", authorities = { SpPermission.UPDATE_TARGET })
void createMetadataPermissionsCheck() {
assertPermissions(() -> {
targetManagement.updateMetadata("controllerId", "key", "value");
targetManagement.createMetadata("controllerId", "key", "value");
return null;
},
List.of(SpPermission.UPDATE_REPOSITORY));
List.of(SpPermission.UPDATE_TARGET));
}
/**
@@ -538,6 +538,6 @@ class TargetManagementSecurityTest extends AbstractJpaIntegrationTest {
assertPermissions(() -> {
targetManagement.deleteMetadata("controllerId", "key");
return null;
}, List.of(SpPermission.UPDATE_REPOSITORY));
}, List.of(SpPermission.UPDATE_TARGET));
}
}

View File

@@ -154,7 +154,7 @@ class TargetManagementTest extends AbstractJpaIntegrationTest {
verifyThrownExceptionBy(() -> targetManagement.deleteMetadata(NOT_EXIST_ID, "xxx"), "Target");
verifyThrownExceptionBy(() -> targetManagement.deleteMetadata(target.getControllerId(), NOT_EXIST_ID), "Target");
verifyThrownExceptionBy(() -> targetManagement.getMetadata(NOT_EXIST_ID).get("xxx"), "Target");
verifyThrownExceptionBy(() -> targetManagement.updateMetadata(NOT_EXIST_ID, "xxx", "xxx"), "Target");
verifyThrownExceptionBy(() -> targetManagement.createMetadata(NOT_EXIST_ID, "xxx", "xxx"), "Target");
}
/**
@@ -773,17 +773,6 @@ class TargetManagementTest extends AbstractJpaIntegrationTest {
assertThat(target.isRequestControllerAttributes()).isFalse();
assertThat(targetManagement.findByControllerAttributesRequested(PAGE).getContent()).contains(updated);
assertThat(targetManagement.isControllerAttributesRequested(knownControllerId)).isTrue();
}
/**
* Checks that metadata for a target can be created.
*/
@Test
void createMetadata() {
insertMetadata(
"targetMetaKnownKey", "targetMetaKnownValue",
testdataFactory.createTarget("targetIdWithMetadata")); // and validate
}
/**
@@ -847,7 +836,7 @@ class TargetManagementTest extends AbstractJpaIntegrationTest {
*/
@Test
@WithUser(allSpPermissions = true)
void updateMetadata() {
void createMetadata() {
final String knownKey = "myKnownKey";
final String knownValue = "myKnownValue";
final String knownUpdateValue = "myNewUpdatedValue";
@@ -864,7 +853,7 @@ class TargetManagementTest extends AbstractJpaIntegrationTest {
assertThat(changedLockRevisionTarget.getOptLockRevision()).isEqualTo(2);
// update the target metadata
targetManagement.updateMetadata(target.getControllerId(), knownKey, knownUpdateValue);
targetManagement.createMetadata(target.getControllerId(), knownKey, knownUpdateValue);
// we are updating the target meta-data so also modifying the target so opt lock revision must be three
final Target changedLockRevisionTarget2 = targetManagement.get(target.getId()).orElseThrow(NoSuchElementException::new);
assertThat(changedLockRevisionTarget2.getOptLockRevision()).isEqualTo(3);