[#2845] Bump Spring boot to 4.x (#2941)

Notes:
1. (!) Eclipselink shall be migrated to 5.0 (in 4.0.8 there are incompatible classes, e.g EJBQueryImpl doesn't implement some newer methods). In the moment is with beta (5.0.0-B12) - JUST for testing!
2. (!) Ethlo plugin doesn't work with Eclipselink 5.0, it builds with Eclipselink 4.0.8 (could be a problem)
3. Dependencies - new starters, test starters changes, some dependencies refactoring
4. Auto-configs split - package changes, some properties classes changes
5. Spring nullable org.springframework.lang.Nullable/NonNull are depecated and replaced with jspcify -> org.jspecify.annotations.Nullable/NonNull (NullMarked)
6. Lombok config - adding lombok.addNullAnnotations=jspecify - to do not mess annotations
7. Distributed lock table changes - SP_LOCK table db migration
8. Spring Retry replaced with Spring Core Retry - does repace retry in hawkbit
9. Specifications -> added Update/Delete(/Predicate) Specifications and JpaSpecificationExecutor changed
10. HawkbitBaseRepositoryFactoryBean modified to register properly
11. Jackson - 2 -> 3, package migrations, finals are not deserialized by default(enable finals deserialization, consider make non-final), too ‘smart’ tries to set complex objects instead of using non args constructor (-> @JsonIgnore), some other default configs made

Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
Avgustin Marinov
2026-04-14 11:31:41 +03:00
committed by GitHub
parent 23cd368e00
commit 1be473b22c
172 changed files with 1254 additions and 1045 deletions

View File

@@ -15,18 +15,27 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.repository.jpa.repository.HawkbitBaseRepository;
import org.eclipse.hawkbit.repository.jpa.utils.ExceptionMapper;
import org.jspecify.annotations.Nullable;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.query.EscapeCharacter;
import org.springframework.data.jpa.repository.query.JpaQueryMethodFactory;
import org.springframework.data.jpa.repository.query.QueryEnhancerSelector;
import org.springframework.data.jpa.repository.support.JpaRepositoryFactory;
import org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean;
import org.springframework.data.jpa.repository.support.JpaRepositoryFragmentsContributor;
import org.springframework.data.jpa.repository.support.JpaRepositoryImplementation;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.querydsl.EntityPathResolver;
@@ -35,16 +44,17 @@ import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
import org.springframework.data.repository.core.support.TransactionalRepositoryFactoryBeanSupport;
import org.springframework.lang.Nullable;
/**
* A {@link TransactionalRepositoryFactoryBeanSupport} extension that uses {@link HawkbitBaseRepository} as base repository and
* proxied repositories in order to convert exceptions to management exceptions.
*/
@Slf4j
@SuppressWarnings("java:S119") // java:S119 - ID is inherited from TransactionalRepositoryFactoryBeanSupport
public class HawkbitBaseRepositoryFactoryBean<T extends Repository<S, ID>, S, ID> extends TransactionalRepositoryFactoryBeanSupport<T, S, ID> {
private EntityPathResolver entityPathResolver;
private JpaRepositoryFragmentsContributor repositoryFragmentsContributor;
private EscapeCharacter escapeCharacter = EscapeCharacter.DEFAULT;
private JpaQueryMethodFactory queryMethodFactory;
@@ -65,6 +75,11 @@ public class HawkbitBaseRepositoryFactoryBean<T extends Repository<S, ID>, S, ID
this.entityPathResolver = resolver.getIfAvailable(() -> SimpleEntityPathResolver.INSTANCE);
}
@Autowired
public void setRepositoryFragmentsContributor(final ObjectProvider<JpaRepositoryFragmentsContributor> repositoryFragmentsContributor) {
this.repositoryFragmentsContributor = repositoryFragmentsContributor.getIfAvailable(() -> JpaRepositoryFragmentsContributor.DEFAULT);
}
@Autowired
public void setEscapeCharacter(final char escapeCharacter) {
this.escapeCharacter = EscapeCharacter.of(escapeCharacter);
@@ -77,6 +92,45 @@ public class HawkbitBaseRepositoryFactoryBean<T extends Repository<S, ID>, S, ID
}
}
@Autowired
public void setQueryMethodFactory(final ObjectProvider<JpaQueryMethodFactory> resolver) {
final JpaQueryMethodFactory factory = resolver.getIfAvailable();
if (factory != null) {
this.queryMethodFactory = factory;
}
}
private @Nullable BeanFactory beanFactory;
@Override
public void setBeanFactory(final BeanFactory beanFactory) {
this.beanFactory = beanFactory;
super.setBeanFactory(beanFactory);
}
private @Nullable Function<@Nullable BeanFactory, QueryEnhancerSelector> queryEnhancerSelectorSource;
public void setQueryEnhancerSelectorSource(QueryEnhancerSelector queryEnhancerSelectorSource) {
this.queryEnhancerSelectorSource = bf -> queryEnhancerSelectorSource;
}
public void setQueryEnhancerSelector(final Class<? extends QueryEnhancerSelector> queryEnhancerSelectorType) {
queryEnhancerSelectorSource = bf -> {
if (bf != null) {
final QueryEnhancerSelector selector = bf.getBeanProvider(queryEnhancerSelectorType).getIfAvailable();
if (selector != null) {
return selector;
}
if (bf instanceof AutowireCapableBeanFactory acbf) {
return acbf.createBean(queryEnhancerSelectorType);
}
}
return BeanUtils.instantiateClass(queryEnhancerSelectorType);
};
}
@PersistenceContext
public void setEntityManager(final EntityManager entityManager) {
this.entityManager = entityManager;
@@ -85,6 +139,7 @@ public class HawkbitBaseRepositoryFactoryBean<T extends Repository<S, ID>, S, ID
@Override
public void afterPropertiesSet() {
Objects.requireNonNull(entityManager, "EntityManager must not be null");
setRepositoryBaseClass(HawkbitBaseRepository.class); // overrides set by properties base class
super.afterPropertiesSet();
}
@@ -107,6 +162,19 @@ public class HawkbitBaseRepositoryFactoryBean<T extends Repository<S, ID>, S, ID
interfaces(jpaRepositoryImplementation.getClass(), new HashSet<>()).toArray(new Class<?>[0]),
(proxy, method, args) -> {
try {
if (args != null) {
final Class<?>[] parameterTypes = method.getParameterTypes();
for (int i = 0; i < parameterTypes.length; i++) {
if (parameterTypes[i] == Specification.class && args[i] == null) {
// replaces null specifications with unrestricted specifications (null not accepted since Spring Boot 4.0
if (log.isTraceEnabled()) {
log.trace("Replace null Specification argument with unrestricted Specification",
new Exception("Method " + method + ", arg[" + i + "]"));
}
args[i] = Specification.unrestricted();
}
}
}
return method.invoke(jpaRepositoryImplementation, args);
} catch (final InvocationTargetException e) {
final Throwable cause = e.getCause() == null ? e : e.getCause();
@@ -120,10 +188,13 @@ public class HawkbitBaseRepositoryFactoryBean<T extends Repository<S, ID>, S, ID
jpaRepositoryFactory.setEntityPathResolver(entityPathResolver);
jpaRepositoryFactory.setEscapeCharacter(escapeCharacter);
jpaRepositoryFactory.setFragmentsContributor(repositoryFragmentsContributor);
if (queryMethodFactory != null) {
jpaRepositoryFactory.setQueryMethodFactory(queryMethodFactory);
}
if (queryEnhancerSelectorSource != null) {
jpaRepositoryFactory.setQueryEnhancerSelector(queryEnhancerSelectorSource.apply(beanFactory));
}
jpaRepositoryFactory.setRepositoryBaseClass(HawkbitBaseRepository.class);
return jpaRepositoryFactory;
}

View File

@@ -95,8 +95,8 @@ import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration;
import org.springframework.boot.persistence.autoconfigure.EntityScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@@ -111,8 +111,8 @@ import org.springframework.integration.jdbc.lock.JdbcLockRegistry;
import org.springframework.integration.jdbc.lock.LockRepository;
import org.springframework.integration.support.locks.DefaultLockRegistry;
import org.springframework.integration.support.locks.LockRegistry;
import org.springframework.lang.NonNull;
import org.springframework.retry.annotation.EnableRetry;
import org.jspecify.annotations.NonNull;
import org.springframework.resilience.annotation.EnableResilientMethods;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.security.authorization.AuthorizationDeniedException;
import org.springframework.security.authorization.AuthorizationResult;
@@ -130,7 +130,7 @@ import org.springframework.validation.beanvalidation.MethodValidationPostProcess
@EnableAspectJAutoProxy
@Configuration
@EnableScheduling
@EnableRetry
@EnableResilientMethods
@EntityScan("org.eclipse.hawkbit.repository.jpa.model")
@ComponentScan({ "org.eclipse.hawkbit.repository.jpa.management", "org.eclipse.hawkbit.repository.jpa.scheduler" })
@PropertySource("classpath:/hawkbit-jpa-defaults.properties")

View File

@@ -11,9 +11,14 @@ package org.eclipse.hawkbit.repository.jpa.acm;
import java.util.Optional;
import jakarta.persistence.criteria.Root;
import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException;
import org.springframework.data.jpa.domain.DeleteSpecification;
import org.springframework.data.jpa.domain.PredicateSpecification;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.lang.Nullable;
import org.springframework.data.jpa.domain.UpdateSpecification;
import org.jspecify.annotations.Nullable;
/**
* Interface of an extended access control by providing means or fine-grained access control.
@@ -49,6 +54,20 @@ public interface AccessController<T> {
.orElse(specification);
}
default UpdateSpecification<T> appendAccessRules(final Operation operation, @Nullable final UpdateSpecification<T> specification) {
return getAccessRules(operation)
.map(this::predicateSpec)
.map(accessRules -> specification == null ? UpdateSpecification.where(accessRules) : specification.and(accessRules))
.orElse(specification);
}
default DeleteSpecification<T> appendAccessRules(final Operation operation, @Nullable final DeleteSpecification<T> specification) {
return getAccessRules(operation)
.map(this::predicateSpec)
.map(accessRules -> specification == null ? DeleteSpecification.where(accessRules) : specification.and(accessRules))
.orElse(specification);
}
/**
* Verify if the given {@link Operation} is permitted for the provided entity.
*
@@ -68,6 +87,11 @@ public interface AccessController<T> {
}
}
@Deprecated
default PredicateSpecification<T> predicateSpec(final Specification<T> spec) {
return (from, cb) -> spec.toPredicate((Root<T>) from, cb.createQuery(), cb);
}
/**
* Enum to define the perform operation to verify
*/

View File

@@ -38,6 +38,8 @@ import org.eclipse.hawkbit.repository.qfields.SoftwareModuleFields;
import org.eclipse.hawkbit.repository.qfields.SoftwareModuleTypeFields;
import org.eclipse.hawkbit.repository.qfields.TargetFields;
import org.eclipse.hawkbit.repository.qfields.TargetTypeFields;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
@@ -145,13 +147,16 @@ public class AccessControllerConfiguration {
final DefaultMethodSecurityExpressionHandler methodSecurityExpressionHandler = new DefaultMethodSecurityExpressionHandler() {
@Override
public EvaluationContext createEvaluationContext(final Supplier<Authentication> authentication, final MethodInvocation mi) {
@NullMarked
public EvaluationContext createEvaluationContext(
final Supplier<? extends @Nullable Authentication> authentication, final MethodInvocation mi) {
return super.createEvaluationContext(SingletonSupplier.of(() -> new RawAuthoritiesAuthentication(authentication.get())), mi);
}
@Override
@NullMarked
protected MethodSecurityExpressionOperations createSecurityExpressionRoot(
final Authentication authentication, final MethodInvocation mi) {
@Nullable final Authentication authentication, final MethodInvocation mi) {
return super.createSecurityExpressionRoot(new RawAuthoritiesAuthentication(authentication), mi);
}
};

View File

@@ -11,8 +11,7 @@ package org.eclipse.hawkbit.repository.jpa.configuration;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
import org.springframework.resilience.annotation.Retryable;
/**
* A constant class which holds only static constants used within the SP server.
@@ -28,12 +27,13 @@ public final class Constants {
* number.
*/
public static final int MAX_ENTRIES_IN_STATEMENT = 999;
/**
* @see Retryable#maxAttempts()
* See {@link Retryable#maxRetries()}
*/
public static final int TX_RT_MAX = 10;
public static final String RETRY_MAX = "${org.eclipse.hawkbit.repository.jpa.retry-max:10}";
/**
* @see Backoff#delay()
* See {@link Retryable#delayString()}
*/
public static final long TX_RT_DELAY = 100;
public static final String RETRY_DELAY = "${org.eclipse.hawkbit.repository.jpa.retry-delay:100}";
}

View File

@@ -9,9 +9,6 @@
*/
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.Field;
import java.lang.reflect.InvocationTargetException;
@@ -47,6 +44,7 @@ import org.eclipse.hawkbit.repository.jpa.Jpa;
import org.eclipse.hawkbit.repository.jpa.JpaManagementHelper;
import org.eclipse.hawkbit.repository.jpa.JpaRepositoryConfiguration;
import org.eclipse.hawkbit.repository.jpa.acm.AccessController;
import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
import org.eclipse.hawkbit.repository.jpa.model.AbstractJpaBaseEntity;
import org.eclipse.hawkbit.repository.jpa.model.AbstractJpaBaseEntity_;
import org.eclipse.hawkbit.repository.jpa.repository.BaseEntityRepository;
@@ -56,8 +54,7 @@ import org.springframework.dao.ConcurrencyFailureException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
import org.springframework.resilience.annotation.Retryable;
import org.springframework.security.authorization.method.HandleAuthorizationDenied;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;
@@ -131,14 +128,14 @@ abstract class AbstractJpaRepositoryManagement<T extends AbstractJpaBaseEntity,
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY))
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
public T create(final C create) {
return jpaRepository.save(AccessController.Operation.CREATE, jpaEntity(create));
}
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY))
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
public List<T> create(final Collection<C> create) {
return jpaRepository.saveAll(AccessController.Operation.CREATE, create.stream().map(this::jpaEntity).toList());
}
@@ -189,7 +186,7 @@ abstract class AbstractJpaRepositoryManagement<T extends AbstractJpaBaseEntity,
@Override
public long count() {
return jpaRepository.count(isNotDeleted().orElse(null));
return jpaRepository.count(isNotDeleted().orElseGet(Specification::unrestricted));
}
@Override
@@ -210,7 +207,7 @@ abstract class AbstractJpaRepositoryManagement<T extends AbstractJpaBaseEntity,
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY))
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
@SuppressWarnings("java:S1066") // javaS1066 - better readable that way
public T update(final U update) {
final T entity = getValid(update.getId());
@@ -225,7 +222,7 @@ abstract class AbstractJpaRepositoryManagement<T extends AbstractJpaBaseEntity,
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY))
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
@SuppressWarnings("java:S1066") // javaS1066 - better readable that way
public Map<Long, T> update(final Collection<U> update) {
final Map<Long, T> toUpdate = findAllById(update.stream().map(Identifiable::getId).toList(), true)
@@ -261,14 +258,14 @@ abstract class AbstractJpaRepositoryManagement<T extends AbstractJpaBaseEntity,
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY))
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
public void delete(final long id) {
delete0(List.of(id));
}
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY))
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
public void delete(final Collection<Long> ids) {
delete0(ids);
}

