Unify target attributes and metadata (#2408)

* Unify target attributes and metadata

Currently, the target attributes are Map while the metadata,
which has the same concept is List.
This PR unifies them making the metadata also a Map

Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
Avgustin Marinov
2025-05-21 11:26:02 +03:00
committed by GitHub
parent 424520bb72
commit ceba4f5cfb
29 changed files with 490 additions and 1107 deletions

View File

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

View File

@@ -132,7 +132,6 @@ import org.eclipse.hawkbit.repository.jpa.repository.SoftwareModuleMetadataRepos
import org.eclipse.hawkbit.repository.jpa.repository.SoftwareModuleRepository;
import org.eclipse.hawkbit.repository.jpa.repository.SoftwareModuleTypeRepository;
import org.eclipse.hawkbit.repository.jpa.repository.TargetFilterQueryRepository;
import org.eclipse.hawkbit.repository.jpa.repository.TargetMetadataRepository;
import org.eclipse.hawkbit.repository.jpa.repository.TargetRepository;
import org.eclipse.hawkbit.repository.jpa.repository.TargetTagRepository;
import org.eclipse.hawkbit.repository.jpa.repository.TargetTypeRepository;
@@ -622,7 +621,7 @@ public class RepositoryApplicationConfiguration {
@Bean
@ConditionalOnMissingBean
TargetManagement targetManagement(final EntityManager entityManager, final QuotaManagement quotaManagement,
final TargetRepository targetRepository, final TargetMetadataRepository targetMetadataRepository,
final TargetRepository targetRepository,
final RolloutGroupRepository rolloutGroupRepository,
final TargetFilterQueryRepository targetFilterQueryRepository,
final TargetTypeRepository targetTypeRepository, final TargetTagRepository targetTagRepository,
@@ -630,7 +629,7 @@ public class RepositoryApplicationConfiguration {
final VirtualPropertyReplacer virtualPropertyReplacer,
final JpaProperties properties, final DistributionSetManagement distributionSetManagement) {
return new JpaTargetManagement(entityManager, distributionSetManagement, quotaManagement, targetRepository,
targetTypeRepository, targetMetadataRepository, rolloutGroupRepository, targetFilterQueryRepository,
targetTypeRepository, rolloutGroupRepository, targetFilterQueryRepository,
targetTagRepository, eventPublisherHolder, tenantAware, virtualPropertyReplacer,
properties.getDatabase());
}

View File

@@ -37,12 +37,7 @@ public class JpaTargetCreate extends AbstractTargetUpdateCreate<TargetCreate> im
@Override
public JpaTarget build() {
final JpaTarget target;
if (ObjectUtils.isEmpty(securityToken)) {
target = new JpaTarget(controllerId);
} else {
target = new JpaTarget(controllerId, securityToken);
}
final JpaTarget target = new JpaTarget(controllerId, securityToken);
if (!ObjectUtils.isEmpty(name)) {
target.setName(name);

View File

@@ -31,6 +31,7 @@ import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.MapJoin;
import jakarta.persistence.criteria.Root;
import jakarta.persistence.metamodel.MapAttribute;
import jakarta.validation.constraints.NotEmpty;
import org.apache.commons.collections4.ListUtils;
@@ -39,12 +40,10 @@ import org.eclipse.hawkbit.repository.FilterParams;
import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.TargetFields;
import org.eclipse.hawkbit.repository.TargetManagement;
import org.eclipse.hawkbit.repository.TargetMetadataFields;
import org.eclipse.hawkbit.repository.TimestampCalculator;
import org.eclipse.hawkbit.repository.builder.TargetCreate;
import org.eclipse.hawkbit.repository.builder.TargetUpdate;
import org.eclipse.hawkbit.repository.event.remote.TargetAttributesRequestedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.TargetUpdatedEvent;
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.jpa.JpaManagementHelper;
@@ -52,18 +51,13 @@ import org.eclipse.hawkbit.repository.jpa.acm.AccessController;
import org.eclipse.hawkbit.repository.jpa.builder.JpaTargetCreate;
import org.eclipse.hawkbit.repository.jpa.builder.JpaTargetUpdate;
import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
import org.eclipse.hawkbit.repository.jpa.model.AbstractJpaBaseEntity_;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetMetadata;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetMetadata_;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetTag;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetType;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget_;
import org.eclipse.hawkbit.repository.jpa.model.TargetMetadataCompositeKey;
import org.eclipse.hawkbit.repository.jpa.model.helper.AfterTransactionCommitExecutorHolder;
import org.eclipse.hawkbit.repository.jpa.repository.RolloutGroupRepository;
import org.eclipse.hawkbit.repository.jpa.repository.TargetFilterQueryRepository;
import org.eclipse.hawkbit.repository.jpa.repository.TargetMetadataRepository;
import org.eclipse.hawkbit.repository.jpa.repository.TargetRepository;
import org.eclipse.hawkbit.repository.jpa.repository.TargetTagRepository;
import org.eclipse.hawkbit.repository.jpa.repository.TargetTypeRepository;
@@ -73,11 +67,9 @@ import org.eclipse.hawkbit.repository.jpa.specifications.TargetSpecifications;
import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.MetaData;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
import org.eclipse.hawkbit.repository.model.TargetMetadata;
import org.eclipse.hawkbit.repository.model.TargetTag;
import org.eclipse.hawkbit.repository.model.TargetType;
import org.eclipse.hawkbit.repository.model.TargetTypeAssignmentResult;
@@ -109,7 +101,6 @@ public class JpaTargetManagement implements TargetManagement {
private final QuotaManagement quotaManagement;
private final TargetRepository targetRepository;
private final TargetTypeRepository targetTypeRepository;
private final TargetMetadataRepository targetMetadataRepository;
private final RolloutGroupRepository rolloutGroupRepository;
private final TargetFilterQueryRepository targetFilterQueryRepository;
private final TargetTagRepository targetTagRepository;
@@ -122,7 +113,6 @@ public class JpaTargetManagement implements TargetManagement {
public JpaTargetManagement(final EntityManager entityManager,
final DistributionSetManagement distributionSetManagement, final QuotaManagement quotaManagement,
final TargetRepository targetRepository, final TargetTypeRepository targetTypeRepository,
final TargetMetadataRepository targetMetadataRepository,
final RolloutGroupRepository rolloutGroupRepository,
final TargetFilterQueryRepository targetFilterQueryRepository,
final TargetTagRepository targetTagRepository, final EventPublisherHolder eventPublisherHolder,
@@ -133,7 +123,6 @@ public class JpaTargetManagement implements TargetManagement {
this.quotaManagement = quotaManagement;
this.targetRepository = targetRepository;
this.targetTypeRepository = targetTypeRepository;
this.targetMetadataRepository = targetMetadataRepository;
this.rolloutGroupRepository = rolloutGroupRepository;
this.targetFilterQueryRepository = targetFilterQueryRepository;
this.targetTagRepository = targetTagRepository;
@@ -597,7 +586,7 @@ public class JpaTargetManagement implements TargetManagement {
final JpaTarget target = getByControllerIdAndThrowIfNotFound(controllerId);
targetRepository.getAccessController().ifPresent(acm ->
acm.assertOperationAllowed(AccessController.Operation.UPDATE, target));
acm.assertOperationAllowed(AccessController.Operation.UPDATE, target));
final JpaTargetType targetType = getTargetTypeByIdAndThrowIfNotFound(targetTypeId);
target.setTargetType(targetType);
@@ -635,57 +624,6 @@ public class JpaTargetManagement implements TargetManagement {
return Collections.unmodifiableList(targetRepository.findAll(TargetSpecifications.hasIdIn(ids)));
}
@Override
public Map<String, String> getControllerAttributes(final String controllerId) {
getByControllerIdAndThrowIfNotFound(controllerId);
final CriteriaBuilder cb = entityManager.getCriteriaBuilder();
final CriteriaQuery<Object[]> query = cb.createQuery(Object[].class);
final Root<JpaTarget> targetRoot = query.from(JpaTarget.class);
query.where(cb.equal(targetRoot.get(JpaTarget_.controllerId), controllerId));
final MapJoin<JpaTarget, String, String> attributes = targetRoot.join(JpaTarget_.controllerAttributes);
query.multiselect(attributes.key(), attributes.value());
query.orderBy(cb.asc(attributes.key()));
final List<Object[]> attr = entityManager.createQuery(query).getResultList();
return attr.stream().collect(Collectors.toMap(entry -> (String) entry[0], entry -> (String) entry[1],
(v1, v2) -> v1, LinkedHashMap::new));
}
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public void requestControllerAttributes(final String controllerId) {
final JpaTarget target = getByControllerIdAndThrowIfNotFound(controllerId);
targetRepository.getAccessController()
.ifPresent(acm -> acm.assertOperationAllowed(AccessController.Operation.UPDATE, target));
target.setRequestControllerAttributes(true);
AfterTransactionCommitExecutorHolder.getInstance().getAfterCommit().afterCommit(() ->
eventPublisherHolder.getEventPublisher()
.publishEvent(new TargetAttributesRequestedEvent(
tenantAware.getCurrentTenant(), target.getId(), target.getControllerId(),
target.getAddress() != null ? target.getAddress().toString() : null,
JpaTarget.class, eventPublisherHolder.getApplicationId())));
}
@Override
public boolean isControllerAttributesRequested(final String controllerId) {
final JpaTarget target = getByControllerIdAndThrowIfNotFound(controllerId);
return target.isRequestControllerAttributes();
}
@Override
public Page<Target> findByControllerAttributesRequested(final Pageable pageReq) {
return JpaManagementHelper.findAllWithCountBySpec(targetRepository, List.of(TargetSpecifications.hasRequestControllerAttributesTrue()),
pageReq
);
}
@Override
public boolean existsByControllerId(final String controllerId) {
return targetRepository.exists(TargetSpecifications.hasControllerId(controllerId));
@@ -710,115 +648,120 @@ public class JpaTargetManagement implements TargetManagement {
}
@Override
public Set<TargetTag> getTagsByControllerId(@NotEmpty String controllerId) {
public Set<TargetTag> getTags(@NotEmpty String controllerId) {
// the method has PreAuthorized by itself
return ((JpaTarget)getWithTags(controllerId)).getTags();
return ((JpaTarget) getWithTags(controllerId)).getTags();
}
@Override
public Map<String, String> getControllerAttributes(final String controllerId) {
return getMap(controllerId, JpaTarget_.controllerAttributes);
}
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public List<TargetMetadata> createMetaData(final String controllerId, final Collection<MetaData> md) {
public void requestControllerAttributes(final String controllerId) {
final JpaTarget target = getByControllerIdAndThrowIfNotFound(controllerId);
md.forEach(meta -> checkAndThrowIfTargetMetadataAlreadyExists(
new TargetMetadataCompositeKey(target.getId(), meta.getKey())));
assertMetaDataQuota(target.getId(), md.size());
final JpaTarget updatedTarget = JpaManagementHelper.touch(entityManager, targetRepository, target);
final List<TargetMetadata> createdMetadata = md.stream()
.map(meta -> targetMetadataRepository.save(new JpaTargetMetadata(meta.getKey(), meta.getValue(), updatedTarget)))
.collect(Collectors.toUnmodifiableList());
// TargetUpdatedEvent is not sent within the touch() method due to the
// "lastModifiedAt" field being ignored in JpaTarget
eventPublisherHolder.getEventPublisher().publishEvent(new TargetUpdatedEvent(updatedTarget, eventPublisherHolder.getApplicationId()));
return createdMetadata;
}
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public void deleteMetaData(final String controllerId, final String key) {
final JpaTargetMetadata metadata = (JpaTargetMetadata) getMetaDataByControllerId(controllerId, key)
.orElseThrow(() -> new EntityNotFoundException(TargetMetadata.class, controllerId, key));
final JpaTarget target = JpaManagementHelper.touch(
entityManager, targetRepository, getByControllerIdAndThrowIfNotFound(controllerId));
targetRepository.getAccessController()
.ifPresent(acm -> acm.assertOperationAllowed(AccessController.Operation.UPDATE, target));
targetMetadataRepository.deleteById(metadata.getId());
// target update event is set to ignore "lastModifiedAt" field, so it is
// not send automatically within the touch() method
eventPublisherHolder.getEventPublisher()
.publishEvent(new TargetUpdatedEvent(target, eventPublisherHolder.getApplicationId()));
target.setRequestControllerAttributes(true);
AfterTransactionCommitExecutorHolder.getInstance().getAfterCommit().afterCommit(() ->
eventPublisherHolder.getEventPublisher()
.publishEvent(new TargetAttributesRequestedEvent(
tenantAware.getCurrentTenant(), target.getId(), target.getControllerId(),
target.getAddress() != null ? target.getAddress().toString() : null,
JpaTarget.class, eventPublisherHolder.getApplicationId())));
}
@Override
public Page<TargetMetadata> findMetaDataByControllerId(final Pageable pageable, final String controllerId) {
final Long id = getByControllerIdAndThrowIfNotFound(controllerId).getId();
return JpaManagementHelper.findAllWithCountBySpec(targetMetadataRepository, Collections.singletonList(metadataByTargetIdSpec(id)),
pageable
);
public boolean isControllerAttributesRequested(final String controllerId) {
return getByControllerIdAndThrowIfNotFound(controllerId).isRequestControllerAttributes();
}
@Override
public long countMetaDataByControllerId(@NotEmpty final String controllerId) {
final Long targetId = getByControllerIdAndThrowIfNotFound(controllerId).getId();
return JpaManagementHelper.countBySpec(targetMetadataRepository,
Collections.singletonList(metadataByTargetIdSpec(targetId)));
}
@Override
public Page<TargetMetadata> findMetaDataByControllerIdAndRsql(final Pageable pageable, final String controllerId,
final String rsqlParam) {
final Long targetId = getByControllerIdAndThrowIfNotFound(controllerId).getId();
final List<Specification<JpaTargetMetadata>> specList = Arrays.asList(RSQLUtility
.buildRsqlSpecification(rsqlParam, TargetMetadataFields.class, virtualPropertyReplacer, database),
metadataByTargetIdSpec(targetId));
return JpaManagementHelper.findAllWithCountBySpec(targetMetadataRepository, specList, pageable);
}
@Override
public Optional<TargetMetadata> getMetaDataByControllerId(final String controllerId, final String key) {
final Long targetId = getByControllerIdAndThrowIfNotFound(controllerId).getId();
return targetMetadataRepository.findById(new TargetMetadataCompositeKey(targetId, key)).map(t -> t);
public Page<Target> findByControllerAttributesRequested(final Pageable pageReq) {
return JpaManagementHelper.findAllWithCountBySpec(
targetRepository, List.of(TargetSpecifications.hasRequestControllerAttributesTrue()), pageReq);
}
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public TargetMetadata updateMetadata(final String controllerId, final MetaData md) {
public void createMetadata(final String controllerId, final Map<String, String> md) {
final JpaTarget target = getByControllerIdAndThrowIfNotFound(controllerId);
// check if exists otherwise throw entity not found exception
final JpaTargetMetadata updatedMetadata = (JpaTargetMetadata) getMetaDataByControllerId(controllerId,
md.getKey())
.orElseThrow(() -> new EntityNotFoundException(TargetMetadata.class, controllerId, md.getKey()));
updatedMetadata.setValue(md.getValue());
// touch it to update the lock revision because we are modifying the
// target indirectly
final JpaTarget target = JpaManagementHelper.touch(entityManager, targetRepository,
getByControllerIdAndThrowIfNotFound(controllerId));
// get the modifiable metadata map
final Map<String, String> metadata = target.getMetadata();
md.keySet().forEach(key -> {
if (metadata.containsKey(key)) {
throw new EntityAlreadyExistsException("Metadata entry with key '" + key + "' already exists");
}
});
metadata.putAll(md);
final JpaTargetMetadata metadata = targetMetadataRepository.save(updatedMetadata);
// target update event is set to ignore "lastModifiedAt" field, so it is
// not send automatically within the touch() method
eventPublisherHolder.getEventPublisher()
.publishEvent(new TargetUpdatedEvent(target, eventPublisherHolder.getApplicationId()));
return metadata;
assertMetadataQuota(target.getId(), metadata.size());
targetRepository.save(target);
}
@Override
public Map<String, String> getMetadata(final String controllerId) {
return getMap(controllerId, JpaTarget_.metadata);
}
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public void updateMetadata(final String controllerId, final String key, final String value) {
final JpaTarget target = getByControllerIdAndThrowIfNotFound(controllerId);
// get the modifiable metadata map
final Map<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,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public void deleteMetadata(final String controllerId, final String key) {
final JpaTarget target = getByControllerIdAndThrowIfNotFound(controllerId);
// get the modifiable metadata map
final Map<String, String> metadata = target.getMetadata();
if (metadata.remove(key) == null) {
throw new EntityNotFoundException("Target metadata", controllerId + ":" + key);
}
targetRepository.save(target);
}
private Map<String, String> getMap(final String controllerId, final MapAttribute<JpaTarget, String, String> mapAttribute) {
getByControllerIdAndThrowIfNotFound(controllerId);
final CriteriaBuilder cb = entityManager.getCriteriaBuilder();
final CriteriaQuery<Object[]> query = cb.createQuery(Object[].class);
final Root<JpaTarget> targetRoot = query.from(JpaTarget.class);
query.where(cb.equal(targetRoot.get(JpaTarget_.controllerId), controllerId));
final MapJoin<JpaTarget, String, String> mapJoin = targetRoot.join(mapAttribute);
query.multiselect(mapJoin.key(), mapJoin.value());
query.orderBy(cb.asc(mapJoin.key()));
return entityManager
.createQuery(query)
.getResultList()
.stream()
.collect(Collectors.toMap(entry -> (String) entry[0], entry -> (String) entry[1], (v1, v2) -> v1, LinkedHashMap::new));
}
private static boolean hasTagsFilterActive(final FilterParams filterParams) {
@@ -851,20 +794,9 @@ public class JpaTargetManagement implements TargetManagement {
return targetTypeRepository.findById(id).orElseThrow(() -> new EntityNotFoundException(TargetType.class, id));
}
private void checkAndThrowIfTargetMetadataAlreadyExists(final TargetMetadataCompositeKey metadataId) {
if (targetMetadataRepository.existsById(metadataId)) {
throw new EntityAlreadyExistsException(
"Metadata entry with key '" + metadataId.getKey() + "' already exists");
}
}
private void assertMetaDataQuota(final Long targetId, final int requested) {
QuotaHelper.assertAssignmentQuota(targetId, requested, quotaManagement.getMaxMetaDataEntriesPerTarget(),
TargetMetadata.class, Target.class, targetMetadataRepository::countByTargetId);
}
private Specification<JpaTargetMetadata> metadataByTargetIdSpec(final Long targetId) {
return (root, query, cb) -> cb.equal(root.get(JpaTargetMetadata_.target).get(AbstractJpaBaseEntity_.id), targetId);
private void assertMetadataQuota(final Long targetId, final int requested) {
final int limit = quotaManagement.getMaxMetaDataEntriesPerTarget();
QuotaHelper.assertAssignmentQuota(targetId, requested, limit, "Metadata", Target.class.getSimpleName(), null);
}
private List<Specification<JpaTarget>> buildSpecificationList(final FilterParams filterParams) {

View File

@@ -59,7 +59,6 @@ import org.eclipse.hawkbit.repository.jpa.utils.MapAttributeConverter;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.PollStatus;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetMetadata;
import org.eclipse.hawkbit.repository.model.TargetTag;
import org.eclipse.hawkbit.repository.model.TargetType;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
@@ -181,7 +180,7 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw
})
private Set<TargetTag> tags;
// no cascade option on an ElementCollection, the target objects are always persisted, merged, removed with their parent.
// no cascade option on an ElementCollection, the target objects are always persisted, merged, removed with their parent
@Getter
@ElementCollection
@CollectionTable(
@@ -192,8 +191,16 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw
@Column(name = "attribute_value", length = Target.CONTROLLER_ATTRIBUTE_VALUE_SIZE)
private Map<String, String> controllerAttributes;
@OneToMany(mappedBy = "target", fetch = FetchType.LAZY, cascade = { CascadeType.REMOVE }, targetEntity = JpaTargetMetadata.class)
private List<TargetMetadata> metadata;
// no cascade option on an ElementCollection, the target objects are always persisted, merged, removed with their parent
@Getter
@ElementCollection
@CollectionTable(
name = "sp_target_metadata",
joinColumns = { @JoinColumn(name = "target", nullable = false) },
foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_target_metadata_target"))
@MapKeyColumn(name = "meta_key", length = Target.CONTROLLER_METADATA_KEY_SIZE)
@Column(name = "meta_value", length = Target.CONTROLLER_METADATA_VALUE_SIZE)
private Map<String, String> metadata;
@OneToMany(mappedBy = "target", fetch = FetchType.LAZY, cascade = { CascadeType.REMOVE }, targetEntity = JpaAction.class)
private List<JpaAction> actions;
@@ -201,15 +208,11 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw
@OneToMany(mappedBy = "target", fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST, CascadeType.REMOVE })
private List<RolloutTargetGroup> rolloutTargetGroup;
public JpaTarget(final String controllerId) {
this(controllerId, SecurityTokenGeneratorHolder.getInstance().generateToken());
}
public JpaTarget(final String controllerId, final String securityToken) {
this.controllerId = controllerId;
// truncate controller ID to max name length (if needed)
// truncate controller ID to max name length (if too big)
setName(controllerId != null && controllerId.length() > NAME_MAX_SIZE ? controllerId.substring(0, NAME_MAX_SIZE) : controllerId);
this.securityToken = securityToken;
this.securityToken = ObjectUtils.isEmpty(securityToken) ? SecurityTokenGeneratorHolder.getInstance().generateToken() : securityToken;
}
@Override
@@ -287,10 +290,6 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw
return tags == null ? Collections.emptySet() : Collections.unmodifiableSet(tags);
}
public List<TargetMetadata> getMetadata() {
return metadata == null ? Collections.emptyList() : Collections.unmodifiableList(metadata);
}
public void addAction(final Action action) {
if (actions == null) {
actions = new ArrayList<>();

View File

@@ -1,100 +0,0 @@
/**
* Copyright (c) 2018 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.repository.jpa.model;
import java.io.Serial;
import jakarta.persistence.ConstraintMode;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.ForeignKey;
import jakarta.persistence.Id;
import jakarta.persistence.IdClass;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetMetadata;
/**
* Meta data for {@link Target}.
*/
@NoArgsConstructor // Default constructor for JPA
@Setter
@Getter
@IdClass(TargetMetadataCompositeKey.class)
@Entity
@Table(name = "sp_target_metadata")
public class JpaTargetMetadata extends AbstractJpaMetaData implements TargetMetadata {
@Serial
private static final long serialVersionUID = 1L;
@Id
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(
name = "target", nullable = false, updatable = false,
foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_target_metadata_target"))
private JpaTarget target;
/**
* Creates a single metadata entry with the given key and value.
*
* @param key of the meta-data entry
* @param value of the meta-data entry
*/
public JpaTargetMetadata(final String key, final String value) {
super(key, value);
}
/**
* Creates a single metadata entry with the given key and value for the
* given {@link Target}.
*
* @param key of the meta-data entry
* @param value of the meta-data entry
* @param target the meta-data entry is associated with
*/
public JpaTargetMetadata(final String key, final String value, final Target target) {
super(key, value);
this.target = (JpaTarget) target;
}
public TargetMetadataCompositeKey getId() {
return new TargetMetadataCompositeKey(target.getId(), getKey());
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + ((target == null) ? 0 : target.hashCode());
return result;
}
@Override
// exception squid:S2259 - obj is checked for null in super
@SuppressWarnings("squid:S2259")
public boolean equals(final Object obj) {
if (!super.equals(obj)) {
return false;
}
final JpaTargetMetadata other = (JpaTargetMetadata) obj;
if (target == null) {
return other.target == null;
} else {
return target.equals(other.target);
}
}
}

View File

@@ -1,39 +0,0 @@
/**
* Copyright (c) 2018 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.repository.jpa.model;
import java.io.Serial;
import java.io.Serializable;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* The Target Metadata composite key which contains the meta-data key and the ID of the Target itself.
*/
@NoArgsConstructor // Default constructor for JPA
@Data
public final class TargetMetadataCompositeKey implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private String key;
private Long target;
/**
* @param target the target Id for this meta-data
* @param key the key of the meta-data
*/
public TargetMetadataCompositeKey(final Long target, final String key) {
this.target = target;
this.key = key;
}
}

View File

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

View File

@@ -13,35 +13,31 @@ import java.util.function.ToLongFunction;
import jakarta.validation.constraints.NotNull;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException;
/**
* Helper class to check quotas.
*/
@NoArgsConstructor
@Slf4j
public final class QuotaHelper {
private static final String MAX_ASSIGNMENT_QUOTA_EXCEEDED = "Quota exceeded: Cannot assign %s entities at once. The maximum is %s.";
private QuotaHelper() {
// no need to instantiate this class
}
/**
* Asserts the specified assignment quota.
*
* @param requested The number of entities that shall be assigned to the parent
* entity.
* @param limit The maximum number of entities that may be assigned to the
* parent entity.
* @param requested The number of entities that shall be assigned to the parent entity.
* @param limit The maximum number of entities that may be assigned to the parent entity.
* @param type The type of the entities that shall be assigned.
* @param parentType The type of the parent entity.
* @throws AssignmentQuotaExceededException if the assignment operation would cause the quota to be
* exceeded
* @throws AssignmentQuotaExceededException if the assignment operation would cause the quota to be exceeded
*/
public static void assertAssignmentQuota(final long requested, final long limit, @NotNull final Class<?> type,
@NotNull final Class<?> parentType) {
public static void assertAssignmentQuota(
final long requested, final long limit,
@NotNull final Class<?> type, @NotNull final Class<?> parentType) {
assertAssignmentQuota(null, requested, limit, type.getSimpleName(), parentType.getSimpleName(), null);
}
@@ -49,19 +45,18 @@ public final class QuotaHelper {
* Asserts the specified assignment quota.
*
* @param parentId The ID of the parent entity.
* @param requested The number of entities that shall be assigned to the parent
* entity.
* @param limit The maximum number of entities that may be assigned to the
* parent entity.
* @param requested The number of entities that shall be assigned to the parent entity.
* @param limit The maximum number of entities that may be assigned to the parent entity.
* @param type The type of the entities that shall be assigned.
* @param parentType The type of the parent entity.
* @param countFct Function to count the entities that are currently assigned to
* the parent entity.
* @throws AssignmentQuotaExceededException if the assignment operation would cause the quota to be
* exceeded
* @param countFct Function to count the entities that are currently assigned to the parent entity.
* @throws AssignmentQuotaExceededException if the assignment operation would cause the quota to be exceeded
*/
public static <T> void assertAssignmentQuota(final T parentId, final long requested, final long limit,
@NotNull final Class<?> type, @NotNull final Class<?> parentType, final ToLongFunction<T> countFct) {
public static <T> void assertAssignmentQuota(
final T parentId,
final long requested, final long limit,
@NotNull final Class<?> type, @NotNull final Class<?> parentType,
final ToLongFunction<T> countFct) {
assertAssignmentQuota(parentId, requested, limit, type.getSimpleName(), parentType.getSimpleName(), countFct);
}
@@ -69,20 +64,18 @@ public final class QuotaHelper {
* Asserts the specified assignment quota.
*
* @param parentId The ID of the parent entity.
* @param requested The number of entities that shall be assigned to the parent
* entity.
* @param limit The maximum number of entities that may be assigned to the
* parent entity.
* @param requested The number of entities that shall be assigned to the parent entity.
* @param limit The maximum number of entities that may be assigned to the parent entity.
* @param type The type of the entities that shall be assigned.
* @param parentType The type of the parent entity.
* @param countFct Function to count the entities that are currently assigned to
* the parent entity.
* @throws AssignmentQuotaExceededException if the assignment operation would cause the quota to be
* exceeded
* @param countFct Function to count the entities that are currently assigned to the parent entity.
* @throws AssignmentQuotaExceededException if the assignment operation would cause the quota to be exceeded
*/
public static <T> void assertAssignmentQuota(final T parentId, final long requested, final long limit,
@NotNull final String type, @NotNull final String parentType, final ToLongFunction<T> countFct) {
public static <T> void assertAssignmentQuota(
final T parentId,
final long requested, final long limit,
@NotNull final String type, @NotNull final String parentType,
final ToLongFunction<T> countFct) {
// check if the quota is unlimited
if (limit <= 0) {
log.debug("Quota 'Max {} entities per {}' is unlimited.", type, parentType);
@@ -91,8 +84,9 @@ public final class QuotaHelper {
if (requested > limit) {
final String parentIdStr = parentId != null ? String.valueOf(parentId) : "<new>";
log.warn("Cannot assign {} {} entities to {} '{}' because of the configured quota limit {}.", requested,
type, parentType, parentIdStr, limit);
log.warn(
"Cannot assign {} {} entities to {} '{}' because of the configured quota limit {}.",
requested, type, parentType, parentIdStr, limit);
throw new AssignmentQuotaExceededException(type, parentType, parentId, requested, limit);
}
@@ -100,7 +94,8 @@ public final class QuotaHelper {
final long currentCount = countFct.applyAsLong(parentId);
if (currentCount + requested > limit) {
log.warn(
"Cannot assign {} {} entities to {} '{}' because of the configured quota limit {}. Currently, there are {} {} entities assigned.",
"Cannot assign {} {} entities to {} '{}' because of the configured quota limit {}. " +
"Currently, there are {} {} entities assigned.",
requested, type, parentType, parentId, limit, currentCount, type);
throw new AssignmentQuotaExceededException(type, parentType, parentId, requested, limit);
}
@@ -108,8 +103,7 @@ public final class QuotaHelper {
}
/**
* Assert that the number of assignments in a request does not exceed the
* limit.
* Assert that the number of assignments in a request does not exceed the limit.
*
* @param requested the number of assignments that are to be made
* @param limit the maximum number of assignments per request
@@ -121,4 +115,4 @@ public final class QuotaHelper {
throw new AssignmentQuotaExceededException(message);
}
}
}
}