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:
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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<>();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user