View File

@@ -9,9 +9,6 @@
*/
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;
@@ -28,13 +25,13 @@ import org.eclipse.hawkbit.ql.QueryField;
import org.eclipse.hawkbit.repository.Identifiable;
import org.eclipse.hawkbit.repository.MetadataSupport;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
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.resilience.annotation.Retryable;
import org.springframework.transaction.annotation.Transactional;
@SuppressWarnings("java:S119") // java:S119 - better self explainable
@@ -76,7 +73,7 @@ abstract class AbstractJpaRepositoryWithMetadataManagement<T extends AbstractJpa
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY))
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_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();
@@ -91,7 +88,7 @@ abstract class AbstractJpaRepositoryWithMetadataManagement<T extends AbstractJpa
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY))
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_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();
@@ -126,7 +123,7 @@ abstract class AbstractJpaRepositoryWithMetadataManagement<T extends AbstractJpa
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY))
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
public void deleteMetadata(final Long id, final String key) {
final T jpaEntity = getValid(id);
final Map<String, MVI> metadataValueMap = jpaEntity.getMetadata();

View File

@@ -52,8 +52,7 @@ import org.eclipse.hawkbit.repository.model.ArtifactUpload;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
import org.springframework.dao.ConcurrencyFailureException;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
import org.springframework.resilience.annotation.Retryable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.Transactional;
@@ -93,8 +92,7 @@ public class JpaArtifactManagement implements ArtifactManagement {
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
public Artifact create(final ArtifactUpload artifactUpload) {
if (artifactStorage == null) {
throw new UnsupportedOperationException();
@@ -164,8 +162,7 @@ public class JpaArtifactManagement implements ArtifactManagement {
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
public void delete(final long id) {
if (artifactStorage == null) {
throw new UnsupportedOperationException();

View File

@@ -42,8 +42,7 @@ import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.AutoConfirmationStatus;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
import org.springframework.dao.ConcurrencyFailureException;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
import org.springframework.resilience.annotation.Retryable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
@@ -103,8 +102,7 @@ public class JpaConfirmationManagement extends JpaActionManagement implements Co
@Override
@Transactional(isolation = Isolation.READ_COMMITTED)
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
public Action confirmAction(final long actionId, final Integer code, final Collection<String> deviceMessages) {
log.trace("Action with id {} confirm request is triggered.", actionId);
final Action action = actionRepository.getById(actionId);
@@ -119,8 +117,7 @@ public class JpaConfirmationManagement extends JpaActionManagement implements Co
@Override
@Transactional(isolation = Isolation.READ_COMMITTED)
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
public Action denyAction(final long actionId, final Integer code, final Collection<String> deviceMessages) {
log.trace("Action with id {} deny request is triggered.", actionId);
final Action action = actionRepository.getById(actionId);

View File

@@ -118,8 +118,7 @@ import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
import org.springframework.resilience.annotation.Retryable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.Isolation;
@@ -229,8 +228,7 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont
@Override
@Transactional(isolation = Isolation.READ_COMMITTED)
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
public Action addCancelActionStatus(final ActionStatusCreate create) {
final JpaAction action = actionRepository.getById(create.getActionId());
if (!action.isCancelingOrCanceled()) {
@@ -275,8 +273,7 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
public ActionStatus addInformationalActionStatus(final ActionStatusCreate create) {
final JpaAction action = actionRepository.getById(create.getActionId());
assertActionStatusQuota(create, action);
@@ -290,8 +287,7 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont
@Override
@Transactional(isolation = Isolation.READ_COMMITTED)
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
public Action addUpdateActionStatus(final ActionStatusCreate statusCreate) {
return addActionStatus(statusCreate);
}
@@ -334,14 +330,14 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont
@Override
@Transactional(isolation = Isolation.READ_COMMITTED)
@Retryable(retryFor = ConcurrencyFailureException.class, noRetryFor = EntityAlreadyExistsException.class, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
@Retryable(includes = ConcurrencyFailureException.class, excludes = EntityAlreadyExistsException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
public Target findOrRegisterTargetIfItDoesNotExist(final String controllerId, final URI address) {
return findOrRegisterTargetIfItDoesNotExist0(controllerId, address, null, null);
}
@Override
@Transactional(isolation = Isolation.READ_COMMITTED)
@Retryable(retryFor = ConcurrencyFailureException.class, noRetryFor = EntityAlreadyExistsException.class, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
@Retryable(includes = ConcurrencyFailureException.class, excludes = EntityAlreadyExistsException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
public Target findOrRegisterTargetIfItDoesNotExist(final String controllerId, final URI address, final String name, final String type) {
return findOrRegisterTargetIfItDoesNotExist0(controllerId, address, name, type);
}
@@ -425,16 +421,14 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
public void registerRetrieved(final long actionId, final String message) {
handleRegisterRetrieved(actionId, message);
}
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
public Target updateControllerAttributes(final String controllerId, final Map<String, String> data, final UpdateMode mode) {
// Constraints on attribute keys & values are not validated by EclipseLink. Hence, they are validated here.
if (data.entrySet().stream().anyMatch(e -> !isAttributeEntryValid(e))) {
@@ -518,7 +512,7 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont
jpaAction.setStatus(Status.CANCELING);
// document that the status has been retrieved
actionStatusRepository.save(
new JpaActionStatus(jpaAction, Status.CANCELING, java.lang.System.currentTimeMillis(), "manual cancelation requested"));
new JpaActionStatus(jpaAction, Status.CANCELING, System.currentTimeMillis(), "manual cancelation requested"));
final Action saveAction = actionRepository.save(jpaAction);
cancelAssignDistributionSetEvent(jpaAction);
@@ -651,7 +645,7 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont
jpaTarget.setName((StringUtils.hasText(name) ? name : controllerId));
jpaTarget.setSecurityToken(SecurityTokenGenerator.generateToken());
jpaTarget.setUpdateStatus(TargetUpdateStatus.REGISTERED);
jpaTarget.setLastTargetQuery(java.lang.System.currentTimeMillis());
jpaTarget.setLastTargetQuery(System.currentTimeMillis());
jpaTarget.setAddress(Optional.ofNullable(address).map(URI::toString).orElse(null));
if (StringUtils.hasText(type)) {
@@ -712,7 +706,7 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont
Constants.MAX_ENTRIES_IN_STATEMENT);
pollChunks.forEach(chunk -> {
final long lastTargetQuery = java.lang.System.currentTimeMillis();
final long lastTargetQuery = System.currentTimeMillis();
setLastTargetQuery(tenant, lastTargetQuery, chunk);
afterCommit(() -> EventPublisherHolder.getInstance().getEventPublisher()
.publishEvent(new TargetPollEvent(chunk, lastTargetQuery, tenant)));
@@ -774,7 +768,7 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont
if (isStatusUnknown(toUpdate.getUpdateStatus())) {
toUpdate.setUpdateStatus(TargetUpdateStatus.REGISTERED);
}
toUpdate.setLastTargetQuery(java.lang.System.currentTimeMillis());
toUpdate.setLastTargetQuery(System.currentTimeMillis());
afterCommit(() -> EventPublisherHolder.getInstance().getEventPublisher().publishEvent(new TargetPollEvent(toUpdate)));
return targetRepository.save(toUpdate);
}
@@ -901,7 +895,7 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont
// case controller retrieves a action multiple times.
if (resultList.isEmpty() || (Status.RETRIEVED != resultList.get(0)[1])) {
// document that the status has been retrieved
actionStatusRepository.save(new JpaActionStatus(action, Status.RETRIEVED, java.lang.System.currentTimeMillis(), message));
actionStatusRepository.save(new JpaActionStatus(action, Status.RETRIEVED, System.currentTimeMillis(), message));
// don't change the action status itself in case the action is in
// canceling state otherwise

View File

@@ -12,6 +12,7 @@ package org.eclipse.hawkbit.repository.jpa.management;
import static org.eclipse.hawkbit.context.AccessContext.asSystem;
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.REPOSITORY_ACTIONS_AUTOCLOSE_ENABLED;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -39,6 +40,7 @@ import jakarta.persistence.criteria.Root;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.ListUtils;
import org.eclipse.hawkbit.context.AccessContext;
import org.eclipse.hawkbit.exception.GenericSpServerException;
import org.eclipse.hawkbit.ql.jpa.QLSupport;
import org.eclipse.hawkbit.repository.DeploymentManagement;
import org.eclipse.hawkbit.repository.QuotaManagement;
@@ -87,21 +89,23 @@ import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
import org.eclipse.hawkbit.repository.model.TargetWithActionType;
import org.eclipse.hawkbit.repository.qfields.ActionFields;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.boot.jpa.autoconfigure.JpaProperties;
import org.springframework.core.retry.RetryException;
import org.springframework.core.retry.RetryPolicy;
import org.springframework.core.retry.RetryTemplate;
import org.springframework.dao.ConcurrencyFailureException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.jpa.domain.DeleteSpecification;
import org.springframework.data.jpa.domain.PredicateSpecification;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
import org.springframework.retry.backoff.FixedBackOffPolicy;
import org.springframework.retry.policy.SimpleRetryPolicy;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.resilience.annotation.Retryable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.Isolation;
@@ -144,7 +148,8 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
final ActionRepository actionRepository, final ActionStatusRepository actionStatusRepository,
final QuotaManagement quotaManagement, final RepositoryProperties repositoryProperties,
final JpaDistributionSetManagement distributionSetManagement, final TargetRepository targetRepository,
final EntityManager entityManager, final PlatformTransactionManager txManager, final JpaProperties jpaProperties) {
final EntityManager entityManager, final PlatformTransactionManager txManager, final JpaProperties jpaProperties,
@Value(Constants.RETRY_MAX) final long maxRetries, @Value(Constants.RETRY_DELAY) final long delay) {
super(actionRepository, actionStatusRepository, quotaManagement, repositoryProperties);
this.distributionSetManagement = distributionSetManagement;
this.targetRepository = targetRepository;
@@ -152,7 +157,11 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
this.txManager = txManager;
this.database = jpaProperties.getDatabase();
retryTemplate = createRetryTemplate();
retryTemplate = new RetryTemplate(RetryPolicy.builder()
.includes(ConcurrencyFailureException.class)
.maxRetries(maxRetries)
.maxDelay(Duration.ofMillis(delay))
.build());
final Consumer<MaxAssignmentsExceededInfo> maxAssignmentsExceededHandler = maxAssignmentsExceededInfo ->
handleMaxAssignmentsExceeded(
maxAssignmentsExceededInfo.targetId,
@@ -187,8 +196,7 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
@Override
@Transactional(isolation = Isolation.READ_COMMITTED)
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
public Action cancelAction(final long actionId) {
return cancelAction0(actionId);
}
@@ -209,7 +217,7 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
action.setStatus(Status.CANCELING);
// document that the status has been retrieved
actionStatusRepository.save(new JpaActionStatus(action, Status.CANCELING, java.lang.System.currentTimeMillis(),
actionStatusRepository.save(new JpaActionStatus(action, Status.CANCELING, System.currentTimeMillis(),
RepositoryConstants.SERVER_MESSAGE_PREFIX + "manual cancelation requested"));
final Action saveAction = actionRepository.save(action);
@@ -329,8 +337,7 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
@Override
@Transactional(isolation = Isolation.READ_COMMITTED)
@Retryable(retryFor = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
public Action forceQuitAction(final long actionId) {
return forceQuitAction0(actionId);
}
@@ -351,7 +358,7 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
log.warn("action ({}) was still active and has been force quite.", action);
// document that the status has been retrieved
actionStatusRepository.save(new JpaActionStatus(action, Status.CANCELED, java.lang.System.currentTimeMillis(),
actionStatusRepository.save(new JpaActionStatus(action, Status.CANCELED, System.currentTimeMillis(),
RepositoryConstants.SERVER_MESSAGE_PREFIX + "A force quit has been performed."));
DeploymentHelper.successCancellation(action, actionRepository, targetRepository);
@@ -361,8 +368,7 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
@Override
@Transactional
@Retryable(retryFor = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
public Action forceTargetAction(final long actionId) {
final JpaAction action = actionRepository.findById(actionId)
.map(this::assertTargetUpdateAllowed)
@@ -386,7 +392,11 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
@Transactional
public void deleteActionsByRsql(final String rsql) {
log.info("Deleting actions matching rsql {}", rsql);
actionRepository.delete(QLSupport.getInstance().buildSpec(rsql, ActionFields.class));
actionRepository.delete(DeleteSpecification.where(predicateSpec(QLSupport.getInstance().buildSpec(rsql, ActionFields.class))));
}
@Deprecated
static <T> PredicateSpecification<T> predicateSpec(final Specification<T> spec) {
return (from, cb) -> spec.toPredicate((Root<T>) from, cb.createQuery(), cb);
}
@Override
@@ -419,8 +429,7 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
@Override
@Transactional(isolation = Isolation.READ_COMMITTED)
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
public void cancelInactiveScheduledActionsForTargets(final List<Long> targetIds) {
targetRepository.getAccessController().ifPresent(v -> {
if (targetRepository.count(AccessController.Operation.UPDATE, TargetSpecifications.hasIdIn(targetIds)) != targetIds.size()) {
@@ -621,20 +630,6 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
return QUERY_DELETE_ACTIONS_BY_STATE_AND_LAST_MODIFIED.getOrDefault(database, QUERY_DELETE_ACTIONS_BY_STATE_AND_LAST_MODIFIED_DEFAULT);
}
private static RetryTemplate createRetryTemplate() {
final RetryTemplate template = new RetryTemplate();
final FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy();
backOffPolicy.setBackOffPeriod(Constants.TX_RT_DELAY);
template.setBackOffPolicy(backOffPolicy);
final SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(
Constants.TX_RT_MAX, Collections.singletonMap(ConcurrencyFailureException.class, true));
template.setRetryPolicy(retryPolicy);
return template;
}
private List<DistributionSetAssignmentResult> assignDistributionSets(
final List<DeploymentRequest> deploymentRequests, final String actionMessage, final AbstractDsAssignmentStrategy strategy) {
final List<DeploymentRequest> validatedRequests = validateAndFilterRequestForAssignments(deploymentRequests);
@@ -734,8 +729,11 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
private DistributionSetAssignmentResult assignDistributionSetToTargetsWithRetry(
final Long dsId, final Collection<TargetWithActionType> targetsWithActionType, final String actionMessage,
final AbstractDsAssignmentStrategy assignmentStrategy) {
return retryTemplate.execute(retryContext ->
assignDistributionSetToTargets(dsId, targetsWithActionType, actionMessage, assignmentStrategy));
try {
return retryTemplate.execute(() -> assignDistributionSetToTargets(dsId, targetsWithActionType, actionMessage, assignmentStrategy));
} catch (final RetryException e) {
throw new GenericSpServerException(e);
}
}
/**

View File

@@ -9,8 +9,6 @@
*/
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 static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.IMPLICIT_LOCK_ENABLED;
import java.util.ArrayList;
@@ -41,6 +39,7 @@ import org.eclipse.hawkbit.repository.exception.InvalidDistributionSetException;
import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException;
import org.eclipse.hawkbit.repository.helper.TenantConfigHelper;
import org.eclipse.hawkbit.repository.jpa.JpaManagementHelper;
import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetTag;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet_;
@@ -62,8 +61,7 @@ import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
import org.springframework.resilience.annotation.Retryable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;
@@ -195,7 +193,7 @@ public class JpaDistributionSetManagement
// implicitly lock a distribution set if not already locked and implicit lock is enabled and not to skip
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY))
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
public boolean shouldLockImplicitly(final DistributionSet distributionSet) {
final JpaDistributionSet jpaDistributionSet = toJpaDistributionSet(distributionSet);
if (jpaDistributionSet.isLocked()) {
@@ -227,7 +225,7 @@ public class JpaDistributionSetManagement
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY))
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
public JpaDistributionSet lock(final DistributionSet distributionSet) {
final JpaDistributionSet jpaDistributionSet = toJpaDistributionSet(distributionSet);
if (distributionSet.isLocked()) {
@@ -244,7 +242,7 @@ public class JpaDistributionSetManagement
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY))
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
public JpaDistributionSet unlock(final DistributionSet distributionSet) {
final JpaDistributionSet jpaDistributionSet = toJpaDistributionSet(distributionSet);
if (jpaDistributionSet.isLocked()) {
@@ -265,7 +263,7 @@ public class JpaDistributionSetManagement
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY))
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
public JpaDistributionSet assignSoftwareModules(final long id, final Collection<Long> softwareModuleId) {
final JpaDistributionSet set = getValid0(id);
assertSoftwareModuleQuota(id, softwareModuleId.size());
@@ -283,7 +281,7 @@ public class JpaDistributionSetManagement
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY))
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
public JpaDistributionSet unassignSoftwareModule(final long id, final long moduleId) {
final JpaDistributionSet set = getValid0(id);
@@ -295,7 +293,7 @@ public class JpaDistributionSetManagement
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY))
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
public List<JpaDistributionSet> assignTag(final Collection<Long> ids, final long dsTagId) {
return updateTag(ids, dsTagId, (tag, distributionSet) -> {
if (distributionSet.getTags().contains(tag)) {
@@ -309,7 +307,7 @@ public class JpaDistributionSetManagement
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY))
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
public List<JpaDistributionSet> unassignTag(final Collection<Long> ids, final long dsTagId) {
return updateTag(ids, dsTagId, (tag, distributionSet) -> {
if (distributionSet.getTags().contains(tag)) {

View File

@@ -9,9 +9,6 @@
*/
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.util.Collection;
import java.util.List;
import java.util.Optional;
@@ -24,6 +21,7 @@ import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.EntityReadOnlyException;
import org.eclipse.hawkbit.repository.jpa.acm.AccessController;
import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetType;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModuleType;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetType;
@@ -40,8 +38,7 @@ import org.eclipse.hawkbit.tenancy.TenantAwareCacheManager;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
import org.springframework.cache.Cache;
import org.springframework.dao.ConcurrencyFailureException;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
import org.springframework.resilience.annotation.Retryable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -71,7 +68,7 @@ public class JpaDistributionSetTypeManagement
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY))
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
public void delete(final long id) {
final JpaDistributionSetType toDelete = jpaRepository.getById(id);
@@ -103,21 +100,21 @@ public class JpaDistributionSetTypeManagement
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY))
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
public JpaDistributionSetType assignOptionalSoftwareModuleTypes(final long id, final Collection<Long> softwareModulesTypeIds) {
return assignSoftwareModuleTypes(id, softwareModulesTypeIds, false);
}
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY))
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
public JpaDistributionSetType assignMandatorySoftwareModuleTypes(final long id, final Collection<Long> softwareModuleTypeIds) {
return assignSoftwareModuleTypes(id, softwareModuleTypeIds, true);
}
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY))
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
public JpaDistributionSetType unassignSoftwareModuleType(final long id, final long softwareModuleTypeId) {
final JpaDistributionSetType type = jpaRepository.getById(id);
checkDistributionSetTypeNotAssigned(id);

View File

@@ -96,8 +96,7 @@ import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
import org.springframework.resilience.annotation.Retryable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
@@ -197,8 +196,7 @@ public class JpaRolloutManagement implements RolloutManagement {
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
public Rollout create(
final Create rollout, final int amountGroup, final boolean confirmationRequired,
final RolloutGroupConditions conditions, final DynamicRolloutGroupTemplate dynamicRolloutGroupTemplate) {
@@ -240,8 +238,7 @@ public class JpaRolloutManagement implements RolloutManagement {
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
public Rollout create(
@NotNull @Valid Create create, int amountGroup, boolean confirmationRequired,
@NotNull RolloutGroupConditions conditions) {
@@ -250,8 +247,7 @@ public class JpaRolloutManagement implements RolloutManagement {
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
public Rollout create(final Create rollout, final List<GroupCreate> groups, final RolloutGroupConditions conditions) {
if (groups.isEmpty()) {
throw new ValidationException("The amount of groups cannot be 0");
@@ -335,8 +331,7 @@ public class JpaRolloutManagement implements RolloutManagement {
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
public void pauseRollout(final long rolloutId) {
final JpaRollout rollout = rolloutRepository.getById(rolloutId);
if (RolloutStatus.RUNNING != rollout.getStatus()) {
@@ -352,8 +347,7 @@ public class JpaRolloutManagement implements RolloutManagement {
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
public void resumeRollout(final long rolloutId) {
final JpaRollout rollout = rolloutRepository.getById(rolloutId);
if (RolloutStatus.PAUSED != rollout.getStatus()) {
@@ -391,16 +385,14 @@ public class JpaRolloutManagement implements RolloutManagement {
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
public Rollout approveOrDeny(final long rolloutId, final Rollout.ApprovalDecision decision) {
return approveOrDeny0(rolloutId, decision, null);
}
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
public Rollout approveOrDeny(final long rolloutId, final Rollout.ApprovalDecision decision, final String remark) {
return approveOrDeny0(rolloutId, decision, remark);
}
@@ -431,8 +423,7 @@ public class JpaRolloutManagement implements RolloutManagement {
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
public Rollout start(final long rolloutId) {
log.debug("startRollout called for rollout {}", rolloutId);
@@ -445,8 +436,7 @@ public class JpaRolloutManagement implements RolloutManagement {
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
public Rollout update(final Update update) {
final JpaRollout rollout = rolloutRepository.getById(update.getId());
checkIfDeleted(update.getId(), rollout.getStatus());
@@ -457,8 +447,7 @@ public class JpaRolloutManagement implements RolloutManagement {
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
public Rollout stop(long rolloutId) {
final JpaRollout jpaRollout = rolloutRepository.getById(rolloutId);
@@ -474,8 +463,7 @@ public class JpaRolloutManagement implements RolloutManagement {
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
public void delete(final long rolloutId) {
this.delete0(rolloutRepository.getById(rolloutId));
}
@@ -503,8 +491,7 @@ public class JpaRolloutManagement implements RolloutManagement {
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
public void triggerNextGroup(final long rolloutId) {
final JpaRollout rollout = rolloutRepository.getById(rolloutId);
if (RolloutStatus.RUNNING != rollout.getStatus()) {
@@ -519,15 +506,11 @@ public class JpaRolloutManagement implements RolloutManagement {
throw new RolloutIllegalStateException("Rollout does not have any groups left to be triggered");
}
final List<JpaRolloutGroup> startedRolloutGroups = rollout.getRolloutGroups().stream()
final RolloutGroup lastStartedRolloutGroup = rollout.getRolloutGroups().stream()
.filter(group -> group.getStatus() != RolloutGroupStatus.SCHEDULED)
.sorted(ROLLOUT_GROUP_DESC_COMP)
.map(JpaRolloutGroup.class::cast)
.toList();
if (startedRolloutGroups.isEmpty()) {
throw new RolloutIllegalStateException("Cannot find any started rollout group to trigger next from");
}
startNextRolloutGroupAction.exec(rollout, startedRolloutGroups.get(0));
.min(ROLLOUT_GROUP_DESC_COMP)
.orElseThrow(() -> new RolloutIllegalStateException("Cannot find any started rollout group to trigger next from"));
startNextRolloutGroupAction.exec(rollout, lastStartedRolloutGroup);
}
@Override

View File

@@ -9,8 +9,6 @@
*/
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 static org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor.afterCommit;
import java.util.Collection;
@@ -32,6 +30,7 @@ import org.eclipse.hawkbit.repository.exception.IncompleteSoftwareModuleExceptio
import org.eclipse.hawkbit.repository.exception.LockedException;
import org.eclipse.hawkbit.repository.jpa.JpaManagementHelper;
import org.eclipse.hawkbit.repository.jpa.acm.AccessController;
import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule;
import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetRepository;
import org.eclipse.hawkbit.repository.jpa.repository.SoftwareModuleRepository;
@@ -46,8 +45,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProp
import org.springframework.dao.ConcurrencyFailureException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
import org.springframework.resilience.annotation.Retryable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;
@@ -137,7 +135,7 @@ public class JpaSoftwareModuleManagement extends
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY))
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
public JpaSoftwareModule lock(final SoftwareModule softwareModule) {
final JpaSoftwareModule jpaSoftwareModule = toJpaSoftwareModule(softwareModule);
if (jpaSoftwareModule.isLocked()) {
@@ -153,7 +151,7 @@ public class JpaSoftwareModuleManagement extends
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY))
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
public JpaSoftwareModule unlock(final SoftwareModule softwareModule) {
final JpaSoftwareModule jpaSoftwareModule = toJpaSoftwareModule(softwareModule);
if (softwareModule.isLocked()) {

View File

@@ -49,15 +49,14 @@ import org.eclipse.hawkbit.repository.model.TenantMetaData;
import org.eclipse.hawkbit.tenancy.TenantAwareCacheManager.CacheEvictEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.boot.jpa.autoconfigure.JpaProperties;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.dao.ConcurrencyFailureException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.lang.Nullable;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
import org.jspecify.annotations.Nullable;
import org.springframework.resilience.annotation.Retryable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.Propagation;
@@ -179,8 +178,7 @@ public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, Syst
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
public TenantMetaData updateTenantMetadata(final long defaultDsType) {
final JpaTenantMetaData data = (JpaTenantMetaData) getTenantMetadataWithoutDetails();
data.setDefaultDsType(distributionSetTypeRepository.getById(defaultDsType));
@@ -188,8 +186,7 @@ public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, Syst
}
@Override
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
public void deleteTenant(final String t) {
if (artifactStorage == null) {
throw new IllegalStateException("Artifact repository is not available. Can't delete tenant.");

View File

@@ -61,8 +61,7 @@ import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
import org.springframework.resilience.annotation.Retryable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
@@ -241,16 +240,14 @@ public class JpaTargetManagement
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
public void deleteByControllerId(final String controllerId) {
jpaRepository.delete(jpaRepository.getByControllerId(controllerId));
}
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
public List<Target> assignTag(
final Collection<String> controllerIds, final long targetTagId, final Consumer<Collection<String>> notFoundHandler) {
return assignTag0(controllerIds, targetTagId, notFoundHandler);
@@ -258,8 +255,7 @@ public class JpaTargetManagement
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
public List<Target> assignTag(final Collection<String> controllerIds, final long targetTagId) {
return assignTag0(controllerIds, targetTagId, null);
}
@@ -284,8 +280,7 @@ public class JpaTargetManagement
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
public List<Target> unassignTag(
final Collection<String> controllerIds, final long targetTagId, final Consumer<Collection<String>> notFoundHandler) {
return unassignTag0(controllerIds, targetTagId, notFoundHandler);
@@ -293,8 +288,7 @@ public class JpaTargetManagement
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
public List<Target> unassignTag(final Collection<String> controllerIds, final long targetTagId) {
return unassignTag0(controllerIds, targetTagId, null);
}
@@ -313,8 +307,7 @@ public class JpaTargetManagement
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
public Target assignType(final String controllerId, final Long targetTypeId) {
final JpaTarget target = jpaRepository.getByControllerId(controllerId);
@@ -328,8 +321,7 @@ public class JpaTargetManagement
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
public Target unassignType(final String controllerId) {
final JpaTarget target = jpaRepository.getByControllerId(controllerId);
target.setTargetType(null);
@@ -338,8 +330,7 @@ public class JpaTargetManagement
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
public void assignTargetGroupWithRsql(String group, String rsql) {
final Specification<JpaTarget> rsqlSpecification = QLSupport.getInstance().buildSpec(rsql, TargetFields.class);
@@ -356,8 +347,7 @@ public class JpaTargetManagement
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
public void assignTargetsWithGroup(String group, List<String> controllerIds) {
final CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaUpdate<JpaTarget> criteriaQuery = cb.createCriteriaUpdate(JpaTarget.class);
@@ -387,8 +377,7 @@ public class JpaTargetManagement
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
public void createMetadata(final String controllerId, final String key, final String value) {
final JpaTarget target = jpaRepository.getByControllerId(controllerId);
@@ -404,8 +393,7 @@ public class JpaTargetManagement
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
public void createMetadata(final String controllerId, final Map<String, String> md) {
final JpaTarget target = jpaRepository.getByControllerId(controllerId);
@@ -434,8 +422,7 @@ public class JpaTargetManagement
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
public void deleteMetadata(final String controllerId, final String key) {
final JpaTarget target = jpaRepository.getByControllerId(controllerId);

View File

@@ -36,8 +36,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProp
import org.springframework.cache.Cache;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.dao.ConcurrencyFailureException;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
import org.springframework.resilience.annotation.Retryable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
@@ -94,10 +93,8 @@ public class JpaTargetTypeManagement
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public TargetType assignCompatibleDistributionSetTypes(final long id,
final Collection<Long> distributionSetTypeIds) {
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
public TargetType assignCompatibleDistributionSetTypes(final long id, final Collection<Long> distributionSetTypeIds) {
final Collection<JpaDistributionSetType> dsTypes = distributionSetTypeRepository.findAllById(distributionSetTypeIds);
if (dsTypes.size() < distributionSetTypeIds.size()) {
@@ -115,8 +112,7 @@ public class JpaTargetTypeManagement
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
public TargetType unassignDistributionSetType(final long id, final long distributionSetTypeId) {
final JpaTargetType type = jpaRepository.getById(id);
if (!distributionSetTypeRepository.existsById(distributionSetTypeId)) {

View File

@@ -12,7 +12,6 @@ package org.eclipse.hawkbit.repository.jpa.management;
import static org.eclipse.hawkbit.auth.SpPermission.READ_GATEWAY_SECURITY_TOKEN;
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.AUTHENTICATION_GATEWAY_SECURITY_TOKEN_KEY;
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.POLLING_TIME;
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.REPOSITORY_ACTIONS_AUTOCLOSE_ENABLED;
import java.time.Duration;
import java.time.Instant;
@@ -32,7 +31,6 @@ import org.eclipse.hawkbit.ql.jpa.QLSupport;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException;
import org.eclipse.hawkbit.repository.exception.TenantConfigurationValidatorException;
import org.eclipse.hawkbit.repository.exception.TenantConfigurationValueChangeNotAllowedException;
import org.eclipse.hawkbit.repository.helper.TenantConfigHelper;
import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
@@ -57,8 +55,7 @@ import org.springframework.core.convert.ConversionException;
import org.springframework.core.convert.support.ConfigurableConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.dao.ConcurrencyFailureException;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
import org.springframework.resilience.annotation.Retryable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;
@@ -98,16 +95,14 @@ public class JpaTenantConfigurationManagement implements TenantConfigurationMana
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
public void addOrUpdateConfiguration(final String keyName, final Object value) {
addOrUpdateConfiguration0(Map.of(keyName, value));
}
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
public void addOrUpdateConfiguration(final Map<String, Object> configurations) {
addOrUpdateConfiguration0(configurations);
}
@@ -124,8 +119,7 @@ public class JpaTenantConfigurationManagement implements TenantConfigurationMana
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
@Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY)
public void deleteConfiguration(final String keyName) {
tenantConfigurationRepository.deleteByKey(keyName);
}

View File

@@ -13,11 +13,11 @@ import java.util.List;
import java.util.Optional;
import org.eclipse.hawkbit.repository.jpa.acm.AccessController;
import org.jspecify.annotations.NonNull;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.jspecify.annotations.Nullable;
/**
* Repository interface that offers some actions that takes in account a target operation.
@@ -30,35 +30,42 @@ public interface ACMRepository<T> {
* Saves only if the caller have access for the operation over the entity. This method could be used to
* check CREATE access in creating an entity (save without operation would check for UPDATE access).
*
* @param operation access operationIf operation is <code>null</code> no access is checked! Should be used
* only for tenant context.
* @param operation access operationIf operation is <code>null</code> no access is checked! Should be used only for tenant context.
* @param entity the entity to save
* @return the saved entity
*/
@NonNull
<S extends T> S save(@Nullable AccessController.Operation operation, @NonNull final S entity);
<S extends T> S save(AccessController.Operation operation, @NonNull S entity);
/**
* Saves only if the caller have access for the operation over all entities. This method could be used to
* check CREATE access in creating an entity (save without operation would check for UPDATE access).
*
* @param operation access operationIf operation is <code>null</code> no access is checked! Should be used
* only for tenant context.
* @param operation access operationIf operation is <code>null</code> no access is checked! Should be used only for tenant context.
* @param entities the entities to save
* @return the saved entities
*/
<S extends T> List<S> saveAll(@Nullable AccessController.Operation operation, final Iterable<S> entities);
<S extends T> List<S> saveAll(AccessController.Operation operation, Iterable<S> entities);
/**
* Returns single entry that match specification and the operation is allowed for.
*
* @param operation access operation. If operation is <code>null</code> no access is checked! Should be used
* only for tenant context.
* @param operation access operation. If operation is <code>null</code> no access is checked! Should be used only for tenant context.
* @param spec specification
* @return matching entity
*/
@NonNull
Optional<T> findOne(@Nullable AccessController.Operation operation, @NonNull Specification<T> spec);
Optional<T> findOne(AccessController.Operation operation, @NonNull Specification<T> spec);
/**
* Returns all entries that match specification and the operation is allowed for.
*
* @param operation access operation. If operation is <code>null</code> no access is checked! Should be used only for tenant context.
* @param spec specification
* @return matching entities
*/
@NonNull
List<T> findAll(AccessController.Operation operation, @Nullable Specification<T> spec);
/**
* Returns all entries that match specification and the operation is allowed for.
@@ -68,19 +75,7 @@ public interface ACMRepository<T> {
* @param spec specification
* @return matching entities
*/
@NonNull
List<T> findAll(@Nullable AccessController.Operation operation, @Nullable Specification<T> spec);
/**
* Returns all entries that match specification and the operation is allowed for.
*
* @param operation access operation. If operation is <code>null</code> no access is checked! Should be used
* only for tenant context.
* @param spec specification
* @return matching entities
*/
@NonNull
boolean exists(@Nullable AccessController.Operation operation, Specification<T> spec);
boolean exists(AccessController.Operation operation, Specification<T> spec);
/**
* Returns count of all entries that match specification and the operation is allowed for.
@@ -90,8 +85,7 @@ public interface ACMRepository<T> {
* @param spec specification
* @return count of matching entities
*/
@NonNull
long count(@Nullable AccessController.Operation operation, @Nullable Specification<T> spec);
long count(AccessController.Operation operation, @Nullable Specification<T> spec);
/**
* Returns all entries, without count, that match specification and the operation is allowed for.
@@ -103,9 +97,8 @@ public interface ACMRepository<T> {
* @return count of matching entities
*/
@NonNull
Slice<T> findAllWithoutCount(
@Nullable final AccessController.Operation operation, @Nullable Specification<T> spec, Pageable pageable);
Slice<T> findAllWithoutCount(AccessController.Operation operation, @Nullable Specification<T> spec, Pageable pageable);
@NonNull
Class<T> getDomainClass();
}
}

View File

@@ -12,7 +12,11 @@ package org.eclipse.hawkbit.repository.jpa.repository;
import java.util.List;
import java.util.Optional;
import jakarta.persistence.criteria.CriteriaBuilder;
import org.eclipse.hawkbit.repository.jpa.model.AbstractJpaBaseEntity_;
import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
import org.eclipse.hawkbit.repository.jpa.model.JpaAction_;
import org.eclipse.hawkbit.repository.jpa.model.JpaRollout;
import org.eclipse.hawkbit.repository.jpa.model.JpaRolloutGroup;
import org.eclipse.hawkbit.repository.model.Action;
@@ -74,14 +78,6 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction> {
@Param("statusToSet") Action.Status statusToSet, @Param("targetsIds") List<Long> targetIds,
@Param("active") boolean active, @Param("currentStatus") Action.Status currentStatus);
/**
* Retrieves an {@link Action} that matches the queried externalRef.
*
* @param externalRef of the action. See {@link Action#getExternalRef()}
* @return the found {@link Action}
*/
Optional<Action> findByExternalRef(@Param("externalRef") String externalRef);
/**
* Counts all {@link Action}s referring to the given target.
* <p/>
@@ -145,7 +141,19 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction> {
* @param statuses the list of statuses the action should not have
* @return the count of actions referring the rollout and rollout group and are not in given states
*/
Long countByRolloutAndRolloutGroupAndStatusNotIn(JpaRollout rollout, JpaRolloutGroup rolloutGroup, List<Status> statuses);
// TODO (Spring Boot 4 Migration): Eclipse link (5 beta) doesn't handle correctly the IN clause generated by Spring Data JPA
// Long countByRolloutAndRolloutGroupAndStatusNotIn(JpaRollout rollout, JpaRolloutGroup rolloutGroup, List<Status> statuses);
default Long countByRolloutAndRolloutGroupAndStatusNotIn(
final JpaRollout rollout, final JpaRolloutGroup rolloutGroup, final List<Status> statuses) {
return count((root, query, cb) -> {
final CriteriaBuilder.In<Status> in = cb.in(root.get(JpaAction_.status));
statuses.forEach(in::value);
return cb.and(
cb.equal(root.get(JpaAction_.rollout).get(AbstractJpaBaseEntity_.id), rollout.getId()),
cb.equal(root.get(JpaAction_.rolloutGroup).get(AbstractJpaBaseEntity_.id), rolloutGroup.getId()),
cb.not(in));
});
}
/**
* Counts all actions referring to a given rollout and rollout group.
@@ -160,7 +168,7 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction> {
/**
* Counts all actions referring to a given rollout, rollout group and status.
* <p/>
* <p/>
* No access control applied
*
* @param rolloutId the ID of rollout the actions belong to
@@ -214,7 +222,7 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction> {
/**
* Returns {@code true} if actions for the given rollout exists, otherwise {@code false}
* <p/>
* <p/>
* No access control applied
*
* @param rolloutId the ID of the rollout the actions belong to
@@ -222,13 +230,13 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction> {
* @return {@code true} if actions for the given rollout exists, otherwise {@code false}
*/
@Query("SELECT CASE WHEN COUNT(a)>0 THEN 'true' ELSE 'false' END FROM JpaAction a WHERE a.rollout.id=:rolloutId AND a.status != :status")
boolean existsByRolloutIdAndStatusNotIn(@Param("rolloutId") Long rolloutId, @Param("status") Status status);
boolean existsByRolloutIdAndStatusNot(@Param("rolloutId") Long rolloutId, @Param("status") Status status);
/**
* Retrieving all actions referring to a given rollout with a specific action as parent reference and a specific status.
* <p/>
* <p/>
* Finding all actions of a specific rollout group parent relation.
* <p/>
* <p/>
* No access control applied
*
* @param pageable page parameters
@@ -242,7 +250,7 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction> {
/**
* Retrieving all actions referring to the first group of a rollout.
* <p/>
* <p/>
* No access control applied
*
* @param pageable page parameters
@@ -255,7 +263,7 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction> {
/**
* Retrieves all actions for a specific rollout and in a specific status.
* <p/>
* <p/>
* No access control applied
*
* @param pageable page parameters
@@ -267,7 +275,7 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction> {
/**
* Get list of objects which has details of status and count of targets in each status in specified rollout.
* <p/>
* <p/>
* No access control applied
*
* @param rolloutId id of {@link Rollout}
@@ -278,7 +286,7 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction> {
/**
* Get list of objects which has details of status and count of targets in each status in specified rollout.
* <p/>
* <p/>
* No access control applied
*
* @param rolloutId id of {@link Rollout}
@@ -289,7 +297,7 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction> {
/**
* Get list of objects which has details of status and count of targets in each status in specified rollout group.
* <p/>
* <p/>
* No access control applied
*
* @param rolloutGroupId id of {@link RolloutGroup}
@@ -300,7 +308,7 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction> {
/**
* Get list of objects which has details of status and count of targets in each status in specified rollout group.
* <p/>
* <p/>
* No access control applied
*
* @param rolloutGroupId list of id of {@link RolloutGroup}

View File

@@ -27,7 +27,7 @@ import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.lang.Nullable;
import org.jspecify.annotations.Nullable;
import org.springframework.transaction.annotation.Transactional;
/**

View File

@@ -25,14 +25,17 @@ import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException;
import org.eclipse.hawkbit.repository.jpa.acm.AccessController;
import org.eclipse.hawkbit.repository.jpa.acm.AccessController.Operation;
import org.eclipse.hawkbit.repository.jpa.model.AbstractJpaBaseEntity;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.DeleteSpecification;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.data.jpa.domain.UpdateSpecification;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;
@Slf4j
public class BaseEntityRepositoryACM<T extends AbstractJpaBaseEntity> implements BaseEntityRepository<T> {
@@ -52,7 +55,7 @@ public class BaseEntityRepositoryACM<T extends AbstractJpaBaseEntity> implements
@Override
@NonNull
public <S extends T> S save(@NonNull final S entity) {
accessController.assertOperationAllowed(AccessController.Operation.UPDATE, entity);
accessController.assertOperationAllowed(Operation.UPDATE, entity);
return repository.save(entity);
}
@@ -75,15 +78,15 @@ public class BaseEntityRepositoryACM<T extends AbstractJpaBaseEntity> implements
@Override
public long count() {
return count(null);
return count((Specification<T>) null);
}
@Override
public void deleteById(@NonNull final Long id) {
if (!exists(AccessController.Operation.READ, byIdSpec(id))) {
if (!exists(Operation.READ, byIdSpec(id))) {
throw new EntityNotFoundException(repository.getDomainClass(), id);
}
if (!exists(AccessController.Operation.DELETE, byIdSpec(id))) {
if (!exists(Operation.DELETE, byIdSpec(id))) {
throw new InsufficientPermissionException();
}
repository.deleteById(id);
@@ -91,7 +94,7 @@ public class BaseEntityRepositoryACM<T extends AbstractJpaBaseEntity> implements
@Override
public void delete(@NonNull final T entity) {
accessController.assertOperationAllowed(AccessController.Operation.DELETE, entity);
accessController.assertOperationAllowed(Operation.DELETE, entity);
repository.delete(entity);
}
@@ -99,7 +102,7 @@ public class BaseEntityRepositoryACM<T extends AbstractJpaBaseEntity> implements
public void deleteAllById(@NonNull final Iterable<? extends Long> ids) {
final Set<Long> idList = new HashSet<>();
ids.forEach(idList::add);
if (count(AccessController.Operation.DELETE, byIdsSpec(idList)) != idList.size()) {
if (count(Operation.DELETE, byIdsSpec(idList)) != idList.size()) {
throw new InsufficientPermissionException("Has at least one id that is not allowed for deletion!");
}
repository.deleteAllById(idList);
@@ -107,7 +110,7 @@ public class BaseEntityRepositoryACM<T extends AbstractJpaBaseEntity> implements
@Override
public void deleteAll(@NonNull final Iterable<? extends T> entities) {
accessController.assertOperationAllowed(AccessController.Operation.DELETE, entities);
accessController.assertOperationAllowed(Operation.DELETE, entities);
repository.deleteAll(entities);
}
@@ -124,7 +127,7 @@ public class BaseEntityRepositoryACM<T extends AbstractJpaBaseEntity> implements
@Override
public <S extends T> List<S> saveAll(final Iterable<S> entities) {
accessController.assertOperationAllowed(AccessController.Operation.UPDATE, entities);
accessController.assertOperationAllowed(Operation.UPDATE, entities);
return repository.saveAll(entities);
}
@@ -142,7 +145,7 @@ public class BaseEntityRepositoryACM<T extends AbstractJpaBaseEntity> implements
@Override
public void deleteByTenant(final String tenant) {
if (accessController.getAccessRules(AccessController.Operation.DELETE).isPresent()) {
if (accessController.getAccessRules(Operation.DELETE).isPresent()) {
throw new InsufficientPermissionException("DELETE operation has restriction for given context! deleteAll can't be executed!");
}
repository.deleteByTenant(tenant);
@@ -160,47 +163,52 @@ public class BaseEntityRepositoryACM<T extends AbstractJpaBaseEntity> implements
return repository.findOne(
// spec shall be non-null and the result of appending rules shall be non-null
Objects.requireNonNull(
accessController.appendAccessRules(AccessController.Operation.READ, spec),
accessController.appendAccessRules(Operation.READ, spec),
APPENDED_ACCESS_RULES_SPEC_OF_NON_NULL_SPEC_MUST_NOT_BE_NULL));
}
@Override
@NonNull
public List<T> findAll(final Specification<T> spec) {
return repository.findAll(accessController.appendAccessRules(AccessController.Operation.READ, spec));
return repository.findAll(accessController.appendAccessRules(Operation.READ, spec));
}
@Override
@NonNull
public Page<T> findAll(final Specification<T> spec, @NonNull final Pageable pageable) {
return repository.findAll(accessController.appendAccessRules(AccessController.Operation.READ, spec), pageable);
return repository.findAll(accessController.appendAccessRules(Operation.READ, spec), pageable);
}
@Override
public Page<T> findAll(final Specification<T> spec, final Specification<T> countSpec, final Pageable pageable) {
return repository.findAll(accessController.appendAccessRules(AccessController.Operation.READ, spec), countSpec, pageable);
return repository.findAll(accessController.appendAccessRules(Operation.READ, spec), countSpec, pageable);
}
@Override
@NonNull
public List<T> findAll(final Specification<T> spec, @NonNull final Sort sort) {
return repository.findAll(accessController.appendAccessRules(AccessController.Operation.READ, spec), sort);
return repository.findAll(accessController.appendAccessRules(Operation.READ, spec), sort);
}
@Override
public long count(final Specification<T> spec) {
return repository.count(accessController.appendAccessRules(AccessController.Operation.READ, spec));
return repository.count(accessController.appendAccessRules(Operation.READ, spec));
}
@Override
public boolean exists(@NonNull final Specification<T> spec) {
return repository.exists(
Objects.requireNonNull(accessController.appendAccessRules(AccessController.Operation.READ, spec)));
Objects.requireNonNull(accessController.appendAccessRules(Operation.READ, spec)));
}
@Override
public long delete(final Specification<T> spec) {
return repository.delete(accessController.appendAccessRules(AccessController.Operation.DELETE, spec));
public long update(final UpdateSpecification<T> spec) {
return repository.update(accessController.appendAccessRules(Operation.UPDATE, spec));
}
@Override
public long delete(final DeleteSpecification<T> spec) {
return repository.delete(accessController.appendAccessRules(Operation.DELETE, spec));
}
@Override
@@ -209,7 +217,7 @@ public class BaseEntityRepositoryACM<T extends AbstractJpaBaseEntity> implements
return repository.findBy(
// spec shall be non-null and the result of appending rules shall be non-null
Objects.requireNonNull(
accessController.appendAccessRules(AccessController.Operation.READ, spec),
accessController.appendAccessRules(Operation.READ, spec),
APPENDED_ACCESS_RULES_SPEC_OF_NON_NULL_SPEC_MUST_NOT_BE_NULL),
queryFunction);
}
@@ -234,13 +242,13 @@ public class BaseEntityRepositoryACM<T extends AbstractJpaBaseEntity> implements
@Override
public Slice<T> findAllWithoutCount(final Specification<T> spec, final Pageable pageable) {
return repository.findAllWithoutCount(
accessController.appendAccessRules(AccessController.Operation.READ, spec), pageable);
accessController.appendAccessRules(Operation.READ, spec), pageable);
}
@Override
@Transactional
@NonNull
public <S extends T> S save(@Nullable AccessController.Operation operation, @NonNull final S entity) {
public <S extends T> S save(Operation operation, @NonNull final S entity) {
if (operation != null) {
accessController.assertOperationAllowed(operation, entity);
}
@@ -249,7 +257,7 @@ public class BaseEntityRepositoryACM<T extends AbstractJpaBaseEntity> implements
@Override
@Transactional
public <S extends T> List<S> saveAll(@Nullable AccessController.Operation operation, final Iterable<S> entities) {
public <S extends T> List<S> saveAll(final Operation operation, final Iterable<S> entities) {
if (operation != null) {
accessController.assertOperationAllowed(operation, entities);
}
@@ -257,7 +265,7 @@ public class BaseEntityRepositoryACM<T extends AbstractJpaBaseEntity> implements
}
@NonNull
public Optional<T> findOne(@Nullable AccessController.Operation operation, @NonNull Specification<T> spec) {
public Optional<T> findOne(final Operation operation, @NonNull Specification<T> spec) {
Objects.requireNonNull(spec, SPEC_MUST_NOT_BE_NULL);
if (operation == null) {
return repository.findOne(spec);
@@ -272,7 +280,7 @@ public class BaseEntityRepositoryACM<T extends AbstractJpaBaseEntity> implements
@Override
@NonNull
public List<T> findAll(@Nullable final AccessController.Operation operation, @Nullable final Specification<T> spec) {
public List<T> findAll(final Operation operation, @Nullable final Specification<T> spec) {
if (operation == null) {
return repository.findAll(spec);
} else {
@@ -282,7 +290,7 @@ public class BaseEntityRepositoryACM<T extends AbstractJpaBaseEntity> implements
@Override
@NonNull
public boolean exists(@Nullable AccessController.Operation operation, Specification<T> spec) {
public boolean exists(final Operation operation, Specification<T> spec) {
if (operation == null) {
return repository.exists(spec);
} else {
@@ -292,8 +300,7 @@ public class BaseEntityRepositoryACM<T extends AbstractJpaBaseEntity> implements
}
@Override
@NonNull
public long count(@Nullable final AccessController.Operation operation, @Nullable final Specification<T> spec) {
public long count(final Operation operation, @Nullable final Specification<T> spec) {
if (operation == null) {
return repository.count(spec);
} else {
@@ -303,8 +310,7 @@ public class BaseEntityRepositoryACM<T extends AbstractJpaBaseEntity> implements
@Override
@NonNull
public Slice<T> findAllWithoutCount(
@Nullable final AccessController.Operation operation, @Nullable Specification<T> spec, Pageable pageable) {
public Slice<T> findAllWithoutCount(final Operation operation, @Nullable Specification<T> spec, Pageable pageable) {
if (operation == null) {
return repository.findAllWithoutCount(spec, pageable);
} else {
@@ -321,25 +327,25 @@ public class BaseEntityRepositoryACM<T extends AbstractJpaBaseEntity> implements
@Override
public Optional<T> findOne(final Specification<T> spec, final String entityGraph) {
return repository.findOne(
accessController.appendAccessRules(AccessController.Operation.READ, spec), entityGraph);
accessController.appendAccessRules(Operation.READ, spec), entityGraph);
}
@Override
public List<T> findAll(final Specification<T> spec, final String entityGraph) {
return repository.findAll(
accessController.appendAccessRules(AccessController.Operation.READ, spec), entityGraph);
accessController.appendAccessRules(Operation.READ, spec), entityGraph);
}
@Override
public Page<T> findAll(final Specification<T> spec, final String entityGraph, final Pageable pageable) {
return repository.findAll(
accessController.appendAccessRules(AccessController.Operation.READ, spec), entityGraph, pageable);
accessController.appendAccessRules(Operation.READ, spec), entityGraph, pageable);
}
@Override
public List<T> findAll(final Specification<T> spec, final String entityGraph, final Sort sort) {
return repository.findAll(
accessController.appendAccessRules(AccessController.Operation.READ, spec), entityGraph, sort);
accessController.appendAccessRules(Operation.READ, spec), entityGraph, sort);
}
@SuppressWarnings("unchecked")
@@ -370,7 +376,7 @@ public class BaseEntityRepositoryACM<T extends AbstractJpaBaseEntity> implements
if (Iterable.class.isAssignableFrom(method.getReturnType())) {
for (final Object e : (Iterable<?>) result) {
if (repository.getDomainClass().isAssignableFrom(e.getClass())) {
accessController.assertOperationAllowed(AccessController.Operation.READ, (T) e);
accessController.assertOperationAllowed(Operation.READ, (T) e);
}
}
} else if (Optional.class.isAssignableFrom(method.getReturnType()) && ((Optional<?>) result)
@@ -379,11 +385,11 @@ public class BaseEntityRepositoryACM<T extends AbstractJpaBaseEntity> implements
return ((Optional<T>) result).filter(
t -> {
// if not accessible - throws exception (as for iterables or single entities)
accessController.assertOperationAllowed(AccessController.Operation.READ, t);
accessController.assertOperationAllowed(Operation.READ, t);
return true;
});
} else if (repository.getDomainClass().isAssignableFrom(method.getReturnType())) {
accessController.assertOperationAllowed(AccessController.Operation.READ, (T) result);
accessController.assertOperationAllowed(Operation.READ, (T) result);
}
return result;
} else if ("toString".equals(method.getName()) && method.getParameterCount() == 0) {

View File

@@ -19,7 +19,9 @@ import jakarta.persistence.NoResultException;
import jakarta.persistence.TypedQuery;
import jakarta.transaction.Transactional;
import org.eclipse.hawkbit.repository.jpa.acm.AccessController;
import org.eclipse.hawkbit.repository.jpa.acm.AccessController.Operation;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Page;
@@ -30,8 +32,6 @@ import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.util.ObjectUtils;
/**
@@ -61,7 +61,7 @@ public final class HawkbitBaseRepository<T, ID extends Serializable> extends Sim
@Override
public Slice<T> findAllWithoutCount(final Pageable pageable) {
return findAllWithoutCount(null, pageable);
return findAllWithoutCount(Specification.unrestricted(), pageable);
}
@Override
@@ -74,38 +74,36 @@ public final class HawkbitBaseRepository<T, ID extends Serializable> extends Sim
@Transactional
@NonNull
@SuppressWarnings("java:S6809") // this method already has a transactional annotation witch shall be applied
public <S extends T> S save(@Nullable AccessController.Operation operation, @NonNull final S entity) {
public <S extends T> S save(final Operation operation, @NonNull final S entity) {
return save(entity);
}
@Override
@Transactional
@SuppressWarnings("java:S6809") // this method already has a transactional annotation witch shall be applied
public <S extends T> List<S> saveAll(@Nullable AccessController.Operation operation, final Iterable<S> entities) {
public <S extends T> List<S> saveAll(final Operation operation, final Iterable<S> entities) {
return saveAll(entities);
}
@NonNull
public Optional<T> findOne(@Nullable AccessController.Operation operation, @NonNull Specification<T> spec) {
public Optional<T> findOne(final Operation operation, @NonNull Specification<T> spec) {
return findOne(spec);
}
@Override
@NonNull
@SuppressWarnings("java:S4449") // find all accepts null
public List<T> findAll(@Nullable final AccessController.Operation operation, @Nullable final Specification<T> spec) {
public List<T> findAll(@Nullable final Operation operation, @Nullable final Specification<T> spec) {
return findAll(spec);
}
@Override
@NonNull
public boolean exists(@Nullable AccessController.Operation operation, Specification<T> spec) {
public boolean exists(@Nullable Operation operation, Specification<T> spec) {
return exists(spec);
}
@Override
@NonNull
public long count(@Nullable final AccessController.Operation operation, @Nullable final Specification<T> spec) {
public long count(@Nullable final Operation operation, @Nullable final Specification<T> spec) {
return count(spec);
}
@@ -137,7 +135,7 @@ public final class HawkbitBaseRepository<T, ID extends Serializable> extends Sim
@NonNull
@Override
public Slice<T> findAllWithoutCount(@Nullable final AccessController.Operation operation, @Nullable Specification<T> spec,
public Slice<T> findAllWithoutCount(@Nullable final Operation operation, @Nullable Specification<T> spec,
Pageable pageable) {
return findAllWithoutCount(spec, pageable);
}

View File

@@ -16,7 +16,7 @@ import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.lang.Nullable;
import org.jspecify.annotations.Nullable;
/**
* Repository interface that offers JpaSpecificationExecutor#findOne/All methods with entity graph loading

View File

@@ -13,7 +13,7 @@ import org.eclipse.hawkbit.repository.model.BaseEntity;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.lang.Nullable;
import org.jspecify.annotations.Nullable;
/**
* Repository interface that offers findAll with disabled count query.

View File

@@ -12,8 +12,12 @@ package org.eclipse.hawkbit.repository.jpa.repository;
import java.util.Collection;
import java.util.List;
import jakarta.persistence.criteria.CriteriaBuilder;
import org.eclipse.hawkbit.repository.jpa.model.AbstractJpaBaseEntity_;
import org.eclipse.hawkbit.repository.jpa.model.JpaRollout;
import org.eclipse.hawkbit.repository.jpa.model.JpaRolloutGroup;
import org.eclipse.hawkbit.repository.jpa.model.JpaRolloutGroup_;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupStatus;
@@ -72,10 +76,20 @@ public interface RolloutGroupRepository extends BaseEntityRepository<JpaRolloutG
* Retrieves all {@link RolloutGroup}s for a specific rollout and status not having.
*
* @param rollout the rollout the rolloutgroup belong to
* @param status the status which the rolloutgroup should not have
* @param statuses the status which the rolloutgroup should not have
* @return rolloutgroup referring to a rollout and not having a specific status.
*/
List<JpaRolloutGroup> findByRolloutAndStatusNotIn(JpaRollout rollout, Collection<RolloutGroupStatus> status);
// TODO (Spring Boot 4 Migration): Eclipse link (5 beta) doesn't handle correctly the IN clause generated by Spring Data JPA
// List<JpaRolloutGroup> findByRolloutAndStatusNotIn(JpaRollout rollout, Collection<RolloutGroupStatus> statuses);
default List<JpaRolloutGroup> findByRolloutAndStatusNotIn(final JpaRollout rollout, final Collection<RolloutGroupStatus> statuses) {
return findAll((root, query, cb) -> {
final CriteriaBuilder.In<RolloutGroupStatus> in = cb.in(root.get(JpaRolloutGroup_.status));
statuses.forEach(in::value);
return cb.and(
cb.equal(root.get(JpaRolloutGroup_.rollout).get(AbstractJpaBaseEntity_.id), rollout.getId()),
cb.not(in));
});
}
/**
* Retrieves all {@link RolloutGroup} for a specific rollout.

View File

@@ -14,12 +14,18 @@ import java.util.List;
import java.util.Optional;
import jakarta.persistence.EntityManager;
import jakarta.persistence.criteria.CriteriaBuilder;
import org.eclipse.hawkbit.repository.jpa.model.AbstractJpaBaseEntity_;
import org.eclipse.hawkbit.repository.jpa.model.JpaRollout;
import org.eclipse.hawkbit.repository.jpa.model.JpaRolloutGroup_;
import org.eclipse.hawkbit.repository.jpa.model.JpaRollout_;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.Rollout.RolloutStatus;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
@@ -37,6 +43,9 @@ public interface RolloutRepository extends BaseEntityRepository<JpaRollout> {
* @param status the status of the rollouts to find
* @return the list of {@link Rollout} for specific status
*/
// TODO (Spring Boot 4 Migration): seems native queries are fine with collections. However, this is used only
// in handleAll and maybe the caller method RolloutManagement#findActiveRollouts could be moved in handler
// less visibility
@Query("SELECT sm.id FROM JpaRollout sm WHERE sm.status IN ?1 ORDER BY sm.id ASC")
List<Long> findByStatusIn(Collection<RolloutStatus> status);
@@ -65,18 +74,36 @@ public interface RolloutRepository extends BaseEntityRepository<JpaRollout> {
* Retrieves all {@link Rollout}s for a specific {@link DistributionSet} in a given {@link RolloutStatus}.
*
* @param set the distribution set
* @param status the status of the rollout
* @param statuses the status of the rollout
* @return {@link Rollout} for specific distribution set
*/
List<Rollout> findByDistributionSetAndStatusIn(DistributionSet set, Collection<RolloutStatus> status);
// TODO (Spring Boot 4 Migration): Eclipse link (5 beta) doesn't handle correctly the IN clause generated by Spring Data JPA
// List<Rollout> findByDistributionSetAndStatusIn(DistributionSet set, Collection<RolloutStatus> statusese);
default List<JpaRollout> findByDistributionSetAndStatusIn(final DistributionSet set, final Collection<RolloutStatus> statuses) {
return findAll(byDsAndStatuses(set.getId(), statuses));
}
/**
* Counts all {@link Rollout}s for a specific {@link DistributionSet} in a
* given {@link RolloutStatus}.
*
* @param distributionSetId the distribution set
* @param status the status of the rollout
* @param statuses the status of the rollout
* @return the count
*/
long countByDistributionSetIdAndStatusIn(long distributionSetId, Collection<RolloutStatus> status);
// TODO (Spring Boot 4 Migration): Eclipse link (5 beta) doesn't handle correctly the IN clause generated by Spring Data JPA
// long countByDistributionSetIdAndStatusIn(long distributionSetId, Collection<RolloutStatus> statuses);
default long countByDistributionSetIdAndStatusIn(final long distributionSetId, final Collection<RolloutStatus> statuses) {
return count(byDsAndStatuses(distributionSetId, statuses));
}
private static Specification<JpaRollout> byDsAndStatuses(final long distributionSetId, final Collection<RolloutStatus> statuses) {
return (root, query, cb) -> {
final CriteriaBuilder.In<RolloutStatus> in = cb.in(root.get(JpaRollout_.status));
statuses.forEach(in::value);
return cb.and(
cb.equal(root.get(JpaRollout_.distributionSet).get(AbstractJpaBaseEntity_.id), distributionSetId),
in);
};
}
}

View File

@@ -44,5 +44,4 @@ public abstract class AbstractPauseRolloutGroupAction<T extends Enum<T>> impleme
asSystem(() -> rolloutManagement.pauseRollout(rollout.getId()));
}
}
}
}

View File

@@ -246,7 +246,7 @@ public class JpaRolloutExecutor implements RolloutExecutor {
log.debug("handleDeleteRollout called for {}", rollout.getId());
// check if there are actions beyond schedule
boolean hardDeleteRolloutGroups = !actionRepository.existsByRolloutIdAndStatusNotIn(rollout.getId(),
boolean hardDeleteRolloutGroups = !actionRepository.existsByRolloutIdAndStatusNot(rollout.getId(),
Status.SCHEDULED);
if (hardDeleteRolloutGroups) {
log.debug("Rollout {} has no actions other than scheduled -> hard delete", rollout.getId());
@@ -321,7 +321,7 @@ public class JpaRolloutExecutor implements RolloutExecutor {
}
private void handleReadyRollout(final Rollout rollout) {
if (rollout.getStartAt() != null && rollout.getStartAt() <= java.lang.System.currentTimeMillis()) {
if (rollout.getStartAt() != null && rollout.getStartAt() <= System.currentTimeMillis()) {
log.debug("handleReadyRollout called for rollout {} with autostart beyond define time. Switch to STARTING", rollout.getId());
rolloutManagement.start(rollout.getId());
}
@@ -627,7 +627,7 @@ public class JpaRolloutExecutor implements RolloutExecutor {
// return if group change is made
private boolean fillDynamicRolloutGroupsWithTargets(final JpaRollout rollout) {
final AtomicLong lastFill = lastDynamicGroupFill.computeIfAbsent(rollout.getId(), id -> new AtomicLong(0));
final long now = java.lang.System.currentTimeMillis();
final long now = System.currentTimeMillis();
if (now - lastFill.get() < repositoryProperties.getDynamicRolloutsMinInvolvePeriodMS()) {
// too early to make another dynamic involvement attempt
return false;

View File

@@ -29,6 +29,7 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule_;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget_;
import org.eclipse.hawkbit.repository.model.Action;
import org.springframework.data.jpa.domain.DeleteSpecification;
import org.springframework.data.jpa.domain.Specification;
/**
@@ -102,15 +103,14 @@ public final class ActionSpecifications {
);
}
public static Specification<JpaAction> byControllerIdAndIdIn(final String controllerId, final List<Long> actionIds) {
return ((root, query, cb) -> {
final Join<JpaAction, JpaTarget> targetJoin = root.join(JpaAction_.target);
return cb.and(
cb.equal(targetJoin.get(JpaTarget_.controllerId), controllerId),
root.get(AbstractJpaBaseEntity_.id).in(actionIds)
);
});
public static DeleteSpecification<JpaAction> byControllerIdAndIdIn(final String controllerId, final List<Long> actionIds) {
return (root, cd, cb) -> {
final Join<JpaAction, JpaTarget> targetJoin = root.join(JpaAction_.target);
return cb.and(
cb.equal(targetJoin.get(JpaTarget_.controllerId), controllerId),
root.get(AbstractJpaBaseEntity_.id).in(actionIds)
);
};
}
public static Specification<JpaAction> byDistributionSetIdAndActiveAndStatusIsNot(final Long distributionSetId, final Action.Status status) {

View File

@@ -9,15 +9,17 @@
*/
package org.eclipse.hawkbit.repository.event.remote;
import static org.springframework.messaging.MessageHeaders.CONTENT_TYPE;
import java.util.Map;
import org.eclipse.hawkbit.event.EventJacksonMessageConverter;
import org.eclipse.hawkbit.event.EventProtoStuffMessageConverter;
import org.eclipse.hawkbit.repository.event.TenantAwareEvent;
import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.support.MutableMessageHeaders;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import tools.jackson.databind.json.JsonMapper;
/**
* Test the remote entity events.
@@ -25,33 +27,27 @@ import org.springframework.messaging.MessageHeaders;
@SuppressWarnings("java:S6813") // constructor injects are not possible for test classes
public abstract class AbstractRemoteEventTest extends AbstractJpaIntegrationTest {
private EventProtoStuffMessageConverter eventProtoStuffMessageConverter = new EventProtoStuffMessageConverter();
private EventJacksonMessageConverter jacksonMessageConverter = new EventJacksonMessageConverter();
private final EventProtoStuffMessageConverter eventProtoStuffMessageConverter = new EventProtoStuffMessageConverter();
private EventJacksonMessageConverter jacksonMessageConverter;
@Autowired
void setJsonMapper(final JsonMapper jsonMapper) {
jacksonMessageConverter = new EventJacksonMessageConverter(jsonMapper);
}
@SuppressWarnings("unchecked")
protected <T extends TenantAwareEvent> T createJacksonEvent(final T event) {
final Message<?> message = createJsonMessage(event);
return (T) jacksonMessageConverter.fromMessage(message, event.getClass());
return (T) jacksonMessageConverter.fromMessage(
jacksonMessageConverter.toMessage
(event, new MutableMessageHeaders(Map.of(CONTENT_TYPE, EventJacksonMessageConverter.APPLICATION_REMOTE_EVENT_JSON))),
event.getClass());
}
@SuppressWarnings("unchecked")
protected <T extends TenantAwareEvent> T createProtoStuffEvent(final T event) {
final Message<?> message = createProtoStuffMessage(event);
return (T) eventProtoStuffMessageConverter.fromMessage(message, event.getClass());
return (T) eventProtoStuffMessageConverter.fromMessage(
eventProtoStuffMessageConverter.toMessage(
event, new MutableMessageHeaders(Map.of(CONTENT_TYPE, EventProtoStuffMessageConverter.APPLICATION_BINARY_PROTOSTUFF))),
event.getClass());
}
private Message<?> createProtoStuffMessage(final TenantAwareEvent event) {
return eventProtoStuffMessageConverter.toMessage(
event, new MutableMessageHeaders(Map.of(MessageHeaders.CONTENT_TYPE,
EventProtoStuffMessageConverter.APPLICATION_BINARY_PROTOSTUFF))
);
}
private Message<?> createJsonMessage(final Object event) {
return jacksonMessageConverter.toMessage(event, new MutableMessageHeaders(Map.of(MessageHeaders.CONTENT_TYPE,
EventJacksonMessageConverter.APPLICATION_REMOTE_EVENT_JSON)));
}
}

View File

@@ -58,7 +58,7 @@ import org.eclipse.hawkbit.repository.test.util.AbstractIntegrationTest;
import org.eclipse.hawkbit.repository.test.util.RolloutTestApprovalStrategy;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.boot.jpa.autoconfigure.JpaProperties;
import org.springframework.data.domain.Page;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.orm.jpa.vendor.Database;

View File

@@ -109,7 +109,7 @@ class SystemExecutionTest extends AbstractAccessControllerManagementTest {
for (final Operation operation : Operation.values()) {
accessController.appendAccessRules(operation, mock);
}
verify(mock, times(times)).and(any()); // once for every access controller is scoped only
verify(mock, times(times)).and(any(Specification.class)); // once for every access controller is scoped only
final Specification mockAsSystem = mock(Specification.class);
for (final Operation operation : Operation.values()) {

View File

@@ -11,11 +11,12 @@ package org.eclipse.hawkbit.repository.jpa.management;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
@@ -25,7 +26,6 @@ import java.util.concurrent.Callable;
import jakarta.validation.ConstraintViolationException;
import org.apache.commons.io.IOUtils;
import org.eclipse.hawkbit.artifact.exception.ArtifactBinaryNotFoundException;
import org.eclipse.hawkbit.artifact.exception.FileSizeQuotaExceededException;
import org.eclipse.hawkbit.artifact.exception.StorageQuotaExceededException;
@@ -81,12 +81,12 @@ class ArtifactManagementTest extends AbstractJpaIntegrationTest {
final int artifactSize = artifactData.length();
verifyThrownExceptionBy(
() -> artifactManagement.create(new ArtifactUpload(
IOUtils.toInputStream(artifactData, "UTF-8"),
new ByteArrayInputStream(artifactData.getBytes(StandardCharsets.UTF_8)),
null, artifactSize, null, NOT_EXIST_IDL, "xxx", false)), "SoftwareModule");
verifyThrownExceptionBy(
() -> artifactManagement.create(new ArtifactUpload(
IOUtils.toInputStream(artifactData, "UTF-8"),
new ByteArrayInputStream(artifactData.getBytes(StandardCharsets.UTF_8)),
null, artifactSize, null, NOT_EXIST_IDL, "xxx", false)), "SoftwareModule");
verifyThrownExceptionBy(() -> artifactManagement.delete(NOT_EXIST_IDL), "Artifact");
@@ -143,7 +143,8 @@ class ArtifactManagementTest extends AbstractJpaIntegrationTest {
final long smID = softwareModuleRepository.save(new JpaSoftwareModule(osType, "smIllegalFilenameTest", "1.0")).getId();
final ArtifactUpload artifactUpload = new ArtifactUpload(
IOUtils.toInputStream(artifactData, "UTF-8"), null, artifactSize, null, smID, illegalFilename, false);
new ByteArrayInputStream(artifactData.getBytes(StandardCharsets.UTF_8)), null, artifactSize, null, smID, illegalFilename,
false);
assertThatExceptionOfType(ConstraintViolationException.class).isThrownBy(() -> artifactManagement.create(artifactUpload));
assertThat(softwareModuleManagement.get(smID).getArtifacts()).isEmpty();
}
@@ -542,9 +543,7 @@ class ArtifactManagementTest extends AbstractJpaIntegrationTest {
private void assertEqualFileContents(final ArtifactStream artifact, final byte[] randomBytes) {
try (final InputStream inputStream = artifact) {
assertTrue(
IOUtils.contentEquals(new ByteArrayInputStream(randomBytes), inputStream),
"The stored binary matches the given binary");
assertArrayEquals(inputStream.readAllBytes(), randomBytes);
} catch (final IOException e) {
throw new AssertionError(e);
}

View File

@@ -14,7 +14,6 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatNoException;
import static org.eclipse.hawkbit.auth.SpRole.CONTROLLER_ROLE;
import static org.eclipse.hawkbit.context.AccessContext.asSystem;
import static org.eclipse.hawkbit.repository.jpa.configuration.Constants.TX_RT_MAX;
import static org.eclipse.hawkbit.repository.model.Action.ActionType.DOWNLOAD_ONLY;
import static org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch.runAs;
import static org.eclipse.hawkbit.repository.test.util.TestdataFactory.DEFAULT_CONTROLLER_ID;
@@ -37,6 +36,7 @@ import jakarta.validation.ConstraintViolationException;
import org.assertj.core.api.Assertions;
import org.eclipse.hawkbit.auth.SpPermission;
import org.eclipse.hawkbit.ql.jpa.SpecificationBuilder;
import org.eclipse.hawkbit.repository.RepositoryProperties;
import org.eclipse.hawkbit.repository.TargetTypeManagement;
import org.eclipse.hawkbit.repository.UpdateMode;
@@ -85,6 +85,7 @@ import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.ConcurrencyFailureException;
import org.springframework.data.jpa.domain.Specification;
/**
* Feature: Component Tests - Repository<br/>
@@ -203,12 +204,12 @@ class ControllerManagementTest extends AbstractJpaIntegrationTest {
final Long actionId = getFirstAssignedActionId(assignDistributionSet(testDs, testTarget));
controllerManagement.addUpdateActionStatus(ActionStatusCreate.builder().actionId(actionId)
.status(Action.Status.RUNNING).timestamp(java.lang.System.currentTimeMillis()).messages(List.of("proceeding message 1"))
.status(Action.Status.RUNNING).timestamp(System.currentTimeMillis()).messages(List.of("proceeding message 1"))
.build());
waitNextMillis();
controllerManagement.addUpdateActionStatus(ActionStatusCreate.builder().actionId(actionId)
.status(Action.Status.RUNNING).timestamp(java.lang.System.currentTimeMillis()).messages(List.of("proceeding message 2"))
.status(Action.Status.RUNNING).timestamp(System.currentTimeMillis()).messages(List.of("proceeding message 2"))
.build());
final List<String> messages = controllerManagement.getActionHistoryMessages(actionId, 2);
@@ -242,7 +243,7 @@ class ControllerManagementTest extends AbstractJpaIntegrationTest {
assertThat(actionId1).isNotNull();
final ActionStatusCreateBuilder status = ActionStatusCreate.builder().actionId(actionId1).status(Status.WARNING);
for (int i = 0; i < maxStatusEntries; i++) {
controllerManagement.addInformationalActionStatus(status.messages(List.of("Msg " + i)).timestamp(java.lang.System.currentTimeMillis()).build());
controllerManagement.addInformationalActionStatus(status.messages(List.of("Msg " + i)).timestamp(System.currentTimeMillis()).build());
}
final ActionStatusCreate actionStatusCreate = status.build();
assertThatExceptionOfType(AssignmentQuotaExceededException.class)
@@ -254,7 +255,7 @@ class ControllerManagementTest extends AbstractJpaIntegrationTest {
assertThat(actionId2).isNotEqualTo(actionId1);
final ActionStatusCreateBuilder statusWarning = ActionStatusCreate.builder().actionId(actionId2).status(Status.WARNING);
for (int i = 0; i < maxStatusEntries; i++) {
controllerManagement.addUpdateActionStatus(statusWarning.messages(List.of("Msg " + i)).timestamp(java.lang.System.currentTimeMillis()).build());
controllerManagement.addUpdateActionStatus(statusWarning.messages(List.of("Msg " + i)).timestamp(System.currentTimeMillis()).build());
}
final ActionStatusCreate actionStatusCreateQE = statusWarning.build();
assertThatExceptionOfType(AssignmentQuotaExceededException.class)
@@ -858,7 +859,7 @@ class ControllerManagementTest extends AbstractJpaIntegrationTest {
@Test
void findOrRegisterTargetIfItDoesNotExistThrowsExceptionAfterMaxRetries() {
final TargetRepository mockTargetRepository = Mockito.mock(TargetRepository.class);
when(mockTargetRepository.findOne(any())).thenThrow(ConcurrencyFailureException.class);
when(mockTargetRepository.findOne(any(Specification.class))).thenThrow(ConcurrencyFailureException.class);
((JpaControllerManagement) controllerManagement).setTargetRepository(mockTargetRepository);
try {
@@ -866,7 +867,7 @@ class ControllerManagementTest extends AbstractJpaIntegrationTest {
.as("Expected an ConcurrencyFailureException to be thrown!")
.isThrownBy(() -> controllerManagement.findOrRegisterTargetIfItDoesNotExist("AA", LOCALHOST));
verify(mockTargetRepository, times(TX_RT_MAX)).findOne(any());
verify(mockTargetRepository, times(10 /* default retry max */+ 1)).findOne(any(Specification.class));
} finally {
// revert
((JpaControllerManagement) controllerManagement).setTargetRepository(targetRepository);
@@ -887,14 +888,14 @@ class ControllerManagementTest extends AbstractJpaIntegrationTest {
((JpaControllerManagement) controllerManagement).setTargetRepository(mockTargetRepository);
final Target target = testdataFactory.createTarget();
when(mockTargetRepository.findOne(any())).thenThrow(ConcurrencyFailureException.class)
when(mockTargetRepository.findOne(any(Specification.class))).thenThrow(ConcurrencyFailureException.class)
.thenThrow(ConcurrencyFailureException.class).thenReturn(Optional.of((JpaTarget) target));
when(mockTargetRepository.save(any())).thenReturn(target);
try {
final Target targetFromControllerManagement = controllerManagement
.findOrRegisterTargetIfItDoesNotExist(target.getControllerId(), LOCALHOST);
verify(mockTargetRepository, times(3)).findOne(any());
verify(mockTargetRepository, times(3)).findOne(any(Specification.class));
verify(mockTargetRepository, times(1)).save(any());
assertThat(target).isEqualTo(targetFromControllerManagement);
} finally {
@@ -937,14 +938,14 @@ class ControllerManagementTest extends AbstractJpaIntegrationTest {
final TargetRepository mockTargetRepository = Mockito.mock(TargetRepository.class);
((JpaControllerManagement) controllerManagement).setTargetRepository(mockTargetRepository);
when(mockTargetRepository.findOne(any())).thenReturn(Optional.empty());
when(mockTargetRepository.findOne(any(Specification.class))).thenReturn(Optional.empty());
when(mockTargetRepository.save(any())).thenThrow(EntityAlreadyExistsException.class);
try {
assertThatExceptionOfType(EntityAlreadyExistsException.class)
.as("Expected an EntityAlreadyExistsException to be thrown!")
.isThrownBy(() -> controllerManagement.findOrRegisterTargetIfItDoesNotExist("1234", LOCALHOST));
verify(mockTargetRepository, times(1)).findOne(any());
verify(mockTargetRepository, times(1)).findOne(any(Specification.class));
verify(mockTargetRepository, times(1)).save(any());
} finally {
// revert
@@ -962,13 +963,13 @@ class ControllerManagementTest extends AbstractJpaIntegrationTest {
final TargetRepository mockTargetRepository = Mockito.mock(TargetRepository.class);
((JpaControllerManagement) controllerManagement).setTargetRepository(mockTargetRepository);
when(mockTargetRepository.findOne(any())).thenThrow(RuntimeException.class);
when(mockTargetRepository.findOne(any(Specification.class))).thenThrow(RuntimeException.class);
try {
assertThatExceptionOfType(RuntimeException.class).as("Expected a RuntimeException to be thrown!")
.isThrownBy(() -> controllerManagement.findOrRegisterTargetIfItDoesNotExist("aControllerId",
LOCALHOST));
verify(mockTargetRepository, times(1)).findOne(any());
verify(mockTargetRepository, times(1)).findOne(any(Specification.class));
} finally {
// revert
((JpaControllerManagement) controllerManagement).setTargetRepository(targetRepository);

View File

@@ -211,7 +211,6 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest {
*/
@Test
void assertMaxActionsPerTargetQuotaIsEnforced() {
final int maxActions = quotaManagement.getMaxActionsPerTarget();
final Target testTarget = testdataFactory.createTarget();
final List<Long> dsIds = new ArrayList<>();

View File

@@ -25,7 +25,6 @@ import java.util.stream.Stream;
import jakarta.validation.ConstraintViolationException;
import jakarta.validation.ValidationException;
import lombok.extern.slf4j.Slf4j;
import org.assertj.core.api.Assertions;
import org.assertj.core.api.Condition;
import org.eclipse.hawkbit.auth.SpPermission;
@@ -118,7 +117,8 @@ class RolloutManagementTest extends AbstractJpaIntegrationTest {
testdataFactory.createTargets(targetPrefix, 0, amountGroups * 2);
final Rollout dynamicRollout = testdataFactory.createRolloutByVariables("dynamic", "static rollout", amountGroups,
"controllerid==" + targetPrefix + "*", distributionSet, "0", RolloutGroup.RolloutGroupSuccessAction.NEXTGROUP, "30", ActionType.FORCED, 1000, false, true);
"controllerid==" + targetPrefix + "*", distributionSet, "0", RolloutGroup.RolloutGroupSuccessAction.NEXTGROUP, "30",
ActionType.FORCED, 1000, false, true);
rolloutManagement.start(dynamicRollout.getId());
rolloutHandler.handleAll();
assertRollout(dynamicRollout, true, RolloutStatus.RUNNING, amountGroups + 1, amountGroups * 2);
@@ -153,7 +153,8 @@ class RolloutManagementTest extends AbstractJpaIntegrationTest {
testdataFactory.createTargets(targetPrefix, amountGroups * 2, amountGroups);
final Rollout staticRollout = testdataFactory.createRolloutByVariables("static", "static rollout", amountGroups,
"controllerid==" + targetPrefix + "*", distributionSet, "0", RolloutGroup.RolloutGroupSuccessAction.NEXTGROUP,"30", ActionType.FORCED, 0, false, false);
"controllerid==" + targetPrefix + "*", distributionSet, "0", RolloutGroup.RolloutGroupSuccessAction.NEXTGROUP, "30",
ActionType.FORCED, 0, false, false);
rolloutManagement.start(staticRollout.getId());
rolloutHandler.handleAll();
assertRollout(staticRollout, false, RolloutStatus.RUNNING, amountGroups, amountGroups * 3);

View File

@@ -217,7 +217,7 @@ class TargetManagementTest extends AbstractRepositoryManagementWithMetadataTest<
createTargetWithAttributes("4711");
final long current = java.lang.System.currentTimeMillis();
final long current = System.currentTimeMillis();
controllerManagement.findOrRegisterTargetIfItDoesNotExist("4711", LOCALHOST);
final DistributionSetAssignmentResult result = assignDistributionSet(testDs1.getId(), "4711");

View File

@@ -34,6 +34,7 @@ import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
import org.eclipse.hawkbit.repository.model.TargetType;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
import org.jspecify.annotations.NonNull;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
@@ -185,23 +186,20 @@ class AutoAssignHandlerIntTest extends AbstractJpaIntegrationTest {
*/
@ParameterizedTest
@MethodSource("confirmationOptions")
void checkAutoAssignWithConfirmationOptions(final boolean confirmationFlowActive, final boolean confirmationRequired,
final Action.Status expectedStatus) {
final DistributionSet distributionSet = testdataFactory.createDistributionSet("dsA");
void checkAutoAssignWithConfirmationOptions(
final boolean confirmationFlowActive, final boolean confirmationRequired, final Action.Status expectedStatus) {
if (confirmationFlowActive) {
enableConfirmationFlow();
}
final DistributionSet distributionSet = testdataFactory.createDistributionSet("dsA");
targetFilterQueryManagement.updateAutoAssignDS(
new AutoAssignDistributionSetUpdate(targetFilterQueryManagement
.create(Create.builder().name("filterA").query("name==*").build()).getId())
.ds(distributionSet.getId()).confirmationRequired(confirmationRequired));
final String targetDsAIdPref = "targ";
final List<Target> targets = testdataFactory.createTargets(20, targetDsAIdPref,
targetDsAIdPref.concat(SPACE_AND_DESCRIPTION));
final List<Target> targets = testdataFactory.createTargets(20, targetDsAIdPref, targetDsAIdPref.concat(SPACE_AND_DESCRIPTION));
// Run the check
autoAssignChecker.handleAll();
@@ -214,15 +212,13 @@ class AutoAssignHandlerIntTest extends AbstractJpaIntegrationTest {
*/
@ParameterizedTest
@MethodSource("confirmationOptions")
void checkAutoAssignmentForDeviceWithConfirmationRequired(final boolean confirmationFlowActive,
final boolean confirmationRequired, final Action.Status expectedStatus) {
final DistributionSet toAssignDs = testdataFactory.createDistributionSet();
void checkAutoAssignmentForDeviceWithConfirmationRequired(
final boolean confirmationFlowActive, final boolean confirmationRequired, final Action.Status expectedStatus) {
if (confirmationFlowActive) {
enableConfirmationFlow();
}
final DistributionSet toAssignDs = testdataFactory.createDistributionSet();
// target filter query that matches all targets
targetFilterQueryManagement.updateAutoAssignDS(
new AutoAssignDistributionSetUpdate(targetFilterQueryManagement
@@ -243,7 +239,6 @@ class AutoAssignHandlerIntTest extends AbstractJpaIntegrationTest {
*/
@Test
void checkAutoAssignWithFailures() {
// incomplete distribution set that will be assigned
final DistributionSet setF = distributionSetManagement.create(DistributionSetManagement.Create.builder()
.type(testdataFactory.findOrCreateDefaultTestDsType())
@@ -255,23 +250,21 @@ class AutoAssignHandlerIntTest extends AbstractJpaIntegrationTest {
final String targetDsAIdPref = "targA";
final String targetDsFIdPref = "targB";
final Long filterId = targetFilterQueryManagement.create(
Create.builder().name("filterA").query("id==" + targetDsFIdPref + "*").build())
.getId();
final Long filterId = targetFilterQueryManagement
.create(Create.builder().name("filterA").query("id==" + targetDsFIdPref + "*").build()).getId();
final AutoAssignDistributionSetUpdate targetFilterQuery = new AutoAssignDistributionSetUpdate(filterId).ds(setF.getId());
// target filter query that matches first bunch of targets, that should fail
assertThatExceptionOfType(IncompleteDistributionSetException.class).isThrownBy(
() -> targetFilterQueryManagement.updateAutoAssignDS(targetFilterQuery));
assertThatExceptionOfType(IncompleteDistributionSetException.class)
.isThrownBy(() -> targetFilterQueryManagement.updateAutoAssignDS(targetFilterQuery));
// target filter query that matches failed bunch of targets
targetFilterQueryManagement.create(Create.builder().name("filterB")
.query("id==" + targetDsAIdPref + "*").autoAssignDistributionSet(setA).build());
targetFilterQueryManagement.create(Create.builder()
.name("filterB").query("id==" + targetDsAIdPref + "*").autoAssignDistributionSet(setA)
.build());
implicitLock(setA);
final List<Target> targetsF = testdataFactory.createTargets(10, targetDsFIdPref,
targetDsFIdPref.concat(SPACE_AND_DESCRIPTION));
final List<Target> targetsF = testdataFactory.createTargets(10, targetDsFIdPref, targetDsFIdPref.concat(SPACE_AND_DESCRIPTION));
final List<Target> targetsA = testdataFactory.createTargets(10, targetDsAIdPref,
targetDsAIdPref.concat(SPACE_AND_DESCRIPTION));
final List<Target> targetsA = testdataFactory.createTargets(10, targetDsAIdPref, targetDsAIdPref.concat(SPACE_AND_DESCRIPTION));
final int targetsCount = targetsA.size() + targetsF.size();
@@ -288,7 +281,6 @@ class AutoAssignHandlerIntTest extends AbstractJpaIntegrationTest {
// all targets of A group should have received setA
verifyThatTargetsHaveDistributionSetAssignment(setA, targetsA, targetsCount);
}
/**
@@ -448,16 +440,13 @@ class AutoAssignHandlerIntTest extends AbstractJpaIntegrationTest {
.isEqualTo(targetFilterQuery.getAutoAssignInitiatedBy()));
}
private List<Target> createTargetsAndAutoAssignDistSet(final String prefix, final int targetCount,
final DistributionSet distributionSet, final ActionType actionType) {
final List<Target> targets = testdataFactory.createTargets(targetCount, "target" + prefix,
prefix.concat(SPACE_AND_DESCRIPTION));
private List<Target> createTargetsAndAutoAssignDistSet(
final String prefix, final int targetCount, final DistributionSet distributionSet, final ActionType actionType) {
final List<Target> targets = testdataFactory.createTargets(targetCount, "target" + prefix, prefix.concat(SPACE_AND_DESCRIPTION));
targetFilterQueryManagement.create(Create.builder()
.name("filter" + prefix).query("id==target" + prefix + "*")
.autoAssignDistributionSet(distributionSet).autoAssignActionType(actionType)
.build());
return targets;
}
@@ -466,12 +455,12 @@ class AutoAssignHandlerIntTest extends AbstractJpaIntegrationTest {
.map(Target::getControllerId)
.flatMap(controllerId -> deploymentManagement.findActionsByTarget(controllerId, PAGE).getContent().stream())
.toList();
assertThat(actions).hasSize(targets.size());
assertThat(actions).allMatch(action -> action.getActionType().equals(actionType));
}
private Slice<Action> findActionsByDistributionSet(final Pageable pageable, final long distributionSetId) {
private Slice<@NonNull Action> findActionsByDistributionSet(final Pageable pageable, final long distributionSetId) {
return actionRepository
.findAll(byDistributionSetId(distributionSetId), pageable)
.map(Action.class::cast);

View File

@@ -58,8 +58,8 @@ logging.level.org.eclipse.persistence=${NOISE_SUPPRESS_LEVEL}
## enable / disable case sensitiveness of the DB when playing around
#hawkbit.ql.case-insensitive-db=true
hawkbit.repository.cluster.lock.ttl=3000
hawkbit.repository.cluster.lock.refreshOnRemainMS=2000
hawkbit.repository.cluster.lock.ttl=1000
hawkbit.repository.cluster.lock.refreshOnRemainMS=200
hawkbit.repository.cluster.lock.refreshOnRemainPercent=10
## reduce scheduler tic period to speed up tests
hawkbit.repository.cluster.lock.ticPeriodMS=10