Code format hawkbit-repository-jpa (#1928)

Signed-off-by: Marinov Avgustin <Avgustin.Marinov@bosch.com>
This commit is contained in:
Avgustin Marinov
2024-11-05 09:54:34 +02:00
committed by GitHub
parent da7fa9e022
commit ef857baa9e
371 changed files with 12299 additions and 12525 deletions

View File

@@ -1,3 +1,4 @@
# hawkBit JPA implementation
JPA implementation of the repository. Based on [EclipseLink](http://www.eclipse.org/eclipselink/) and [Spring Data Jpa](http://projects.spring.io/spring-data-jpa/).
JPA implementation of the repository. Based on [EclipseLink](http://www.eclipse.org/eclipselink/)
and [Spring Data Jpa](http://projects.spring.io/spring-data-jpa/).

View File

@@ -9,7 +9,7 @@
SPDX-License-Identifier: EPL-2.0
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>

View File

@@ -25,7 +25,6 @@ import org.springframework.stereotype.Service;
* the bean by the implemented interfaces. So introduce a single interface for
* the {@link JpaSystemManagement} implementation to allow it to register the
* key-generator bean.
*
*/
@FunctionalInterface
public interface CurrentTenantCacheKeyGenerator {

View File

@@ -36,8 +36,7 @@ public class CustomBaseRepositoryFactoryBean<T extends Repository<S, ID>, S, ID>
* Creates a new {@link JpaRepositoryFactoryBean} for the given repository
* interface.
*
* @param repositoryInterface
* must not be {@literal null}.
* @param repositoryInterface must not be {@literal null}.
*/
public CustomBaseRepositoryFactoryBean(final Class<? extends T> repositoryInterface) {
super(repositoryInterface);

View File

@@ -57,6 +57,30 @@ public class DefaultRolloutApprovalStrategy implements RolloutApprovalStrategy {
return isApprovalEnabled() && hasNoApproveRolloutPermission(getActorAuthorities(rollout));
}
/***
* Per default do nothing.
*
* @param rollout
* rollout to create approval task for.
*/
@Override
public void onApprovalRequired(final Rollout rollout) {
// do nothing per default, can be extended by further implementations.
}
@Override
public String getApprovalUser(final Rollout rollout) {
return getCurrentAuthentication().getName();
}
private static Authentication getCurrentAuthentication() {
return SecurityContextHolder.getContext().getAuthentication();
}
private static boolean hasNoApproveRolloutPermission(final Collection<String> authorities) {
return authorities.stream().noneMatch(SpPermission.APPROVE_ROLLOUT::equals);
}
private boolean isApprovalEnabled() {
return systemSecurityContext.runAsSystem(() -> tenantConfigurationManagement
.getConfigurationValue(TenantConfigurationKey.ROLLOUT_APPROVAL_ENABLED, Boolean.class).getValue());
@@ -78,28 +102,4 @@ public class DefaultRolloutApprovalStrategy implements RolloutApprovalStrategy {
return ((User) getCurrentAuthentication().getPrincipal()).getAuthorities().stream()
.map(GrantedAuthority::getAuthority).toList();
}
private static Authentication getCurrentAuthentication() {
return SecurityContextHolder.getContext().getAuthentication();
}
private static boolean hasNoApproveRolloutPermission(final Collection<String> authorities) {
return authorities.stream().noneMatch(SpPermission.APPROVE_ROLLOUT::equals);
}
/***
* Per default do nothing.
*
* @param rollout
* rollout to create approval task for.
*/
@Override
public void onApprovalRequired(final Rollout rollout) {
// do nothing per default, can be extended by further implementations.
}
@Override
public String getApprovalUser(final Rollout rollout) {
return getCurrentAuthentication().getName();
}
}

View File

@@ -28,8 +28,7 @@ public interface EntityInterceptor {
/**
* Callback for the {@link PrePersist} lifecycle event.
*
* @param entity
* the model entity
* @param entity the model entity
*/
default void prePersist(final Object entity) {
}
@@ -37,8 +36,7 @@ public interface EntityInterceptor {
/**
* Callback for the {@link PostPersist} lifecycle event.
*
* @param entity
* the model entity
* @param entity the model entity
*/
default void postPersist(final Object entity) {
}
@@ -46,8 +44,7 @@ public interface EntityInterceptor {
/**
* Callback for the {@link PostRemove} lifecycle event.
*
* @param entity
* the model entity
* @param entity the model entity
*/
default void postRemove(final Object entity) {
}
@@ -55,8 +52,7 @@ public interface EntityInterceptor {
/**
* Callback for the {@link PreRemove} lifecycle event.
*
* @param entity
* the model entity
* @param entity the model entity
*/
default void preRemove(final Object entity) {
}
@@ -64,8 +60,7 @@ public interface EntityInterceptor {
/**
* Callback for the {@link PostLoad} lifecycle event.
*
* @param entity
* the model entity
* @param entity the model entity
*/
default void postLoad(final Object entity) {
}
@@ -73,8 +68,7 @@ public interface EntityInterceptor {
/**
* Callback for the {@link PreUpdate} lifecycle event.
*
* @param entity
* the model entity
* @param entity the model entity
*/
default void preUpdate(final Object entity) {
}
@@ -82,8 +76,7 @@ public interface EntityInterceptor {
/**
* Callback for the {@link PostUpdate} lifecycle event.
*
* @param entity
* the model entity
* @param entity the model entity
*/
default void postUpdate(final Object entity) {
}

View File

@@ -47,9 +47,9 @@ import org.springframework.orm.jpa.vendor.EclipseLinkJpaDialect;
* <p>
* 3.b.) the cause is not an {@link SQLException} and as a result cannot be
* mapped.
*
*/
public class HawkBitEclipseLinkJpaDialect extends EclipseLinkJpaDialect {
private static final long serialVersionUID = 1L;
private static final SQLStateSQLExceptionTranslator SQLSTATE_EXCEPTION_TRANSLATOR = new SQLStateSQLExceptionTranslator();

View File

@@ -26,7 +26,6 @@ import org.eclipse.hawkbit.repository.jpa.builder.JpaActionStatusBuilder;
import org.eclipse.hawkbit.repository.jpa.builder.JpaRolloutGroupBuilder;
import org.eclipse.hawkbit.repository.jpa.builder.JpaSoftwareModuleTypeBuilder;
import org.eclipse.hawkbit.repository.jpa.builder.JpaTagBuilder;
import org.eclipse.hawkbit.repository.jpa.builder.JpaTargetBuilder;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetMetadata;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetMetadata;
import org.eclipse.hawkbit.repository.model.MetaData;
@@ -36,7 +35,6 @@ import org.springframework.validation.annotation.Validated;
/**
* JPA Implementation of {@link EntityFactory}.
*
*/
@Validated
public class JpaEntityFactory implements EntityFactory {
@@ -65,6 +63,16 @@ public class JpaEntityFactory implements EntityFactory {
@Autowired
private TargetTypeBuilder targetTypeBuilder;
@Override
public ActionStatusBuilder actionStatus() {
return new JpaActionStatusBuilder();
}
@Override
public DistributionSetBuilder distributionSet() {
return distributionSetBuilder;
}
@Override
public MetaData generateDsMetadata(final String key, final String value) {
return new JpaDistributionSetMetadata(key, StringUtils.trimWhitespace(value));
@@ -76,23 +84,8 @@ public class JpaEntityFactory implements EntityFactory {
}
@Override
public DistributionSetTypeBuilder distributionSetType() {
return distributionSetTypeBuilder;
}
@Override
public DistributionSetBuilder distributionSet() {
return distributionSetBuilder;
}
@Override
public TargetBuilder target() {
return targetBuilder;
}
@Override
public TargetTypeBuilder targetType() {
return targetTypeBuilder;
public SoftwareModuleMetadataBuilder softwareModuleMetadata() {
return softwareModuleMetadataBuilder;
}
@Override
@@ -101,8 +94,18 @@ public class JpaEntityFactory implements EntityFactory {
}
@Override
public TargetFilterQueryBuilder targetFilterQuery() {
return targetFilterQueryBuilder;
public RolloutGroupBuilder rolloutGroup() {
return new JpaRolloutGroupBuilder();
}
@Override
public DistributionSetTypeBuilder distributionSetType() {
return distributionSetTypeBuilder;
}
@Override
public RolloutBuilder rollout() {
return rolloutBuilder;
}
@Override
@@ -116,23 +119,18 @@ public class JpaEntityFactory implements EntityFactory {
}
@Override
public ActionStatusBuilder actionStatus() {
return new JpaActionStatusBuilder();
public TargetBuilder target() {
return targetBuilder;
}
@Override
public RolloutBuilder rollout() {
return rolloutBuilder;
public TargetTypeBuilder targetType() {
return targetTypeBuilder;
}
@Override
public RolloutGroupBuilder rolloutGroup() {
return new JpaRolloutGroupBuilder();
}
@Override
public SoftwareModuleMetadataBuilder softwareModuleMetadata() {
return softwareModuleMetadataBuilder;
public TargetFilterQueryBuilder targetFilterQuery() {
return targetFilterQueryBuilder;
}
}

View File

@@ -15,10 +15,7 @@ import java.util.Optional;
import jakarta.persistence.EntityManager;
import org.eclipse.hawkbit.repository.jpa.acm.AccessController;
import org.eclipse.hawkbit.repository.jpa.model.AbstractJpaBaseEntity;
import org.eclipse.hawkbit.repository.jpa.model.AbstractJpaTenantAwareBaseEntity;
import org.eclipse.hawkbit.repository.jpa.repository.BaseEntityRepository;
import org.eclipse.hawkbit.repository.jpa.repository.NoCountSliceRepository;
import org.eclipse.hawkbit.repository.jpa.specifications.SpecificationsBuilder;
import org.springframework.data.domain.Page;
@@ -36,6 +33,7 @@ import org.springframework.util.StringUtils;
* A collection of static helper methods for the management classes
*/
public final class JpaManagementHelper {
private JpaManagementHelper() {
}

View File

@@ -9,6 +9,8 @@
*/
package org.eclipse.hawkbit.repository.jpa;
import static org.eclipse.hawkbit.repository.jpa.builder.JpaRolloutGroupCreate.addSuccessAndErrorConditionsAndActions;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;
import java.util.Arrays;
@@ -73,8 +75,6 @@ import org.springframework.data.domain.Sort.Direction;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionException;
import static org.eclipse.hawkbit.repository.jpa.builder.JpaRolloutGroupCreate.addSuccessAndErrorConditionsAndActions;
/**
* A Jpa implementation of {@link RolloutExecutor}
*/
@@ -101,28 +101,25 @@ public class JpaRolloutExecutor implements RolloutExecutor {
*/
private static final List<Status> DOWNLOAD_ONLY_ACTION_TERMINATION_STATUSES = Arrays.asList(Status.ERROR,
Status.FINISHED, Status.CANCELED, Status.DOWNLOADED);
private static final Comparator<RolloutGroup> DESC_COMP = Comparator.comparingLong(RolloutGroup::getId).reversed();
private final ActionRepository actionRepository;
private final RolloutGroupRepository rolloutGroupRepository;
private final RolloutTargetGroupRepository rolloutTargetGroupRepository;
private final RolloutRepository rolloutRepository;
private final TargetManagement targetManagement;
private final DeploymentManagement deploymentManagement;
private final RolloutGroupManagement rolloutGroupManagement;
private final RolloutManagement rolloutManagement;
private final QuotaManagement quotaManagement;
private final RolloutGroupEvaluationManager evaluationManager;
private final RolloutApprovalStrategy rolloutApprovalStrategy;
private final EntityManager entityManager;
private final PlatformTransactionManager txManager;
private final AfterTransactionCommitExecutor afterCommit;
private final EventPublisherHolder eventPublisherHolder;
private final TenantAware tenantAware;
private final RepositoryProperties repositoryProperties;
private final Map<Long, AtomicLong> lastDynamicGroupFill = new ConcurrentHashMap<>();
public JpaRolloutExecutor(
final ActionRepository actionRepository, final RolloutGroupRepository rolloutGroupRepository,
@@ -417,7 +414,6 @@ public class JpaRolloutExecutor implements RolloutExecutor {
return groupsActiveLeft == 0;
}
private static final Comparator<RolloutGroup> DESC_COMP = Comparator.comparingLong(RolloutGroup::getId).reversed();
private void executeLatestRolloutGroup(final JpaRollout rollout) {
// was - rolloutGroupRepository.findByRolloutAndStatusNotOrderByIdDesc(rollout, RolloutGroupStatus.SCHEDULED);
final List<JpaRolloutGroup> latestRolloutGroup = rollout.getRolloutGroups().stream()
@@ -435,10 +431,10 @@ public class JpaRolloutExecutor implements RolloutExecutor {
// so the evaluation to use total targets to properly
private RolloutGroup evalProxy(final RolloutGroup group) {
if (group.isDynamic()) {
final int expected = Math.max((int)group.getTargetPercentage(), 1);
final int expected = Math.max((int) group.getTargetPercentage(), 1);
return (RolloutGroup) Proxy.newProxyInstance(
RolloutGroup.class.getClassLoader(),
new Class<?>[] {RolloutGroup.class},
new Class<?>[] { RolloutGroup.class },
(proxy, method, args) -> {
if ("getTotalTargets".equals(method.getName())) {
return expected;
@@ -677,7 +673,6 @@ public class JpaRolloutExecutor implements RolloutExecutor {
});
}
private final Map<Long, AtomicLong> lastDynamicGroupFill = new ConcurrentHashMap<>();
// return if group change is made
private boolean fillDynamicRolloutGroupsWithTargets(final JpaRollout rollout) {
final AtomicLong lastFill = lastDynamicGroupFill.computeIfAbsent(rollout.getId(), id -> new AtomicLong(0));
@@ -690,9 +685,9 @@ public class JpaRolloutExecutor implements RolloutExecutor {
RolloutHelper.verifyRolloutInStatus(rollout, RolloutStatus.RUNNING);
final List<RolloutGroup> rolloutGroups = rollout.getRolloutGroups();
final JpaRolloutGroup group = (JpaRolloutGroup)rolloutGroups.get(rolloutGroups.size() - 1);
final JpaRolloutGroup group = (JpaRolloutGroup) rolloutGroups.get(rolloutGroups.size() - 1);
final long expectedInGroup = Math.max((int)group.getTargetPercentage(), 1);
final long expectedInGroup = Math.max((int) group.getTargetPercentage(), 1);
final long currentlyInGroup = group.getTotalTargets();
if (currentlyInGroup >= expectedInGroup || group.getStatus() == RolloutGroupStatus.FINISHED) {
// the last one is full. create new and start filling it on the next iteration
@@ -741,7 +736,8 @@ public class JpaRolloutExecutor implements RolloutExecutor {
return false;
}
private void createDynamicGroup(final JpaRollout rollout, final RolloutGroup lastGroup, final int groupCount, final RolloutGroupStatus status) {
private void createDynamicGroup(final JpaRollout rollout, final RolloutGroup lastGroup, final int groupCount,
final RolloutGroupStatus status) {
try {
RolloutHelper.verifyRolloutGroupAmount(groupCount + 1, quotaManagement);
} catch (final AssignmentQuotaExceededException e) {
@@ -755,7 +751,8 @@ public class JpaRolloutExecutor implements RolloutExecutor {
final JpaRolloutGroup group = new JpaRolloutGroup();
final String lastGroupWithoutSuffix = "group-" + groupCount;
final String suffix = lastGroup.getName().startsWith(lastGroupWithoutSuffix) ? lastGroup.getName().substring(lastGroupWithoutSuffix.length()) : "";
final String suffix = lastGroup.getName().startsWith(lastGroupWithoutSuffix) ? lastGroup.getName()
.substring(lastGroupWithoutSuffix.length()) : "";
final String nameAndDesc = "group-" + (groupCount + 1) + suffix;
group.setName(nameAndDesc);
group.setDescription(nameAndDesc);

View File

@@ -38,16 +38,11 @@ public class JpaRolloutHandler implements RolloutHandler {
/**
* Constructor
*
* @param tenantAware
* the {@link TenantAware} bean holding the tenant information
* @param rolloutManagement
* to fetch rollout related information from the datasource
* @param rolloutExecutor
* to trigger executions for a specific rollout
* @param lockRegistry
* to lock processes
* @param txManager
* transaction manager interface
* @param tenantAware the {@link TenantAware} bean holding the tenant information
* @param rolloutManagement to fetch rollout related information from the datasource
* @param rolloutExecutor to trigger executions for a specific rollout
* @param lockRegistry to lock processes
* @param txManager transaction manager interface
*/
public JpaRolloutHandler(final TenantAware tenantAware, final RolloutManagement rolloutManagement,
final RolloutExecutor rolloutExecutor, final LockRegistry lockRegistry,
@@ -83,8 +78,9 @@ public class JpaRolloutHandler implements RolloutHandler {
try {
handleRolloutInNewTransaction(rolloutId, handlerId);
} catch (final Throwable throwable) {
log.error("Failed to process rollout with id {}", rolloutId , throwable);
}});
log.error("Failed to process rollout with id {}", rolloutId, throwable);
}
});
log.debug("Finished handling of the rollouts.");
} finally {
if (log.isTraceEnabled()) {

View File

@@ -14,8 +14,9 @@ import java.util.List;
import java.util.Map;
import java.util.concurrent.ScheduledExecutorService;
import jakarta.persistence.EntityManager;
import javax.sql.DataSource;
import jakarta.persistence.EntityManager;
import jakarta.validation.Validation;
import org.eclipse.hawkbit.ContextAware;
@@ -120,11 +121,11 @@ import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetMetadataRepo
import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetRepository;
import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetTagRepository;
import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetTypeRepository;
import org.eclipse.hawkbit.repository.jpa.repository.HawkBitBaseRepository;
import org.eclipse.hawkbit.repository.jpa.repository.LocalArtifactRepository;
import org.eclipse.hawkbit.repository.jpa.repository.RolloutGroupRepository;
import org.eclipse.hawkbit.repository.jpa.repository.RolloutRepository;
import org.eclipse.hawkbit.repository.jpa.repository.RolloutTargetGroupRepository;
import org.eclipse.hawkbit.repository.jpa.repository.HawkBitBaseRepository;
import org.eclipse.hawkbit.repository.jpa.repository.SoftwareModuleMetadataRepository;
import org.eclipse.hawkbit.repository.jpa.repository.SoftwareModuleRepository;
import org.eclipse.hawkbit.repository.jpa.repository.SoftwareModuleTypeRepository;
@@ -155,9 +156,9 @@ import org.eclipse.hawkbit.repository.model.helper.EventPublisherHolder;
import org.eclipse.hawkbit.repository.model.helper.SystemManagementHolder;
import org.eclipse.hawkbit.repository.model.helper.SystemSecurityContextHolder;
import org.eclipse.hawkbit.repository.model.helper.TenantConfigurationManagementHolder;
import org.eclipse.hawkbit.repository.rsql.RsqlConfigHolder;
import org.eclipse.hawkbit.repository.rsql.RsqlValidationOracle;
import org.eclipse.hawkbit.repository.rsql.RsqlVisitorFactory;
import org.eclipse.hawkbit.repository.rsql.RsqlConfigHolder;
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer;
import org.eclipse.hawkbit.security.HawkbitSecurityProperties;
import org.eclipse.hawkbit.security.SecurityTokenGenerator;
@@ -202,7 +203,6 @@ import org.springframework.validation.beanvalidation.MethodValidationPostProcess
/**
* General configuration for hawkBit's Repository.
*
*/
@EnableJpaRepositories(value = "org.eclipse.hawkbit.repository.jpa.repository", repositoryFactoryBeanClass = CustomBaseRepositoryFactoryBean.class)
@EnableTransactionManagement
@@ -223,6 +223,106 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
super(dataSource, properties, jtaTransactionManagerProvider);
}
/**
* Defines the validation processor bean.
*
* @return the {@link MethodValidationPostProcessor}
*/
@Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
final MethodValidationPostProcessor processor = new MethodValidationPostProcessor();
// ValidatorFactory shall NOT be closed because after closing the generated Validator
// methods shall not be called - we need the validator in future
processor.setValidator(Validation.byDefaultProvider().configure()
.addProperty(BaseHibernateValidatorConfiguration.ALLOW_PARALLEL_METHODS_DEFINE_PARAMETER_CONSTRAINTS,
"true")
.buildValidatorFactory().getValidator());
return processor;
}
/**
* {@link MultiTenantJpaTransactionManager} bean.
*
* @return a new {@link PlatformTransactionManager}
* @see org.springframework.boot.autoconfigure.orm.jpa.JpaBaseConfiguration#transactionManager(ObjectProvider)
*/
@Override
@Bean
public PlatformTransactionManager transactionManager(
final ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
return new MultiTenantJpaTransactionManager();
}
@Override
protected AbstractJpaVendorAdapter createJpaVendorAdapter() {
return new EclipseLinkJpaVendorAdapter() {
private final HawkBitEclipseLinkJpaDialect jpaDialect = new HawkBitEclipseLinkJpaDialect();
@Override
public EclipseLinkJpaDialect getJpaDialect() {
return jpaDialect;
}
};
}
@Override
protected Map<String, Object> getVendorProperties() {
final Map<String, Object> properties = new HashMap<>(7);
// Turn off dynamic weaving to disable LTW lookup in static weaving mode
properties.put(PersistenceUnitProperties.WEAVING, "false");
// needed for reports
properties.put(PersistenceUnitProperties.ALLOW_NATIVE_SQL_QUERIES, "true");
// flyway
properties.put(PersistenceUnitProperties.DDL_GENERATION, "none");
// Embed into hawkBit logging
properties.put(PersistenceUnitProperties.LOGGING_LOGGER, "JavaLogger");
// Ensure that we flush only at the end of the transaction
properties.put(PersistenceUnitProperties.PERSISTENCE_CONTEXT_FLUSH_MODE, "COMMIT");
// Enable batch writing
properties.put(PersistenceUnitProperties.BATCH_WRITING, "JDBC");
// Batch size
properties.put(PersistenceUnitProperties.BATCH_WRITING_SIZE, "500");
return properties;
}
@Bean
public BeanPostProcessor entityManagerBeanPostProcessor(
@Autowired(required = false) final AccessController<JpaArtifact> artifactAccessController,
@Autowired(required = false) final AccessController<JpaSoftwareModuleType> softwareModuleTypeAccessController,
@Autowired(required = false) final AccessController<JpaSoftwareModule> softwareModuleAccessController,
@Autowired(required = false) final AccessController<JpaDistributionSetType> distributionSetTypeAccessController,
@Autowired(required = false) final AccessController<JpaDistributionSet> distributionSetAccessController,
@Autowired(required = false) final AccessController<JpaTargetType> targetTypeAccessControlManager,
@Autowired(required = false) final AccessController<JpaTarget> targetAccessControlManager,
@Autowired(required = false) final AccessController<JpaAction> actionAccessController) {
return new BeanPostProcessor() {
@Override
public Object postProcessAfterInitialization(@NonNull final Object bean, @NonNull final String beanName)
throws BeansException {
if (bean instanceof LocalArtifactRepository repo) {
return repo.withACM(artifactAccessController);
} else if (bean instanceof SoftwareModuleTypeRepository repo) {
return repo.withACM(softwareModuleTypeAccessController);
} else if (bean instanceof SoftwareModuleRepository repo) {
return repo.withACM(softwareModuleAccessController);
} else if (bean instanceof DistributionSetTypeRepository repo) {
return repo.withACM(distributionSetTypeAccessController);
} else if (bean instanceof DistributionSetRepository repo) {
return repo.withACM(distributionSetAccessController);
} else if (bean instanceof TargetTypeRepository repo) {
return repo.withACM(targetTypeAccessControlManager);
} else if (bean instanceof TargetRepository repo) {
return repo.withACM(targetAccessControlManager);
} else if (bean instanceof ActionRepository repo) {
return repo.withACM(actionAccessController);
}
return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
}
};
}
@Bean
@ConditionalOnMissingBean
PauseRolloutGroupAction pauseRolloutGroupAction(final RolloutManagement rolloutManagement,
@@ -299,10 +399,8 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
}
/**
* @param distributionSetTypeManagement
* to loading the {@link DistributionSetType}
* @param softwareManagement
* for loading {@link DistributionSet#getModules()}
* @param distributionSetTypeManagement to loading the {@link DistributionSetType}
* @param softwareManagement for loading {@link DistributionSet#getModules()}
* @return DistributionSetBuilder bean
*/
@Bean
@@ -317,8 +415,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
}
/**
* @param dsTypeManagement
* for loading {@link TargetType#getCompatibleDistributionSetTypes()}
* @param dsTypeManagement for loading {@link TargetType#getCompatibleDistributionSetTypes()}
* @return TargetTypeBuilder bean
*/
@Bean
@@ -333,8 +430,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
}
/**
* @param softwareModuleTypeManagement
* for loading {@link DistributionSetType#getMandatoryModuleTypes()}
* @param softwareModuleTypeManagement for loading {@link DistributionSetType#getMandatoryModuleTypes()}
* and {@link DistributionSetType#getOptionalModuleTypes()}
* @return DistributionSetTypeBuilder bean
*/
@@ -345,8 +441,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
}
/**
* @param softwareModuleTypeManagement
* for loading {@link SoftwareModule#getType()}
* @param softwareModuleTypeManagement for loading {@link SoftwareModule#getType()}
* @return SoftwareModuleBuilder bean
*/
@Bean
@@ -355,8 +450,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
}
/**
* @param distributionSetManagement
* for loading {@link Rollout#getDistributionSet()}
* @param distributionSetManagement for loading {@link Rollout#getDistributionSet()}
* @return RolloutBuilder bean
*/
@Bean
@@ -365,8 +459,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
}
/**
* @param distributionSetManagement
* for loading
* @param distributionSetManagement for loading
* {@link TargetFilterQuery#getAutoAssignDistributionSet()}
* @return TargetFilterQueryBuilder bean
*/
@@ -434,7 +527,6 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
}
/**
*
* @return the singleton instance of the
* {@link AfterTransactionCommitExecutorHolder}
*/
@@ -443,23 +535,6 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
return AfterTransactionCommitExecutorHolder.getInstance();
}
/**
* Defines the validation processor bean.
*
* @return the {@link MethodValidationPostProcessor}
*/
@Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
final MethodValidationPostProcessor processor = new MethodValidationPostProcessor();
// ValidatorFactory shall NOT be closed because after closing the generated Validator
// methods shall not be called - we need the validator in future
processor.setValidator(Validation.byDefaultProvider().configure()
.addProperty(BaseHibernateValidatorConfiguration.ALLOW_PARALLEL_METHODS_DEFINE_PARAMETER_CONSTRAINTS,
"true")
.buildValidatorFactory().getValidator());
return processor;
}
/**
* @return {@link ExceptionMappingAspectHandler} aspect bean
*/
@@ -468,51 +543,6 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
return new ExceptionMappingAspectHandler();
}
@Override
protected AbstractJpaVendorAdapter createJpaVendorAdapter() {
return new EclipseLinkJpaVendorAdapter() {
private final HawkBitEclipseLinkJpaDialect jpaDialect = new HawkBitEclipseLinkJpaDialect();
@Override
public EclipseLinkJpaDialect getJpaDialect() {
return jpaDialect;
}
};
}
@Override
protected Map<String, Object> getVendorProperties() {
final Map<String, Object> properties = new HashMap<>(7);
// Turn off dynamic weaving to disable LTW lookup in static weaving mode
properties.put(PersistenceUnitProperties.WEAVING, "false");
// needed for reports
properties.put(PersistenceUnitProperties.ALLOW_NATIVE_SQL_QUERIES, "true");
// flyway
properties.put(PersistenceUnitProperties.DDL_GENERATION, "none");
// Embed into hawkBit logging
properties.put(PersistenceUnitProperties.LOGGING_LOGGER, "JavaLogger");
// Ensure that we flush only at the end of the transaction
properties.put(PersistenceUnitProperties.PERSISTENCE_CONTEXT_FLUSH_MODE, "COMMIT");
// Enable batch writing
properties.put(PersistenceUnitProperties.BATCH_WRITING, "JDBC");
// Batch size
properties.put(PersistenceUnitProperties.BATCH_WRITING_SIZE, "500");
return properties;
}
/**
* {@link MultiTenantJpaTransactionManager} bean.
*
* @see org.springframework.boot.autoconfigure.orm.jpa.JpaBaseConfiguration#transactionManager(ObjectProvider)
* @return a new {@link PlatformTransactionManager}
*/
@Override
@Bean
public PlatformTransactionManager transactionManager(
final ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
return new MultiTenantJpaTransactionManager();
}
/**
* {@link JpaSystemManagement} bean.
*
@@ -630,19 +660,12 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
/**
* {@link JpaTargetFilterQueryManagement} bean.
*
* @param targetFilterQueryRepository
* holding {@link TargetFilterQuery} entities
* @param targetManagement
* managing {@link Target} entities
* @param virtualPropertyReplacer
* for RSQL handling
* @param distributionSetManagement
* for auto assign DS access
* @param quotaManagement
* to access quotas
* @param properties
* JPA properties
*
* @param targetFilterQueryRepository holding {@link TargetFilterQuery} entities
* @param targetManagement managing {@link Target} entities
* @param virtualPropertyReplacer for RSQL handling
* @param distributionSetManagement for auto assign DS access
* @param quotaManagement to access quotas
* @param properties JPA properties
* @return a new {@link TargetFilterQueryManagement}
*/
@Bean
@@ -659,7 +682,6 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
tenantConfigurationManagement, repositoryProperties, systemSecurityContext, contextAware, auditorAware);
}
/**
* {@link JpaTargetTagManagement} bean.
*
@@ -816,7 +838,8 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
final TenantConfigurationManagement tenantConfigurationManagement, final QuotaManagement quotaManagement,
final SystemSecurityContext systemSecurityContext, final TenantAware tenantAware, final AuditorAware<String> auditorAware,
final JpaProperties properties, final RepositoryProperties repositoryProperties) {
return new JpaDeploymentManagement(entityManager, actionRepository, distributionSetManagement, targetRepository, actionStatusRepository, auditorProvider,
return new JpaDeploymentManagement(entityManager, actionRepository, distributionSetManagement, targetRepository, actionStatusRepository,
auditorProvider,
eventPublisherHolder, afterCommit, virtualPropertyReplacer, txManager, tenantConfigurationManagement,
quotaManagement, systemSecurityContext, tenantAware, auditorAware, properties.getDatabase(), repositoryProperties);
}
@@ -880,10 +903,8 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
/**
* {@link EventEntityManager} bean.
*
* @param aware
* the tenant aware
* @param entityManager
* the entity manager
* @param aware the tenant aware
* @param entityManager the entity manager
* @return a new {@link EventEntityManager}
*/
@Bean
@@ -895,14 +916,10 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
/**
* {@link AutoAssignChecker} bean.
*
* @param targetFilterQueryManagement
* to get all target filter queries
* @param targetManagement
* to get targets
* @param deploymentManagement
* to assign distribution sets to targets
* @param transactionManager
* to run transactions
* @param targetFilterQueryManagement to get all target filter queries
* @param targetManagement to get targets
* @param deploymentManagement to assign distribution sets to targets
* @param transactionManager to run transactions
* @return a new {@link AutoAssignChecker}
*/
@Bean
@@ -920,14 +937,10 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
* Note: does not activate in test profile, otherwise it is hard to test the
* auto assign functionality.
*
* @param systemManagement
* to find all tenants
* @param systemSecurityContext
* to run as system
* @param autoAssignExecutor
* to run a check as tenant
* @param lockRegistry
* to lock the tenant for auto assignment
* @param systemManagement to find all tenants
* @param systemSecurityContext to run as system
* @param autoAssignExecutor to run a check as tenant
* @param lockRegistry to lock the tenant for auto assignment
* @return a new {@link AutoAssignChecker}
*/
@Bean
@@ -945,11 +958,8 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
/**
* {@link AutoActionCleanup} bean.
*
* @param deploymentManagement
* Deployment management service
* @param configManagement
* Tenant configuration service
*
* @param deploymentManagement Deployment management service
* @param configManagement Tenant configuration service
* @return a new {@link AutoActionCleanup} bean
*/
@Bean
@@ -961,15 +971,10 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
/**
* {@link AutoCleanupScheduler} bean.
*
* @param systemManagement
* to find all tenants
* @param systemSecurityContext
* to run as system
* @param lockRegistry
* to lock the tenant for auto assignment
* @param cleanupTasks
* a list of cleanup tasks
*
* @param systemManagement to find all tenants
* @param systemSecurityContext to run as system
* @param lockRegistry to lock the tenant for auto assignment
* @param cleanupTasks a list of cleanup tasks
* @return a new {@link AutoCleanupScheduler} bean
*/
@Bean
@@ -988,12 +993,9 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
* Note: does not activate in test profile, otherwise it is hard to test the
* rollout handling functionality.
*
* @param systemManagement
* to find all tenants
* @param rolloutHandler
* to run the rollout handler
* @param systemSecurityContext
* to run as system
* @param systemManagement to find all tenants
* @param rolloutHandler to run the rollout handler
* @param systemSecurityContext to run as system
* @return a new {@link RolloutScheduler} bean.
*/
@Bean
@@ -1069,40 +1071,4 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
ArtifactEncryptionService artifactEncryptionService() {
return ArtifactEncryptionService.getInstance();
}
@Bean
public BeanPostProcessor entityManagerBeanPostProcessor(
@Autowired(required = false) final AccessController<JpaArtifact> artifactAccessController,
@Autowired(required = false) final AccessController<JpaSoftwareModuleType> softwareModuleTypeAccessController,
@Autowired(required = false) final AccessController<JpaSoftwareModule> softwareModuleAccessController,
@Autowired(required = false) final AccessController<JpaDistributionSetType> distributionSetTypeAccessController,
@Autowired(required = false) final AccessController<JpaDistributionSet> distributionSetAccessController,
@Autowired(required = false) final AccessController<JpaTargetType> targetTypeAccessControlManager,
@Autowired(required = false) final AccessController<JpaTarget> targetAccessControlManager,
@Autowired(required = false) final AccessController<JpaAction> actionAccessController) {
return new BeanPostProcessor() {
@Override
public Object postProcessAfterInitialization(@NonNull final Object bean, @NonNull final String beanName)
throws BeansException {
if (bean instanceof LocalArtifactRepository repo) {
return repo.withACM(artifactAccessController);
} else if (bean instanceof SoftwareModuleTypeRepository repo) {
return repo.withACM(softwareModuleTypeAccessController);
} else if (bean instanceof SoftwareModuleRepository repo) {
return repo.withACM(softwareModuleAccessController);
} else if (bean instanceof DistributionSetTypeRepository repo) {
return repo.withACM(distributionSetTypeAccessController);
} else if (bean instanceof DistributionSetRepository repo) {
return repo.withACM(distributionSetAccessController);
} else if (bean instanceof TargetTypeRepository repo) {
return repo.withACM(targetTypeAccessControlManager);
} else if (bean instanceof TargetRepository repo) {
return repo.withACM(targetAccessControlManager);
} else if (bean instanceof ActionRepository repo) {
return repo.withACM(actionAccessController);
}
return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
}
};
}
}

View File

@@ -26,26 +26,10 @@ import org.springframework.context.annotation.Bean;
*/
public class SystemManagementCacheKeyGenerator implements CurrentTenantCacheKeyGenerator {
private final ThreadLocal<String> createInitialTenant = new ThreadLocal<>();
@Autowired
private TenantAware tenantAware;
private final ThreadLocal<String> createInitialTenant = new ThreadLocal<>();
/**
* An implementation of the {@link KeyGenerator} to generate a key based on
* either the {@code createInitialTenant} thread local and the
* {@link TenantAware}, but in case we are in a tenant creation with its default
* types we need to use as the tenant the current tenant which is currently
* created and not the one currently in the {@link TenantAware}.
*/
public class CurrentTenantKeyGenerator implements KeyGenerator {
@Override
public Object generate(final Object target, final Method method, final Object... params) {
String tenant = getTenantInCreation().orElseGet(() -> tenantAware.getCurrentTenant()).toUpperCase();
return SimpleKeyGenerator.generateKey(tenant, tenant);
}
}
@Override
@Bean
public KeyGenerator currentTenantKeyGenerator() {
@@ -67,8 +51,7 @@ public class SystemManagementCacheKeyGenerator implements CurrentTenantCacheKeyG
* Overwrite the tenant used by the key generator in case that the tenant is in
* the process of creation.
*
* @param tenant
* the tenant which should be used instead of the actual one.
* @param tenant the tenant which should be used instead of the actual one.
*/
public void setTenantInCreation(@NotNull String tenant) {
createInitialTenant.set(Objects.requireNonNull(tenant));
@@ -81,4 +64,20 @@ public class SystemManagementCacheKeyGenerator implements CurrentTenantCacheKeyG
public void removeTenantInCreation() {
createInitialTenant.remove();
}
/**
* An implementation of the {@link KeyGenerator} to generate a key based on
* either the {@code createInitialTenant} thread local and the
* {@link TenantAware}, but in case we are in a tenant creation with its default
* types we need to use as the tenant the current tenant which is currently
* created and not the one currently in the {@link TenantAware}.
*/
public class CurrentTenantKeyGenerator implements KeyGenerator {
@Override
public Object generate(final Object target, final Method method, final Object... params) {
String tenant = getTenantInCreation().orElseGet(() -> tenantAware.getCurrentTenant()).toUpperCase();
return SimpleKeyGenerator.generateKey(tenant, tenant);
}
}
}

View File

@@ -18,7 +18,6 @@ import org.springframework.stereotype.Service;
/**
* {@link KeyGenerator} for tenant related caches.
*
*/
@Service
public class TenantKeyGenerator implements KeyGenerator {

View File

@@ -40,8 +40,7 @@ public interface AccessController<T> {
/**
* Append the resource limitation on an already existing specification.
*
* @param specification
* is the root specification which needs to be appended by the
* @param specification is the root specification which needs to be appended by the
* resource limitation
* @return a new appended specification
*/
@@ -55,18 +54,17 @@ public interface AccessController<T> {
/**
* Verify if the given {@link Operation} is permitted for the provided entity.
*
* @throws InsufficientPermissionException
* if operation is not permitted for given entities
* @throws InsufficientPermissionException if operation is not permitted for given entities
*/
void assertOperationAllowed(final Operation operation, final T entity) throws InsufficientPermissionException;
/**
* Verify if the given {@link Operation} is permitted for the provided entities.
*
* @throws InsufficientPermissionException
* if operation is not permitted for given entities
* @throws InsufficientPermissionException if operation is not permitted for given entities
*/
default void assertOperationAllowed(final Operation operation, final Iterable<? extends T> entities) throws InsufficientPermissionException {
default void assertOperationAllowed(final Operation operation, final Iterable<? extends T> entities)
throws InsufficientPermissionException {
for (final T entity : entities) {
assertOperationAllowed(operation, entity);
}

View File

@@ -68,8 +68,7 @@ public class ExceptionMappingAspectHandler implements Ordered {
* catch exceptions of the {@link TransactionManager} and wrap them to
* custom exceptions.
*
* @param ex
* the thrown and catched exception
* @param ex the thrown and catched exception
* @throws Throwable
*/
@AfterThrowing(pointcut = "execution( * org.eclipse.hawkbit.repository.jpa.management.*Management.*(..))", throwing = "ex")
@@ -103,6 +102,11 @@ public class ExceptionMappingAspectHandler implements Ordered {
throw ex;
}
@Override
public int getOrder() {
return 1;
}
private static Exception replaceWithCauseIfConstraintViolationException(final TransactionSystemException rex) {
Throwable exception = rex;
do {
@@ -115,9 +119,4 @@ public class ExceptionMappingAspectHandler implements Ordered {
return rex;
}
@Override
public int getOrder() {
return 1;
}
}

View File

@@ -57,14 +57,10 @@ public abstract class AbstractAutoAssignExecutor implements AutoAssignExecutor {
/**
* Constructor
*
* @param targetFilterQueryManagement
* to get all target filter queries
* @param deploymentManagement
* to assign distribution sets to targets
* @param transactionManager
* to run transactions
* @param contextAware
* to handle the context
* @param targetFilterQueryManagement to get all target filter queries
* @param deploymentManagement to assign distribution sets to targets
* @param transactionManager to run transactions
* @param contextAware to handle the context
*/
protected AbstractAutoAssignExecutor(final TargetFilterQueryManagement targetFilterQueryManagement,
final DeploymentManagement deploymentManagement, final PlatformTransactionManager transactionManager,
@@ -75,6 +71,12 @@ public abstract class AbstractAutoAssignExecutor implements AutoAssignExecutor {
this.contextAware = contextAware;
}
protected static String getAutoAssignmentInitiatedBy(final TargetFilterQuery targetFilterQuery) {
return StringUtils.hasText(targetFilterQuery.getAutoAssignInitiatedBy())
? targetFilterQuery.getAutoAssignInitiatedBy()
: targetFilterQuery.getCreatedBy();
}
protected DeploymentManagement getDeploymentManagement() {
return deploymentManagement;
}
@@ -129,10 +131,8 @@ public abstract class AbstractAutoAssignExecutor implements AutoAssignExecutor {
* Runs target assignments within a dedicated transaction for a given list of
* controllerIDs
*
* @param targetFilterQuery
* the target filter query
* @param controllerIds
* the controllerIDs
* @param targetFilterQuery the target filter query
* @param controllerIds the controllerIDs
* @return count of targets
*/
protected int runTransactionalAssignment(final TargetFilterQuery targetFilterQuery,
@@ -158,10 +158,8 @@ public abstract class AbstractAutoAssignExecutor implements AutoAssignExecutor {
* Creates a list of {@link DeploymentRequest} for given list of controllerIds
* and {@link TargetFilterQuery}
*
* @param controllerIds
* list of controllerIds
* @param filterQuery
* the query the targets have to match
* @param controllerIds list of controllerIds
* @param filterQuery the query the targets have to match
* @return list of deployment request
*/
protected List<DeploymentRequest> mapToDeploymentRequests(final List<String> controllerIds,
@@ -179,10 +177,4 @@ public abstract class AbstractAutoAssignExecutor implements AutoAssignExecutor {
.setConfirmationRequired(filterQuery.isConfirmationRequired()).build())
.toList();
}
protected static String getAutoAssignmentInitiatedBy(final TargetFilterQuery targetFilterQuery) {
return StringUtils.hasText(targetFilterQuery.getAutoAssignInitiatedBy())
? targetFilterQuery.getAutoAssignInitiatedBy()
: targetFilterQuery.getCreatedBy();
}
}

View File

@@ -43,16 +43,11 @@ public class AutoAssignChecker extends AbstractAutoAssignExecutor {
/**
* Instantiates a new auto assign checker
*
* @param targetFilterQueryManagement
* to get all target filter queries
* @param targetManagement
* to get targets
* @param deploymentManagement
* to assign distribution sets to targets
* @param transactionManager
* to run transactions
* @param contextAware
* to handle the context
* @param targetFilterQueryManagement to get all target filter queries
* @param targetManagement to get targets
* @param deploymentManagement to assign distribution sets to targets
* @param transactionManager to run transactions
* @param contextAware to handle the context
*/
public AutoAssignChecker(final TargetFilterQueryManagement targetFilterQueryManagement,
final TargetManagement targetManagement, final DeploymentManagement deploymentManagement,
@@ -83,8 +78,7 @@ public class AutoAssignChecker extends AbstractAutoAssignExecutor {
* them. Catches PersistenceException and own exceptions derived from
* AbstractServerRtException
*
* @param targetFilterQuery
* the target filter query
* @param targetFilterQuery the target filter query
*/
private void checkByTargetFilterQueryAndAssignDS(final TargetFilterQuery targetFilterQuery) {
log.debug("Auto assign check call for tenant {} and target filter query id {} started",

View File

@@ -37,14 +37,10 @@ public class AutoAssignScheduler {
/**
* Instantiates a new AutoAssignScheduler
*
* @param systemManagement
* to find all tenants
* @param systemSecurityContext
* to run as system
* @param autoAssignExecutor
* to run a check as tenant
* @param lockRegistry
* to acquire a lock per tenant
* @param systemManagement to find all tenants
* @param systemSecurityContext to run as system
* @param autoAssignExecutor to run a check as tenant
* @param lockRegistry to acquire a lock per tenant
*/
public AutoAssignScheduler(final SystemManagement systemManagement,
final SystemSecurityContext systemSecurityContext, final AutoAssignExecutor autoAssignExecutor,

View File

@@ -50,10 +50,8 @@ public class AutoActionCleanup implements CleanupTask {
/**
* Constructs the action cleanup handler.
*
* @param deploymentMgmt
* The {@link DeploymentManagement} to operate on.
* @param configMgmt
* The {@link TenantConfigurationManagement} service.
* @param deploymentMgmt The {@link DeploymentManagement} to operate on.
* @param configMgmt The {@link TenantConfigurationManagement} service.
*/
public AutoActionCleanup(final DeploymentManagement deploymentMgmt,
final TenantConfigurationManagement configMgmt) {

View File

@@ -37,14 +37,10 @@ public class AutoCleanupScheduler {
* Constructs the cleanup schedulers and initializes it with a set of
* cleanup handlers.
*
* @param systemManagement
* Management APIs to invoke actions in a certain tenant context.
* @param systemSecurityContext
* The system security context.
* @param lockRegistry
* A registry for shared locks.
* @param cleanupTasks
* A list of cleanup tasks.
* @param systemManagement Management APIs to invoke actions in a certain tenant context.
* @param systemSecurityContext The system security context.
* @param lockRegistry A registry for shared locks.
* @param cleanupTasks A list of cleanup tasks.
*/
public AutoCleanupScheduler(final SystemManagement systemManagement,
final SystemSecurityContext systemSecurityContext, final LockRegistry lockRegistry,

View File

@@ -15,7 +15,6 @@ import org.eclipse.hawkbit.repository.model.ActionStatus;
/**
* Builder implementation for {@link ActionStatus}.
*
*/
public class JpaActionStatusBuilder implements ActionStatusBuilder {

View File

@@ -15,7 +15,6 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaActionStatus;
/**
* Create/build implementation.
*
*/
public class JpaActionStatusCreate extends AbstractActionStatusCreate<ActionStatusCreate>
implements ActionStatusCreate {

View File

@@ -19,7 +19,6 @@ import org.eclipse.hawkbit.repository.model.DistributionSet;
/**
* Builder implementation for {@link DistributionSet}.
*
*/
public class JpaDistributionSetBuilder implements DistributionSetBuilder {

View File

@@ -27,16 +27,14 @@ import org.springframework.util.StringUtils;
/**
* Create/build implementation.
*
*/
public class JpaDistributionSetCreate extends AbstractDistributionSetUpdateCreate<DistributionSetCreate>
implements DistributionSetCreate {
@ValidString
private String type;
private final DistributionSetTypeManagement distributionSetTypeManagement;
private final SoftwareModuleManagement softwareModuleManagement;
@ValidString
private String type;
JpaDistributionSetCreate(final DistributionSetTypeManagement distributionSetTypeManagement,
final SoftwareModuleManagement softwareManagement) {
@@ -44,6 +42,12 @@ public class JpaDistributionSetCreate extends AbstractDistributionSetUpdateCreat
this.softwareModuleManagement = softwareManagement;
}
@Override
public DistributionSetCreate type(final String type) {
this.type = StringUtils.trimWhitespace(type);
return this;
}
@Override
public JpaDistributionSet build() {
return new JpaDistributionSet(name, version, description,
@@ -52,12 +56,6 @@ public class JpaDistributionSetCreate extends AbstractDistributionSetUpdateCreat
Optional.ofNullable(requiredMigrationStep).orElse(Boolean.FALSE));
}
@Override
public DistributionSetCreate type(final String type) {
this.type = StringUtils.trimWhitespace(type);
return this;
}
public String getType() {
return type;
}

View File

@@ -18,7 +18,6 @@ import org.eclipse.hawkbit.repository.model.DistributionSetType;
/**
* Builder implementation for {@link DistributionSetType}.
*
*/
public class JpaDistributionSetTypeBuilder implements DistributionSetTypeBuilder {

View File

@@ -22,7 +22,6 @@ import org.springframework.util.CollectionUtils;
/**
* Create/build implementation.
*
*/
public class JpaDistributionSetTypeCreate extends AbstractDistributionSetTypeUpdateCreate<DistributionSetTypeCreate>
implements DistributionSetTypeCreate {

View File

@@ -18,9 +18,9 @@ import org.eclipse.hawkbit.repository.model.Rollout;
/**
* Builder implementation for {@link Rollout}.
*
*/
public class JpaRolloutBuilder implements RolloutBuilder {
private final DistributionSetManagement distributionSetManagement;
public JpaRolloutBuilder(final DistributionSetManagement distributionSetManagement) {

View File

@@ -9,8 +9,11 @@
*/
package org.eclipse.hawkbit.repository.jpa.builder;
import java.util.Optional;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import org.eclipse.hawkbit.repository.DistributionSetManagement;
import org.eclipse.hawkbit.repository.ValidString;
import org.eclipse.hawkbit.repository.builder.AbstractNamedEntityBuilder;
@@ -20,12 +23,8 @@ import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.springframework.util.StringUtils;
import java.util.Optional;
public class JpaRolloutCreate extends AbstractNamedEntityBuilder<RolloutCreate> implements RolloutCreate {
private final DistributionSetManagement distributionSetManagement;
protected Long distributionSetId;
@ValidString
protected String targetFilterQuery;
@@ -35,12 +34,13 @@ public class JpaRolloutCreate extends AbstractNamedEntityBuilder<RolloutCreate>
@Min(Action.WEIGHT_MIN)
@Max(Action.WEIGHT_MAX)
protected Integer weight;
private final DistributionSetManagement distributionSetManagement;
private boolean dynamic;
JpaRolloutCreate(final DistributionSetManagement distributionSetManagement) {
this.distributionSetManagement = distributionSetManagement;
}
/**
* {@link DistributionSet} of rollout
*
@@ -62,6 +62,7 @@ public class JpaRolloutCreate extends AbstractNamedEntityBuilder<RolloutCreate>
this.targetFilterQuery = StringUtils.trimWhitespace(targetFilterQuery);
return this;
}
/**
* {@link Action.ActionType} used for {@link Action}s
*
@@ -95,6 +96,11 @@ public class JpaRolloutCreate extends AbstractNamedEntityBuilder<RolloutCreate>
return this;
}
public RolloutCreate dynamic(final boolean dynamic) {
this.dynamic = dynamic;
return this;
}
/**
* Set start of the Rollout
*
@@ -106,31 +112,6 @@ public class JpaRolloutCreate extends AbstractNamedEntityBuilder<RolloutCreate>
return this;
}
public Optional<Long> getDistributionSetId() {
return Optional.ofNullable(distributionSetId);
}
public Optional<Action.ActionType> getActionType() {
return Optional.ofNullable(actionType);
}
public Optional<Long> getForcedTime() {
return Optional.ofNullable(forcedTime);
}
public Optional<Integer> getWeight() {
return Optional.ofNullable(weight);
}
public Optional<Long> getStartAt() {
return Optional.ofNullable(startAt);
}
public RolloutCreate dynamic(final boolean dynamic) {
this.dynamic = dynamic;
return this;
}
@Override
public JpaRollout build() {
final JpaRollout rollout = new JpaRollout();
@@ -153,4 +134,24 @@ public class JpaRolloutCreate extends AbstractNamedEntityBuilder<RolloutCreate>
return rollout;
}
public Optional<Long> getDistributionSetId() {
return Optional.ofNullable(distributionSetId);
}
public Optional<Action.ActionType> getActionType() {
return Optional.ofNullable(actionType);
}
public Optional<Long> getForcedTime() {
return Optional.ofNullable(forcedTime);
}
public Optional<Integer> getWeight() {
return Optional.ofNullable(weight);
}
public Optional<Long> getStartAt() {
return Optional.ofNullable(startAt);
}
}

View File

@@ -15,9 +15,9 @@ import org.eclipse.hawkbit.repository.model.RolloutGroup;
/**
* Builder implementation for {@link RolloutGroup}.
*
*/
public class JpaRolloutGroupBuilder implements RolloutGroupBuilder {
@Override
public RolloutGroupCreate create() {
return new JpaRolloutGroupCreate();

View File

@@ -18,6 +18,51 @@ import org.eclipse.hawkbit.repository.model.RolloutGroupConditions;
public class JpaRolloutGroupCreate extends AbstractRolloutGroupCreate<RolloutGroupCreate>
implements RolloutGroupCreate {
/**
* Set the Success And Error conditions for the rollout group
*
* @param group The Rollout group
* @param conditions The Rollout Success and Error Conditions
*/
public static void addSuccessAndErrorConditionsAndActions(final JpaRolloutGroup group,
final RolloutGroupConditions conditions) {
addSuccessAndErrorConditionsAndActions(group, conditions.getSuccessCondition(),
conditions.getSuccessConditionExp(), conditions.getSuccessAction(), conditions.getSuccessActionExp(),
conditions.getErrorCondition(), conditions.getErrorConditionExp(), conditions.getErrorAction(),
conditions.getErrorActionExp());
}
/**
* Set the Success And Error conditions for the rollout group
*
* @param group The Rollout group
* @param successCondition The Rollout group success condition
* @param successConditionExp The Rollout group success expression
* @param successAction The Rollout group success action
* @param successActionExp The Rollout group success action expression
* @param errorCondition The Rollout group error condition
* @param errorConditionExp The Rollout group error expression
* @param errorAction The Rollout group error action
* @param errorActionExp The Rollout group error action expression
*/
public static void addSuccessAndErrorConditionsAndActions(final JpaRolloutGroup group,
final RolloutGroup.RolloutGroupSuccessCondition successCondition, final String successConditionExp,
final RolloutGroup.RolloutGroupSuccessAction successAction, final String successActionExp,
final RolloutGroup.RolloutGroupErrorCondition errorCondition, final String errorConditionExp,
final RolloutGroup.RolloutGroupErrorAction errorAction, final String errorActionExp) {
group.setSuccessCondition(successCondition);
group.setSuccessConditionExp(successConditionExp);
group.setSuccessAction(successAction);
group.setSuccessActionExp(successActionExp);
group.setErrorCondition(errorCondition);
group.setErrorConditionExp(errorConditionExp);
group.setErrorAction(errorAction);
group.setErrorActionExp(errorActionExp);
}
@Override
public JpaRolloutGroup build() {
final JpaRolloutGroup group = new JpaRolloutGroup();
@@ -40,60 +85,4 @@ public class JpaRolloutGroupCreate extends AbstractRolloutGroupCreate<RolloutGro
return group;
}
/**
* Set the Success And Error conditions for the rollout group
*
* @param group
* The Rollout group
* @param conditions
* The Rollout Success and Error Conditions
*/
public static void addSuccessAndErrorConditionsAndActions(final JpaRolloutGroup group,
final RolloutGroupConditions conditions) {
addSuccessAndErrorConditionsAndActions(group, conditions.getSuccessCondition(),
conditions.getSuccessConditionExp(), conditions.getSuccessAction(), conditions.getSuccessActionExp(),
conditions.getErrorCondition(), conditions.getErrorConditionExp(), conditions.getErrorAction(),
conditions.getErrorActionExp());
}
/**
* Set the Success And Error conditions for the rollout group
*
* @param group
* The Rollout group
* @param successCondition
* The Rollout group success condition
* @param successConditionExp
* The Rollout group success expression
* @param successAction
* The Rollout group success action
* @param successActionExp
* The Rollout group success action expression
* @param errorCondition
* The Rollout group error condition
* @param errorConditionExp
* The Rollout group error expression
* @param errorAction
* The Rollout group error action
* @param errorActionExp
* The Rollout group error action expression
*/
public static void addSuccessAndErrorConditionsAndActions(final JpaRolloutGroup group,
final RolloutGroup.RolloutGroupSuccessCondition successCondition, final String successConditionExp,
final RolloutGroup.RolloutGroupSuccessAction successAction, final String successActionExp,
final RolloutGroup.RolloutGroupErrorCondition errorCondition, final String errorConditionExp,
final RolloutGroup.RolloutGroupErrorAction errorAction, final String errorActionExp) {
group.setSuccessCondition(successCondition);
group.setSuccessConditionExp(successConditionExp);
group.setSuccessAction(successAction);
group.setSuccessActionExp(successActionExp);
group.setErrorCondition(errorCondition);
group.setErrorConditionExp(errorConditionExp);
group.setErrorAction(errorAction);
group.setErrorActionExp(errorActionExp);
}
}

View File

@@ -18,7 +18,6 @@ import org.eclipse.hawkbit.repository.model.SoftwareModule;
/**
* Builder implementation for {@link SoftwareModule}.
*
*/
public class JpaSoftwareModuleBuilder implements SoftwareModuleBuilder {

View File

@@ -20,7 +20,6 @@ import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
/**
* Create/build implementation.
*
*/
public class JpaSoftwareModuleCreate extends AbstractSoftwareModuleUpdateCreate<SoftwareModuleCreate>
implements SoftwareModuleCreate {
@@ -39,16 +38,16 @@ public class JpaSoftwareModuleCreate extends AbstractSoftwareModuleUpdateCreate<
return this;
}
public boolean isEncrypted() {
return encrypted;
}
@Override
public JpaSoftwareModule build() {
return new JpaSoftwareModule(getSoftwareModuleTypeFromKeyString(type), name, version, description, vendor,
encrypted);
}
public boolean isEncrypted() {
return encrypted;
}
private SoftwareModuleType getSoftwareModuleTypeFromKeyString(final String type) {
if (type == null) {
throw new ValidationException("type cannot be null");

View File

@@ -18,7 +18,6 @@ import org.eclipse.hawkbit.repository.model.SoftwareModuleMetadata;
/**
* Builder implementation for {@link SoftwareModuleMetadata}.
*
*/
public class JpaSoftwareModuleMetadataBuilder implements SoftwareModuleMetadataBuilder {

View File

@@ -18,7 +18,6 @@ import org.eclipse.hawkbit.repository.model.SoftwareModule;
/**
* Create/build implementation.
*
*/
public class JpaSoftwareModuleMetadataCreate
extends AbstractSoftwareModuleMetadataUpdateCreate<SoftwareModuleMetadataCreate>

View File

@@ -17,7 +17,6 @@ import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
/**
* Builder implementation for {@link SoftwareModuleType}.
*
*/
public class JpaSoftwareModuleTypeBuilder implements SoftwareModuleTypeBuilder {

View File

@@ -15,7 +15,6 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModuleType;
/**
* Create/build implementation.
*
*/
public class JpaSoftwareModuleTypeCreate extends AbstractSoftwareModuleTypeUpdateCreate<SoftwareModuleTypeCreate>
implements SoftwareModuleTypeCreate {

View File

@@ -17,7 +17,6 @@ import org.eclipse.hawkbit.repository.model.Tag;
/**
* Builder implementation for {@link Tag}.
*
*/
public class JpaTagBuilder implements TagBuilder {

View File

@@ -18,9 +18,9 @@ import org.eclipse.hawkbit.repository.model.Tag;
/**
* Create/build implementation.
*
*/
public class JpaTagCreate extends AbstractTagUpdateCreate<TagCreate> implements TagCreate {
JpaTagCreate() {
}

View File

@@ -17,14 +17,13 @@ import org.eclipse.hawkbit.repository.model.Target;
/**
* Builder implementation for {@link Target}.
*
*/
public class JpaTargetBuilder implements TargetBuilder {
final private TargetTypeManagement targetTypeManagement;
/**
* @param targetTypeManagement
* Target type management
* @param targetTypeManagement Target type management
*/
public JpaTargetBuilder(TargetTypeManagement targetTypeManagement) {
this.targetTypeManagement = targetTypeManagement;

View File

@@ -20,7 +20,6 @@ import org.springframework.util.StringUtils;
/**
* Create/build implementation.
*
*/
public class JpaTargetCreate extends AbstractTargetUpdateCreate<TargetCreate> implements TargetCreate {
@@ -29,8 +28,7 @@ public class JpaTargetCreate extends AbstractTargetUpdateCreate<TargetCreate> im
/**
* Constructor
*
* @param targetTypeManagement
* Target type management
* @param targetTypeManagement Target type management
*/
JpaTargetCreate(final TargetTypeManagement targetTypeManagement) {
super(null);
@@ -51,7 +49,7 @@ public class JpaTargetCreate extends AbstractTargetUpdateCreate<TargetCreate> im
target.setName(name);
}
if (targetTypeId != null){
if (targetTypeId != null) {
TargetType targetType = targetTypeManagement.get(targetTypeId)
.orElseThrow(() -> new EntityNotFoundException(TargetType.class, targetTypeId));
target.setTargetType(targetType);

View File

@@ -19,9 +19,9 @@ import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
/**
* Builder implementation for {@link TargetFilterQuery}.
*
*/
public class JpaTargetFilterQueryBuilder implements TargetFilterQueryBuilder {
private final DistributionSetManagement distributionSetManagement;
public JpaTargetFilterQueryBuilder(final DistributionSetManagement distributionSetManagement) {
@@ -33,14 +33,14 @@ public class JpaTargetFilterQueryBuilder implements TargetFilterQueryBuilder {
return new GenericTargetFilterQueryUpdate(id);
}
@Override
public TargetFilterQueryCreate create() {
return new JpaTargetFilterQueryCreate(distributionSetManagement);
}
@Override
public AutoAssignDistributionSetUpdate updateAutoAssign(final long id) {
return new AutoAssignDistributionSetUpdate(id);
}
@Override
public TargetFilterQueryCreate create() {
return new JpaTargetFilterQueryCreate(distributionSetManagement);
}
}

View File

@@ -19,7 +19,6 @@ import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
/**
* Create/build implementation.
*
*/
public class JpaTargetFilterQueryCreate extends AbstractTargetFilterQueryUpdateCreate<TargetFilterQueryCreate>
implements TargetFilterQueryCreate {

View File

@@ -10,7 +10,6 @@
package org.eclipse.hawkbit.repository.jpa.builder;
import org.eclipse.hawkbit.repository.DistributionSetTypeManagement;
import org.eclipse.hawkbit.repository.TargetTypeManagement;
import org.eclipse.hawkbit.repository.builder.GenericTargetTypeUpdate;
import org.eclipse.hawkbit.repository.builder.TargetTypeBuilder;
import org.eclipse.hawkbit.repository.builder.TargetTypeCreate;
@@ -19,16 +18,15 @@ import org.eclipse.hawkbit.repository.model.TargetType;
/**
* Builder implementation for {@link TargetType}.
*
*/
public class JpaTargetTypeBuilder implements TargetTypeBuilder {
private final DistributionSetTypeManagement distributionSetTypeManagement;
/**
* Constructor
*
* @param distributionSetTypeManagement
* Distribution set type management
* @param distributionSetTypeManagement Distribution set type management
*/
public JpaTargetTypeBuilder(DistributionSetTypeManagement distributionSetTypeManagement) {
this.distributionSetTypeManagement = distributionSetTypeManagement;

View File

@@ -9,6 +9,9 @@
*/
package org.eclipse.hawkbit.repository.jpa.builder;
import java.util.Collection;
import java.util.Collections;
import org.eclipse.hawkbit.repository.DistributionSetTypeManagement;
import org.eclipse.hawkbit.repository.builder.AbstractTargetTypeUpdateCreate;
import org.eclipse.hawkbit.repository.builder.TargetTypeCreate;
@@ -19,12 +22,8 @@ import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
import org.springframework.util.CollectionUtils;
import java.util.Collection;
import java.util.Collections;
/**
* Create/build implementation.
*
*/
public class JpaTargetTypeCreate extends AbstractTargetTypeUpdateCreate<TargetTypeCreate>
implements TargetTypeCreate {
@@ -34,8 +33,7 @@ public class JpaTargetTypeCreate extends AbstractTargetTypeUpdateCreate<TargetTy
/**
* Constructor
*
* @param distributionSetTypeManagement
* Distribution set type management
* @param distributionSetTypeManagement Distribution set type management
*/
JpaTargetTypeCreate(final DistributionSetTypeManagement distributionSetTypeManagement) {
this.distributionSetTypeManagement = distributionSetTypeManagement;

View File

@@ -28,7 +28,6 @@ import org.springframework.transaction.support.TransactionSynchronizationManager
* {@link JpaTransactionManager} that sets the
* {@link TenantAware#getCurrentTenant()} in the eclipselink session. This has
* to be done in eclipselink after a {@link Transaction} has been started.
*
*/
public class MultiTenantJpaTransactionManager extends JpaTransactionManager {

View File

@@ -42,6 +42,14 @@ public class AfterTransactionCommitDefaultServiceExecutor extends TransactionSyn
}
}
@Override
@SuppressWarnings({ "squid:S1217" })
public void afterCompletion(final int status) {
final String transactionStatus = status == STATUS_COMMITTED ? "COMMITTED" : "ROLLEDBACK";
log.debug("Transaction completed after commit with status {}", transactionStatus);
THREAD_LOCAL_RUNNABLES.remove();
}
@Override
// Exception squid:S1217 - we want to run this synchronous
@SuppressWarnings("squid:S1217")
@@ -62,12 +70,4 @@ public class AfterTransactionCommitDefaultServiceExecutor extends TransactionSyn
runnable.run();
}
@Override
@SuppressWarnings({ "squid:S1217" })
public void afterCompletion(final int status) {
final String transactionStatus = status == STATUS_COMMITTED ? "COMMITTED" : "ROLLEDBACK";
log.debug("Transaction completed after commit with status {}", transactionStatus);
THREAD_LOCAL_RUNNABLES.remove();
}
}

View File

@@ -10,10 +10,8 @@
package org.eclipse.hawkbit.repository.jpa.executor;
/**
*
* A interface to register a runnable, which will be executed after a successful
* spring transaction.
*
*/
@FunctionalInterface
public interface AfterTransactionCommitExecutor {
@@ -22,8 +20,7 @@ public interface AfterTransactionCommitExecutor {
* Register a runnable which will be executed after a successful spring
* transaction.
*
* @param runnable
* the after commit runnable
* @param runnable the after commit runnable
*/
void afterCommit(Runnable runnable);
}

View File

@@ -17,6 +17,8 @@ import java.util.Set;
import java.util.function.BooleanSupplier;
import java.util.stream.Collectors;
import jakarta.persistence.criteria.JoinType;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.RepositoryConstants;
@@ -48,8 +50,6 @@ import org.eclipse.hawkbit.repository.model.helper.EventPublisherHolder;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import jakarta.persistence.criteria.JoinType;
/**
* {@link DistributionSet} to {@link Target} assignment strategy as utility for
* {@link JpaDeploymentManagement}.
@@ -83,63 +83,47 @@ public abstract class AbstractDsAssignmentStrategy {
this.repositoryProperties = repositoryProperties;
}
/**
* Find targets to be considered for assignment.
*
* @param controllerIDs
* as provided by repository caller
* @param distributionSetId
* to assign
* @return list of targets up to {@link Constants#MAX_ENTRIES_IN_STATEMENT}
*/
abstract List<JpaTarget> findTargetsForAssignment(final List<String> controllerIDs, final long distributionSetId);
public JpaAction createTargetAction(final String initiatedBy, final TargetWithActionType targetWithActionType,
final List<JpaTarget> targets, final JpaDistributionSet set) {
final Optional<JpaTarget> optTarget = targets.stream()
.filter(t -> t.getControllerId().equals(targetWithActionType.getControllerId())).findFirst();
/**
*
* @param set
* @param targets
*/
abstract void sendTargetUpdatedEvents(final DistributionSet set, final List<JpaTarget> targets);
// create the action
return optTarget.map(target -> {
assertActionsPerTargetQuota(target, 1);
final JpaAction actionForTarget = new JpaAction();
actionForTarget.setActionType(targetWithActionType.getActionType());
actionForTarget.setForcedTime(targetWithActionType.getForceTime());
actionForTarget.setWeight(
targetWithActionType.getWeight() == null ?
repositoryProperties.getActionWeightIfAbsent() : targetWithActionType.getWeight());
actionForTarget.setActive(true);
actionForTarget.setTarget(target);
actionForTarget.setDistributionSet(set);
actionForTarget.setMaintenanceWindowSchedule(targetWithActionType.getMaintenanceSchedule());
actionForTarget.setMaintenanceWindowDuration(targetWithActionType.getMaintenanceWindowDuration());
actionForTarget.setMaintenanceWindowTimeZone(targetWithActionType.getMaintenanceWindowTimeZone());
actionForTarget.setInitiatedBy(initiatedBy);
return actionForTarget;
}).orElseGet(() -> {
log.warn("Cannot find target for targetWithActionType '{}'.", targetWithActionType.getControllerId());
return null;
});
}
/**
* Update status and DS fields of given target.
*
* @param distributionSet
* to set
* @param targetIds
* to change
* @param currentUser
* for auditing
*/
abstract void setAssignedDistributionSetAndTargetStatus(final JpaDistributionSet distributionSet,
final List<List<Long>> targetIds, final String currentUser);
public JpaActionStatus createActionStatus(final JpaAction action, final String actionMessage) {
final JpaActionStatus actionStatus = new JpaActionStatus();
actionStatus.setAction(action);
actionStatus.setOccurredAt(action.getCreatedAt());
/**
* Cancels actions that can be canceled (i.e.
* {@link DistributionSet#isRequiredMigrationStep() is <code>false</code>})
* as a result of the new assignment and returns all {@link Target}s where
* such actions existed.
*
* @param targetIds
* to cancel actions for
* @return {@link Set} of {@link Target#getId()}s
*/
abstract Set<Long> cancelActiveActions(List<List<Long>> targetIds);
if (StringUtils.hasText(actionMessage)) {
actionStatus.addMessage(actionMessage);
} else {
actionStatus.addMessage(getActionMessage(action));
}
/**
* Cancels actions that can be canceled (i.e.
* {@link DistributionSet#isRequiredMigrationStep() is <code>false</code>})
* as a result of the new assignment and returns all {@link Target}s where
* such actions existed.
*
* @param targetIds
* to cancel actions for
*/
abstract void closeActiveActions(List<List<Long>> targetIds);
abstract void sendDeploymentEvents(final DistributionSetAssignmentResult assignmentResult);
abstract void sendDeploymentEvents(final List<DistributionSetAssignmentResult> assignmentResults);
return actionStatus;
}
protected void sendTargetUpdatedEvent(final JpaTarget target) {
afterCommit.afterCommit(() -> eventPublisherHolder.getEventPublisher()
@@ -223,14 +207,81 @@ public abstract class AbstractDsAssignmentStrategy {
* Sends the {@link CancelTargetAssignmentEvent} for a specific action to
* the eventPublisher.
*
* @param action
* the action of the assignment
* @param action the action of the assignment
*/
protected void cancelAssignDistributionSetEvent(final Action action) {
afterCommit.afterCommit(() -> eventPublisherHolder.getEventPublisher().publishEvent(
new CancelTargetAssignmentEvent(action, eventPublisherHolder.getApplicationId())));
}
protected boolean isMultiAssignmentsEnabled() {
return multiAssignmentsConfig.getAsBoolean();
}
protected boolean isConfirmationFlowEnabled() {
return confirmationFlowConfig.getAsBoolean();
}
/**
* Find targets to be considered for assignment.
*
* @param controllerIDs as provided by repository caller
* @param distributionSetId to assign
* @return list of targets up to {@link Constants#MAX_ENTRIES_IN_STATEMENT}
*/
abstract List<JpaTarget> findTargetsForAssignment(final List<String> controllerIDs, final long distributionSetId);
/**
* @param set
* @param targets
*/
abstract void sendTargetUpdatedEvents(final DistributionSet set, final List<JpaTarget> targets);
/**
* Update status and DS fields of given target.
*
* @param distributionSet to set
* @param targetIds to change
* @param currentUser for auditing
*/
abstract void setAssignedDistributionSetAndTargetStatus(final JpaDistributionSet distributionSet,
final List<List<Long>> targetIds, final String currentUser);
/**
* Cancels actions that can be canceled (i.e.
* {@link DistributionSet#isRequiredMigrationStep() is <code>false</code>})
* as a result of the new assignment and returns all {@link Target}s where
* such actions existed.
*
* @param targetIds to cancel actions for
* @return {@link Set} of {@link Target#getId()}s
*/
abstract Set<Long> cancelActiveActions(List<List<Long>> targetIds);
/**
* Cancels actions that can be canceled (i.e.
* {@link DistributionSet#isRequiredMigrationStep() is <code>false</code>})
* as a result of the new assignment and returns all {@link Target}s where
* such actions existed.
*
* @param targetIds to cancel actions for
*/
abstract void closeActiveActions(List<List<Long>> targetIds);
abstract void sendDeploymentEvents(final DistributionSetAssignmentResult assignmentResult);
abstract void sendDeploymentEvents(final List<DistributionSetAssignmentResult> assignmentResults);
private static String getActionMessage(final Action action) {
final RolloutGroup rolloutGroup = action.getRolloutGroup();
if (rolloutGroup != null) {
final Rollout rollout = rolloutGroup.getRollout();
return String.format("Initiated by Rollout Group '%s' [Rollout %s:%s]", rolloutGroup.getName(),
rollout.getName(), rollout.getId());
}
return String.format("Assignment initiated by user '%s'", action.getInitiatedBy());
}
private void cancelAssignDistributionSetEvent(final List<Action> actions) {
if (CollectionUtils.isEmpty(actions)) {
return;
@@ -241,69 +292,9 @@ public abstract class AbstractDsAssignmentStrategy {
actions, eventPublisherHolder.getApplicationId())));
}
public JpaAction createTargetAction(final String initiatedBy, final TargetWithActionType targetWithActionType,
final List<JpaTarget> targets, final JpaDistributionSet set) {
final Optional<JpaTarget> optTarget = targets.stream()
.filter(t -> t.getControllerId().equals(targetWithActionType.getControllerId())).findFirst();
// create the action
return optTarget.map(target -> {
assertActionsPerTargetQuota(target, 1);
final JpaAction actionForTarget = new JpaAction();
actionForTarget.setActionType(targetWithActionType.getActionType());
actionForTarget.setForcedTime(targetWithActionType.getForceTime());
actionForTarget.setWeight(
targetWithActionType.getWeight() == null ?
repositoryProperties.getActionWeightIfAbsent() : targetWithActionType.getWeight());
actionForTarget.setActive(true);
actionForTarget.setTarget(target);
actionForTarget.setDistributionSet(set);
actionForTarget.setMaintenanceWindowSchedule(targetWithActionType.getMaintenanceSchedule());
actionForTarget.setMaintenanceWindowDuration(targetWithActionType.getMaintenanceWindowDuration());
actionForTarget.setMaintenanceWindowTimeZone(targetWithActionType.getMaintenanceWindowTimeZone());
actionForTarget.setInitiatedBy(initiatedBy);
return actionForTarget;
}).orElseGet(() -> {
log.warn("Cannot find target for targetWithActionType '{}'.", targetWithActionType.getControllerId());
return null;
});
}
public JpaActionStatus createActionStatus(final JpaAction action, final String actionMessage) {
final JpaActionStatus actionStatus = new JpaActionStatus();
actionStatus.setAction(action);
actionStatus.setOccurredAt(action.getCreatedAt());
if (StringUtils.hasText(actionMessage)) {
actionStatus.addMessage(actionMessage);
} else {
actionStatus.addMessage(getActionMessage(action));
}
return actionStatus;
}
private static String getActionMessage(final Action action) {
final RolloutGroup rolloutGroup = action.getRolloutGroup();
if (rolloutGroup != null) {
final Rollout rollout = rolloutGroup.getRollout();
return String.format("Initiated by Rollout Group '%s' [Rollout %s:%s]", rolloutGroup.getName(),
rollout.getName(), rollout.getId());
}
return String.format("Assignment initiated by user '%s'", action.getInitiatedBy());
}
private void assertActionsPerTargetQuota(final Target target, final int requested) {
final int quota = quotaManagement.getMaxActionsPerTarget();
QuotaHelper.assertAssignmentQuota(target.getId(), requested, quota, Action.class, Target.class,
actionRepository::countByTargetId);
}
protected boolean isMultiAssignmentsEnabled() {
return multiAssignmentsConfig.getAsBoolean();
}
protected boolean isConfirmationFlowEnabled() {
return confirmationFlowConfig.getAsBoolean();
}
}

View File

@@ -9,6 +9,15 @@
*/
package org.eclipse.hawkbit.repository.jpa.management;
import static org.eclipse.hawkbit.repository.model.Action.ActionType.DOWNLOAD_ONLY;
import static org.eclipse.hawkbit.repository.model.Action.Status.ERROR;
import static org.eclipse.hawkbit.repository.model.Action.Status.FINISHED;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.RepositoryProperties;
@@ -27,15 +36,6 @@ import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import static org.eclipse.hawkbit.repository.model.Action.ActionType.DOWNLOAD_ONLY;
import static org.eclipse.hawkbit.repository.model.Action.Status.ERROR;
import static org.eclipse.hawkbit.repository.model.Action.Status.FINISHED;
/**
* Implements utility methods for managing {@link Action}s
*/
@@ -56,6 +56,54 @@ public class JpaActionManagement {
this.repositoryProperties = repositoryProperties;
}
public int getWeightConsideringDefault(final Action action) {
return action.getWeight().orElse(repositoryProperties.getActionWeightIfAbsent());
}
protected static boolean isDownloadOnly(final JpaAction action) {
return DOWNLOAD_ONLY == action.getActionType();
}
protected List<JpaAction> findActiveActionsHavingStatus(final String controllerId, final Action.Status status) {
return actionRepository.findAll(
ActionSpecifications.byTargetControllerIdAndIsActiveAndStatus(controllerId, status));
}
protected Action addActionStatus(final JpaActionStatusCreate statusCreate) {
final Long actionId = statusCreate.getActionId();
final JpaActionStatus actionStatus = statusCreate.build();
final JpaAction action = getActionAndThrowExceptionIfNotFound(actionId);
if (isUpdatingActionStatusAllowed(action, actionStatus)) {
return handleAddUpdateActionStatus(actionStatus, action);
}
log.debug("Update of actionStatus {} for action {} not possible since action not active anymore.",
actionStatus.getStatus(), action.getId());
return action;
}
protected JpaAction getActionAndThrowExceptionIfNotFound(final Long actionId) {
return actionRepository.findById(actionId)
.orElseThrow(() -> new EntityNotFoundException(Action.class, actionId));
}
protected void onActionStatusUpdate(final Action.Status updatedActionStatus, final JpaAction action) {
// can be overwritten to intercept the persistence of the action status
}
protected void assertActionStatusQuota(final JpaActionStatus newActionStatus, final JpaAction action) {
if (isIntermediateStatus(newActionStatus)) {// check for quota only for intermediate statuses
QuotaHelper.assertAssignmentQuota(action.getId(), 1, quotaManagement.getMaxStatusEntriesPerAction(),
ActionStatus.class, Action.class, actionStatusRepository::countByActionId);
}
}
protected void assertActionStatusMessageQuota(final JpaActionStatus actionStatus) {
QuotaHelper.assertAssignmentQuota(actionStatus.getId(), actionStatus.getMessages().size(),
quotaManagement.getMaxMessagesPerActionStatus(), "Message", ActionStatus.class.getSimpleName(), null);
}
List<Action> findActiveActionsWithHighestWeightConsideringDefault(final String controllerId,
final int maxActionCount) {
final List<Action> actions = new ArrayList<>();
@@ -84,23 +132,8 @@ public class JpaActionManagement {
return actions.stream().sorted(actionImportance).limit(maxActionCount).collect(Collectors.toList());
}
protected List<JpaAction> findActiveActionsHavingStatus(final String controllerId, final Action.Status status) {
return actionRepository.findAll(
ActionSpecifications.byTargetControllerIdAndIsActiveAndStatus(controllerId, status));
}
protected Action addActionStatus(final JpaActionStatusCreate statusCreate) {
final Long actionId = statusCreate.getActionId();
final JpaActionStatus actionStatus = statusCreate.build();
final JpaAction action = getActionAndThrowExceptionIfNotFound(actionId);
if (isUpdatingActionStatusAllowed(action, actionStatus)) {
return handleAddUpdateActionStatus(actionStatus, action);
}
log.debug("Update of actionStatus {} for action {} not possible since action not active anymore.",
actionStatus.getStatus(), action.getId());
return action;
private static boolean isIntermediateStatus(final JpaActionStatus actionStatus) {
return FINISHED != actionStatus.getStatus() && ERROR != actionStatus.getStatus();
}
/**
@@ -117,25 +150,12 @@ public class JpaActionManagement {
final boolean isAllowedByRepositoryConfiguration = intermediateStatus && !repositoryProperties.isRejectActionStatusForClosedAction();
//in case of download_only action Status#DOWNLOADED is treated as 'final' already, so we accept one final status from device in case it sends
final boolean isAllowedForDownloadOnlyActions = isDownloadOnly(action) && action.getStatus() == Action.Status.DOWNLOADED && !intermediateStatus;
final boolean isAllowedForDownloadOnlyActions = isDownloadOnly(
action) && action.getStatus() == Action.Status.DOWNLOADED && !intermediateStatus;
return action.isActive() || isAllowedByRepositoryConfiguration || isAllowedForDownloadOnlyActions;
}
public int getWeightConsideringDefault(final Action action) {
return action.getWeight().orElse(repositoryProperties.getActionWeightIfAbsent());
}
protected JpaAction getActionAndThrowExceptionIfNotFound(final Long actionId) {
return actionRepository.findById(actionId)
.orElseThrow(() -> new EntityNotFoundException(Action.class, actionId));
}
protected static boolean isDownloadOnly(final JpaAction action) {
return DOWNLOAD_ONLY == action.getActionType();
}
/**
* Sets {@link TargetUpdateStatus} based on given {@link ActionStatus}.
*/
@@ -152,24 +172,4 @@ public class JpaActionManagement {
action.setLastActionStatusCode(actionStatus.getCode().orElse(null));
return actionRepository.save(action);
}
protected void onActionStatusUpdate(final Action.Status updatedActionStatus, final JpaAction action){
// can be overwritten to intercept the persistence of the action status
}
protected void assertActionStatusQuota(final JpaActionStatus newActionStatus, final JpaAction action) {
if (isIntermediateStatus(newActionStatus)) {// check for quota only for intermediate statuses
QuotaHelper.assertAssignmentQuota(action.getId(), 1, quotaManagement.getMaxStatusEntriesPerAction(),
ActionStatus.class, Action.class, actionStatusRepository::countByActionId);
}
}
protected void assertActionStatusMessageQuota(final JpaActionStatus actionStatus) {
QuotaHelper.assertAssignmentQuota(actionStatus.getId(), actionStatus.getMessages().size(),
quotaManagement.getMaxMessagesPerActionStatus(), "Message", ActionStatus.class.getSimpleName(), null);
}
private static boolean isIntermediateStatus(final JpaActionStatus actionStatus) {
return FINISHED != actionStatus.getStatus() && ERROR != actionStatus.getStatus();
}
}

View File

@@ -13,6 +13,8 @@ import java.io.IOException;
import java.io.InputStream;
import java.util.Optional;
import jakarta.persistence.EntityManager;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.artifact.repository.ArtifactRepository;
import org.eclipse.hawkbit.artifact.repository.ArtifactStoreException;
@@ -58,8 +60,6 @@ import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.validation.annotation.Validated;
import jakarta.persistence.EntityManager;
/**
* JPA based {@link ArtifactManagement} implementation.
*/
@@ -92,6 +92,11 @@ public class JpaArtifactManagement implements ArtifactManagement {
this.tenantAware = tenantAware;
}
@Override
public long count() {
return localArtifactRepository.count();
}
@Override
@Transactional
@Retryable(include = {
@@ -127,90 +132,6 @@ public class JpaArtifactManagement implements ArtifactManagement {
}
}
private AbstractDbArtifact storeArtifact(final ArtifactUpload artifactUpload, final boolean isSmEncrypted) {
final String tenant = tenantAware.getCurrentTenant();
final long smId = artifactUpload.getModuleId();
final InputStream stream = artifactUpload.getInputStream();
final String fileName = artifactUpload.getFilename();
final String contentType = artifactUpload.getContentType();
final String providedSha1 = artifactUpload.getProvidedSha1Sum();
final String providedMd5 = artifactUpload.getProvidedMd5Sum();
final String providedSha256 = artifactUpload.getProvidedSha256Sum();
try (final InputStream wrappedStream = wrapInQuotaStream(
isSmEncrypted ? wrapInEncryptionStream(smId, stream) : stream)) {
return artifactRepository.store(tenant, wrappedStream, fileName, contentType,
new DbArtifactHash(providedSha1, providedMd5, providedSha256));
} catch (final ArtifactStoreException | IOException e) {
throw new ArtifactUploadFailedException(e);
} catch (final HashNotMatchException e) {
if (e.getHashFunction().equals(HashNotMatchException.SHA1)) {
throw new InvalidSHA1HashException(e.getMessage(), e);
} else if (e.getHashFunction().equals(HashNotMatchException.SHA256)) {
throw new InvalidSHA256HashException(e.getMessage(), e);
} else {
throw new InvalidMD5HashException(e.getMessage(), e);
}
}
}
private InputStream wrapInEncryptionStream(final long smId, final InputStream stream) {
return ArtifactEncryptionService.getInstance().encryptSoftwareModuleArtifact(smId, stream);
}
private void assertArtifactQuota(final long moduleId, final int requested) {
QuotaHelper.assertAssignmentQuota(
moduleId, requested, quotaManagement.getMaxArtifactsPerSoftwareModule(),
Artifact.class, SoftwareModule.class,
// get all artifacts without user context
softwareModuleId -> localArtifactRepository
.count(null, ArtifactSpecifications.bySoftwareModuleId(softwareModuleId)));
}
private InputStream wrapInQuotaStream(final InputStream in) {
final long maxArtifactSize = quotaManagement.getMaxArtifactSize();
final long currentlyUsed = localArtifactRepository.sumOfNonDeletedArtifactSize().orElse(0L);
final long maxArtifactSizeTotal = quotaManagement.getMaxArtifactStorage();
return new FileSizeAndStorageQuotaCheckingInputStream(in, maxArtifactSize,
maxArtifactSizeTotal - currentlyUsed);
}
/**
* Garbage collects artifact binaries if only referenced by given
* {@link SoftwareModule#getId()} or {@link SoftwareModule}'s that are
* marked as deleted.
* <p/>
* Software module related UPDATE permission shall be checked by the callers!
*
* @param sha1Hash no longer needed
* @param softwareModuleId the garbage collection call is made for
*/
@PreAuthorize(SpPermission.SpringEvalExpressions.HAS_AUTH_DELETE_REPOSITORY)
void clearArtifactBinary(final String sha1Hash) {
// countBySha1HashAndTenantAndSoftwareModuleDeletedIsFalse will skip ACM checks and
// will return total count as it should be
final long count = localArtifactRepository.countBySha1HashAndTenantAndSoftwareModuleDeletedIsFalse(
sha1Hash,
tenantAware.getCurrentTenant());
if (count <= 1) { // 1 artifact is the one being deleted!
// removes the real artifact ONLY AFTER the delete of artifact or software module
// in local history has passed successfully (caller has permission and no errors)
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@Override
public void afterCommit() {
try {
log.debug("deleting artifact from repository {}", sha1Hash);
artifactRepository.deleteBySha1(tenantAware.getCurrentTenant(), sha1Hash);
} catch (final ArtifactStoreException e) {
throw new ArtifactDeleteFailedException(e);
}
}
});
} // else there are still other artifacts that need the binary
}
@Override
@Transactional
@Retryable(include = {
@@ -268,11 +189,6 @@ public class JpaArtifactManagement implements ArtifactManagement {
return localArtifactRepository.count(ArtifactSpecifications.bySoftwareModuleId(softwareModuleId));
}
@Override
public long count() {
return localArtifactRepository.count();
}
@Override
public Optional<DbArtifact> loadArtifactBinary(final String sha1Hash, final long softwareModuleId,
final boolean isEncrypted) {
@@ -293,6 +209,91 @@ public class JpaArtifactManagement implements ArtifactManagement {
return Optional.empty();
}
/**
* Garbage collects artifact binaries if only referenced by given
* {@link SoftwareModule#getId()} or {@link SoftwareModule}'s that are
* marked as deleted.
* <p/>
* Software module related UPDATE permission shall be checked by the callers!
*
* @param sha1Hash no longer needed
* @param softwareModuleId the garbage collection call is made for
*/
@PreAuthorize(SpPermission.SpringEvalExpressions.HAS_AUTH_DELETE_REPOSITORY)
void clearArtifactBinary(final String sha1Hash) {
// countBySha1HashAndTenantAndSoftwareModuleDeletedIsFalse will skip ACM checks and
// will return total count as it should be
final long count = localArtifactRepository.countBySha1HashAndTenantAndSoftwareModuleDeletedIsFalse(
sha1Hash,
tenantAware.getCurrentTenant());
if (count <= 1) { // 1 artifact is the one being deleted!
// removes the real artifact ONLY AFTER the delete of artifact or software module
// in local history has passed successfully (caller has permission and no errors)
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@Override
public void afterCommit() {
try {
log.debug("deleting artifact from repository {}", sha1Hash);
artifactRepository.deleteBySha1(tenantAware.getCurrentTenant(), sha1Hash);
} catch (final ArtifactStoreException e) {
throw new ArtifactDeleteFailedException(e);
}
}
});
} // else there are still other artifacts that need the binary
}
private AbstractDbArtifact storeArtifact(final ArtifactUpload artifactUpload, final boolean isSmEncrypted) {
final String tenant = tenantAware.getCurrentTenant();
final long smId = artifactUpload.getModuleId();
final InputStream stream = artifactUpload.getInputStream();
final String fileName = artifactUpload.getFilename();
final String contentType = artifactUpload.getContentType();
final String providedSha1 = artifactUpload.getProvidedSha1Sum();
final String providedMd5 = artifactUpload.getProvidedMd5Sum();
final String providedSha256 = artifactUpload.getProvidedSha256Sum();
try (final InputStream wrappedStream = wrapInQuotaStream(
isSmEncrypted ? wrapInEncryptionStream(smId, stream) : stream)) {
return artifactRepository.store(tenant, wrappedStream, fileName, contentType,
new DbArtifactHash(providedSha1, providedMd5, providedSha256));
} catch (final ArtifactStoreException | IOException e) {
throw new ArtifactUploadFailedException(e);
} catch (final HashNotMatchException e) {
if (e.getHashFunction().equals(HashNotMatchException.SHA1)) {
throw new InvalidSHA1HashException(e.getMessage(), e);
} else if (e.getHashFunction().equals(HashNotMatchException.SHA256)) {
throw new InvalidSHA256HashException(e.getMessage(), e);
} else {
throw new InvalidMD5HashException(e.getMessage(), e);
}
}
}
private InputStream wrapInEncryptionStream(final long smId, final InputStream stream) {
return ArtifactEncryptionService.getInstance().encryptSoftwareModuleArtifact(smId, stream);
}
private void assertArtifactQuota(final long moduleId, final int requested) {
QuotaHelper.assertAssignmentQuota(
moduleId, requested, quotaManagement.getMaxArtifactsPerSoftwareModule(),
Artifact.class, SoftwareModule.class,
// get all artifacts without user context
softwareModuleId -> localArtifactRepository
.count(null, ArtifactSpecifications.bySoftwareModuleId(softwareModuleId)));
}
private InputStream wrapInQuotaStream(final InputStream in) {
final long maxArtifactSize = quotaManagement.getMaxArtifactSize();
final long currentlyUsed = localArtifactRepository.sumOfNonDeletedArtifactSize().orElse(0L);
final long maxArtifactSizeTotal = quotaManagement.getMaxArtifactStorage();
return new FileSizeAndStorageQuotaCheckingInputStream(in, maxArtifactSize,
maxArtifactSizeTotal - currentlyUsed);
}
private DbArtifact wrapInEncryptionAwareDbArtifact(final long softwareModuleId, final DbArtifact dbArtifact) {
if (dbArtifact == null) {
return null;

View File

@@ -159,17 +159,20 @@ public class JpaConfirmationManagement extends JpaActionManagement implements Co
return addActionStatus((JpaActionStatusCreate) statusCreate);
}
private ActionStatusCreate createConfirmationActionStatus(final long actionId, final Integer code,
final Collection<String> messages) {
final ActionStatusCreate statusCreate = entityFactory.actionStatus().create(actionId);
if (!CollectionUtils.isEmpty(messages)) {
statusCreate.messages(messages);
@Override
@Transactional
public void deactivateAutoConfirmation(String controllerId) {
log.debug("Deactivate auto confirmation for controllerId '{}'", controllerId);
final JpaTarget target = getTargetByControllerIdAndThrowIfNotFound(controllerId);
target.setAutoConfirmationStatus(null);
targetRepository.save(target);
}
if (code != null) {
statusCreate.code(code);
statusCreate.message(String.format(CONFIRMATION_CODE_MSG_PREFIX, code));
@Override
protected void onActionStatusUpdate(final Status updatedActionStatus, final JpaAction action) {
if (updatedActionStatus == Status.RUNNING && action.isActive()) {
action.setStatus(Status.RUNNING);
}
return statusCreate;
}
private static void assertActionCanAcceptFeedback(final Action action) {
@@ -188,6 +191,19 @@ public class JpaConfirmationManagement extends JpaActionManagement implements Co
}
}
private ActionStatusCreate createConfirmationActionStatus(final long actionId, final Integer code,
final Collection<String> messages) {
final ActionStatusCreate statusCreate = entityFactory.actionStatus().create(actionId);
if (!CollectionUtils.isEmpty(messages)) {
statusCreate.messages(messages);
}
if (code != null) {
statusCreate.code(code);
statusCreate.message(String.format(CONFIRMATION_CODE_MSG_PREFIX, code));
}
return statusCreate;
}
private List<Action> giveConfirmationForActiveActions(final AutoConfirmationStatus autoConfirmationStatus) {
final Target target = autoConfirmationStatus.getTarget();
return findActiveActionsHavingStatus(target.getControllerId(), Status.WAIT_FOR_CONFIRMATION).stream()
@@ -218,24 +234,8 @@ public class JpaConfirmationManagement extends JpaActionManagement implements Co
return actionRepository.save(action);
}
@Override
@Transactional
public void deactivateAutoConfirmation(String controllerId) {
log.debug("Deactivate auto confirmation for controllerId '{}'", controllerId);
final JpaTarget target = getTargetByControllerIdAndThrowIfNotFound(controllerId);
target.setAutoConfirmationStatus(null);
targetRepository.save(target);
}
private JpaTarget getTargetByControllerIdAndThrowIfNotFound(final String controllerId) {
return targetRepository.findOne(TargetSpecifications.hasControllerId(controllerId))
.orElseThrow(() -> new EntityNotFoundException(Target.class, controllerId));
}
@Override
protected void onActionStatusUpdate(final Status updatedActionStatus, final JpaAction action) {
if (updatedActionStatus == Status.RUNNING && action.isActive()) {
action.setStatus(Status.RUNNING);
}
}
}

View File

@@ -127,16 +127,6 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
+ ACTION_PAGE_LIMIT;
private static final EnumMap<Database, String> QUERY_DELETE_ACTIONS_BY_STATE_AND_LAST_MODIFIED;
static {
QUERY_DELETE_ACTIONS_BY_STATE_AND_LAST_MODIFIED = new EnumMap<>(Database.class);
QUERY_DELETE_ACTIONS_BY_STATE_AND_LAST_MODIFIED.put(Database.SQL_SERVER, "DELETE TOP (" + ACTION_PAGE_LIMIT
+ ") FROM sp_action WHERE tenant=#tenant AND status IN (%s) AND last_modified_at<#last_modified_at ");
QUERY_DELETE_ACTIONS_BY_STATE_AND_LAST_MODIFIED.put(Database.POSTGRESQL,
"DELETE FROM sp_action WHERE id IN (SELECT id FROM sp_action WHERE tenant=#tenant AND status IN (%s) AND last_modified_at<#last_modified_at LIMIT "
+ ACTION_PAGE_LIMIT + ")");
}
private final EntityManager entityManager;
private final DistributionSetManagement distributionSetManagement;
private final TargetRepository targetRepository;
@@ -152,6 +142,15 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
private final Database database;
private final RetryTemplate retryTemplate;
static {
QUERY_DELETE_ACTIONS_BY_STATE_AND_LAST_MODIFIED = new EnumMap<>(Database.class);
QUERY_DELETE_ACTIONS_BY_STATE_AND_LAST_MODIFIED.put(Database.SQL_SERVER, "DELETE TOP (" + ACTION_PAGE_LIMIT
+ ") FROM sp_action WHERE tenant=#tenant AND status IN (%s) AND last_modified_at<#last_modified_at ");
QUERY_DELETE_ACTIONS_BY_STATE_AND_LAST_MODIFIED.put(Database.POSTGRESQL,
"DELETE FROM sp_action WHERE id IN (SELECT id FROM sp_action WHERE tenant=#tenant AND status IN (%s) AND last_modified_at<#last_modified_at LIMIT "
+ ACTION_PAGE_LIMIT + ")");
}
public JpaDeploymentManagement(final EntityManager entityManager, final ActionRepository actionRepository,
final DistributionSetManagement distributionSetManagement, final TargetRepository targetRepository,
final ActionStatusRepository actionStatusRepository, final AuditorAware<String> auditorProvider,
@@ -183,9 +182,18 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
@Override
@Transactional(isolation = Isolation.READ_COMMITTED)
public List<DistributionSetAssignmentResult> offlineAssignedDistributionSets(
final Collection<Entry<String, Long>> assignments) {
return offlineAssignedDistributionSets(assignments,tenantAware.getCurrentUsername());
public List<DistributionSetAssignmentResult> assignDistributionSets(
final List<DeploymentRequest> deploymentRequests) {
return assignDistributionSets(tenantAware.getCurrentUsername(), deploymentRequests, null);
}
@Override
@Transactional(isolation = Isolation.READ_COMMITTED)
public List<DistributionSetAssignmentResult> assignDistributionSets(final String initiatedBy,
final List<DeploymentRequest> deploymentRequests, final String actionMessage) {
WeightValidationHelper.usingContext(systemSecurityContext, tenantConfigurationManagement)
.validate(deploymentRequests);
return assignDistributionSets(initiatedBy, deploymentRequests, actionMessage, onlineDsAssignmentStrategy);
}
@Override
@@ -205,18 +213,422 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
@Override
@Transactional(isolation = Isolation.READ_COMMITTED)
public List<DistributionSetAssignmentResult> assignDistributionSets(
final List<DeploymentRequest> deploymentRequests) {
return assignDistributionSets(tenantAware.getCurrentUsername(), deploymentRequests, null);
public List<DistributionSetAssignmentResult> offlineAssignedDistributionSets(
final Collection<Entry<String, Long>> assignments) {
return offlineAssignedDistributionSets(assignments, tenantAware.getCurrentUsername());
}
@Override
@Transactional(isolation = Isolation.READ_COMMITTED)
public List<DistributionSetAssignmentResult> assignDistributionSets(final String initiatedBy,
final List<DeploymentRequest> deploymentRequests, final String actionMessage) {
WeightValidationHelper.usingContext(systemSecurityContext, tenantConfigurationManagement)
.validate(deploymentRequests);
return assignDistributionSets(initiatedBy, deploymentRequests, actionMessage, onlineDsAssignmentStrategy);
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public Action cancelAction(final long actionId) {
log.debug("cancelAction({})", actionId);
final JpaAction action = actionRepository.findById(actionId)
.orElseThrow(() -> new EntityNotFoundException(Action.class, actionId));
if (action.isCancelingOrCanceled()) {
throw new CancelActionNotAllowedException("Actions in canceling or canceled state cannot be canceled");
}
assertTargetUpdateAllowed(action);
if (action.isActive()) {
log.debug("action ({}) was still active. Change to {}.", action, Status.CANCELING);
action.setStatus(Status.CANCELING);
// document that the status has been retrieved
actionStatusRepository.save(new JpaActionStatus(action, Status.CANCELING, System.currentTimeMillis(),
RepositoryConstants.SERVER_MESSAGE_PREFIX + "manual cancelation requested"));
final Action saveAction = actionRepository.save(action);
onlineDsAssignmentStrategy.cancelAssignment(action);
return saveAction;
} else {
throw new CancelActionNotAllowedException(action.getId() + " is not active and cannot be canceled");
}
}
@Override
public long countActionsByTarget(final String rsqlParam, final String controllerId) {
assertTargetReadAllowed(controllerId);
final List<Specification<JpaAction>> specList = Arrays.asList(
RSQLUtility.buildRsqlSpecification(rsqlParam, ActionFields.class, virtualPropertyReplacer, database),
ActionSpecifications.byTargetControllerId(controllerId));
return JpaManagementHelper.countBySpec(actionRepository, specList);
}
@Override
public long countActionsAll() {
return actionRepository.count();
}
@Override
public long countActions(final String rsqlParam) {
final List<Specification<JpaAction>> specList = List.of(
RSQLUtility.buildRsqlSpecification(rsqlParam, ActionFields.class, virtualPropertyReplacer, database));
return JpaManagementHelper.countBySpec(actionRepository, specList);
}
@Override
public long countActionsByTarget(final String controllerId) {
assertTargetReadAllowed(controllerId);
return actionRepository.countByTargetControllerId(controllerId);
}
@Override
public Optional<Action> findAction(final long actionId) {
return actionRepository.findById(actionId)
.filter(action -> targetRepository.exists(TargetSpecifications.hasId(action.getTarget().getId())))
.map(JpaAction.class::cast);
}
@Override
public Slice<Action> findActionsAll(final Pageable pageable) {
return JpaManagementHelper.findAllWithoutCountBySpec(actionRepository, pageable, null);
}
@Override
public Slice<Action> findActions(final String rsqlParam, final Pageable pageable) {
final List<Specification<JpaAction>> specList = List.of(
RSQLUtility.buildRsqlSpecification(rsqlParam, ActionFields.class, virtualPropertyReplacer, database));
return JpaManagementHelper.findAllWithoutCountBySpec(actionRepository, pageable, specList);
}
@Override
public Page<Action> findActionsByTarget(final String rsqlParam, final String controllerId,
final Pageable pageable) {
assertTargetReadAllowed(controllerId);
final List<Specification<JpaAction>> specList = Arrays.asList(
RSQLUtility.buildRsqlSpecification(rsqlParam, ActionFields.class, virtualPropertyReplacer, database),
ActionSpecifications.byTargetControllerId(controllerId));
return JpaManagementHelper.findAllWithCountBySpec(actionRepository, pageable, specList);
}
@Override
public Slice<Action> findActionsByTarget(final String controllerId, final Pageable pageable) {
assertTargetReadAllowed(controllerId);
return actionRepository.findAll(ActionSpecifications.byTargetControllerId(controllerId), pageable)
.map(Action.class::cast);
}
@Override
public Page<ActionStatus> findActionStatusByAction(final Pageable pageReq, final long actionId) {
assertActionExistsAndAccessible(actionId);
return actionStatusRepository.findByActionId(pageReq, actionId);
}
@Override
public long countActionStatusByAction(final long actionId) {
assertActionExistsAndAccessible(actionId);
return actionStatusRepository.countByActionId(actionId);
}
// action is already got and there are checked read permissions - do not check
// permissions
// and UI which is to be removed
@Override
public Page<String> findMessagesByActionStatusId(final Pageable pageable, final long actionStatusId) {
final CriteriaBuilder cb = entityManager.getCriteriaBuilder();
final CriteriaQuery<String> msgQuery = cb.createQuery(String.class);
final Root<JpaActionStatus> as = msgQuery.from(JpaActionStatus.class);
final ListJoin<JpaActionStatus, String> join = as.joinList("messages", JoinType.LEFT);
final CriteriaQuery<String> selMsgQuery = msgQuery.select(join);
selMsgQuery.where(cb.equal(as.get(JpaActionStatus_.id), actionStatusId));
final List<String> result = new ArrayList<>(entityManager.createQuery(selMsgQuery)
.setFirstResult((int) pageable.getOffset()).setMaxResults(pageable.getPageSize()).getResultList());
return new PageImpl<>(result, pageable, result.size());
}
@Override
public Optional<Action> findActionWithDetails(final long actionId) {
return actionRepository.findWithDetailsById(actionId)
.filter(action -> targetRepository.exists(TargetSpecifications.hasId(action.getTarget().getId())));
}
@Override
public Page<Action> findActiveActionsByTarget(final Pageable pageable, final String controllerId) {
assertTargetReadAllowed(controllerId);
return actionRepository
.findAll(ActionSpecifications.byTargetControllerIdAndActive(controllerId, true), pageable)
.map(Action.class::cast);
}
@Override
public Page<Action> findInActiveActionsByTarget(final Pageable pageable, final String controllerId) {
assertTargetReadAllowed(controllerId);
return actionRepository
.findAll(ActionSpecifications.byTargetControllerIdAndActive(controllerId, false), pageable)
.map(Action.class::cast);
}
@Override
public List<Action> findActiveActionsWithHighestWeight(final String controllerId, final int maxActionCount) {
assertTargetReadAllowed(controllerId);
return findActiveActionsWithHighestWeightConsideringDefault(controllerId, maxActionCount);
}
@Override
@Transactional(isolation = Isolation.READ_COMMITTED)
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public Action forceQuitAction(final long actionId) {
final JpaAction action = actionRepository.findById(actionId)
.orElseThrow(() -> new EntityNotFoundException(Action.class, actionId));
if (!action.isCancelingOrCanceled()) {
throw new ForceQuitActionNotAllowedException(
action.getId() + " is not canceled yet and cannot be force quit");
}
if (!action.isActive()) {
throw new ForceQuitActionNotAllowedException(action.getId() + " is not active and cannot be force quit");
}
assertTargetUpdateAllowed(action);
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, System.currentTimeMillis(),
RepositoryConstants.SERVER_MESSAGE_PREFIX + "A force quit has been performed."));
DeploymentHelper.successCancellation(action, actionRepository, targetRepository);
return actionRepository.save(action);
}
@Override
@Transactional
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public Action forceTargetAction(final long actionId) {
final JpaAction action = actionRepository.findById(actionId).map(this::assertTargetUpdateAllowed)
.orElseThrow(() -> new EntityNotFoundException(Action.class, actionId));
if (!action.isForcedOrTimeForced()) {
action.setActionType(ActionType.FORCED);
return actionRepository.save(action);
}
return action;
}
@Override
@Transactional(isolation = Isolation.READ_COMMITTED)
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public void cancelInactiveScheduledActionsForTargets(final List<Long> targetIds) {
if (!isMultiAssignmentsEnabled()) {
targetRepository.getAccessController().ifPresent(v -> {
if (targetRepository.count(AccessController.Operation.UPDATE,
TargetSpecifications.hasIdIn(targetIds)) != targetIds.size()) {
throw new EntityNotFoundException(Target.class, targetIds);
}
});
actionRepository.switchStatus(Status.CANCELED, targetIds, false, Status.SCHEDULED);
} else {
log.debug("The Multi Assignments feature is enabled: No need to cancel inactive scheduled actions.");
}
}
@Override
public void startScheduledActionsByRolloutGroupParent(final long rolloutId, final long distributionSetId,
final Long rolloutGroupParentId) {
while (DeploymentHelper.runInNewTransaction(
txManager,
"startScheduledActions-" + rolloutId,
status -> {
final PageRequest pageRequest = PageRequest.of(0, ACTION_PAGE_LIMIT);
final Page<Action> groupScheduledActions;
if (rolloutGroupParentId == null) {
groupScheduledActions = actionRepository.findByRolloutIdAndRolloutGroupParentIsNullAndStatus(pageRequest, rolloutId,
Action.Status.SCHEDULED);
} else {
groupScheduledActions = actionRepository.findByRolloutIdAndRolloutGroupParentIdAndStatus(pageRequest, rolloutId,
rolloutGroupParentId, Action.Status.SCHEDULED);
}
if (groupScheduledActions.getContent().isEmpty()) {
return 0L;
} else {
// self invocation won't check @PreAuthorize but it is already checked for the method
startScheduledActions(groupScheduledActions.getContent());
return groupScheduledActions.getTotalElements();
}
}) > 0) ;
}
@Override
public void startScheduledActions(final List<Action> rolloutGroupActions) {
// Close actions already assigned and collect pending assignments
final List<JpaAction> pendingTargetAssignments = rolloutGroupActions.stream()
.map(JpaAction.class::cast)
.map(this::closeActionIfSetWasAlreadyAssigned)
.filter(Objects::nonNull)
.toList();
if (pendingTargetAssignments.isEmpty()) {
return;
}
// check if old actions needs to be canceled first
final List<Action> newTargetAssignments = startScheduledActionsAndHandleOpenCancellationFirst(pendingTargetAssignments);
if (!newTargetAssignments.isEmpty()) {
onlineDsAssignmentStrategy.sendDeploymentEvents(newTargetAssignments.get(0).getDistributionSet().getId(), newTargetAssignments);
}
}
@Override
public Optional<DistributionSet> getAssignedDistributionSet(final String controllerId) {
return targetRepository
.findOne(TargetSpecifications.hasControllerId(controllerId))
.map(JpaTarget::getAssignedDistributionSet);
}
@Override
public Optional<DistributionSet> getInstalledDistributionSet(final String controllerId) {
return targetRepository
.findOne(TargetSpecifications.hasControllerId(controllerId))
.map(JpaTarget::getInstalledDistributionSet);
}
@Override
@Transactional(readOnly = false)
public int deleteActionsByStatusAndLastModifiedBefore(final Set<Status> status, final long lastModified) {
if (status.isEmpty()) {
return 0;
}
/*
* We use a native query here because Spring JPA does not support to specify a
* LIMIT clause on a DELETE statement. However, for this specific use case
* (action cleanup), we must specify a row limit to reduce the overall load on
* the database.
*/
final int statusCount = status.size();
final Status[] statusArr = status.toArray(new Status[statusCount]);
final String queryStr = String.format(getQueryForDeleteActionsByStatusAndLastModifiedBeforeString(database),
formatInClauseWithNumberKeys(statusCount));
final Query deleteQuery = entityManager.createNativeQuery(queryStr);
IntStream.range(0, statusCount)
.forEach(i -> deleteQuery.setParameter(String.valueOf(i), statusArr[i].ordinal()));
deleteQuery.setParameter("tenant", tenantAware.getCurrentTenant().toUpperCase());
deleteQuery.setParameter("last_modified_at", lastModified);
log.debug("Action cleanup: Executing the following (native) query: {}", deleteQuery);
return deleteQuery.executeUpdate();
}
@Override
public boolean hasPendingCancellations(final Long targetId) {
// target access checked in assertTargetReadAllowed
assertTargetReadAllowed(targetId);
return actionRepository
.exists(ActionSpecifications.byTargetIdAndIsActiveAndStatus(targetId, Action.Status.CANCELING));
}
@Override
@Transactional
public void cancelActionsForDistributionSet(final CancelationType cancelationType,
final DistributionSet distributionSet) {
actionRepository.findAll(ActionSpecifications
.byDistributionSetIdAndActiveAndStatusIsNot(distributionSet.getId(), Status.CANCELING))
.forEach(action -> {
try {
assertTargetUpdateAllowed(action);
cancelAction(action.getId());
log.debug("Action {} canceled", action.getId());
} catch (final InsufficientPermissionException e) {
log.trace("Could not cancel action {} due to insufficient permissions.", action.getId(), e);
} catch (final EntityNotFoundException e) {
log.trace("Could not cancel action {} due to entity not found exception.", action.getId(), e);
}
});
if (cancelationType == CancelationType.FORCE) {
actionRepository.findAll(ActionSpecifications.byDistributionSetIdAndActive(distributionSet.getId()))
.forEach(action -> {
try {
assertTargetUpdateAllowed(action);
forceQuitAction(action.getId());
log.debug("Action {} force canceled", action.getId());
} catch (final InsufficientPermissionException e) {
log.trace("Could not cancel action {} due to insufficient permissions.", action.getId(), e);
} catch (final EntityNotFoundException e) {
log.trace("Could not cancel action {} due to entity not found exception.", action.getId(),
e);
}
});
}
}
protected ActionRepository getActionRepository() {
return actionRepository;
}
protected boolean isActionsAutocloseEnabled() {
return getConfigValue(REPOSITORY_ACTIONS_AUTOCLOSE_ENABLED, Boolean.class);
}
private static Map<Long, List<TargetWithActionType>> convertRequest(
final Collection<DeploymentRequest> deploymentRequests) {
return deploymentRequests.stream().collect(Collectors.groupingBy(DeploymentRequest::getDistributionSetId,
Collectors.mapping(DeploymentRequest::getTargetWithActionType, Collectors.toList())));
}
/**
* split tIDs length into max entries in-statement because many database have
* constraint of max entries in in-statements e.g. Oracle with maximum 1000
* elements, so we need to split the entries here and execute multiple
* statements
*/
private static List<List<Long>> getTargetEntitiesAsChunks(final List<JpaTarget> targetEntities) {
return ListUtils.partition(targetEntities.stream().map(Target::getId).collect(Collectors.toList()),
Constants.MAX_ENTRIES_IN_STATEMENT);
}
private static DistributionSetAssignmentResult buildAssignmentResult(final JpaDistributionSet distributionSet,
final List<JpaAction> assignedActions, final int totalTargetsForAssignment) {
final int alreadyAssignedTargetsCount = totalTargetsForAssignment - assignedActions.size();
return new DistributionSetAssignmentResult(distributionSet, alreadyAssignedTargetsCount, assignedActions);
}
private static String getQueryForDeleteActionsByStatusAndLastModifiedBeforeString(final Database database) {
return QUERY_DELETE_ACTIONS_BY_STATE_AND_LAST_MODIFIED.getOrDefault(database,
QUERY_DELETE_ACTIONS_BY_STATE_AND_LAST_MODIFIED_DEFAULT);
}
private static String formatInClauseWithNumberKeys(final int count) {
return formatInClause(IntStream.range(0, count).mapToObj(String::valueOf).collect(Collectors.toList()));
}
private static String formatInClause(final Collection<String> elements) {
return "#" + String.join(",#", elements);
}
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 String initiatedBy,
@@ -327,12 +739,6 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
return deploymentRequests;
}
private static Map<Long, List<TargetWithActionType>> convertRequest(
final Collection<DeploymentRequest> deploymentRequests) {
return deploymentRequests.stream().collect(Collectors.groupingBy(DeploymentRequest::getDistributionSetId,
Collectors.mapping(DeploymentRequest::getTargetWithActionType, Collectors.toList())));
}
private DistributionSetAssignmentResult assignDistributionSetToTargetsWithRetry(final String initiatedBy,
final Long dsId, final Collection<TargetWithActionType> targetsWithActionType, final String actionMessage,
final AbstractDsAssignmentStrategy assignmentStrategy) {
@@ -355,20 +761,13 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
* to {@link TargetUpdateStatus#IN_SYNC} <br/>
* D. does not send a {@link TargetAssignDistributionSetEvent}.<br/>
*
* @param initiatedBy
* the username of the user who initiated the assignment
* @param dsId
* the ID of the distribution set to assign
* @param targetsWithActionType
* a list of all targets and their action type
* @param actionMessage
* an optional message to be written into the action status
* @param assignmentStrategy
* the assignment strategy (online /offline)
* @param initiatedBy the username of the user who initiated the assignment
* @param dsId the ID of the distribution set to assign
* @param targetsWithActionType a list of all targets and their action type
* @param actionMessage an optional message to be written into the action status
* @param assignmentStrategy the assignment strategy (online /offline)
* @return the assignment result
*
* @throws IncompleteDistributionSetException
* if mandatory {@link SoftwareModuleType} are not assigned as
* @throws IncompleteDistributionSetException if mandatory {@link SoftwareModuleType} are not assigned as
* define by the {@link DistributionSetType}.
*/
private DistributionSetAssignmentResult assignDistributionSetToTargets(final String initiatedBy, final Long dsId,
@@ -377,7 +776,7 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
final JpaDistributionSet distributionSet =
(JpaDistributionSet) distributionSetManagement.getValidAndComplete(dsId);
if (((JpaDistributionSetManagement)distributionSetManagement).isImplicitLockApplicable(distributionSet)) {
if (((JpaDistributionSetManagement) distributionSetManagement).isImplicitLockApplicable(distributionSet)) {
// without new transaction DS changed event is not thrown
DeploymentHelper.runInNewTransaction(txManager, "Implicit lock", status -> {
distributionSetManagement.lock(distributionSet.getId());
@@ -446,24 +845,6 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
return new ArrayList<>(assignedActions.values());
}
/**
* split tIDs length into max entries in-statement because many database have
* constraint of max entries in in-statements e.g. Oracle with maximum 1000
* elements, so we need to split the entries here and execute multiple
* statements
*/
private static List<List<Long>> getTargetEntitiesAsChunks(final List<JpaTarget> targetEntities) {
return ListUtils.partition(targetEntities.stream().map(Target::getId).collect(Collectors.toList()),
Constants.MAX_ENTRIES_IN_STATEMENT);
}
private static DistributionSetAssignmentResult buildAssignmentResult(final JpaDistributionSet distributionSet,
final List<JpaAction> assignedActions, final int totalTargetsForAssignment) {
final int alreadyAssignedTargetsCount = totalTargetsForAssignment - assignedActions.size();
return new DistributionSetAssignmentResult(distributionSet, alreadyAssignedTargetsCount, assignedActions);
}
private void enforceMaxAssignmentsPerRequest(final int requestedActions) {
QuotaHelper.assertAssignmentRequestSizeQuota(requestedActions,
quotaManagement.getMaxTargetDistributionSetAssignmentsPerManualAssignment());
@@ -489,24 +870,6 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
}
}
@Override
@Transactional(isolation = Isolation.READ_COMMITTED)
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public void cancelInactiveScheduledActionsForTargets(final List<Long> targetIds) {
if (!isMultiAssignmentsEnabled()) {
targetRepository.getAccessController().ifPresent(v -> {
if (targetRepository.count(AccessController.Operation.UPDATE,
TargetSpecifications.hasIdIn(targetIds)) != targetIds.size()) {
throw new EntityNotFoundException(Target.class, targetIds);
}
});
actionRepository.switchStatus(Status.CANCELED, targetIds, false, Status.SCHEDULED);
} else {
log.debug("The Multi Assignments feature is enabled: No need to cancel inactive scheduled actions.");
}
}
private void setAssignedDistributionSetAndTargetUpdateStatus(final AbstractDsAssignmentStrategy assignmentStrategy,
final JpaDistributionSet set, final List<List<Long>> targetIdsChunks) {
final String currentUser = auditorProvider.getCurrentAuditor().orElse(null);
@@ -581,112 +944,6 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
assignmentStrategy.sendTargetUpdatedEvents(set, targets);
}
@Override
@Transactional(isolation = Isolation.READ_COMMITTED)
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public Action cancelAction(final long actionId) {
log.debug("cancelAction({})", actionId);
final JpaAction action = actionRepository.findById(actionId)
.orElseThrow(() -> new EntityNotFoundException(Action.class, actionId));
if (action.isCancelingOrCanceled()) {
throw new CancelActionNotAllowedException("Actions in canceling or canceled state cannot be canceled");
}
assertTargetUpdateAllowed(action);
if (action.isActive()) {
log.debug("action ({}) was still active. Change to {}.", action, Status.CANCELING);
action.setStatus(Status.CANCELING);
// document that the status has been retrieved
actionStatusRepository.save(new JpaActionStatus(action, Status.CANCELING, System.currentTimeMillis(),
RepositoryConstants.SERVER_MESSAGE_PREFIX + "manual cancelation requested"));
final Action saveAction = actionRepository.save(action);
onlineDsAssignmentStrategy.cancelAssignment(action);
return saveAction;
} else {
throw new CancelActionNotAllowedException(action.getId() + " is not active and cannot be canceled");
}
}
@Override
@Transactional(isolation = Isolation.READ_COMMITTED)
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public Action forceQuitAction(final long actionId) {
final JpaAction action = actionRepository.findById(actionId)
.orElseThrow(() -> new EntityNotFoundException(Action.class, actionId));
if (!action.isCancelingOrCanceled()) {
throw new ForceQuitActionNotAllowedException(
action.getId() + " is not canceled yet and cannot be force quit");
}
if (!action.isActive()) {
throw new ForceQuitActionNotAllowedException(action.getId() + " is not active and cannot be force quit");
}
assertTargetUpdateAllowed(action);
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, System.currentTimeMillis(),
RepositoryConstants.SERVER_MESSAGE_PREFIX + "A force quit has been performed."));
DeploymentHelper.successCancellation(action, actionRepository, targetRepository);
return actionRepository.save(action);
}
@Override
public void startScheduledActionsByRolloutGroupParent(final long rolloutId, final long distributionSetId,
final Long rolloutGroupParentId) {
while (DeploymentHelper.runInNewTransaction(
txManager,
"startScheduledActions-" + rolloutId,
status -> {
final PageRequest pageRequest = PageRequest.of(0, ACTION_PAGE_LIMIT);
final Page<Action> groupScheduledActions;
if (rolloutGroupParentId == null) {
groupScheduledActions = actionRepository.findByRolloutIdAndRolloutGroupParentIsNullAndStatus(pageRequest, rolloutId, Action.Status.SCHEDULED);
} else {
groupScheduledActions = actionRepository.findByRolloutIdAndRolloutGroupParentIdAndStatus(pageRequest, rolloutId, rolloutGroupParentId, Action.Status.SCHEDULED);
}
if (groupScheduledActions.getContent().isEmpty()) {
return 0L;
} else {
// self invocation won't check @PreAuthorize but it is already checked for the method
startScheduledActions(groupScheduledActions.getContent());
return groupScheduledActions.getTotalElements();
}
}) > 0);
}
@Override
public void startScheduledActions(final List<Action> rolloutGroupActions) {
// Close actions already assigned and collect pending assignments
final List<JpaAction> pendingTargetAssignments = rolloutGroupActions.stream()
.map(JpaAction.class::cast)
.map(this::closeActionIfSetWasAlreadyAssigned)
.filter(Objects::nonNull)
.toList();
if (pendingTargetAssignments.isEmpty()) {
return;
}
// check if old actions needs to be canceled first
final List<Action> newTargetAssignments = startScheduledActionsAndHandleOpenCancellationFirst(pendingTargetAssignments);
if (!newTargetAssignments.isEmpty()) {
onlineDsAssignmentStrategy.sendDeploymentEvents(newTargetAssignments.get(0).getDistributionSet().getId(), newTargetAssignments);
}
}
private JpaAction closeActionIfSetWasAlreadyAssigned(final JpaAction action) {
if (isMultiAssignmentsEnabled()) {
return action;
@@ -764,221 +1021,6 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
actionStatusRepository.save(actionStatus);
}
@Override
public Optional<Action> findAction(final long actionId) {
return actionRepository.findById(actionId)
.filter(action -> targetRepository.exists(TargetSpecifications.hasId(action.getTarget().getId())))
.map(JpaAction.class::cast);
}
@Override
public Optional<Action> findActionWithDetails(final long actionId) {
return actionRepository.findWithDetailsById(actionId)
.filter(action -> targetRepository.exists(TargetSpecifications.hasId(action.getTarget().getId())));
}
@Override
public Slice<Action> findActionsByTarget(final String controllerId, final Pageable pageable) {
assertTargetReadAllowed(controllerId);
return actionRepository.findAll(ActionSpecifications.byTargetControllerId(controllerId), pageable)
.map(Action.class::cast);
}
@Override
public Page<Action> findActionsByTarget(final String rsqlParam, final String controllerId,
final Pageable pageable) {
assertTargetReadAllowed(controllerId);
final List<Specification<JpaAction>> specList = Arrays.asList(
RSQLUtility.buildRsqlSpecification(rsqlParam, ActionFields.class, virtualPropertyReplacer, database),
ActionSpecifications.byTargetControllerId(controllerId));
return JpaManagementHelper.findAllWithCountBySpec(actionRepository, pageable, specList);
}
@Override
public Page<Action> findActiveActionsByTarget(final Pageable pageable, final String controllerId) {
assertTargetReadAllowed(controllerId);
return actionRepository
.findAll(ActionSpecifications.byTargetControllerIdAndActive(controllerId, true), pageable)
.map(Action.class::cast);
}
@Override
public Page<Action> findInActiveActionsByTarget(final Pageable pageable, final String controllerId) {
assertTargetReadAllowed(controllerId);
return actionRepository
.findAll(ActionSpecifications.byTargetControllerIdAndActive(controllerId, false), pageable)
.map(Action.class::cast);
}
@Override
public List<Action> findActiveActionsWithHighestWeight(final String controllerId, final int maxActionCount) {
assertTargetReadAllowed(controllerId);
return findActiveActionsWithHighestWeightConsideringDefault(controllerId, maxActionCount);
}
@Override
public long countActionsByTarget(final String controllerId) {
assertTargetReadAllowed(controllerId);
return actionRepository.countByTargetControllerId(controllerId);
}
@Override
public long countActionsByTarget(final String rsqlParam, final String controllerId) {
assertTargetReadAllowed(controllerId);
final List<Specification<JpaAction>> specList = Arrays.asList(
RSQLUtility.buildRsqlSpecification(rsqlParam, ActionFields.class, virtualPropertyReplacer, database),
ActionSpecifications.byTargetControllerId(controllerId));
return JpaManagementHelper.countBySpec(actionRepository, specList);
}
@Override
@Transactional
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public Action forceTargetAction(final long actionId) {
final JpaAction action = actionRepository.findById(actionId).map(this::assertTargetUpdateAllowed)
.orElseThrow(() -> new EntityNotFoundException(Action.class, actionId));
if (!action.isForcedOrTimeForced()) {
action.setActionType(ActionType.FORCED);
return actionRepository.save(action);
}
return action;
}
@Override
public Page<ActionStatus> findActionStatusByAction(final Pageable pageReq, final long actionId) {
assertActionExistsAndAccessible(actionId);
return actionStatusRepository.findByActionId(pageReq, actionId);
}
@Override
public long countActionStatusByAction(final long actionId) {
assertActionExistsAndAccessible(actionId);
return actionStatusRepository.countByActionId(actionId);
}
// action is already got and there are checked read permissions - do not check
// permissions
// and UI which is to be removed
@Override
public Page<String> findMessagesByActionStatusId(final Pageable pageable, final long actionStatusId) {
final CriteriaBuilder cb = entityManager.getCriteriaBuilder();
final CriteriaQuery<String> msgQuery = cb.createQuery(String.class);
final Root<JpaActionStatus> as = msgQuery.from(JpaActionStatus.class);
final ListJoin<JpaActionStatus, String> join = as.joinList("messages", JoinType.LEFT);
final CriteriaQuery<String> selMsgQuery = msgQuery.select(join);
selMsgQuery.where(cb.equal(as.get(JpaActionStatus_.id), actionStatusId));
final List<String> result = new ArrayList<>(entityManager.createQuery(selMsgQuery)
.setFirstResult((int) pageable.getOffset()).setMaxResults(pageable.getPageSize()).getResultList());
return new PageImpl<>(result, pageable, result.size());
}
@Override
public long countActionsAll() {
return actionRepository.count();
}
@Override
public long countActions(final String rsqlParam) {
final List<Specification<JpaAction>> specList = List.of(
RSQLUtility.buildRsqlSpecification(rsqlParam, ActionFields.class, virtualPropertyReplacer, database));
return JpaManagementHelper.countBySpec(actionRepository, specList);
}
@Override
public Slice<Action> findActionsAll(final Pageable pageable) {
return JpaManagementHelper.findAllWithoutCountBySpec(actionRepository, pageable, null);
}
@Override
public Slice<Action> findActions(final String rsqlParam, final Pageable pageable) {
final List<Specification<JpaAction>> specList = List.of(
RSQLUtility.buildRsqlSpecification(rsqlParam, ActionFields.class, virtualPropertyReplacer, database));
return JpaManagementHelper.findAllWithoutCountBySpec(actionRepository, pageable, specList);
}
@Override
public Optional<DistributionSet> getAssignedDistributionSet(final String controllerId) {
return targetRepository
.findOne(TargetSpecifications.hasControllerId(controllerId))
.map(JpaTarget::getAssignedDistributionSet);
}
@Override
public Optional<DistributionSet> getInstalledDistributionSet(final String controllerId) {
return targetRepository
.findOne(TargetSpecifications.hasControllerId(controllerId))
.map(JpaTarget::getInstalledDistributionSet);
}
@Override
@Transactional(readOnly = false)
public int deleteActionsByStatusAndLastModifiedBefore(final Set<Status> status, final long lastModified) {
if (status.isEmpty()) {
return 0;
}
/*
* We use a native query here because Spring JPA does not support to specify a
* LIMIT clause on a DELETE statement. However, for this specific use case
* (action cleanup), we must specify a row limit to reduce the overall load on
* the database.
*/
final int statusCount = status.size();
final Status[] statusArr = status.toArray(new Status[statusCount]);
final String queryStr = String.format(getQueryForDeleteActionsByStatusAndLastModifiedBeforeString(database),
formatInClauseWithNumberKeys(statusCount));
final Query deleteQuery = entityManager.createNativeQuery(queryStr);
IntStream.range(0, statusCount)
.forEach(i -> deleteQuery.setParameter(String.valueOf(i), statusArr[i].ordinal()));
deleteQuery.setParameter("tenant", tenantAware.getCurrentTenant().toUpperCase());
deleteQuery.setParameter("last_modified_at", lastModified);
log.debug("Action cleanup: Executing the following (native) query: {}", deleteQuery);
return deleteQuery.executeUpdate();
}
@Override
public boolean hasPendingCancellations(final Long targetId) {
// target access checked in assertTargetReadAllowed
assertTargetReadAllowed(targetId);
return actionRepository
.exists(ActionSpecifications.byTargetIdAndIsActiveAndStatus(targetId, Action.Status.CANCELING));
}
private static String getQueryForDeleteActionsByStatusAndLastModifiedBeforeString(final Database database) {
return QUERY_DELETE_ACTIONS_BY_STATE_AND_LAST_MODIFIED.getOrDefault(database,
QUERY_DELETE_ACTIONS_BY_STATE_AND_LAST_MODIFIED_DEFAULT);
}
private static String formatInClauseWithNumberKeys(final int count) {
return formatInClause(IntStream.range(0, count).mapToObj(String::valueOf).collect(Collectors.toList()));
}
private static String formatInClause(final Collection<String> elements) {
return "#" + String.join(",#", elements);
}
protected ActionRepository getActionRepository() {
return actionRepository;
}
protected boolean isActionsAutocloseEnabled() {
return getConfigValue(REPOSITORY_ACTIONS_AUTOCLOSE_ENABLED, Boolean.class);
}
private boolean isMultiAssignmentsEnabled() {
return TenantConfigHelper.usingContext(systemSecurityContext, tenantConfigurationManagement)
.isMultiAssignmentsEnabled();
@@ -994,54 +1036,6 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
.runAsSystem(() -> tenantConfigurationManagement.getConfigurationValue(key, valueType).getValue());
}
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;
}
@Override
@Transactional
public void cancelActionsForDistributionSet(final CancelationType cancelationType,
final DistributionSet distributionSet) {
actionRepository.findAll(ActionSpecifications
.byDistributionSetIdAndActiveAndStatusIsNot(distributionSet.getId(), Status.CANCELING))
.forEach(action -> {
try {
assertTargetUpdateAllowed(action);
cancelAction(action.getId());
log.debug("Action {} canceled", action.getId());
} catch (final InsufficientPermissionException e) {
log.trace("Could not cancel action {} due to insufficient permissions.", action.getId(), e);
} catch (final EntityNotFoundException e) {
log.trace("Could not cancel action {} due to entity not found exception.", action.getId(), e);
}
});
if (cancelationType == CancelationType.FORCE) {
actionRepository.findAll(ActionSpecifications.byDistributionSetIdAndActive(distributionSet.getId()))
.forEach(action -> {
try {
assertTargetUpdateAllowed(action);
forceQuitAction(action.getId());
log.debug("Action {} force canceled", action.getId());
} catch (final InsufficientPermissionException e) {
log.trace("Could not cancel action {} due to insufficient permissions.", action.getId(), e);
} catch (final EntityNotFoundException e) {
log.trace("Could not cancel action {} due to entity not found exception.", action.getId(),
e);
}
});
}
}
private void assertTargetReadAllowed(final Long targetId) {
if (!targetRepository.existsById(targetId)) {
throw new EntityNotFoundException(Target.class, targetId);

View File

@@ -97,6 +97,26 @@ public class JpaDistributionSetInvalidationManagement implements DistributionSet
}
}
@Override
public DistributionSetInvalidationCount countEntitiesForInvalidation(
final DistributionSetInvalidation distributionSetInvalidation) {
return systemSecurityContext.runAsSystem(() -> {
final Collection<Long> setIds = distributionSetInvalidation.getDistributionSetIds();
final long rolloutsCount = shouldRolloutsBeCanceled(distributionSetInvalidation.getCancelationType(),
distributionSetInvalidation.isCancelRollouts()) ? countRolloutsForInvalidation(setIds) : 0;
final long autoAssignmentsCount = countAutoAssignmentsForInvalidation(setIds);
final long actionsCount = countActionsForInvalidation(setIds,
distributionSetInvalidation.getCancelationType());
return new DistributionSetInvalidationCount(rolloutsCount, autoAssignmentsCount, actionsCount);
});
}
private static boolean shouldRolloutsBeCanceled(final CancelationType cancelationType,
final boolean cancelRollouts) {
return cancelationType != CancelationType.NONE || cancelRollouts;
}
private void invalidateDistributionSetsInTransaction(final DistributionSetInvalidation distributionSetInvalidation,
final String tenant) {
DeploymentHelper.runInNewTransaction(txManager, tenant + "-invalidateDS", status -> {
@@ -135,26 +155,6 @@ public class JpaDistributionSetInvalidationManagement implements DistributionSet
});
}
private static boolean shouldRolloutsBeCanceled(final CancelationType cancelationType,
final boolean cancelRollouts) {
return cancelationType != CancelationType.NONE || cancelRollouts;
}
@Override
public DistributionSetInvalidationCount countEntitiesForInvalidation(
final DistributionSetInvalidation distributionSetInvalidation) {
return systemSecurityContext.runAsSystem(() -> {
final Collection<Long> setIds = distributionSetInvalidation.getDistributionSetIds();
final long rolloutsCount = shouldRolloutsBeCanceled(distributionSetInvalidation.getCancelationType(),
distributionSetInvalidation.isCancelRollouts()) ? countRolloutsForInvalidation(setIds) : 0;
final long autoAssignmentsCount = countAutoAssignmentsForInvalidation(setIds);
final long actionsCount = countActionsForInvalidation(setIds,
distributionSetInvalidation.getCancelationType());
return new DistributionSetInvalidationCount(rolloutsCount, autoAssignmentsCount, actionsCount);
});
}
private long countRolloutsForInvalidation(final Collection<Long> setIds) {
return setIds.stream().mapToLong(rolloutManagement::countByDistributionSetIdAndRolloutIsStoppable).sum();
}

View File

@@ -27,7 +27,6 @@ import org.eclipse.hawkbit.repository.jpa.acm.AccessController;
import org.eclipse.hawkbit.repository.jpa.builder.JpaTagCreate;
import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetTag;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetType;
import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetRepository;
import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetTagRepository;
import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility;
@@ -49,7 +48,6 @@ import org.springframework.validation.annotation.Validated;
/**
* JPA implementation of {@link TargetTagManagement}.
*
*/
@Transactional(readOnly = true)
@Validated
@@ -76,22 +74,11 @@ public class JpaDistributionSetTagManagement implements DistributionSetTagManage
@Transactional
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public DistributionSetTag update(final TagUpdate u) {
final GenericTagUpdate update = (GenericTagUpdate) u;
final JpaDistributionSetTag tag = distributionSetTagRepository.findById(update.getId())
.orElseThrow(() -> new EntityNotFoundException(DistributionSetTag.class, update.getId()));
update.getName().ifPresent(tag::setName);
update.getDescription().ifPresent(tag::setDescription);
update.getColour().ifPresent(tag::setColour);
return distributionSetTagRepository.save(tag);
}
@Override
public Optional<DistributionSetTag> getByName(final String name) {
return distributionSetTagRepository.findByNameEquals(name);
public List<DistributionSetTag> create(final Collection<TagCreate> dst) {
final List<JpaDistributionSetTag> toCreate = dst.stream().map(JpaTagCreate.class::cast)
.map(JpaTagCreate::buildDistributionSetTag).toList();
return Collections
.unmodifiableList(distributionSetTagRepository.saveAll(AccessController.Operation.CREATE, toCreate));
}
@Override
@@ -107,47 +94,30 @@ public class JpaDistributionSetTagManagement implements DistributionSetTagManage
@Transactional
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public List<DistributionSetTag> create(final Collection<TagCreate> dst) {
final List<JpaDistributionSetTag> toCreate = dst.stream().map(JpaTagCreate.class::cast)
.map(JpaTagCreate::buildDistributionSetTag).toList();
return Collections
.unmodifiableList(distributionSetTagRepository.saveAll(AccessController.Operation.CREATE, toCreate));
public DistributionSetTag update(final TagUpdate u) {
final GenericTagUpdate update = (GenericTagUpdate) u;
final JpaDistributionSetTag tag = distributionSetTagRepository.findById(update.getId())
.orElseThrow(() -> new EntityNotFoundException(DistributionSetTag.class, update.getId()));
update.getName().ifPresent(tag::setName);
update.getDescription().ifPresent(tag::setDescription);
update.getColour().ifPresent(tag::setColour);
return distributionSetTagRepository.save(tag);
}
@Override
public long count() {
return distributionSetTagRepository.count();
}
@Override
@Transactional
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public void delete(final String tagName) {
final JpaDistributionSetTag dsTag = distributionSetTagRepository
.findOne(DistributionSetTagSpecifications.byName(tagName))
.orElseThrow(() -> new EntityNotFoundException(DistributionSetTag.class, tagName));
distributionSetTagRepository.delete(dsTag);
}
@Override
public Slice<DistributionSetTag> findAll(final Pageable pageable) {
return JpaManagementHelper.findAllWithoutCountBySpec(distributionSetTagRepository, pageable, null);
}
@Override
public Page<DistributionSetTag> findByRsql(final Pageable pageable, final String rsqlParam) {
final Specification<JpaDistributionSetTag> spec = RSQLUtility.buildRsqlSpecification(rsqlParam, DistributionSetTagFields.class,
virtualPropertyReplacer, database);
return JpaManagementHelper.findAllWithCountBySpec(distributionSetTagRepository, pageable,
Collections.singletonList(spec));
}
@Override
public Page<DistributionSetTag> findByDistributionSet(final Pageable pageable, final long distributionSetId) {
if (!distributionSetRepository.existsById(distributionSetId)) {
throw new EntityNotFoundException(DistributionSet.class, distributionSetId);
}
return JpaManagementHelper.findAllWithCountBySpec(distributionSetTagRepository, pageable,
Collections.singletonList(TagSpecification.ofDistributionSet(distributionSetId)));
public void delete(final long id) {
distributionSetTagRepository.deleteById(id);
}
@Override
@@ -170,26 +140,54 @@ public class JpaDistributionSetTagManagement implements DistributionSetTagManage
return Collections.unmodifiableList(distributionSetTagRepository.findAllById(ids));
}
@Override
public boolean exists(final long id) {
return distributionSetTagRepository.existsById(id);
}
@Override
public Optional<DistributionSetTag> get(final long id) {
return distributionSetTagRepository.findById(id).map(DistributionSetTag.class::cast);
}
@Override
public Slice<DistributionSetTag> findAll(final Pageable pageable) {
return JpaManagementHelper.findAllWithoutCountBySpec(distributionSetTagRepository, pageable, null);
}
@Override
public Page<DistributionSetTag> findByRsql(final Pageable pageable, final String rsqlParam) {
final Specification<JpaDistributionSetTag> spec = RSQLUtility.buildRsqlSpecification(rsqlParam, DistributionSetTagFields.class,
virtualPropertyReplacer, database);
return JpaManagementHelper.findAllWithCountBySpec(distributionSetTagRepository, pageable,
Collections.singletonList(spec));
}
@Override
@Transactional
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public void delete(final long id) {
distributionSetTagRepository.deleteById(id);
public void delete(final String tagName) {
final JpaDistributionSetTag dsTag = distributionSetTagRepository
.findOne(DistributionSetTagSpecifications.byName(tagName))
.orElseThrow(() -> new EntityNotFoundException(DistributionSetTag.class, tagName));
distributionSetTagRepository.delete(dsTag);
}
@Override
public boolean exists(final long id) {
return distributionSetTagRepository.existsById(id);
public Optional<DistributionSetTag> getByName(final String name) {
return distributionSetTagRepository.findByNameEquals(name);
}
@Override
public long count() {
return distributionSetTagRepository.count();
public Page<DistributionSetTag> findByDistributionSet(final Pageable pageable, final long distributionSetId) {
if (!distributionSetRepository.existsById(distributionSetId)) {
throw new EntityNotFoundException(DistributionSet.class, distributionSetId);
}
return JpaManagementHelper.findAllWithCountBySpec(distributionSetTagRepository, pageable,
Collections.singletonList(TagSpecification.ofDistributionSet(distributionSetId)));
}
}

View File

@@ -58,7 +58,6 @@ import org.springframework.validation.annotation.Validated;
/**
* JPA implementation of {@link DistributionSetTypeManagement}.
*
*/
@Transactional(readOnly = true)
@Validated
@@ -92,6 +91,72 @@ public class JpaDistributionSetTypeManagement implements DistributionSetTypeMana
this.quotaManagement = quotaManagement;
}
@Override
public Optional<DistributionSetType> getByKey(final String key) {
return distributionSetTypeRepository
.findOne(DistributionSetTypeSpecification.byKey(key)).map(DistributionSetType.class::cast);
}
@Override
public Optional<DistributionSetType> getByName(final String name) {
return distributionSetTypeRepository
.findOne(DistributionSetTypeSpecification.byName(name)).map(DistributionSetType.class::cast);
}
@Override
@Transactional
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public DistributionSetType assignOptionalSoftwareModuleTypes(final long id,
final Collection<Long> softwareModulesTypeIds) {
return assignSoftwareModuleTypes(id, softwareModulesTypeIds, false);
}
@Override
@Transactional
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public DistributionSetType assignMandatorySoftwareModuleTypes(final long id,
final Collection<Long> softwareModuleTypeIds) {
return assignSoftwareModuleTypes(id, softwareModuleTypeIds, true);
}
@Override
@Transactional
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public DistributionSetType unassignSoftwareModuleType(final long id, final long softwareModuleTypeId) {
final JpaDistributionSetType type = findDistributionSetTypeAndThrowExceptionIfNotFound(id);
checkDistributionSetTypeNotAssigned(id);
type.removeModuleType(softwareModuleTypeId);
return distributionSetTypeRepository.save(type);
}
@Override
@Transactional
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public List<DistributionSetType> create(final Collection<DistributionSetTypeCreate> types) {
final List<JpaDistributionSetType> typesToCreate = types.stream().map(JpaDistributionSetTypeCreate.class::cast)
.map(JpaDistributionSetTypeCreate::build).toList();
return Collections.unmodifiableList(
distributionSetTypeRepository.saveAll(AccessController.Operation.CREATE, typesToCreate));
}
@Override
@Transactional
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public DistributionSetType create(final DistributionSetTypeCreate c) {
final JpaDistributionSetType distributionSetType = ((JpaDistributionSetTypeCreate) c).build();
return distributionSetTypeRepository.save(AccessController.Operation.CREATE, distributionSetType);
}
@Override
@Transactional
@Retryable(include = {
@@ -130,13 +195,64 @@ public class JpaDistributionSetTypeManagement implements DistributionSetTypeMana
return distributionSetTypeRepository.save(type);
}
private void addModuleTypes(final Collection<Long> currentSmTypeIds, final Collection<Long> updatedSmTypeIds,
final Function<SoftwareModuleType, JpaDistributionSetType> addModuleTypeCallback) {
final Set<Long> smTypeIdsToAdd = updatedSmTypeIds.stream().filter(id -> !currentSmTypeIds.contains(id))
.collect(Collectors.toSet());
if (!CollectionUtils.isEmpty(smTypeIdsToAdd)) {
softwareModuleTypeRepository.findAllById(smTypeIdsToAdd).forEach(addModuleTypeCallback::apply);
@Override
public long count() {
return distributionSetTypeRepository.count(DistributionSetTypeSpecification.isNotDeleted());
}
@Override
@Transactional
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public void delete(final long id) {
final JpaDistributionSetType toDelete = distributionSetTypeRepository.findById(id)
.orElseThrow(() -> new EntityNotFoundException(DistributionSetType.class, id));
unassignDsTypeFromTargetTypes(id);
if (distributionSetRepository.countByTypeId(id) > 0) {
toDelete.setDeleted(true);
distributionSetTypeRepository.save(AccessController.Operation.DELETE, toDelete);
} else {
distributionSetTypeRepository.deleteById(id);
}
}
@Override
@Transactional
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public void delete(final Collection<Long> ids) {
distributionSetTypeRepository.deleteAllById(ids);
}
@Override
public List<DistributionSetType> get(final Collection<Long> ids) {
return Collections.unmodifiableList(distributionSetTypeRepository.findAllById(ids));
}
@Override
public boolean exists(final long id) {
return distributionSetTypeRepository.existsById(id);
}
@Override
public Optional<DistributionSetType> get(final long id) {
return distributionSetTypeRepository.findById(id).map(DistributionSetType.class::cast);
}
@Override
public Slice<DistributionSetType> findAll(final Pageable pageable) {
return JpaManagementHelper.findAllWithoutCountBySpec(distributionSetTypeRepository, pageable, List.of(
DistributionSetTypeSpecification.isNotDeleted()));
}
@Override
public Page<DistributionSetType> findByRsql(final Pageable pageable, final String rsqlParam) {
return JpaManagementHelper.findAllWithCountBySpec(distributionSetTypeRepository, pageable, List.of(
RSQLUtility.buildRsqlSpecification(rsqlParam, DistributionSetTypeFields.class, virtualPropertyReplacer,
database),
DistributionSetTypeSpecification.isNotDeleted()));
}
private static void removeModuleTypes(final Collection<Long> currentSmTypeIds,
@@ -149,22 +265,17 @@ public class JpaDistributionSetTypeManagement implements DistributionSetTypeMana
}
}
@Override
@Transactional
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public DistributionSetType assignMandatorySoftwareModuleTypes(final long id,
final Collection<Long> softwareModuleTypeIds) {
return assignSoftwareModuleTypes(id, softwareModuleTypeIds, true);
private static boolean hasModuleChanges(final GenericDistributionSetTypeUpdate update) {
return update.getOptional().isPresent() || update.getMandatory().isPresent();
}
@Override
@Transactional
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public DistributionSetType assignOptionalSoftwareModuleTypes(final long id,
final Collection<Long> softwareModulesTypeIds) {
return assignSoftwareModuleTypes(id, softwareModulesTypeIds, false);
private void addModuleTypes(final Collection<Long> currentSmTypeIds, final Collection<Long> updatedSmTypeIds,
final Function<SoftwareModuleType, JpaDistributionSetType> addModuleTypeCallback) {
final Set<Long> smTypeIdsToAdd = updatedSmTypeIds.stream().filter(id -> !currentSmTypeIds.contains(id))
.collect(Collectors.toSet());
if (!CollectionUtils.isEmpty(smTypeIdsToAdd)) {
softwareModuleTypeRepository.findAllById(smTypeIdsToAdd).forEach(addModuleTypeCallback::apply);
}
}
private DistributionSetType assignSoftwareModuleTypes(
@@ -190,13 +301,9 @@ public class JpaDistributionSetTypeManagement implements DistributionSetTypeMana
* Enforces the quota specifying the maximum number of
* {@link SoftwareModuleType}s per {@link DistributionSetType}.
*
* @param id
* of the distribution set type
* @param requested
* number of software module types to check
*
* @throws AssignmentQuotaExceededException
* if the software module type quota is exceeded
* @param id of the distribution set type
* @param requested number of software module types to check
* @throws AssignmentQuotaExceededException if the software module type quota is exceeded
*/
private void assertSoftwareModuleTypeQuota(final long id, final int requested) {
QuotaHelper.assertAssignmentQuota(id, requested,
@@ -204,79 +311,6 @@ public class JpaDistributionSetTypeManagement implements DistributionSetTypeMana
DistributionSetType.class, distributionSetTypeRepository::countSmTypesById);
}
@Override
@Transactional
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public DistributionSetType unassignSoftwareModuleType(final long id, final long softwareModuleTypeId) {
final JpaDistributionSetType type = findDistributionSetTypeAndThrowExceptionIfNotFound(id);
checkDistributionSetTypeNotAssigned(id);
type.removeModuleType(softwareModuleTypeId);
return distributionSetTypeRepository.save(type);
}
@Override
public Page<DistributionSetType> findByRsql(final Pageable pageable, final String rsqlParam) {
return JpaManagementHelper.findAllWithCountBySpec(distributionSetTypeRepository, pageable, List.of(
RSQLUtility.buildRsqlSpecification(rsqlParam, DistributionSetTypeFields.class, virtualPropertyReplacer,
database),
DistributionSetTypeSpecification.isNotDeleted()));
}
@Override
public Slice<DistributionSetType> findAll(final Pageable pageable) {
return JpaManagementHelper.findAllWithoutCountBySpec(distributionSetTypeRepository, pageable, List.of(
DistributionSetTypeSpecification.isNotDeleted()));
}
@Override
public long count() {
return distributionSetTypeRepository.count(DistributionSetTypeSpecification.isNotDeleted());
}
@Override
public Optional<DistributionSetType> getByName(final String name) {
return distributionSetTypeRepository
.findOne(DistributionSetTypeSpecification.byName(name)).map(DistributionSetType.class::cast);
}
@Override
public Optional<DistributionSetType> getByKey(final String key) {
return distributionSetTypeRepository
.findOne(DistributionSetTypeSpecification.byKey(key)).map(DistributionSetType.class::cast);
}
@Override
@Transactional
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public DistributionSetType create(final DistributionSetTypeCreate c) {
final JpaDistributionSetType distributionSetType = ((JpaDistributionSetTypeCreate) c).build();
return distributionSetTypeRepository.save(AccessController.Operation.CREATE, distributionSetType);
}
@Override
@Transactional
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public void delete(final long id) {
final JpaDistributionSetType toDelete = distributionSetTypeRepository.findById(id)
.orElseThrow(() -> new EntityNotFoundException(DistributionSetType.class, id));
unassignDsTypeFromTargetTypes(id);
if (distributionSetRepository.countByTypeId(id) > 0) {
toDelete.setDeleted(true);
distributionSetTypeRepository.save(AccessController.Operation.DELETE, toDelete);
} else {
distributionSetTypeRepository.deleteById(id);
}
}
private void unassignDsTypeFromTargetTypes(final long typeId) {
final List<JpaTargetType> targetTypesByDsType = targetTypeRepository.findByDsType(typeId);
targetTypesByDsType.forEach(targetType -> {
@@ -285,54 +319,15 @@ public class JpaDistributionSetTypeManagement implements DistributionSetTypeMana
});
}
@Override
@Transactional
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public List<DistributionSetType> create(final Collection<DistributionSetTypeCreate> types) {
final List<JpaDistributionSetType> typesToCreate = types.stream().map(JpaDistributionSetTypeCreate.class::cast)
.map(JpaDistributionSetTypeCreate::build).toList();
return Collections.unmodifiableList(
distributionSetTypeRepository.saveAll(AccessController.Operation.CREATE, typesToCreate));
}
private JpaDistributionSetType findDistributionSetTypeAndThrowExceptionIfNotFound(final Long setId) {
return (JpaDistributionSetType) get(setId)
.orElseThrow(() -> new EntityNotFoundException(DistributionSetType.class, setId));
}
private static boolean hasModuleChanges(final GenericDistributionSetTypeUpdate update) {
return update.getOptional().isPresent() || update.getMandatory().isPresent();
}
private void checkDistributionSetTypeNotAssigned(final Long id) {
if (distributionSetRepository.countByTypeId(id) > 0) {
throw new EntityReadOnlyException(String.format(
"Distribution set type %s is already assigned to distribution sets and cannot be changed!", id));
}
}
@Override
@Transactional
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public void delete(final Collection<Long> ids) {
distributionSetTypeRepository.deleteAllById(ids);
}
@Override
public List<DistributionSetType> get(final Collection<Long> ids) {
return Collections.unmodifiableList(distributionSetTypeRepository.findAllById(ids));
}
@Override
public Optional<DistributionSetType> get(final long id) {
return distributionSetTypeRepository.findById(id).map(DistributionSetType.class::cast);
}
@Override
public boolean exists(final long id) {
return distributionSetTypeRepository.existsById(id);
}
}

View File

@@ -105,37 +105,6 @@ public class JpaRolloutGroupManagement implements RolloutGroupManagement {
this.database = database;
}
@Override
public Optional<RolloutGroup> get(final long rolloutGroupId) {
return rolloutGroupRepository.findById(rolloutGroupId).map(RolloutGroup.class::cast);
}
@Override
public Page<RolloutGroup> findByRollout(final Pageable pageable, final long rolloutId) {
throwEntityNotFoundExceptionIfRolloutDoesNotExist(rolloutId);
return JpaManagementHelper.convertPage(rolloutGroupRepository.findByRolloutId(rolloutId, pageable), pageable);
}
@Override
public Page<RolloutGroup> findByRolloutAndRsql(final Pageable pageable, final long rolloutId,
final String rsqlParam) {
throwEntityNotFoundExceptionIfRolloutDoesNotExist(rolloutId);
final List<Specification<JpaRolloutGroup>> specList = Arrays.asList(
RSQLUtility.buildRsqlSpecification(rsqlParam, RolloutGroupFields.class, virtualPropertyReplacer,
database),
(root, query, cb) -> cb.equal(root.get(JpaRolloutGroup_.rollout).get(JpaRollout_.id), rolloutId));
return JpaManagementHelper.findAllWithCountBySpec(rolloutGroupRepository, pageable, specList);
}
private void throwEntityNotFoundExceptionIfRolloutDoesNotExist(final Long rolloutId) {
if (!rolloutRepository.existsById(rolloutId)) {
throw new EntityNotFoundException(Rollout.class, rolloutId);
}
}
@Override
public Page<RolloutGroup> findByRolloutWithDetailedStatus(final Pageable pageable, final long rolloutId) {
throwEntityNotFoundExceptionIfRolloutDoesNotExist(rolloutId);
@@ -154,7 +123,7 @@ public class JpaRolloutGroupManagement implements RolloutGroupManagement {
for (final JpaRolloutGroup rolloutGroup : rolloutGroups) {
final TotalTargetCountStatus totalTargetCountStatus = new TotalTargetCountStatus(
allStatesForRollout.get(rolloutGroup.getId()), (long)rolloutGroup.getTotalTargets(),
allStatesForRollout.get(rolloutGroup.getId()), (long) rolloutGroup.getTotalTargets(),
rolloutGroup.getRollout().getActionType());
rolloutGroup.setTotalTargetCountStatus(totalTargetCountStatus);
}
@@ -162,6 +131,49 @@ public class JpaRolloutGroupManagement implements RolloutGroupManagement {
return JpaManagementHelper.convertPage(rolloutGroups, pageable);
}
@Override
public Page<TargetWithActionStatus> findAllTargetsOfRolloutGroupWithActionStatus(final Pageable pageRequest,
final long rolloutGroupId) {
throwExceptionIfRolloutGroupDoesNotExist(rolloutGroupId);
final CriteriaBuilder cb = entityManager.getCriteriaBuilder();
final CriteriaQuery<Object[]> query = cb.createQuery(Object[].class);
final Root<RolloutTargetGroup> targetRoot = query.distinct(true).from(RolloutTargetGroup.class);
final Join<RolloutTargetGroup, JpaTarget> targetJoin = targetRoot.join(RolloutTargetGroup_.target);
final ListJoin<RolloutTargetGroup, JpaAction> actionJoin = targetRoot.join(RolloutTargetGroup_.actions,
JoinType.LEFT);
final CriteriaQuery<Object[]> multiselect = query
.multiselect(targetJoin, actionJoin.get(JpaAction_.status),
actionJoin.get(JpaAction_.lastActionStatusCode))
.where(getRolloutGroupTargetWithRolloutGroupJoinCondition(rolloutGroupId, cb, targetRoot))
.orderBy(getOrderBy(pageRequest, cb, targetJoin, actionJoin));
final List<TargetWithActionStatus> targetWithActionStatus = entityManager.createQuery(multiselect)
.setFirstResult((int) pageRequest.getOffset()).setMaxResults(pageRequest.getPageSize()).getResultList()
.stream().map(this::getTargetWithActionStatusFromQuery).collect(Collectors.toList());
return new PageImpl<>(targetWithActionStatus, pageRequest, 0);
}
@Override
public Optional<RolloutGroup> get(final long rolloutGroupId) {
return rolloutGroupRepository.findById(rolloutGroupId).map(RolloutGroup.class::cast);
}
@Override
public Page<RolloutGroup> findByRolloutAndRsql(final Pageable pageable, final long rolloutId,
final String rsqlParam) {
throwEntityNotFoundExceptionIfRolloutDoesNotExist(rolloutId);
final List<Specification<JpaRolloutGroup>> specList = Arrays.asList(
RSQLUtility.buildRsqlSpecification(rsqlParam, RolloutGroupFields.class, virtualPropertyReplacer,
database),
(root, query, cb) -> cb.equal(root.get(JpaRolloutGroup_.rollout).get(JpaRollout_.id), rolloutId));
return JpaManagementHelper.findAllWithCountBySpec(rolloutGroupRepository, pageable, specList);
}
@Override
public Page<RolloutGroup> findByRolloutAndRsqlWithDetailedStatus(final Pageable pageable, final long rolloutId,
final String rsqlParam) {
@@ -189,6 +201,53 @@ public class JpaRolloutGroupManagement implements RolloutGroupManagement {
return JpaManagementHelper.convertPage(rolloutGroups, pageable);
}
@Override
public Page<RolloutGroup> findByRollout(final Pageable pageable, final long rolloutId) {
throwEntityNotFoundExceptionIfRolloutDoesNotExist(rolloutId);
return JpaManagementHelper.convertPage(rolloutGroupRepository.findByRolloutId(rolloutId, pageable), pageable);
}
@Override
public long countByRollout(final long rolloutId) {
throwEntityNotFoundExceptionIfRolloutDoesNotExist(rolloutId);
return rolloutGroupRepository.countByRolloutId(rolloutId);
}
@Override
public Page<Target> findTargetsOfRolloutGroup(final Pageable page, final long rolloutGroupId) {
final JpaRolloutGroup rolloutGroup = rolloutGroupRepository.findById(rolloutGroupId)
.orElseThrow(() -> new EntityNotFoundException(RolloutGroup.class, rolloutGroupId));
if (isRolloutStatusReady(rolloutGroup)) {
// in case of status ready the action has not been created yet and
// the relation information between target and rollout-group is
// stored in the #TargetRolloutGroup.
return JpaManagementHelper.findAllWithCountBySpec(targetRepository, page,
Collections.singletonList(TargetSpecifications.isInRolloutGroup(rolloutGroupId)));
}
return JpaManagementHelper.findAllWithCountBySpec(targetRepository, page,
Collections.singletonList(TargetSpecifications.isInActionRolloutGroup(rolloutGroupId)));
}
@Override
public Page<Target> findTargetsOfRolloutGroupByRsql(final Pageable pageable, final long rolloutGroupId,
final String rsqlParam) {
throwExceptionIfRolloutGroupDoesNotExist(rolloutGroupId);
final List<Specification<JpaTarget>> specList = Arrays.asList(
RSQLUtility.buildRsqlSpecification(rsqlParam, TargetFields.class, virtualPropertyReplacer, database),
(root, query, cb) -> {
final ListJoin<JpaTarget, RolloutTargetGroup> rolloutTargetJoin = root
.join(JpaTarget_.rolloutTargetGroup);
return cb.equal(rolloutTargetJoin.get(RolloutTargetGroup_.rolloutGroup).get(JpaRolloutGroup_.id),
rolloutGroupId);
});
return JpaManagementHelper.findAllWithCountBySpec(targetRepository, pageable, specList);
}
@Override
public Optional<RolloutGroup> getWithDetailedStatus(final long rolloutGroupId) {
final Optional<RolloutGroup> rolloutGroup = get(rolloutGroupId);
@@ -214,6 +273,28 @@ public class JpaRolloutGroupManagement implements RolloutGroupManagement {
}
@Override
public long countTargetsOfRolloutsGroup(final long rolloutGroupId) {
throwExceptionIfRolloutGroupDoesNotExist(rolloutGroupId);
final CriteriaBuilder cb = entityManager.getCriteriaBuilder();
final CriteriaQuery<Long> countQuery = cb.createQuery(Long.class);
final Root<RolloutTargetGroup> countQueryFrom = countQuery.from(RolloutTargetGroup.class);
countQuery.select(cb.count(countQueryFrom))
.where(getRolloutGroupTargetWithRolloutGroupJoinCondition(rolloutGroupId, cb, countQueryFrom));
return entityManager.createQuery(countQuery).getSingleResult();
}
private static boolean isRolloutStatusReady(final RolloutGroup rolloutGroup) {
return rolloutGroup != null && (RolloutStatus.READY == rolloutGroup.getRollout().getStatus());
}
private void throwEntityNotFoundExceptionIfRolloutDoesNotExist(final Long rolloutId) {
if (!rolloutRepository.existsById(rolloutId)) {
throw new EntityNotFoundException(Rollout.class, rolloutId);
}
}
private Map<Long, List<TotalTargetCountActionStatus>> getStatusCountItemForRolloutGroup(final List<Long> groupIds) {
final Map<Long, List<TotalTargetCountActionStatus>> fromCache = rolloutStatusCache
.getRolloutGroupStatus(groupIds);
@@ -235,68 +316,6 @@ public class JpaRolloutGroupManagement implements RolloutGroupManagement {
return fromCache;
}
@Override
public Page<Target> findTargetsOfRolloutGroupByRsql(final Pageable pageable, final long rolloutGroupId,
final String rsqlParam) {
throwExceptionIfRolloutGroupDoesNotExist(rolloutGroupId);
final List<Specification<JpaTarget>> specList = Arrays.asList(
RSQLUtility.buildRsqlSpecification(rsqlParam, TargetFields.class, virtualPropertyReplacer, database),
(root, query, cb) -> {
final ListJoin<JpaTarget, RolloutTargetGroup> rolloutTargetJoin = root
.join(JpaTarget_.rolloutTargetGroup);
return cb.equal(rolloutTargetJoin.get(RolloutTargetGroup_.rolloutGroup).get(JpaRolloutGroup_.id),
rolloutGroupId);
});
return JpaManagementHelper.findAllWithCountBySpec(targetRepository, pageable, specList);
}
@Override
public Page<Target> findTargetsOfRolloutGroup(final Pageable page, final long rolloutGroupId) {
final JpaRolloutGroup rolloutGroup = rolloutGroupRepository.findById(rolloutGroupId)
.orElseThrow(() -> new EntityNotFoundException(RolloutGroup.class, rolloutGroupId));
if (isRolloutStatusReady(rolloutGroup)) {
// in case of status ready the action has not been created yet and
// the relation information between target and rollout-group is
// stored in the #TargetRolloutGroup.
return JpaManagementHelper.findAllWithCountBySpec(targetRepository, page,
Collections.singletonList(TargetSpecifications.isInRolloutGroup(rolloutGroupId)));
}
return JpaManagementHelper.findAllWithCountBySpec(targetRepository, page,
Collections.singletonList(TargetSpecifications.isInActionRolloutGroup(rolloutGroupId)));
}
private static boolean isRolloutStatusReady(final RolloutGroup rolloutGroup) {
return rolloutGroup != null && (RolloutStatus.READY == rolloutGroup.getRollout().getStatus());
}
@Override
public Page<TargetWithActionStatus> findAllTargetsOfRolloutGroupWithActionStatus(final Pageable pageRequest,
final long rolloutGroupId) {
throwExceptionIfRolloutGroupDoesNotExist(rolloutGroupId);
final CriteriaBuilder cb = entityManager.getCriteriaBuilder();
final CriteriaQuery<Object[]> query = cb.createQuery(Object[].class);
final Root<RolloutTargetGroup> targetRoot = query.distinct(true).from(RolloutTargetGroup.class);
final Join<RolloutTargetGroup, JpaTarget> targetJoin = targetRoot.join(RolloutTargetGroup_.target);
final ListJoin<RolloutTargetGroup, JpaAction> actionJoin = targetRoot.join(RolloutTargetGroup_.actions,
JoinType.LEFT);
final CriteriaQuery<Object[]> multiselect = query
.multiselect(targetJoin, actionJoin.get(JpaAction_.status),
actionJoin.get(JpaAction_.lastActionStatusCode))
.where(getRolloutGroupTargetWithRolloutGroupJoinCondition(rolloutGroupId, cb, targetRoot))
.orderBy(getOrderBy(pageRequest, cb, targetJoin, actionJoin));
final List<TargetWithActionStatus> targetWithActionStatus = entityManager.createQuery(multiselect)
.setFirstResult((int) pageRequest.getOffset()).setMaxResults(pageRequest.getPageSize()).getResultList()
.stream().map(this::getTargetWithActionStatusFromQuery).collect(Collectors.toList());
return new PageImpl<>(targetWithActionStatus, pageRequest, 0);
}
private Predicate getRolloutGroupTargetWithRolloutGroupJoinCondition(final long rolloutGroupId,
final CriteriaBuilder cb, final Root<RolloutTargetGroup> targetRoot) {
return cb.equal(targetRoot.get(RolloutTargetGroup_.rolloutGroup).get(JpaRolloutGroup_.id), //
@@ -327,29 +346,10 @@ public class JpaRolloutGroupManagement implements RolloutGroupManagement {
}).collect(Collectors.toList());
}
@Override
public long countTargetsOfRolloutsGroup(final long rolloutGroupId) {
throwExceptionIfRolloutGroupDoesNotExist(rolloutGroupId);
final CriteriaBuilder cb = entityManager.getCriteriaBuilder();
final CriteriaQuery<Long> countQuery = cb.createQuery(Long.class);
final Root<RolloutTargetGroup> countQueryFrom = countQuery.from(RolloutTargetGroup.class);
countQuery.select(cb.count(countQueryFrom))
.where(getRolloutGroupTargetWithRolloutGroupJoinCondition(rolloutGroupId, cb, countQueryFrom));
return entityManager.createQuery(countQuery).getSingleResult();
}
private void throwExceptionIfRolloutGroupDoesNotExist(final Long rolloutGroupId) {
if (!rolloutGroupRepository.existsById(rolloutGroupId)) {
throw new EntityNotFoundException(RolloutGroup.class, rolloutGroupId);
}
}
@Override
public long countByRollout(final long rolloutId) {
throwEntityNotFoundExceptionIfRolloutDoesNotExist(rolloutId);
return rolloutGroupRepository.countByRolloutId(rolloutId);
}
}

View File

@@ -24,9 +24,10 @@ import java.util.stream.Collectors;
import jakarta.validation.ConstraintDeclarationException;
import jakarta.validation.Valid;
import jakarta.validation.ValidationException;
import jakarta.validation.constraints.NotNull;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.ContextAware;
import org.eclipse.hawkbit.repository.DistributionSetManagement;
import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.RepositoryProperties;
@@ -37,7 +38,6 @@ import org.eclipse.hawkbit.repository.RolloutManagement;
import org.eclipse.hawkbit.repository.RolloutStatusCache;
import org.eclipse.hawkbit.repository.TargetManagement;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.ContextAware;
import org.eclipse.hawkbit.repository.builder.DynamicRolloutGroupTemplate;
import org.eclipse.hawkbit.repository.builder.GenericRolloutUpdate;
import org.eclipse.hawkbit.repository.builder.RolloutCreate;
@@ -110,31 +110,6 @@ public class JpaRolloutManagement implements RolloutManagement {
private static final List<RolloutStatus> ROLLOUT_STATUS_STOPPABLE = Arrays.asList(RolloutStatus.RUNNING,
RolloutStatus.CREATING, RolloutStatus.PAUSED, RolloutStatus.READY, RolloutStatus.STARTING,
RolloutStatus.WAITING_FOR_APPROVAL, RolloutStatus.APPROVAL_DENIED);
@Autowired
private RepositoryProperties repositoryProperties;
@Autowired
private RolloutRepository rolloutRepository;
@Autowired
private RolloutGroupRepository rolloutGroupRepository;
@Autowired
private ActionRepository actionRepository;
@Autowired
private AfterTransactionCommitExecutor afterCommit;
@Autowired
private QuotaManagement quotaManagement;
@Autowired
private RolloutStatusCache rolloutStatusCache;
@Autowired
private StartNextGroupRolloutGroupSuccessAction startNextRolloutGroupAction;
private final TargetManagement targetManagement;
private final DistributionSetManagement distributionSetManagement;
private final VirtualPropertyReplacer virtualPropertyReplacer;
@@ -144,6 +119,22 @@ public class JpaRolloutManagement implements RolloutManagement {
private final EventPublisherHolder eventPublisherHolder;
private final ContextAware contextAware;
private final Database database;
@Autowired
private RepositoryProperties repositoryProperties;
@Autowired
private RolloutRepository rolloutRepository;
@Autowired
private RolloutGroupRepository rolloutGroupRepository;
@Autowired
private ActionRepository actionRepository;
@Autowired
private AfterTransactionCommitExecutor afterCommit;
@Autowired
private QuotaManagement quotaManagement;
@Autowired
private RolloutStatusCache rolloutStatusCache;
@Autowired
private StartNextGroupRolloutGroupSuccessAction startNextRolloutGroupAction;
public JpaRolloutManagement(final TargetManagement targetManagement,
final DistributionSetManagement distributionSetManagement, final EventPublisherHolder eventPublisherHolder,
@@ -163,34 +154,29 @@ public class JpaRolloutManagement implements RolloutManagement {
this.contextAware = contextAware;
}
@Override
public Page<Rollout> findAll(final Pageable pageable, final boolean deleted) {
return JpaManagementHelper.findAllWithCountBySpec(rolloutRepository, pageable, Collections
.singletonList(RolloutSpecification.isDeletedWithDistributionSet(deleted, pageable.getSort())));
public static String createRolloutLockKey(final String tenant) {
return tenant + "-rollout";
}
public void publishRolloutGroupCreatedEventAfterCommit(final RolloutGroup group, final Rollout rollout) {
afterCommit.afterCommit(() -> eventPublisherHolder.getEventPublisher().publishEvent(
new RolloutGroupCreatedEvent(group, rollout.getId(), eventPublisherHolder.getApplicationId())));
}
@Override
public Page<Rollout> findByRsql(final Pageable pageable, final String rsqlParam, final boolean deleted) {
final List<Specification<JpaRollout>> specList = new ArrayList<>(2);
specList.add(
RSQLUtility.buildRsqlSpecification(rsqlParam, RolloutFields.class, virtualPropertyReplacer, database));
specList.add(RolloutSpecification.isDeletedWithDistributionSet(deleted, pageable.getSort()));
return JpaManagementHelper.findAllWithCountBySpec(rolloutRepository, pageable, specList);
public long count() {
return rolloutRepository.count(
RolloutSpecification.isDeletedWithDistributionSet(false, Sort.by(Direction.DESC, JpaRollout_.ID)));
}
@Override
public Optional<Rollout> get(final long rolloutId) {
return rolloutRepository.findById(rolloutId).map(Rollout.class::cast);
public long countByFilters(final String searchText) {
return rolloutRepository.count(RolloutSpecification.likeName(searchText, false));
}
@Override
@Transactional
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public Rollout create(@NotNull @Valid RolloutCreate create, int amountGroup, boolean confirmationRequired,
@NotNull RolloutGroupConditions conditions) {
return create(create, amountGroup, confirmationRequired, conditions, null);
public long countByDistributionSetIdAndRolloutIsStoppable(final long setId) {
return rolloutRepository.countByDistributionSetIdAndStatusIn(setId, ROLLOUT_STATUS_STOPPABLE);
}
@Override
@@ -219,6 +205,15 @@ public class JpaRolloutManagement implements RolloutManagement {
return createRolloutGroups(amountGroup, conditions, savedRollout, confirmationRequired, dynamicRolloutGroupTemplate);
}
@Override
@Transactional
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public Rollout create(@NotNull @Valid RolloutCreate create, int amountGroup, boolean confirmationRequired,
@NotNull RolloutGroupConditions conditions) {
return create(create, amountGroup, confirmationRequired, conditions, null);
}
@Override
@Transactional
@Retryable(include = {
@@ -234,6 +229,314 @@ public class JpaRolloutManagement implements RolloutManagement {
return createRolloutGroups(groups, conditions, savedRollout);
}
@Override
@Async
public ListenableFuture<RolloutGroupsValidation> validateTargetsInGroups(final List<RolloutGroupCreate> groups,
final String targetFilter, final Long createdAt, final Long dsTypeId) {
final TargetCount targets = calculateTargets(targetFilter, createdAt, dsTypeId);
long totalTargets = targets.total();
final String baseFilter = targets.filter();
if (totalTargets == 0) {
throw new ConstraintDeclarationException("Rollout target filter does not match any targets");
}
return new AsyncResult<>(
validateTargetsInGroups(groups.stream().map(RolloutGroupCreate::build).collect(Collectors.toList()),
baseFilter, totalTargets, dsTypeId));
}
@Override
public Page<Rollout> findAll(final Pageable pageable, final boolean deleted) {
return JpaManagementHelper.findAllWithCountBySpec(rolloutRepository, pageable, Collections
.singletonList(RolloutSpecification.isDeletedWithDistributionSet(deleted, pageable.getSort())));
}
@Override
public Slice<Rollout> findAllWithDetailedStatus(final Pageable pageable, final boolean deleted) {
final Slice<Rollout> rollouts = JpaManagementHelper.findAllWithoutCountBySpec(rolloutRepository, pageable,
Collections
.singletonList(RolloutSpecification.isDeletedWithDistributionSet(deleted, pageable.getSort())));
setRolloutStatusDetails(rollouts);
return rollouts;
}
@Override
public Page<Rollout> findByRsql(final Pageable pageable, final String rsqlParam, final boolean deleted) {
final List<Specification<JpaRollout>> specList = new ArrayList<>(2);
specList.add(
RSQLUtility.buildRsqlSpecification(rsqlParam, RolloutFields.class, virtualPropertyReplacer, database));
specList.add(RolloutSpecification.isDeletedWithDistributionSet(deleted, pageable.getSort()));
return JpaManagementHelper.findAllWithCountBySpec(rolloutRepository, pageable, specList);
}
@Override
public Slice<Rollout> findByFiltersWithDetailedStatus(final Pageable pageable, final String searchText,
final boolean deleted) {
final Slice<Rollout> findAll = JpaManagementHelper.findAllWithoutCountBySpec(rolloutRepository, pageable,
Collections.singletonList(RolloutSpecification.likeName(searchText, deleted)));
setRolloutStatusDetails(findAll);
return findAll;
}
@Override
public List<Long> findActiveRollouts() {
return rolloutRepository.findByStatusIn(ACTIVE_ROLLOUTS);
}
@Override
public Optional<Rollout> get(final long rolloutId) {
return rolloutRepository.findById(rolloutId).map(Rollout.class::cast);
}
@Override
public Optional<Rollout> getByName(final String rolloutName) {
return rolloutRepository.findByName(rolloutName);
}
@Override
public Optional<Rollout> getWithDetailedStatus(final long rolloutId) {
final Optional<Rollout> rollout = get(rolloutId);
if (!rollout.isPresent()) {
return rollout;
}
List<TotalTargetCountActionStatus> rolloutStatusCountItems = rolloutStatusCache.getRolloutStatus(rolloutId);
if (CollectionUtils.isEmpty(rolloutStatusCountItems)) {
rolloutStatusCountItems = actionRepository.getStatusCountByRolloutId(rolloutId);
rolloutStatusCache.putRolloutStatus(rolloutId, rolloutStatusCountItems);
}
final TotalTargetCountStatus totalTargetCountStatus = new TotalTargetCountStatus(rolloutStatusCountItems,
rollout.get().getTotalTargets(), rollout.get().getActionType());
((JpaRollout) rollout.get()).setTotalTargetCountStatus(totalTargetCountStatus);
return rollout;
}
@Override
public boolean exists(final long rolloutId) {
return rolloutRepository.existsById(rolloutId);
}
@Override
@Transactional
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public void pauseRollout(final long rolloutId) {
final JpaRollout rollout = getRolloutOrThrowExceptionIfNotFound(rolloutId);
if (RolloutStatus.RUNNING != rollout.getStatus()) {
throw new RolloutIllegalStateException("Rollout can only be paused in state running but current state is "
+ rollout.getStatus().name().toLowerCase());
}
// setting the complete rollout only in paused state. This is sufficient
// due the currently running groups will be completed and new groups are
// not started until rollout goes back to running state again. The
// periodically check for running rollouts will skip rollouts in pause
// state.
rollout.setStatus(RolloutStatus.PAUSED);
rolloutRepository.save(rollout);
}
@Override
@Transactional
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public void resumeRollout(final long rolloutId) {
final JpaRollout rollout = getRolloutOrThrowExceptionIfNotFound(rolloutId);
if (RolloutStatus.PAUSED != rollout.getStatus()) {
throw new RolloutIllegalStateException("Rollout can only be resumed in state paused but current state is "
+ rollout.getStatus().name().toLowerCase());
}
rollout.setStatus(RolloutStatus.RUNNING);
rolloutRepository.save(rollout);
}
@Override
@Transactional
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public Rollout approveOrDeny(final long rolloutId, final Rollout.ApprovalDecision decision) {
return this.approveOrDeny(rolloutId, decision, null);
}
@Override
@Transactional
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public Rollout approveOrDeny(final long rolloutId, final Rollout.ApprovalDecision decision, final String remark) {
log.debug("approveOrDeny rollout called for rollout {} with decision {}", rolloutId, decision);
final JpaRollout rollout = getRolloutOrThrowExceptionIfNotFound(rolloutId);
RolloutHelper.verifyRolloutInStatus(rollout, RolloutStatus.WAITING_FOR_APPROVAL);
switch (decision) {
case APPROVED:
rollout.setStatus(RolloutStatus.READY);
break;
case DENIED:
rollout.setStatus(RolloutStatus.APPROVAL_DENIED);
break;
default:
throw new IllegalArgumentException("Unknown approval decision: " + decision);
}
rollout.setApprovalDecidedBy(rolloutApprovalStrategy.getApprovalUser(rollout));
if (remark != null) {
rollout.setApprovalRemark(remark);
}
return rolloutRepository.save(rollout);
}
@Override
@Transactional
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public Rollout start(final long rolloutId) {
log.debug("startRollout called for rollout {}", rolloutId);
final JpaRollout rollout = getRolloutOrThrowExceptionIfNotFound(rolloutId);
RolloutHelper.checkIfRolloutCanStarted(rollout, rollout);
rollout.setStatus(RolloutStatus.STARTING);
rollout.setLastCheck(0);
return rolloutRepository.save(rollout);
}
@Override
@Transactional
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public Rollout update(final RolloutUpdate u) {
final GenericRolloutUpdate update = (GenericRolloutUpdate) u;
final JpaRollout rollout = getRolloutOrThrowExceptionIfNotFound(update.getId());
checkIfDeleted(update.getId(), rollout.getStatus());
update.getName().ifPresent(rollout::setName);
update.getDescription().ifPresent(rollout::setDescription);
return rolloutRepository.save(rollout);
}
@Override
@Transactional
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public void delete(final long rolloutId) {
final JpaRollout jpaRollout = rolloutRepository.findById(rolloutId)
.orElseThrow(() -> new EntityNotFoundException(Rollout.class, rolloutId));
if (jpaRollout == null) {
throw new EntityNotFoundException(Rollout.class, rolloutId);
}
if (RolloutStatus.DELETING == jpaRollout.getStatus()) {
return;
}
jpaRollout.setStatus(RolloutStatus.DELETING);
rolloutRepository.save(jpaRollout);
}
@Override
@Transactional
public void cancelRolloutsForDistributionSet(final DistributionSet set) {
// stop all rollouts for this distribution set
rolloutRepository.findByDistributionSetAndStatusIn(set, ROLLOUT_STATUS_STOPPABLE).forEach(rollout -> {
final JpaRollout jpaRollout = (JpaRollout) rollout;
jpaRollout.setStatus(RolloutStatus.STOPPING);
rolloutRepository.save(jpaRollout);
log.debug("Rollout {} stopped", jpaRollout.getId());
});
}
@Override
@Transactional
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public void triggerNextGroup(final long rolloutId) {
final JpaRollout rollout = getRolloutOrThrowExceptionIfNotFound(rolloutId);
if (RolloutStatus.RUNNING != rollout.getStatus()) {
throw new RolloutIllegalStateException("Rollout is not in running state");
}
final List<RolloutGroup> groups = rollout.getRolloutGroups();
final boolean isNextGroupTriggerable = groups.stream()
.anyMatch(g -> RolloutGroupStatus.SCHEDULED.equals(g.getStatus()));
if (!isNextGroupTriggerable) {
throw new RolloutIllegalStateException("Rollout does not have any groups left to be triggered");
}
final RolloutGroup latestRunning = groups.stream()
.sorted(Comparator.comparingLong(RolloutGroup::getId).reversed())
.filter(g -> RolloutGroupStatus.RUNNING.equals(g.getStatus()))
.findFirst()
.orElseThrow(() -> new RolloutIllegalStateException("No group is running"));
startNextRolloutGroupAction.exec(rollout, latestRunning);
}
@Override
public void setRolloutStatusDetails(final Slice<Rollout> rollouts) {
final List<Long> rolloutIds = rollouts.getContent().stream().map(Rollout::getId).collect(Collectors.toList());
final Map<Long, List<TotalTargetCountActionStatus>> allStatesForRollout = getStatusCountItemForRollout(
rolloutIds);
if (allStatesForRollout != null) {
rollouts.forEach(rollout -> {
final TotalTargetCountStatus totalTargetCountStatus = new TotalTargetCountStatus(
allStatesForRollout.get(rollout.getId()), rollout.getTotalTargets(), rollout.getActionType());
((JpaRollout) rollout).setTotalTargetCountStatus(totalTargetCountStatus);
});
}
}
/**
* In case the given group is missing conditions or actions, they will be set
* from the supplied default conditions.
*
* @param create group to check
* @param conditions default conditions and actions
*/
private static JpaRolloutGroup prepareRolloutGroupWithDefaultConditions(final RolloutGroupCreate create,
final RolloutGroupConditions conditions) {
final JpaRolloutGroup group = ((JpaRolloutGroupCreate) create).build();
if (group.getSuccessCondition() == null) {
group.setSuccessCondition(conditions.getSuccessCondition());
}
if (group.getSuccessConditionExp() == null) {
group.setSuccessConditionExp(conditions.getSuccessConditionExp());
}
if (group.getSuccessAction() == null) {
group.setSuccessAction(conditions.getSuccessAction());
}
if (group.getSuccessActionExp() == null) {
group.setSuccessActionExp(conditions.getSuccessActionExp());
}
if (group.getErrorCondition() == null) {
group.setErrorCondition(conditions.getErrorCondition());
}
if (group.getErrorConditionExp() == null) {
group.setErrorConditionExp(conditions.getErrorConditionExp());
}
if (group.getErrorAction() == null) {
group.setErrorAction(conditions.getErrorAction());
}
if (group.getErrorActionExp() == null) {
group.setErrorActionExp(conditions.getErrorActionExp());
}
return group;
}
private static void checkIfDeleted(final Long rolloutId, final RolloutStatus status) {
if (RolloutStatus.DELETING == status || RolloutStatus.DELETED == status) {
throw new EntityReadOnlyException("Rollout " + rolloutId + " is soft deleted and cannot be changed");
}
}
private JpaRollout createRollout(final JpaRollout rollout, final boolean pureDynamic) {
WeightValidationHelper.usingContext(systemSecurityContext, tenantConfigurationManagement).validate(rollout);
final JpaDistributionSet distributionSet = (JpaDistributionSet) rollout.getDistributionSet();
@@ -259,7 +562,7 @@ public class JpaRolloutManagement implements RolloutManagement {
rollout.setTotalTargets(totalTargets);
}
if (((JpaDistributionSetManagement)distributionSetManagement).isImplicitLockApplicable(distributionSet)) {
if (((JpaDistributionSetManagement) distributionSetManagement).isImplicitLockApplicable(distributionSet)) {
distributionSetManagement.lock(distributionSet.getId());
}
@@ -391,253 +694,11 @@ public class JpaRolloutManagement implements RolloutManagement {
return rolloutRepository.save(savedRollout);
}
/**
* In case the given group is missing conditions or actions, they will be set
* from the supplied default conditions.
*
* @param create
* group to check
* @param conditions
* default conditions and actions
*/
private static JpaRolloutGroup prepareRolloutGroupWithDefaultConditions(final RolloutGroupCreate create,
final RolloutGroupConditions conditions) {
final JpaRolloutGroup group = ((JpaRolloutGroupCreate) create).build();
if (group.getSuccessCondition() == null) {
group.setSuccessCondition(conditions.getSuccessCondition());
}
if (group.getSuccessConditionExp() == null) {
group.setSuccessConditionExp(conditions.getSuccessConditionExp());
}
if (group.getSuccessAction() == null) {
group.setSuccessAction(conditions.getSuccessAction());
}
if (group.getSuccessActionExp() == null) {
group.setSuccessActionExp(conditions.getSuccessActionExp());
}
if (group.getErrorCondition() == null) {
group.setErrorCondition(conditions.getErrorCondition());
}
if (group.getErrorConditionExp() == null) {
group.setErrorConditionExp(conditions.getErrorConditionExp());
}
if (group.getErrorAction() == null) {
group.setErrorAction(conditions.getErrorAction());
}
if (group.getErrorActionExp() == null) {
group.setErrorActionExp(conditions.getErrorActionExp());
}
return group;
}
public void publishRolloutGroupCreatedEventAfterCommit(final RolloutGroup group, final Rollout rollout) {
afterCommit.afterCommit(() -> eventPublisherHolder.getEventPublisher().publishEvent(
new RolloutGroupCreatedEvent(group, rollout.getId(), eventPublisherHolder.getApplicationId())));
}
@Override
@Transactional
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public Rollout approveOrDeny(final long rolloutId, final Rollout.ApprovalDecision decision) {
return this.approveOrDeny(rolloutId, decision, null);
}
@Override
@Transactional
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public Rollout approveOrDeny(final long rolloutId, final Rollout.ApprovalDecision decision, final String remark) {
log.debug("approveOrDeny rollout called for rollout {} with decision {}", rolloutId, decision);
final JpaRollout rollout = getRolloutOrThrowExceptionIfNotFound(rolloutId);
RolloutHelper.verifyRolloutInStatus(rollout, RolloutStatus.WAITING_FOR_APPROVAL);
switch (decision) {
case APPROVED:
rollout.setStatus(RolloutStatus.READY);
break;
case DENIED:
rollout.setStatus(RolloutStatus.APPROVAL_DENIED);
break;
default:
throw new IllegalArgumentException("Unknown approval decision: " + decision);
}
rollout.setApprovalDecidedBy(rolloutApprovalStrategy.getApprovalUser(rollout));
if (remark != null) {
rollout.setApprovalRemark(remark);
}
return rolloutRepository.save(rollout);
}
@Override
@Transactional
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public Rollout start(final long rolloutId) {
log.debug("startRollout called for rollout {}", rolloutId);
final JpaRollout rollout = getRolloutOrThrowExceptionIfNotFound(rolloutId);
RolloutHelper.checkIfRolloutCanStarted(rollout, rollout);
rollout.setStatus(RolloutStatus.STARTING);
rollout.setLastCheck(0);
return rolloutRepository.save(rollout);
}
@Override
@Transactional
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public void pauseRollout(final long rolloutId) {
final JpaRollout rollout = getRolloutOrThrowExceptionIfNotFound(rolloutId);
if (RolloutStatus.RUNNING != rollout.getStatus()) {
throw new RolloutIllegalStateException("Rollout can only be paused in state running but current state is "
+ rollout.getStatus().name().toLowerCase());
}
// setting the complete rollout only in paused state. This is sufficient
// due the currently running groups will be completed and new groups are
// not started until rollout goes back to running state again. The
// periodically check for running rollouts will skip rollouts in pause
// state.
rollout.setStatus(RolloutStatus.PAUSED);
rolloutRepository.save(rollout);
}
@Override
@Transactional
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public void resumeRollout(final long rolloutId) {
final JpaRollout rollout = getRolloutOrThrowExceptionIfNotFound(rolloutId);
if (RolloutStatus.PAUSED != rollout.getStatus()) {
throw new RolloutIllegalStateException("Rollout can only be resumed in state paused but current state is "
+ rollout.getStatus().name().toLowerCase());
}
rollout.setStatus(RolloutStatus.RUNNING);
rolloutRepository.save(rollout);
}
public static String createRolloutLockKey(final String tenant) {
return tenant + "-rollout";
}
@Override
@Transactional
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public void delete(final long rolloutId) {
final JpaRollout jpaRollout = rolloutRepository.findById(rolloutId)
.orElseThrow(() -> new EntityNotFoundException(Rollout.class, rolloutId));
if (jpaRollout == null) {
throw new EntityNotFoundException(Rollout.class, rolloutId);
}
if (RolloutStatus.DELETING == jpaRollout.getStatus()) {
return;
}
jpaRollout.setStatus(RolloutStatus.DELETING);
rolloutRepository.save(jpaRollout);
}
@Override
public long count() {
return rolloutRepository.count(
RolloutSpecification.isDeletedWithDistributionSet(false, Sort.by(Direction.DESC, JpaRollout_.ID)));
}
@Override
public long countByFilters(final String searchText) {
return rolloutRepository.count(RolloutSpecification.likeName(searchText, false));
}
@Override
public long countByDistributionSetIdAndRolloutIsStoppable(final long setId) {
return rolloutRepository.countByDistributionSetIdAndStatusIn(setId, ROLLOUT_STATUS_STOPPABLE);
}
@Override
public Slice<Rollout> findByFiltersWithDetailedStatus(final Pageable pageable, final String searchText,
final boolean deleted) {
final Slice<Rollout> findAll = JpaManagementHelper.findAllWithoutCountBySpec(rolloutRepository, pageable,
Collections.singletonList(RolloutSpecification.likeName(searchText, deleted)));
setRolloutStatusDetails(findAll);
return findAll;
}
@Override
public List<Long> findActiveRollouts() {
return rolloutRepository.findByStatusIn(ACTIVE_ROLLOUTS);
}
@Override
public Optional<Rollout> getByName(final String rolloutName) {
return rolloutRepository.findByName(rolloutName);
}
@Override
@Transactional
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public Rollout update(final RolloutUpdate u) {
final GenericRolloutUpdate update = (GenericRolloutUpdate) u;
final JpaRollout rollout = getRolloutOrThrowExceptionIfNotFound(update.getId());
checkIfDeleted(update.getId(), rollout.getStatus());
update.getName().ifPresent(rollout::setName);
update.getDescription().ifPresent(rollout::setDescription);
return rolloutRepository.save(rollout);
}
private static void checkIfDeleted(final Long rolloutId, final RolloutStatus status) {
if (RolloutStatus.DELETING == status || RolloutStatus.DELETED == status) {
throw new EntityReadOnlyException("Rollout " + rolloutId + " is soft deleted and cannot be changed");
}
}
private JpaRollout getRolloutOrThrowExceptionIfNotFound(final Long rolloutId) {
return rolloutRepository.findById(rolloutId)
.orElseThrow(() -> new EntityNotFoundException(Rollout.class, rolloutId));
}
@Override
public Slice<Rollout> findAllWithDetailedStatus(final Pageable pageable, final boolean deleted) {
final Slice<Rollout> rollouts = JpaManagementHelper.findAllWithoutCountBySpec(rolloutRepository, pageable,
Collections
.singletonList(RolloutSpecification.isDeletedWithDistributionSet(deleted, pageable.getSort())));
setRolloutStatusDetails(rollouts);
return rollouts;
}
@Override
public Optional<Rollout> getWithDetailedStatus(final long rolloutId) {
final Optional<Rollout> rollout = get(rolloutId);
if (!rollout.isPresent()) {
return rollout;
}
List<TotalTargetCountActionStatus> rolloutStatusCountItems = rolloutStatusCache.getRolloutStatus(rolloutId);
if (CollectionUtils.isEmpty(rolloutStatusCountItems)) {
rolloutStatusCountItems = actionRepository.getStatusCountByRolloutId(rolloutId);
rolloutStatusCache.putRolloutStatus(rolloutId, rolloutStatusCountItems);
}
final TotalTargetCountStatus totalTargetCountStatus = new TotalTargetCountStatus(rolloutStatusCountItems,
rollout.get().getTotalTargets(), rollout.get().getActionType());
((JpaRollout) rollout.get()).setTotalTargetCountStatus(totalTargetCountStatus);
return rollout;
}
@Override
public boolean exists(final long rolloutId) {
return rolloutRepository.existsById(rolloutId);
}
private Map<Long, List<TotalTargetCountActionStatus>> getStatusCountItemForRollout(final List<Long> rollouts) {
if (rollouts.isEmpty()) {
return null;
@@ -662,45 +723,17 @@ public class JpaRolloutManagement implements RolloutManagement {
return fromCache;
}
@Override
public void setRolloutStatusDetails(final Slice<Rollout> rollouts) {
final List<Long> rolloutIds = rollouts.getContent().stream().map(Rollout::getId).collect(Collectors.toList());
final Map<Long, List<TotalTargetCountActionStatus>> allStatesForRollout = getStatusCountItemForRollout(
rolloutIds);
if (allStatesForRollout != null) {
rollouts.forEach(rollout -> {
final TotalTargetCountStatus totalTargetCountStatus = new TotalTargetCountStatus(
allStatesForRollout.get(rollout.getId()), rollout.getTotalTargets(), rollout.getActionType());
((JpaRollout) rollout).setTotalTargetCountStatus(totalTargetCountStatus);
});
}
}
/**
* Enforces the quota defining the maximum number of {@link Target}s per
* {@link RolloutGroup}.
*
* @param requested
* number of targets to check
* @param requested number of targets to check
*/
private void assertTargetsPerRolloutGroupQuota(final long requested) {
final int quota = quotaManagement.getMaxTargetsPerRolloutGroup();
QuotaHelper.assertAssignmentQuota(requested, quota, Target.class, RolloutGroup.class);
}
@Override
@Transactional
public void cancelRolloutsForDistributionSet(final DistributionSet set) {
// stop all rollouts for this distribution set
rolloutRepository.findByDistributionSetAndStatusIn(set, ROLLOUT_STATUS_STOPPABLE).forEach(rollout -> {
final JpaRollout jpaRollout = (JpaRollout) rollout;
jpaRollout.setStatus(RolloutStatus.STOPPING);
rolloutRepository.save(jpaRollout);
log.debug("Rollout {} stopped", jpaRollout.getId());
});
}
private RolloutGroupsValidation validateTargetsInGroups(final List<RolloutGroup> groups, final String baseFilter,
final long totalTargets, final Long dsTypeId) {
final List<Long> groupTargetCounts = new ArrayList<>(groups.size());
@@ -786,51 +819,6 @@ public class JpaRolloutManagement implements RolloutManagement {
return totalTargets - validation.getTargetsInGroups();
}
@Override
@Async
public ListenableFuture<RolloutGroupsValidation> validateTargetsInGroups(final List<RolloutGroupCreate> groups,
final String targetFilter, final Long createdAt, final Long dsTypeId) {
final TargetCount targets = calculateTargets(targetFilter, createdAt, dsTypeId);
long totalTargets = targets.total();
final String baseFilter = targets.filter();
if (totalTargets == 0) {
throw new ConstraintDeclarationException("Rollout target filter does not match any targets");
}
return new AsyncResult<>(
validateTargetsInGroups(groups.stream().map(RolloutGroupCreate::build).collect(Collectors.toList()),
baseFilter, totalTargets, dsTypeId));
}
@Override
@Transactional
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public void triggerNextGroup(final long rolloutId) {
final JpaRollout rollout = getRolloutOrThrowExceptionIfNotFound(rolloutId);
if (RolloutStatus.RUNNING != rollout.getStatus()) {
throw new RolloutIllegalStateException("Rollout is not in running state");
}
final List<RolloutGroup> groups = rollout.getRolloutGroups();
final boolean isNextGroupTriggerable = groups.stream()
.anyMatch(g -> RolloutGroupStatus.SCHEDULED.equals(g.getStatus()));
if (!isNextGroupTriggerable) {
throw new RolloutIllegalStateException("Rollout does not have any groups left to be triggered");
}
final RolloutGroup latestRunning = groups.stream()
.sorted(Comparator.comparingLong(RolloutGroup::getId).reversed())
.filter(g -> RolloutGroupStatus.RUNNING.equals(g.getStatus()))
.findFirst()
.orElseThrow(() -> new RolloutIllegalStateException("No group is running"));
startNextRolloutGroupAction.exec(rollout, latestRunning);
}
private TargetCount calculateTargets(final String targetFilter, final Long createdAt, final Long dsTypeId) {
String baseFilter;
long totalTargets;

View File

@@ -22,15 +22,6 @@ import java.util.stream.Collectors;
import java.util.stream.Stream;
import jakarta.persistence.EntityManager;
import jakarta.persistence.Tuple;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Expression;
import jakarta.persistence.criteria.JoinType;
import jakarta.persistence.criteria.ListJoin;
import jakarta.persistence.criteria.Order;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root;
import org.eclipse.hawkbit.repository.ArtifactEncryptionService;
import org.eclipse.hawkbit.repository.ArtifactManagement;
@@ -47,14 +38,11 @@ import org.eclipse.hawkbit.repository.builder.SoftwareModuleMetadataUpdate;
import org.eclipse.hawkbit.repository.builder.SoftwareModuleUpdate;
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException;
import org.eclipse.hawkbit.repository.jpa.JpaManagementHelper;
import org.eclipse.hawkbit.repository.jpa.acm.AccessController;
import org.eclipse.hawkbit.repository.jpa.builder.JpaSoftwareModuleCreate;
import org.eclipse.hawkbit.repository.jpa.builder.JpaSoftwareModuleMetadataCreate;
import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet_;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModuleMetadata;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModuleMetadata_;
@@ -68,7 +56,6 @@ import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility;
import org.eclipse.hawkbit.repository.jpa.specifications.SoftwareModuleSpecification;
import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper;
import org.eclipse.hawkbit.repository.model.Artifact;
import org.eclipse.hawkbit.repository.model.AssignedSoftwareModule;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.SoftwareModuleMetadata;
@@ -80,10 +67,7 @@ import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.SliceImpl;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.query.QueryUtils;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
@@ -133,23 +117,20 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement {
@Transactional
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public SoftwareModule update(final SoftwareModuleUpdate u) {
final GenericSoftwareModuleUpdate update = (GenericSoftwareModuleUpdate) u;
public List<SoftwareModule> create(final Collection<SoftwareModuleCreate> swModules) {
final List<JpaSoftwareModule> modulesToCreate = swModules.stream().map(JpaSoftwareModuleCreate.class::cast)
.map(JpaSoftwareModuleCreate::build).toList();
final JpaSoftwareModule module = softwareModuleRepository.findById(update.getId())
.orElseThrow(() -> new EntityNotFoundException(SoftwareModule.class, update.getId()));
final List<SoftwareModule> createdModules = Collections
.unmodifiableList(softwareModuleRepository.saveAll(AccessController.Operation.CREATE, modulesToCreate));
update.getDescription().ifPresent(module::setDescription);
update.getVendor().ifPresent(module::setVendor);
// lock/unlock ONLY if locked flag is present!
if (Boolean.TRUE.equals(update.locked())) {
module.lock();
} else if (Boolean.FALSE.equals(update.locked())) {
module.unlock();
if (createdModules.stream().anyMatch(SoftwareModule::isEncrypted)) {
entityManager.flush();
createdModules.stream().filter(SoftwareModule::isEncrypted).map(SoftwareModule::getId)
.forEach(encryptedModuleId -> ArtifactEncryptionService.getInstance()
.addSoftwareModuleEncryptionSecrets(encryptedModuleId));
}
return softwareModuleRepository.save(module);
return createdModules;
}
@Override
@@ -173,61 +154,36 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement {
@Transactional
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public List<SoftwareModule> create(final Collection<SoftwareModuleCreate> swModules) {
final List<JpaSoftwareModule> modulesToCreate = swModules.stream().map(JpaSoftwareModuleCreate.class::cast)
.map(JpaSoftwareModuleCreate::build).toList();
public SoftwareModule update(final SoftwareModuleUpdate u) {
final GenericSoftwareModuleUpdate update = (GenericSoftwareModuleUpdate) u;
final List<SoftwareModule> createdModules = Collections
.unmodifiableList(softwareModuleRepository.saveAll(AccessController.Operation.CREATE, modulesToCreate));
final JpaSoftwareModule module = softwareModuleRepository.findById(update.getId())
.orElseThrow(() -> new EntityNotFoundException(SoftwareModule.class, update.getId()));
if (createdModules.stream().anyMatch(SoftwareModule::isEncrypted)) {
entityManager.flush();
createdModules.stream().filter(SoftwareModule::isEncrypted).map(SoftwareModule::getId)
.forEach(encryptedModuleId -> ArtifactEncryptionService.getInstance()
.addSoftwareModuleEncryptionSecrets(encryptedModuleId));
update.getDescription().ifPresent(module::setDescription);
update.getVendor().ifPresent(module::setVendor);
// lock/unlock ONLY if locked flag is present!
if (Boolean.TRUE.equals(update.locked())) {
module.lock();
} else if (Boolean.FALSE.equals(update.locked())) {
module.unlock();
}
return createdModules;
return softwareModuleRepository.save(module);
}
@Override
public Slice<SoftwareModule> findByType(final Pageable pageable, final long typeId) {
assertSoftwareModuleTypeExists(typeId);
return JpaManagementHelper.findAllWithoutCountBySpec(
softwareModuleRepository,
pageable,
List.of(
SoftwareModuleSpecification.equalType(typeId),
SoftwareModuleSpecification.isNotDeleted()));
public long count() {
return softwareModuleRepository.count(SoftwareModuleSpecification.isNotDeleted());
}
@Override
public Optional<SoftwareModule> get(final long id) {
return softwareModuleRepository.findById(id).map(SoftwareModule.class::cast);
}
@Override
public Optional<SoftwareModule> getByNameAndVersionAndType(final String name, final String version,
final long typeId) {
assertSoftwareModuleTypeExists(typeId);
// TODO AC - Access is restricted. This could have problem with UI when access control is enabled.
// Vaadin UI use this for validation. May need to be called via elevated access
return JpaManagementHelper
.findOneBySpec(softwareModuleRepository, List.of(
SoftwareModuleSpecification.likeNameAndVersion(name, version),
SoftwareModuleSpecification.equalType(typeId),
SoftwareModuleSpecification.fetchType()))
.map(SoftwareModule.class::cast);
}
private void deleteGridFsArtifacts(final JpaSoftwareModule swModule) {
softwareModuleRepository.getAccessController().ifPresent(accessController ->
accessController.assertOperationAllowed(AccessController.Operation.DELETE, swModule));
for (final Artifact localArtifact : swModule.getArtifacts()) {
((JpaArtifactManagement)artifactManagement)
.clearArtifactBinary(localArtifact.getSha1Hash());
}
@Transactional
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public void delete(final long id) {
delete(List.of(id));
}
@Override
@@ -279,6 +235,21 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement {
}
}
@Override
public List<SoftwareModule> get(final Collection<Long> ids) {
return Collections.unmodifiableList(softwareModuleRepository.findAllById(ids));
}
@Override
public boolean exists(final long id) {
return softwareModuleRepository.existsById(id);
}
@Override
public Optional<SoftwareModule> get(final long id) {
return softwareModuleRepository.findById(id).map(SoftwareModule.class::cast);
}
@Override
public Slice<SoftwareModule> findAll(final Pageable pageable) {
return JpaManagementHelper.findAllWithoutCountBySpec(softwareModuleRepository, pageable, List.of(
@@ -286,11 +257,6 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement {
SoftwareModuleSpecification.fetchType()));
}
@Override
public long count() {
return softwareModuleRepository.count(SoftwareModuleSpecification.isNotDeleted());
}
@Override
public Page<SoftwareModule> findByRsql(final Pageable pageable, final String rsqlParam) {
return JpaManagementHelper.findAllWithCountBySpec(softwareModuleRepository, pageable, List.of(
@@ -299,70 +265,6 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement {
SoftwareModuleSpecification.isNotDeleted()));
}
@Override
public List<SoftwareModule> get(final Collection<Long> ids) {
return Collections.unmodifiableList(softwareModuleRepository.findAllById(ids));
}
@Override
public Slice<SoftwareModule> findByTextAndType(final Pageable pageable, final String searchText,
final Long typeId) {
final List<Specification<JpaSoftwareModule>> specList = new ArrayList<>(3);
specList.add(SoftwareModuleSpecification.isNotDeleted());
if (!ObjectUtils.isEmpty(searchText)) {
specList.add(buildSmSearchQuerySpec(searchText));
}
if (null != typeId) {
assertSoftwareModuleTypeExists(typeId);
specList.add(SoftwareModuleSpecification.equalType(typeId));
}
specList.add(SoftwareModuleSpecification.fetchType());
return JpaManagementHelper.findAllWithoutCountBySpec(softwareModuleRepository, pageable, specList);
}
private Specification<JpaSoftwareModule> buildSmSearchQuerySpec(final String searchText) {
final String[] smFilterNameAndVersionEntries = JpaManagementHelper
.getFilterNameAndVersionEntries(searchText.trim());
return SoftwareModuleSpecification.likeNameAndVersion(smFilterNameAndVersionEntries[0],
smFilterNameAndVersionEntries[1]);
}
@Override
public Page<SoftwareModule> findByAssignedTo(final Pageable pageable, final long distributionSetId) {
assertDistributionSetExists(distributionSetId);
return JpaManagementHelper.findAllWithCountBySpec(softwareModuleRepository, pageable,
Collections.singletonList(SoftwareModuleSpecification.byAssignedToDs(distributionSetId)));
}
@Override
public long countByAssignedTo(final long distributionSetId) {
assertDistributionSetExists(distributionSetId);
return softwareModuleRepository.count(SoftwareModuleSpecification.byAssignedToDs(distributionSetId));
}
@Override
@Transactional
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public SoftwareModuleMetadata createMetaData(final SoftwareModuleMetadataCreate c) {
final JpaSoftwareModuleMetadataCreate create = (JpaSoftwareModuleMetadataCreate) c;
final Long id = create.getSoftwareModuleId();
assertSoftwareModuleExists(id);
assertMetaDataQuota(id, 1);
// touch to update revision and last modified timestamp
JpaManagementHelper.touch(entityManager, softwareModuleRepository, (JpaSoftwareModule) get(id)
.orElseThrow(() -> new EntityNotFoundException(SoftwareModule.class, id)));
return saveMetadata(create);
}
@Override
@Transactional
@Retryable(include = {
@@ -400,56 +302,21 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement {
return Collections.emptyList();
}
private static Stream<JpaSoftwareModuleMetadataCreate> createJpaMetadataCreateStream(
final Collection<SoftwareModuleMetadataCreate> create) {
return create.stream().map(JpaSoftwareModuleMetadataCreate.class::cast);
}
private SoftwareModuleMetadata saveMetadata(final JpaSoftwareModuleMetadataCreate create) {
assertSoftwareModuleMetadataDoesNotExist(create.getSoftwareModuleId(), create);
return softwareModuleMetadataRepository.save(create.build());
}
private void assertSoftwareModuleMetadataDoesNotExist(final Long id,
final JpaSoftwareModuleMetadataCreate md) {
if (softwareModuleMetadataRepository.existsById(new SwMetadataCompositeKey(id, md.getKey()))) {
throw new EntityAlreadyExistsException("Metadata entry with key '" + md.getKey() + "' already exists!");
}
}
/**
* Asserts the meta data quota for the software module with the given ID.
*
* @param id
* The software module ID.
* @param requested
* Number of meta data entries to be created.
*/
private void assertMetaDataQuota(final Long id, final int requested) {
final int maxMetaData = quotaManagement.getMaxMetaDataEntriesPerSoftwareModule();
QuotaHelper.assertAssignmentQuota(id, requested, maxMetaData, SoftwareModuleMetadata.class,
SoftwareModule.class, softwareModuleMetadataRepository::countBySoftwareModuleId);
}
@Override
@Transactional
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public SoftwareModuleMetadata updateMetaData(final SoftwareModuleMetadataUpdate u) {
final GenericSoftwareModuleMetadataUpdate update = (GenericSoftwareModuleMetadataUpdate) u;
public SoftwareModuleMetadata createMetaData(final SoftwareModuleMetadataCreate c) {
final JpaSoftwareModuleMetadataCreate create = (JpaSoftwareModuleMetadataCreate) c;
final Long id = create.getSoftwareModuleId();
// check if exists otherwise throw entity not found exception
final JpaSoftwareModuleMetadata metadata = (JpaSoftwareModuleMetadata) getMetaDataBySoftwareModuleId(
update.getSoftwareModuleId(), update.getKey())
.orElseThrow(() -> new EntityNotFoundException(SoftwareModuleMetadata.class,
update.getSoftwareModuleId(), update.getKey()));
assertSoftwareModuleExists(id);
assertMetaDataQuota(id, 1);
update.getValue().ifPresent(metadata::setValue);
update.isTargetVisible().ifPresent(metadata::setTargetVisible);
JpaManagementHelper.touch(entityManager, softwareModuleRepository,
(JpaSoftwareModule) metadata.getSoftwareModule());
return softwareModuleMetadataRepository.save(metadata);
// touch to update revision and last modified timestamp
JpaManagementHelper.touch(entityManager, softwareModuleRepository, (JpaSoftwareModule) get(id)
.orElseThrow(() -> new EntityNotFoundException(SoftwareModule.class, id)));
return saveMetadata(create);
}
@Override
@@ -466,14 +333,61 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement {
}
@Override
public Page<SoftwareModuleMetadata> findMetaDataByRsql(final Pageable pageable, final long id,
final String rsqlParam) {
public Page<SoftwareModule> findByAssignedTo(final Pageable pageable, final long distributionSetId) {
assertDistributionSetExists(distributionSetId);
return JpaManagementHelper.findAllWithCountBySpec(softwareModuleRepository, pageable,
Collections.singletonList(SoftwareModuleSpecification.byAssignedToDs(distributionSetId)));
}
@Override
public long countByAssignedTo(final long distributionSetId) {
assertDistributionSetExists(distributionSetId);
return softwareModuleRepository.count(SoftwareModuleSpecification.byAssignedToDs(distributionSetId));
}
@Override
public Slice<SoftwareModule> findByTextAndType(final Pageable pageable, final String searchText,
final Long typeId) {
final List<Specification<JpaSoftwareModule>> specList = new ArrayList<>(3);
specList.add(SoftwareModuleSpecification.isNotDeleted());
if (!ObjectUtils.isEmpty(searchText)) {
specList.add(buildSmSearchQuerySpec(searchText));
}
if (null != typeId) {
assertSoftwareModuleTypeExists(typeId);
specList.add(SoftwareModuleSpecification.equalType(typeId));
}
specList.add(SoftwareModuleSpecification.fetchType());
return JpaManagementHelper.findAllWithoutCountBySpec(softwareModuleRepository, pageable, specList);
}
@Override
public Optional<SoftwareModule> getByNameAndVersionAndType(final String name, final String version,
final long typeId) {
assertSoftwareModuleTypeExists(typeId);
// TODO AC - Access is restricted. This could have problem with UI when access control is enabled.
// Vaadin UI use this for validation. May need to be called via elevated access
return JpaManagementHelper
.findOneBySpec(softwareModuleRepository, List.of(
SoftwareModuleSpecification.likeNameAndVersion(name, version),
SoftwareModuleSpecification.equalType(typeId),
SoftwareModuleSpecification.fetchType()))
.map(SoftwareModule.class::cast);
}
@Override
public Optional<SoftwareModuleMetadata> getMetaDataBySoftwareModuleId(final long id, final String key) {
assertSoftwareModuleExists(id);
final List<Specification<JpaSoftwareModuleMetadata>> specList = Arrays
.asList(RSQLUtility.buildRsqlSpecification(rsqlParam, SoftwareModuleMetadataFields.class,
virtualPropertyReplacer, database), metadataBySoftwareModuleIdSpec(id));
return JpaManagementHelper.findAllWithCountBySpec(softwareModuleMetadataRepository, pageable, specList);
return softwareModuleMetadataRepository.findById(new SwMetadataCompositeKey(id, key))
.map(SoftwareModuleMetadata.class::cast);
}
@Override
@@ -492,11 +406,35 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement {
}
@Override
public Optional<SoftwareModuleMetadata> getMetaDataBySoftwareModuleId(final long id, final String key) {
public Page<SoftwareModuleMetadata> findMetaDataBySoftwareModuleIdAndTargetVisible(final Pageable pageable,
final long id) {
assertSoftwareModuleExists(id);
return softwareModuleMetadataRepository.findById(new SwMetadataCompositeKey(id, key))
.map(SoftwareModuleMetadata.class::cast);
return JpaManagementHelper.convertPage(softwareModuleMetadataRepository.findBySoftwareModuleIdAndTargetVisible(
PageRequest.of(0, RepositoryConstants.MAX_META_DATA_COUNT), id, true), pageable);
}
@Override
public Page<SoftwareModuleMetadata> findMetaDataByRsql(final Pageable pageable, final long id,
final String rsqlParam) {
assertSoftwareModuleExists(id);
final List<Specification<JpaSoftwareModuleMetadata>> specList = Arrays
.asList(RSQLUtility.buildRsqlSpecification(rsqlParam, SoftwareModuleMetadataFields.class,
virtualPropertyReplacer, database), metadataBySoftwareModuleIdSpec(id));
return JpaManagementHelper.findAllWithCountBySpec(softwareModuleMetadataRepository, pageable, specList);
}
@Override
public Slice<SoftwareModule> findByType(final Pageable pageable, final long typeId) {
assertSoftwareModuleTypeExists(typeId);
return JpaManagementHelper.findAllWithoutCountBySpec(
softwareModuleRepository,
pageable,
List.of(
SoftwareModuleSpecification.equalType(typeId),
SoftwareModuleSpecification.isNotDeleted()));
}
@Override
@@ -531,22 +469,71 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement {
@Transactional
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public void delete(final long id) {
delete(List.of(id));
public SoftwareModuleMetadata updateMetaData(final SoftwareModuleMetadataUpdate u) {
final GenericSoftwareModuleMetadataUpdate update = (GenericSoftwareModuleMetadataUpdate) u;
// check if exists otherwise throw entity not found exception
final JpaSoftwareModuleMetadata metadata = (JpaSoftwareModuleMetadata) getMetaDataBySoftwareModuleId(
update.getSoftwareModuleId(), update.getKey())
.orElseThrow(() -> new EntityNotFoundException(SoftwareModuleMetadata.class,
update.getSoftwareModuleId(), update.getKey()));
update.getValue().ifPresent(metadata::setValue);
update.isTargetVisible().ifPresent(metadata::setTargetVisible);
JpaManagementHelper.touch(entityManager, softwareModuleRepository,
(JpaSoftwareModule) metadata.getSoftwareModule());
return softwareModuleMetadataRepository.save(metadata);
}
@Override
public boolean exists(final long id) {
return softwareModuleRepository.existsById(id);
private static Stream<JpaSoftwareModuleMetadataCreate> createJpaMetadataCreateStream(
final Collection<SoftwareModuleMetadataCreate> create) {
return create.stream().map(JpaSoftwareModuleMetadataCreate.class::cast);
}
@Override
public Page<SoftwareModuleMetadata> findMetaDataBySoftwareModuleIdAndTargetVisible(final Pageable pageable,
final long id) {
assertSoftwareModuleExists(id);
private static Specification<JpaSoftwareModuleMetadata> metadataBySoftwareModuleIdSpec(final long id) {
return (root, query, cb) -> cb
.equal(root.get(JpaSoftwareModuleMetadata_.softwareModule).get(JpaSoftwareModule_.id), id);
}
return JpaManagementHelper.convertPage(softwareModuleMetadataRepository.findBySoftwareModuleIdAndTargetVisible(
PageRequest.of(0, RepositoryConstants.MAX_META_DATA_COUNT), id, true), pageable);
private void deleteGridFsArtifacts(final JpaSoftwareModule swModule) {
softwareModuleRepository.getAccessController().ifPresent(accessController ->
accessController.assertOperationAllowed(AccessController.Operation.DELETE, swModule));
for (final Artifact localArtifact : swModule.getArtifacts()) {
((JpaArtifactManagement) artifactManagement)
.clearArtifactBinary(localArtifact.getSha1Hash());
}
}
private Specification<JpaSoftwareModule> buildSmSearchQuerySpec(final String searchText) {
final String[] smFilterNameAndVersionEntries = JpaManagementHelper
.getFilterNameAndVersionEntries(searchText.trim());
return SoftwareModuleSpecification.likeNameAndVersion(smFilterNameAndVersionEntries[0],
smFilterNameAndVersionEntries[1]);
}
private SoftwareModuleMetadata saveMetadata(final JpaSoftwareModuleMetadataCreate create) {
assertSoftwareModuleMetadataDoesNotExist(create.getSoftwareModuleId(), create);
return softwareModuleMetadataRepository.save(create.build());
}
private void assertSoftwareModuleMetadataDoesNotExist(final Long id,
final JpaSoftwareModuleMetadataCreate md) {
if (softwareModuleMetadataRepository.existsById(new SwMetadataCompositeKey(id, md.getKey()))) {
throw new EntityAlreadyExistsException("Metadata entry with key '" + md.getKey() + "' already exists!");
}
}
/**
* Asserts the meta data quota for the software module with the given ID.
*
* @param id The software module ID.
* @param requested Number of meta data entries to be created.
*/
private void assertMetaDataQuota(final Long id, final int requested) {
final int maxMetaData = quotaManagement.getMaxMetaDataEntriesPerSoftwareModule();
QuotaHelper.assertAssignmentQuota(id, requested, maxMetaData, SoftwareModuleMetadata.class,
SoftwareModule.class, softwareModuleMetadataRepository::countBySoftwareModuleId);
}
private void assertSoftwareModuleExists(final Long id) {
@@ -566,9 +553,4 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement {
throw new EntityNotFoundException(DistributionSet.class, distributionSetId);
}
}
private static Specification<JpaSoftwareModuleMetadata> metadataBySoftwareModuleIdSpec(final long id) {
return (root, query, cb) -> cb
.equal(root.get(JpaSoftwareModuleMetadata_.softwareModule).get(JpaSoftwareModule_.id), id);
}
}

View File

@@ -44,7 +44,6 @@ import org.springframework.validation.annotation.Validated;
/**
* JPA implementation of {@link SoftwareModuleTypeManagement}.
*
*/
@Transactional(readOnly = true)
@Validated
@@ -68,42 +67,6 @@ public class JpaSoftwareModuleTypeManagement implements SoftwareModuleTypeManage
this.database = database;
}
@Override
@Transactional
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public SoftwareModuleType update(final SoftwareModuleTypeUpdate u) {
final GenericSoftwareModuleTypeUpdate update = (GenericSoftwareModuleTypeUpdate) u;
final JpaSoftwareModuleType type = (JpaSoftwareModuleType) get(update.getId())
.orElseThrow(() -> new EntityNotFoundException(SoftwareModuleType.class, update.getId()));
update.getDescription().ifPresent(type::setDescription);
update.getColour().ifPresent(type::setColour);
return softwareModuleTypeRepository.save(type);
}
@Override
public Page<SoftwareModuleType> findByRsql(final Pageable pageable, final String rsqlParam) {
return JpaManagementHelper.findAllWithCountBySpec(softwareModuleTypeRepository, pageable,
List.of(
RSQLUtility.buildRsqlSpecification(rsqlParam, SoftwareModuleTypeFields.class,
virtualPropertyReplacer, database),
SoftwareModuleTypeSpecification.isNotDeleted()));
}
@Override
public Slice<SoftwareModuleType> findAll(final Pageable pageable) {
return JpaManagementHelper.findAllWithoutCountBySpec(softwareModuleTypeRepository, pageable,
List.of(SoftwareModuleTypeSpecification.isNotDeleted()));
}
@Override
public long count() {
return softwareModuleTypeRepository.count(SoftwareModuleTypeSpecification.isNotDeleted());
}
@Override
public Optional<SoftwareModuleType> getByKey(final String key) {
return softwareModuleTypeRepository.findByKey(key);
@@ -114,6 +77,17 @@ public class JpaSoftwareModuleTypeManagement implements SoftwareModuleTypeManage
return softwareModuleTypeRepository.findByName(name);
}
@Override
@Transactional
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public List<SoftwareModuleType> create(final Collection<SoftwareModuleTypeCreate> c) {
final List<JpaSoftwareModuleType> creates = c.stream().map(JpaSoftwareModuleTypeCreate.class::cast)
.map(JpaSoftwareModuleTypeCreate::build).toList();
return Collections.unmodifiableList(
softwareModuleTypeRepository.saveAll(AccessController.Operation.CREATE, creates));
}
@Override
@Transactional
@Retryable(include = {
@@ -128,22 +102,32 @@ public class JpaSoftwareModuleTypeManagement implements SoftwareModuleTypeManage
@Transactional
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public void delete(final long id) {
final JpaSoftwareModuleType toDelete = softwareModuleTypeRepository.findById(id)
.orElseThrow(() -> new EntityNotFoundException(SoftwareModuleType.class, id));
public SoftwareModuleType update(final SoftwareModuleTypeUpdate u) {
final GenericSoftwareModuleTypeUpdate update = (GenericSoftwareModuleTypeUpdate) u;
delete(toDelete);
final JpaSoftwareModuleType type = (JpaSoftwareModuleType) get(update.getId())
.orElseThrow(() -> new EntityNotFoundException(SoftwareModuleType.class, update.getId()));
update.getDescription().ifPresent(type::setDescription);
update.getColour().ifPresent(type::setColour);
return softwareModuleTypeRepository.save(type);
}
@Override
public long count() {
return softwareModuleTypeRepository.count(SoftwareModuleTypeSpecification.isNotDeleted());
}
@Override
@Transactional
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public List<SoftwareModuleType> create(final Collection<SoftwareModuleTypeCreate> c) {
final List<JpaSoftwareModuleType> creates = c.stream().map(JpaSoftwareModuleTypeCreate.class::cast)
.map(JpaSoftwareModuleTypeCreate::build).toList();
return Collections.unmodifiableList(
softwareModuleTypeRepository.saveAll(AccessController.Operation.CREATE, creates));
public void delete(final long id) {
final JpaSoftwareModuleType toDelete = softwareModuleTypeRepository.findById(id)
.orElseThrow(() -> new EntityNotFoundException(SoftwareModuleType.class, id));
delete(toDelete);
}
@Override
@@ -161,14 +145,29 @@ public class JpaSoftwareModuleTypeManagement implements SoftwareModuleTypeManage
return Collections.unmodifiableList(softwareModuleTypeRepository.findAllById(ids));
}
@Override
public boolean exists(final long id) {
return softwareModuleTypeRepository.existsById(id);
}
@Override
public Optional<SoftwareModuleType> get(final long id) {
return softwareModuleTypeRepository.findById(id).map(SoftwareModuleType.class::cast);
}
@Override
public boolean exists(final long id) {
return softwareModuleTypeRepository.existsById(id);
public Slice<SoftwareModuleType> findAll(final Pageable pageable) {
return JpaManagementHelper.findAllWithoutCountBySpec(softwareModuleTypeRepository, pageable,
List.of(SoftwareModuleTypeSpecification.isNotDeleted()));
}
@Override
public Page<SoftwareModuleType> findByRsql(final Pageable pageable, final String rsqlParam) {
return JpaManagementHelper.findAllWithCountBySpec(softwareModuleTypeRepository, pageable,
List.of(
RSQLUtility.buildRsqlSpecification(rsqlParam, SoftwareModuleTypeFields.class,
virtualPropertyReplacer, database),
SoftwareModuleTypeSpecification.isNotDeleted()));
}
private void delete(JpaSoftwareModuleType toDelete) {

View File

@@ -74,81 +74,57 @@ import org.springframework.validation.annotation.Validated;
public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, SystemManagement {
private static final int MAX_TENANTS_QUERY = 1000;
@Autowired
private EntityManager entityManager;
@Autowired
private TargetRepository targetRepository;
@Autowired
private TargetFilterQueryRepository targetFilterQueryRepository;
@Autowired
private DistributionSetRepository distributionSetRepository;
@Autowired
private SoftwareModuleRepository softwareModuleRepository;
@Autowired
private TenantMetaDataRepository tenantMetaDataRepository;
@Autowired
private DistributionSetTypeRepository distributionSetTypeRepository;
@Autowired
private SoftwareModuleTypeRepository softwareModuleTypeRepository;
@Autowired
private TargetTagRepository targetTagRepository;
@Autowired
private TargetTypeRepository targetTypeRepository;
@Autowired
private DistributionSetTagRepository distributionSetTagRepository;
@Autowired
private TenantConfigurationRepository tenantConfigurationRepository;
@Autowired
private RolloutRepository rolloutRepository;
@Autowired
private TenantAware tenantAware;
@Autowired
private TenantStatsManagement systemStatsManagement;
@Autowired
private TenancyCacheManager cacheManager;
@Autowired
private SystemManagementCacheKeyGenerator currentTenantCacheKeyGenerator;
@Autowired
private SystemSecurityContext systemSecurityContext;
@Autowired
private PlatformTransactionManager txManager;
@Autowired
private RolloutStatusCache rolloutStatusCache;
@Autowired
private ArtifactRepository artifactRepository;
@Autowired
private RepositoryProperties repositoryProperties;
private final String countArtifactQuery;
private final String countSoftwareModulesQuery;
@Autowired
private EntityManager entityManager;
@Autowired
private TargetRepository targetRepository;
@Autowired
private TargetFilterQueryRepository targetFilterQueryRepository;
@Autowired
private DistributionSetRepository distributionSetRepository;
@Autowired
private SoftwareModuleRepository softwareModuleRepository;
@Autowired
private TenantMetaDataRepository tenantMetaDataRepository;
@Autowired
private DistributionSetTypeRepository distributionSetTypeRepository;
@Autowired
private SoftwareModuleTypeRepository softwareModuleTypeRepository;
@Autowired
private TargetTagRepository targetTagRepository;
@Autowired
private TargetTypeRepository targetTypeRepository;
@Autowired
private DistributionSetTagRepository distributionSetTagRepository;
@Autowired
private TenantConfigurationRepository tenantConfigurationRepository;
@Autowired
private RolloutRepository rolloutRepository;
@Autowired
private TenantAware tenantAware;
@Autowired
private TenantStatsManagement systemStatsManagement;
@Autowired
private TenancyCacheManager cacheManager;
@Autowired
private SystemManagementCacheKeyGenerator currentTenantCacheKeyGenerator;
@Autowired
private SystemSecurityContext systemSecurityContext;
@Autowired
private PlatformTransactionManager txManager;
@Autowired
private RolloutStatusCache rolloutStatusCache;
@Autowired
private ArtifactRepository artifactRepository;
@Autowired
private RepositoryProperties repositoryProperties;
/**
* Constructor.
*
* @param properties
* properties to get the underlying database
* @param properties properties to get the underlying database
*/
public JpaSystemManagement(final JpaProperties properties) {
@@ -159,48 +135,6 @@ public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, Syst
+ isDeleted;
}
@Override
public SystemUsageReport getSystemUsageStatistics() {
final Number count = (Number) entityManager.createNativeQuery(countSoftwareModulesQuery).getSingleResult();
long sumOfArtifacts = 0;
if (count != null) {
sumOfArtifacts = count.longValue();
}
// we use native queries to punch through the tenant boundaries. This
// has to be used with care!
final long targets = ((Number) entityManager.createNativeQuery("SELECT COUNT(id) FROM sp_target")
.getSingleResult()).longValue();
final long artifacts = ((Number) entityManager.createNativeQuery(countArtifactQuery).getSingleResult())
.longValue();
final long actions = ((Number) entityManager.createNativeQuery("SELECT COUNT(id) FROM sp_action")
.getSingleResult()).longValue();
return new SystemUsageReportWithTenants(targets, artifacts, actions, sumOfArtifacts,
tenantMetaDataRepository.count());
}
private static boolean isPostgreSql(final JpaProperties properties) {
return Database.POSTGRESQL == properties.getDatabase();
}
@Override
public SystemUsageReportWithTenants getSystemUsageStatisticsWithTenants() {
final SystemUsageReportWithTenants result = (SystemUsageReportWithTenants) getSystemUsageStatistics();
usageStatsPerTenant(result);
return result;
}
private void usageStatsPerTenant(final SystemUsageReportWithTenants report) {
forEachTenant(tenant -> report.addTenantData(systemStatsManagement.getStatsOfTenant()));
}
@Override
@Transactional(propagation = Propagation.SUPPORTS)
public KeyGenerator currentTenantKeyGenerator() {
@@ -208,18 +142,13 @@ public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, Syst
}
@Override
public TenantMetaData createTenantMetadata(final String tenant) {
final TenantMetaData result = tenantMetaDataRepository.findByTenantIgnoreCase(tenant);
// Create if it does not exist
if (result == null) {
return createTenantMetadata0(tenant);
}
return result;
}
@Override
public Page<String> findTenants(final Pageable pageable) {
return tenantMetaDataRepository.findTenants(pageable);
@Cacheable(value = "currentTenant", keyGenerator = "currentTenantKeyGenerator", cacheManager = "directCacheManager", unless = "#result == null")
public String currentTenant() {
return currentTenantCacheKeyGenerator.getTenantInCreation().orElseGet(() -> {
final TenantMetaData findByTenant = tenantMetaDataRepository
.findByTenantIgnoreCase(tenantAware.getCurrentTenant());
return findByTenant != null ? findByTenant.getTenant() : null;
});
}
@Override
@@ -249,6 +178,70 @@ public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, Syst
});
}
@Override
public Page<String> findTenants(final Pageable pageable) {
return tenantMetaDataRepository.findTenants(pageable);
}
@Override
@Transactional(propagation = Propagation.NOT_SUPPORTED)
// Exception squid:S2229 - calling findTenants without transaction is
// intended in this case
@SuppressWarnings("squid:S2229")
public void forEachTenant(final Consumer<String> consumer) {
Page<String> tenants;
Pageable query = PageRequest.of(0, MAX_TENANTS_QUERY);
do {
tenants = findTenants(query);
tenants.forEach(tenant -> tenantAware.runAsTenant(tenant, () -> {
try {
consumer.accept(tenant);
} catch (final RuntimeException ex) {
log.debug("Exception on forEachTenant execution for tenant {}. Continue with next tenant.",
tenant, ex);
log.error("Exception on forEachTenant execution for tenant {} with error message [{}]. "
+ "Continue with next tenant.", tenant, ex.getMessage());
}
return null;
}));
} while ((query = tenants.nextPageable()) != Pageable.unpaged());
}
@Override
public SystemUsageReportWithTenants getSystemUsageStatisticsWithTenants() {
final SystemUsageReportWithTenants result = (SystemUsageReportWithTenants) getSystemUsageStatistics();
usageStatsPerTenant(result);
return result;
}
@Override
public SystemUsageReport getSystemUsageStatistics() {
final Number count = (Number) entityManager.createNativeQuery(countSoftwareModulesQuery).getSingleResult();
long sumOfArtifacts = 0;
if (count != null) {
sumOfArtifacts = count.longValue();
}
// we use native queries to punch through the tenant boundaries. This
// has to be used with care!
final long targets = ((Number) entityManager.createNativeQuery("SELECT COUNT(id) FROM sp_target")
.getSingleResult()).longValue();
final long artifacts = ((Number) entityManager.createNativeQuery(countArtifactQuery).getSingleResult())
.longValue();
final long actions = ((Number) entityManager.createNativeQuery("SELECT COUNT(id) FROM sp_action")
.getSingleResult()).longValue();
return new SystemUsageReportWithTenants(targets, artifacts, actions, sumOfArtifacts,
tenantMetaDataRepository.count());
}
@Override
public TenantMetaData getTenantMetadata() {
final String tenant = tenantAware.getCurrentTenant();
@@ -270,13 +263,13 @@ public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, Syst
}
@Override
@Cacheable(value = "currentTenant", keyGenerator = "currentTenantKeyGenerator", cacheManager = "directCacheManager", unless = "#result == null")
public String currentTenant() {
return currentTenantCacheKeyGenerator.getTenantInCreation().orElseGet(() -> {
final TenantMetaData findByTenant = tenantMetaDataRepository
.findByTenantIgnoreCase(tenantAware.getCurrentTenant());
return findByTenant != null ? findByTenant.getTenant() : null;
});
public TenantMetaData createTenantMetadata(final String tenant) {
final TenantMetaData result = tenantMetaDataRepository.findByTenantIgnoreCase(tenant);
// Create if it does not exist
if (result == null) {
return createTenantMetadata0(tenant);
}
return result;
}
@Override
@@ -292,6 +285,20 @@ public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, Syst
return tenantMetaDataRepository.save(data);
}
@Override
public TenantMetaData getTenantMetadata(final long tenantId) {
return tenantMetaDataRepository.findById(tenantId)
.orElseThrow(() -> new EntityNotFoundException(TenantMetaData.class, tenantId));
}
private static boolean isPostgreSql(final JpaProperties properties) {
return Database.POSTGRESQL == properties.getDatabase();
}
private void usageStatsPerTenant(final SystemUsageReportWithTenants report) {
forEachTenant(tenant -> report.addTenantData(systemStatsManagement.getStatsOfTenant()));
}
private DistributionSetType createStandardSoftwareDataSetup() {
final SoftwareModuleType app = softwareModuleTypeRepository
.save(new JpaSoftwareModuleType(org.eclipse.hawkbit.repository.Constants.SMT_DEFAULT_APP_KEY,
@@ -321,37 +328,6 @@ public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, Syst
.addOptionalModuleType(app));
}
@Override
public TenantMetaData getTenantMetadata(final long tenantId) {
return tenantMetaDataRepository.findById(tenantId)
.orElseThrow(() -> new EntityNotFoundException(TenantMetaData.class, tenantId));
}
@Override
@Transactional(propagation = Propagation.NOT_SUPPORTED)
// Exception squid:S2229 - calling findTenants without transaction is
// intended in this case
@SuppressWarnings("squid:S2229")
public void forEachTenant(final Consumer<String> consumer) {
Page<String> tenants;
Pageable query = PageRequest.of(0, MAX_TENANTS_QUERY);
do {
tenants = findTenants(query);
tenants.forEach(tenant -> tenantAware.runAsTenant(tenant, () -> {
try {
consumer.accept(tenant);
} catch (final RuntimeException ex) {
log.debug("Exception on forEachTenant execution for tenant {}. Continue with next tenant.",
tenant, ex);
log.error("Exception on forEachTenant execution for tenant {} with error message [{}]. "
+ "Continue with next tenant.", tenant, ex.getMessage());
}
return null;
}));
} while ((query = tenants.nextPageable()) != Pageable.unpaged());
}
private TenantMetaData createTenantMetadata0(final String tenant) {
try {
currentTenantCacheKeyGenerator.setTenantInCreation(tenant);
@@ -371,8 +347,7 @@ public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, Syst
* {@link MultiTenantJpaTransactionManager} is called again and set the
* tenant for this transaction.
*
* @param tenant
* the tenant to be created
* @param tenant the tenant to be created
* @return the initial created {@link TenantMetaData}
*/
private TenantMetaData createInitialTenantMetaData(final String tenant) {

View File

@@ -16,7 +16,9 @@ import java.util.Optional;
import jakarta.validation.constraints.NotNull;
import cz.jirutka.rsql.parser.RSQLParserException;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.ContextAware;
import org.eclipse.hawkbit.repository.DistributionSetManagement;
import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.RepositoryProperties;
@@ -25,7 +27,6 @@ import org.eclipse.hawkbit.repository.TargetFilterQueryFields;
import org.eclipse.hawkbit.repository.TargetFilterQueryManagement;
import org.eclipse.hawkbit.repository.TargetManagement;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.ContextAware;
import org.eclipse.hawkbit.repository.builder.AutoAssignDistributionSetUpdate;
import org.eclipse.hawkbit.repository.builder.GenericTargetFilterQueryUpdate;
import org.eclipse.hawkbit.repository.builder.TargetFilterQueryCreate;
@@ -64,8 +65,6 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;
import org.springframework.validation.annotation.Validated;
import cz.jirutka.rsql.parser.RSQLParserException;
/**
* JPA implementation of {@link TargetFilterQueryManagement}.
*/
@@ -140,6 +139,17 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme
targetFilterQueryRepository.deleteById(targetFilterQueryId);
}
@Override
public boolean verifyTargetFilterQuerySyntax(final String query) {
try {
RSQLUtility.validateRsqlFor(query, TargetFields.class);
return true;
} catch (final RSQLParserException | RSQLParameterUnsupportedFieldException e) {
log.debug("The RSQL query '" + query + "' is invalid.", e);
return false;
}
}
@Override
public Slice<TargetFilterQuery> findAll(final Pageable pageable) {
return JpaManagementHelper.findAllWithoutCountBySpec(targetFilterQueryRepository, pageable, null);
@@ -225,13 +235,13 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme
}
@Override
public Optional<TargetFilterQuery> getByName(final String targetFilterQueryName) {
return targetFilterQueryRepository.findByName(targetFilterQueryName);
public Optional<TargetFilterQuery> get(final long targetFilterQueryId) {
return targetFilterQueryRepository.findById(targetFilterQueryId).map(TargetFilterQuery.class::cast);
}
@Override
public Optional<TargetFilterQuery> get(final long targetFilterQueryId) {
return targetFilterQueryRepository.findById(targetFilterQueryId).map(TargetFilterQuery.class::cast);
public Optional<TargetFilterQuery> getByName(final String targetFilterQueryName) {
return targetFilterQueryRepository.findByName(targetFilterQueryName);
}
@Override
@@ -279,7 +289,7 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme
final JpaDistributionSet distributionSet = (JpaDistributionSet) distributionSetManagement
.getValidAndComplete(update.getDsId());
if (((JpaDistributionSetManagement)distributionSetManagement).isImplicitLockApplicable(distributionSet)) {
if (((JpaDistributionSetManagement) distributionSetManagement).isImplicitLockApplicable(distributionSet)) {
distributionSetManagement.lock(distributionSet.getId());
}
@@ -298,9 +308,11 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme
return targetFilterQueryRepository.save(targetFilterQuery);
}
private boolean isConfirmationFlowEnabled() {
return TenantConfigHelper.usingContext(systemSecurityContext, tenantConfigurationManagement)
.isConfirmationFlowEnabled();
@Override
@Transactional
public void cancelAutoAssignmentForDistributionSet(final long distributionSetId) {
targetFilterQueryRepository.unsetAutoAssignDistributionSetAndActionTypeAndAccessContext(distributionSetId);
log.debug("Auto assignments for distribution sets {} deactivated", distributionSetId);
}
private static ActionType sanitizeAutoAssignActionType(final ActionType actionType) {
@@ -315,32 +327,19 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme
return actionType;
}
private boolean isConfirmationFlowEnabled() {
return TenantConfigHelper.usingContext(systemSecurityContext, tenantConfigurationManagement)
.isConfirmationFlowEnabled();
}
private JpaTargetFilterQuery findTargetFilterQueryOrThrowExceptionIfNotFound(final Long queryId) {
return targetFilterQueryRepository.findById(queryId)
.orElseThrow(() -> new EntityNotFoundException(TargetFilterQuery.class, queryId));
}
@Override
public boolean verifyTargetFilterQuerySyntax(final String query) {
try {
RSQLUtility.validateRsqlFor(query, TargetFields.class);
return true;
} catch (final RSQLParserException | RSQLParameterUnsupportedFieldException e) {
log.debug("The RSQL query '" + query + "' is invalid.", e);
return false;
}
}
private void assertMaxTargetsQuota(final String query, final String filterName, final long dsId) {
QuotaHelper.assertAssignmentQuota(filterName,
targetManagement.countByRsqlAndNonDSAndCompatibleAndUpdatable(dsId, query),
quotaManagement.getMaxTargetsPerAutoAssignment(), Target.class, TargetFilterQuery.class, null);
}
@Override
@Transactional
public void cancelAutoAssignmentForDistributionSet(final long distributionSetId) {
targetFilterQueryRepository.unsetAutoAssignDistributionSetAndActionTypeAndAccessContext(distributionSetId);
log.debug("Auto assignments for distribution sets {} deactivated", distributionSetId);
}
}

View File

@@ -41,7 +41,6 @@ import org.springframework.validation.annotation.Validated;
/**
* JPA implementation of {@link TargetTagManagement}.
*
*/
@Transactional(readOnly = true)
@Validated
@@ -62,8 +61,8 @@ public class JpaTargetTagManagement implements TargetTagManagement {
}
@Override
public Optional<TargetTag> getByName(final String name) {
return targetTagRepository.findByNameEquals(name);
public long count() {
return targetTagRepository.count();
}
@Override
@@ -98,6 +97,11 @@ public class JpaTargetTagManagement implements TargetTagManagement {
.orElseThrow(() -> new EntityNotFoundException(TargetTag.class, targetTagName)));
}
@Override
public Page<TargetTag> findAll(final Pageable pageable) {
return JpaManagementHelper.findAllWithCountBySpec(targetTagRepository, pageable, null);
}
@Override
public Page<TargetTag> findByRsql(final Pageable pageable, final String rsqlParam) {
return JpaManagementHelper.findAllWithCountBySpec(targetTagRepository, pageable, Collections.singletonList(
@@ -105,8 +109,18 @@ public class JpaTargetTagManagement implements TargetTagManagement {
}
@Override
public long count() {
return targetTagRepository.count();
public Optional<TargetTag> getByName(final String name) {
return targetTagRepository.findByNameEquals(name);
}
@Override
public Optional<TargetTag> get(final long id) {
return targetTagRepository.findById(id).map(TargetTag.class::cast);
}
@Override
public List<TargetTag> get(final Collection<Long> ids) {
return Collections.unmodifiableList(targetTagRepository.findAllById(ids));
}
@Override
@@ -125,19 +139,4 @@ public class JpaTargetTagManagement implements TargetTagManagement {
return targetTagRepository.save(tag);
}
@Override
public Optional<TargetTag> get(final long id) {
return targetTagRepository.findById(id).map(TargetTag.class::cast);
}
@Override
public List<TargetTag> get(final Collection<Long> ids) {
return Collections.unmodifiableList(targetTagRepository.findAllById(ids));
}
@Override
public Page<TargetTag> findAll(final Pageable pageable) {
return JpaManagementHelper.findAllWithCountBySpec(targetTagRepository, pageable, null);
}
}

View File

@@ -50,7 +50,6 @@ import org.springframework.validation.annotation.Validated;
/**
* JPA implementation of {@link TargetTypeManagement}.
*
*/
@Transactional(readOnly = true)
@Validated
@@ -68,14 +67,10 @@ public class JpaTargetTypeManagement implements TargetTypeManagement {
/**
* Constructor
*
* @param targetTypeRepository
* Target type repository
* @param targetRepository
* Target repository
* @param virtualPropertyReplacer
* replacer
* @param database
* database
* @param targetTypeRepository Target type repository
* @param targetRepository Target repository
* @param virtualPropertyReplacer replacer
* @param database database
*/
public JpaTargetTypeManagement(final TargetTypeRepository targetTypeRepository,
final TargetRepository targetRepository, final DistributionSetTypeRepository distributionSetTypeRepository,
@@ -152,7 +147,7 @@ public class JpaTargetTypeManagement implements TargetTypeManagement {
return JpaManagementHelper.findAllWithCountBySpec(targetTypeRepository, pageable,
List.of(
RSQLUtility.buildRsqlSpecification(
rsqlParam, TargetTypeFields.class, virtualPropertyReplacer,database)));
rsqlParam, TargetTypeFields.class, virtualPropertyReplacer, database)));
}
@Override
@@ -236,13 +231,9 @@ public class JpaTargetTypeManagement implements TargetTypeManagement {
* Enforces the quota specifying the maximum number of
* {@link DistributionSetType}s per {@link TargetType}.
*
* @param id
* of the target type
* @param requested
* number of distribution set types to check
*
* @throws AssignmentQuotaExceededException
* if the software module type quota is exceeded
* @param id of the target type
* @param requested number of distribution set types to check
* @throws AssignmentQuotaExceededException if the software module type quota is exceeded
*/
private void assertDistributionSetTypeQuota(final long id, final int requested) {
QuotaHelper.assertAssignmentQuota(id, requested, quotaManagement.getMaxDistributionSetTypesPerTargetType(),

View File

@@ -58,76 +58,18 @@ import org.springframework.validation.annotation.Validated;
@Validated
public class JpaTenantConfigurationManagement implements TenantConfigurationManagement {
private static final ConfigurableConversionService conversionService = new DefaultConversionService();
@Autowired
private TenantConfigurationRepository tenantConfigurationRepository;
@Autowired
private TenantConfigurationProperties tenantConfigurationProperties;
@Autowired
private ApplicationContext applicationContext;
@Autowired
private CacheManager cacheManager;
@Autowired
private AfterTransactionCommitExecutor afterCommitExecutor;
private static final ConfigurableConversionService conversionService = new DefaultConversionService();
@Override
@Cacheable(value = "tenantConfiguration", key = "#configurationKeyName")
public <T extends Serializable> TenantConfigurationValue<T> getConfigurationValue(final String configurationKeyName,
final Class<T> propertyType) {
checkAccess(configurationKeyName);
final TenantConfigurationKey configurationKey = tenantConfigurationProperties.fromKeyName(configurationKeyName);
validateTenantConfigurationDataType(configurationKey, propertyType);
final TenantConfiguration tenantConfiguration = tenantConfigurationRepository
.findByKey(configurationKey.getKeyName());
return buildTenantConfigurationValueByKey(configurationKey, propertyType, tenantConfiguration);
}
@Override
public <T extends Serializable> TenantConfigurationValue<T> getConfigurationValue(
final String configurationKeyName) {
checkAccess(configurationKeyName);
final TenantConfigurationKey configurationKey = tenantConfigurationProperties.fromKeyName(configurationKeyName);
return getConfigurationValue(configurationKeyName, (Class<T>)configurationKey.getDataType());
}
@Override
public <T> T getGlobalConfigurationValue(final String configurationKeyName, final Class<T> propertyType) {
checkAccess(configurationKeyName);
final TenantConfigurationKey key = tenantConfigurationProperties.fromKeyName(configurationKeyName);
if (!key.getDataType().isAssignableFrom(propertyType)) {
throw new TenantConfigurationValidatorException(String.format(
"Cannot parse the database value of type %s into the type %s.", key.getDataType(), propertyType));
}
return conversionService.convert(key.getDefaultValue(), propertyType);
}
private void checkAccess(final String configurationKeyName) {
if (TenantConfigurationProperties.TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY
.equalsIgnoreCase(configurationKeyName)) {
final SystemSecurityContext systemSecurityContext =
SystemSecurityContextHolder.getInstance().getSystemSecurityContext();
if (!systemSecurityContext.isCurrentThreadSystemCode() &&
!systemSecurityContext.hasPermission(SpPermission.READ_GATEWAY_SEC_TOKEN)) {
throw new InsufficientPermissionException(
"Can't read gateway security token! " + SpPermission.READ_GATEWAY_SEC_TOKEN + " is required!");
}
}
}
@Override
@CacheEvict(value = "tenantConfiguration", key = "#configurationKeyName")
@Transactional
@@ -154,6 +96,85 @@ public class JpaTenantConfigurationManagement implements TenantConfigurationMana
return addOrUpdateConfiguration0(configurations);
}
@Override
@CacheEvict(value = "tenantConfiguration", key = "#configurationKeyName")
@Transactional
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public void deleteConfiguration(final String configurationKeyName) {
tenantConfigurationRepository.deleteByKey(configurationKeyName);
}
@Override
public <T extends Serializable> TenantConfigurationValue<T> getConfigurationValue(
final String configurationKeyName) {
checkAccess(configurationKeyName);
final TenantConfigurationKey configurationKey = tenantConfigurationProperties.fromKeyName(configurationKeyName);
return getConfigurationValue(configurationKeyName, (Class<T>) configurationKey.getDataType());
}
@Override
@Cacheable(value = "tenantConfiguration", key = "#configurationKeyName")
public <T extends Serializable> TenantConfigurationValue<T> getConfigurationValue(final String configurationKeyName,
final Class<T> propertyType) {
checkAccess(configurationKeyName);
final TenantConfigurationKey configurationKey = tenantConfigurationProperties.fromKeyName(configurationKeyName);
validateTenantConfigurationDataType(configurationKey, propertyType);
final TenantConfiguration tenantConfiguration = tenantConfigurationRepository
.findByKey(configurationKey.getKeyName());
return buildTenantConfigurationValueByKey(configurationKey, propertyType, tenantConfiguration);
}
@Override
public <T> T getGlobalConfigurationValue(final String configurationKeyName, final Class<T> propertyType) {
checkAccess(configurationKeyName);
final TenantConfigurationKey key = tenantConfigurationProperties.fromKeyName(configurationKeyName);
if (!key.getDataType().isAssignableFrom(propertyType)) {
throw new TenantConfigurationValidatorException(String.format(
"Cannot parse the database value of type %s into the type %s.", key.getDataType(), propertyType));
}
return conversionService.convert(key.getDefaultValue(), propertyType);
}
/**
* Validates the data type of the tenant configuration. If it is possible to
* cast to the given data type.
*
* @param configurationKey the key
* @param propertyType the class
*/
private static <T> void validateTenantConfigurationDataType(final TenantConfigurationKey configurationKey,
final Class<T> propertyType) {
if (!configurationKey.getDataType().isAssignableFrom(propertyType)) {
throw new TenantConfigurationValidatorException(
String.format("Cannot parse the database value of type %s into the type %s.",
configurationKey.getDataType(), propertyType));
}
}
private void checkAccess(final String configurationKeyName) {
if (TenantConfigurationProperties.TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY
.equalsIgnoreCase(configurationKeyName)) {
final SystemSecurityContext systemSecurityContext =
SystemSecurityContextHolder.getInstance().getSystemSecurityContext();
if (!systemSecurityContext.isCurrentThreadSystemCode() &&
!systemSecurityContext.hasPermission(SpPermission.READ_GATEWAY_SEC_TOKEN)) {
throw new InsufficientPermissionException(
"Can't read gateway security token! " + SpPermission.READ_GATEWAY_SEC_TOKEN + " is required!");
}
}
}
private <T extends Serializable> Map<String, TenantConfigurationValue<T>> addOrUpdateConfiguration0(Map<String, T> configurations) {
List<JpaTenantConfiguration> configurationList = new ArrayList<>();
configurations.forEach((configurationKeyName, value) -> {
@@ -187,9 +208,9 @@ public class JpaTenantConfigurationManagement implements TenantConfigurationMana
JpaTenantConfiguration::getKey,
updatedTenantConfiguration -> {
@SuppressWarnings("unchecked")
final Class<T> clazzT = (Class<T>) configurations.get(updatedTenantConfiguration.getKey()).getClass();
return TenantConfigurationValue.<T>builder().global(false)
@SuppressWarnings("unchecked") final Class<T> clazzT = (Class<T>) configurations.get(updatedTenantConfiguration.getKey())
.getClass();
return TenantConfigurationValue.<T> builder().global(false)
.createdBy(updatedTenantConfiguration.getCreatedBy())
.createdAt(updatedTenantConfiguration.getCreatedAt())
.lastModifiedAt(updatedTenantConfiguration.getLastModifiedAt())
@@ -199,32 +220,6 @@ public class JpaTenantConfigurationManagement implements TenantConfigurationMana
}));
}
@Override
@CacheEvict(value = "tenantConfiguration", key = "#configurationKeyName")
@Transactional
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public void deleteConfiguration(final String configurationKeyName) {
tenantConfigurationRepository.deleteByKey(configurationKeyName);
}
/**
* Validates the data type of the tenant configuration. If it is possible to
* cast to the given data type.
*
* @param configurationKey the key
* @param propertyType the class
*/
private static <T> void validateTenantConfigurationDataType(final TenantConfigurationKey configurationKey,
final Class<T> propertyType) {
if (!configurationKey.getDataType().isAssignableFrom(propertyType)) {
throw new TenantConfigurationValidatorException(
String.format("Cannot parse the database value of type %s into the type %s.",
configurationKey.getDataType(), propertyType));
}
}
private <T extends Serializable> TenantConfigurationValue<T> buildTenantConfigurationValueByKey(
final TenantConfigurationKey configurationKey, final Class<T> propertyType,
final TenantConfiguration tenantConfiguration) {
@@ -248,13 +243,9 @@ public class JpaTenantConfigurationManagement implements TenantConfigurationMana
* Asserts that the requested configuration value change is allowed. Throws
* a {@link TenantConfigurationValueChangeNotAllowedException} otherwise.
*
* @param key
* The configuration key.
* @param valueChange
* The configuration to be validated.
*
* @throws TenantConfigurationValueChangeNotAllowedException
* if the requested configuration change is not allowed.
* @param key The configuration key.
* @param valueChange The configuration to be validated.
* @throws TenantConfigurationValueChangeNotAllowedException if the requested configuration change is not allowed.
*/
private void assertValueChangeIsAllowed(final String key, final JpaTenantConfiguration valueChange) {
assertMultiAssignmentsValueChange(key, valueChange);
@@ -280,7 +271,7 @@ public class JpaTenantConfigurationManagement implements TenantConfigurationMana
}
if (MULTI_ASSIGNMENTS_ENABLED.equals(key) && Boolean.parseBoolean(valueChange.getValue())) {
JpaTenantConfiguration batchConfig = tenantConfigurationRepository.findByKey(BATCH_ASSIGNMENTS_ENABLED);
if (batchConfig!=null && Boolean.parseBoolean(batchConfig.getValue())) {
if (batchConfig != null && Boolean.parseBoolean(batchConfig.getValue())) {
log.debug("The Multi-Assignments '{}' feature cannot be enabled as it contradicts with " +
"The Batch-Assignments feature, which is already enabled .", key);
throw new TenantConfigurationValueChangeNotAllowedException();

View File

@@ -22,7 +22,6 @@ import org.springframework.validation.annotation.Validated;
/**
* Management service for statistics of a single tenant.
*
*/
@Validated
public class JpaTenantStatsManagement implements TenantStatsManagement {

View File

@@ -44,7 +44,6 @@ import org.eclipse.hawkbit.repository.model.helper.EventPublisherHolder;
/**
* AbstractDsAssignmentStrategy for offline assignments, i.e. not managed by
* hawkBit.
*
*/
public class OfflineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
@@ -57,14 +56,6 @@ public class OfflineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
quotaManagement, multiAssignmentsConfig, confirmationFlowConfig, repositoryProperties);
}
@Override
public void sendTargetUpdatedEvents(final DistributionSet set, final List<JpaTarget> targets) {
targets.forEach(target -> {
target.setUpdateStatus(TargetUpdateStatus.IN_SYNC);
sendTargetUpdatedEvent(target);
});
}
@Override
public List<JpaTarget> findTargetsForAssignment(final List<String> controllerIDs, final long setId) {
final Function<List<String>, List<JpaTarget>> mapper;
@@ -80,13 +71,11 @@ public class OfflineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
}
@Override
public Set<Long> cancelActiveActions(final List<List<Long>> targetIds) {
return Collections.emptySet();
}
@Override
public void closeActiveActions(final List<List<Long>> targetIds) {
// Not supported by offline case
public void sendTargetUpdatedEvents(final DistributionSet set, final List<JpaTarget> targets) {
targets.forEach(target -> {
target.setUpdateStatus(TargetUpdateStatus.IN_SYNC);
sendTargetUpdatedEvent(target);
});
}
@Override
@@ -94,7 +83,8 @@ public class OfflineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
final JpaDistributionSet set, final List<List<Long>> targetIds, final String currentUser) {
final long now = System.currentTimeMillis();
targetIds.forEach(targetIdsChunk -> {
if (targetRepository.count(AccessController.Operation.UPDATE, targetRepository.byIdsSpec(targetIdsChunk)) != targetIdsChunk.size()) {
if (targetRepository.count(AccessController.Operation.UPDATE,
targetRepository.byIdsSpec(targetIdsChunk)) != targetIdsChunk.size()) {
throw new InsufficientPermissionException("No update access to all targets!");
}
targetRepository.setAssignedAndInstalledDistributionSetAndUpdateStatus(
@@ -116,6 +106,26 @@ public class OfflineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
});
}
@Override
public Set<Long> cancelActiveActions(final List<List<Long>> targetIds) {
return Collections.emptySet();
}
@Override
public void closeActiveActions(final List<List<Long>> targetIds) {
// Not supported by offline case
}
@Override
void sendDeploymentEvents(final DistributionSetAssignmentResult assignmentResult) {
// no need to send deployment events in the offline case
}
@Override
void sendDeploymentEvents(final List<DistributionSetAssignmentResult> assignmentResults) {
// no need to send deployment events in the offline case
}
@Override
public JpaAction createTargetAction(final String initiatedBy, final TargetWithActionType targetWithActionType,
final List<JpaTarget> targets, final JpaDistributionSet set) {
@@ -135,14 +145,4 @@ public class OfflineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
return result;
}
@Override
void sendDeploymentEvents(final DistributionSetAssignmentResult assignmentResult) {
// no need to send deployment events in the offline case
}
@Override
void sendDeploymentEvents(final List<DistributionSetAssignmentResult> assignmentResults) {
// no need to send deployment events in the offline case
}
}

View File

@@ -47,7 +47,6 @@ import org.springframework.util.CollectionUtils;
/**
* AbstractDsAssignmentStrategy for online assignments, i.e. managed by hawkBit.
*
*/
public class OnlineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
@@ -60,33 +59,6 @@ public class OnlineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
quotaManagement, multiAssignmentsConfig, confirmationFlowConfig, repositoryProperties);
}
@Override
public void sendTargetUpdatedEvents(final DistributionSet set, final List<JpaTarget> targets) {
targets.forEach(target -> {
target.setUpdateStatus(TargetUpdateStatus.PENDING);
sendTargetUpdatedEvent(target);
});
}
@Override
public void sendDeploymentEvents(final DistributionSetAssignmentResult assignmentResult) {
if (isMultiAssignmentsEnabled()) {
sendDeploymentEvents(Collections.singletonList(assignmentResult));
} else {
sendDistributionSetAssignedEvent(assignmentResult);
}
}
@Override
public void sendDeploymentEvents(final List<DistributionSetAssignmentResult> assignmentResults) {
if (isMultiAssignmentsEnabled()) {
sendDeploymentEvent(assignmentResults.stream().flatMap(result -> result.getAssignedEntity().stream())
.collect(Collectors.toList()));
} else {
assignmentResults.forEach(this::sendDistributionSetAssignedEvent);
}
}
public void sendDeploymentEvents(final long distributionSetId, final List<Action> actions) {
if (isMultiAssignmentsEnabled()) {
sendDeploymentEvent(actions);
@@ -114,14 +86,11 @@ public class OnlineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
}
@Override
public Set<Long> cancelActiveActions(final List<List<Long>> targetIds) {
return targetIds.stream().map(this::overrideObsoleteUpdateActions).flatMap(Collection::stream)
.collect(Collectors.toSet());
}
@Override
public void closeActiveActions(final List<List<Long>> targetIds) {
targetIds.forEach(this::closeObsoleteUpdateActions);
public void sendTargetUpdatedEvents(final DistributionSet set, final List<JpaTarget> targets) {
targets.forEach(target -> {
target.setUpdateStatus(TargetUpdateStatus.PENDING);
sendTargetUpdatedEvent(target);
});
}
@Override
@@ -129,7 +98,8 @@ public class OnlineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
final String currentUser) {
final long now = System.currentTimeMillis();
targetIds.forEach(targetIdsChunk -> {
if (targetRepository.count(AccessController.Operation.UPDATE, targetRepository.byIdsSpec(targetIdsChunk)) != targetIdsChunk.size()) {
if (targetRepository.count(AccessController.Operation.UPDATE,
targetRepository.byIdsSpec(targetIdsChunk)) != targetIdsChunk.size()) {
throw new InsufficientPermissionException("No update access to all targets!");
}
targetRepository.setAssignedDistributionSetAndUpdateStatus(TargetUpdateStatus.PENDING,
@@ -149,6 +119,36 @@ public class OnlineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
});
}
@Override
public Set<Long> cancelActiveActions(final List<List<Long>> targetIds) {
return targetIds.stream().map(this::overrideObsoleteUpdateActions).flatMap(Collection::stream)
.collect(Collectors.toSet());
}
@Override
public void closeActiveActions(final List<List<Long>> targetIds) {
targetIds.forEach(this::closeObsoleteUpdateActions);
}
@Override
public void sendDeploymentEvents(final DistributionSetAssignmentResult assignmentResult) {
if (isMultiAssignmentsEnabled()) {
sendDeploymentEvents(Collections.singletonList(assignmentResult));
} else {
sendDistributionSetAssignedEvent(assignmentResult);
}
}
@Override
public void sendDeploymentEvents(final List<DistributionSetAssignmentResult> assignmentResults) {
if (isMultiAssignmentsEnabled()) {
sendDeploymentEvent(assignmentResults.stream().flatMap(result -> result.getAssignedEntity().stream())
.collect(Collectors.toList()));
} else {
assignmentResults.forEach(this::sendDistributionSetAssignedEvent);
}
}
@Override
public JpaAction createTargetAction(final String initiatedBy, final TargetWithActionType targetWithActionType,
final List<JpaTarget> targets, final JpaDistributionSet set) {
@@ -187,6 +187,20 @@ public class OnlineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
}
}
private static Stream<Action> filterCancellations(final List<Action> actions) {
return actions.stream().filter(action -> {
final Status actionStatus = action.getStatus();
return Status.CANCELING != actionStatus && Status.CANCELED != actionStatus;
});
}
private static List<Action> getActionsWithoutCancellations(final List<Action> actions) {
if (actions == null || actions.isEmpty()) {
return Collections.emptyList();
}
return filterCancellations(actions).collect(Collectors.toList());
}
private void sendMultiActionCancelEvent(final Action action) {
sendMultiActionCancelEvent(action.getTenant(), Collections.singletonList(action));
}
@@ -224,10 +238,8 @@ public class OnlineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
* Helper to fire a {@link MultiActionCancelEvent}. This method may only be
* called if the Multi-Assignments feature is enabled.
*
* @param tenant
* the event is scoped to
* @param actions
* assigned to the targets
* @param tenant the event is scoped to
* @param actions assigned to the targets
*/
private void sendMultiActionCancelEvent(final String tenant, final List<Action> actions) {
afterCommit.afterCommit(() -> eventPublisherHolder.getEventPublisher()
@@ -238,28 +250,12 @@ public class OnlineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
* Helper to fire a {@link MultiActionAssignEvent}. This method may only be
* called if the Multi-Assignments feature is enabled.
*
* @param tenant
* the event is scoped to
* @param actions
* assigned to the targets
* @param tenant the event is scoped to
* @param actions assigned to the targets
*/
private void sendMultiActionAssignEvent(final String tenant, final List<Action> actions) {
afterCommit.afterCommit(() -> eventPublisherHolder.getEventPublisher()
.publishEvent(new MultiActionAssignEvent(tenant, eventPublisherHolder.getApplicationId(), actions)));
}
private static Stream<Action> filterCancellations(final List<Action> actions) {
return actions.stream().filter(action -> {
final Status actionStatus = action.getStatus();
return Status.CANCELING != actionStatus && Status.CANCELED != actionStatus;
});
}
private static List<Action> getActionsWithoutCancellations(final List<Action> actions) {
if (actions == null || actions.isEmpty()) {
return Collections.emptyList();
}
return filterCancellations(actions).collect(Collectors.toList());
}
}

View File

@@ -30,15 +30,14 @@ import org.springframework.security.core.context.SecurityContextHolder;
/**
* Holder of the base attributes common to all entities.
*
*/
@MappedSuperclass
@Access(AccessType.FIELD)
@EntityListeners({ AuditingEntityListener.class, EntityPropertyChangeListener.class, EntityInterceptorListener.class })
public abstract class AbstractJpaBaseEntity implements BaseEntity {
private static final long serialVersionUID = 1L;
protected static final int USERNAME_FIELD_LENGTH = 64;
protected static final int USERNAME_FIELD_LENGTH = 64;
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
@@ -88,6 +87,34 @@ public abstract class AbstractJpaBaseEntity implements BaseEntity {
return lastModifiedBy;
}
@LastModifiedBy
public void setLastModifiedBy(final String lastModifiedBy) {
if (isController()) {
return;
}
this.lastModifiedBy = lastModifiedBy;
}
@Override
public int getOptLockRevision() {
return optLockRevision;
}
public void setOptLockRevision(final int optLockRevision) {
this.optLockRevision = optLockRevision;
}
@LastModifiedDate
public void setLastModifiedAt(final long lastModifiedAt) {
if (isController()) {
return;
}
this.lastModifiedAt = lastModifiedAt;
}
@CreatedBy
public void setCreatedBy(final String createdBy) {
if (isController()) {
@@ -115,52 +142,11 @@ public abstract class AbstractJpaBaseEntity implements BaseEntity {
}
}
@LastModifiedDate
public void setLastModifiedAt(final long lastModifiedAt) {
if (isController()) {
return;
}
this.lastModifiedAt = lastModifiedAt;
}
@LastModifiedBy
public void setLastModifiedBy(final String lastModifiedBy) {
if (isController()) {
return;
}
this.lastModifiedBy = lastModifiedBy;
}
private boolean isController() {
return SecurityContextHolder.getContext().getAuthentication() != null
&& SecurityContextHolder.getContext().getAuthentication()
.getDetails() instanceof TenantAwareAuthenticationDetails
&& ((TenantAwareAuthenticationDetails) SecurityContextHolder.getContext().getAuthentication()
.getDetails()).isController();
}
@Override
public int getOptLockRevision() {
return optLockRevision;
}
public void setOptLockRevision(final int optLockRevision) {
this.optLockRevision = optLockRevision;
}
@Override
public Long getId() {
return id;
}
@Override
public String toString() {
return this.getClass().getSimpleName() + " [id=" + id + "]";
}
public void setId(final Long id) {
this.id = id;
}
@@ -213,4 +199,17 @@ public abstract class AbstractJpaBaseEntity implements BaseEntity {
return optLockRevision == other.optLockRevision;
}
@Override
public String toString() {
return this.getClass().getSimpleName() + " [id=" + id + "]";
}
private boolean isController() {
return SecurityContextHolder.getContext().getAuthentication() != null
&& SecurityContextHolder.getContext().getAuthentication()
.getDetails() instanceof TenantAwareAuthenticationDetails
&& ((TenantAwareAuthenticationDetails) SecurityContextHolder.getContext().getAuthentication()
.getDetails()).isController();
}
}

View File

@@ -22,10 +22,10 @@ import org.eclipse.hawkbit.repository.model.MetaData;
/**
* Meta data for entities.
*
*/
@MappedSuperclass
public abstract class AbstractJpaMetaData implements MetaData {
private static final long serialVersionUID = 1L;
@Id
@@ -66,6 +66,11 @@ public abstract class AbstractJpaMetaData implements MetaData {
this.value = value;
}
@Override
public int hashCode() {
return Objects.hash(key, value);
}
@Override
public boolean equals(final Object o) {
if (this == o)
@@ -75,9 +80,4 @@ public abstract class AbstractJpaMetaData implements MetaData {
final AbstractJpaMetaData that = (AbstractJpaMetaData) o;
return Objects.equals(key, that.key) && Objects.equals(value, that.value);
}
@Override
public int hashCode() {
return Objects.hash(key, value);
}
}

View File

@@ -9,6 +9,8 @@
*/
package org.eclipse.hawkbit.repository.jpa.model;
import java.io.Serial;
import jakarta.persistence.Column;
import jakarta.persistence.MappedSuperclass;
import jakarta.validation.constraints.NotNull;
@@ -17,8 +19,6 @@ import jakarta.validation.constraints.Size;
import org.eclipse.hawkbit.repository.model.NamedEntity;
import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity;
import java.io.Serial;
/**
* {@link TenantAwareBaseEntity} extension for all entities that are named in
* addition to their technical ID.
@@ -51,10 +51,8 @@ public abstract class AbstractJpaNamedEntity extends AbstractJpaTenantAwareBaseE
/**
* Parameterized constructor.
*
* @param name
* of the {@link NamedEntity}
* @param description
* of the {@link NamedEntity}
* @param name of the {@link NamedEntity}
* @param description of the {@link NamedEntity}
*/
AbstractJpaNamedEntity(final String name, final String description) {
this.name = name;
@@ -71,11 +69,11 @@ public abstract class AbstractJpaNamedEntity extends AbstractJpaTenantAwareBaseE
return name;
}
public void setDescription(final String description) {
this.description = description;
}
public void setName(final String name) {
this.name = name;
}
public void setDescription(final String description) {
this.description = description;
}
}

View File

@@ -19,13 +19,13 @@ import org.eclipse.hawkbit.repository.model.NamedVersionedEntity;
/**
* Extension for {@link NamedEntity} that are versioned.
*
*/
@MappedSuperclass
// exception squid:S2160 - BaseEntity equals/hashcode is handling correctly for
// sub entities
@SuppressWarnings("squid:S2160")
public abstract class AbstractJpaNamedVersionedEntity extends AbstractJpaNamedEntity implements NamedVersionedEntity {
private static final long serialVersionUID = 1L;
@Column(name = "version", nullable = false, length = NamedVersionedEntity.VERSION_MAX_SIZE)
@@ -36,10 +36,8 @@ public abstract class AbstractJpaNamedVersionedEntity extends AbstractJpaNamedEn
/**
* parameterized constructor.
*
* @param name
* of the entity
* @param version
* of the entity
* @param name of the entity
* @param version of the entity
* @param description
*/
AbstractJpaNamedVersionedEntity(final String name, final String version, final String description) {

View File

@@ -25,12 +25,12 @@ import org.eclipse.persistence.annotations.TenantDiscriminatorColumn;
/**
* Holder of the base attributes common to all tenant aware entities.
*
*/
@MappedSuperclass
@TenantDiscriminatorColumn(name = "tenant", length = 40)
@Multitenant(MultitenantType.SINGLE_TABLE)
public abstract class AbstractJpaTenantAwareBaseEntity extends AbstractJpaBaseEntity implements TenantAwareBaseEntity {
private static final long serialVersionUID = 1L;
@Column(name = "tenant", nullable = false, insertable = false, updatable = false, length = 40)
@@ -45,24 +45,6 @@ public abstract class AbstractJpaTenantAwareBaseEntity extends AbstractJpaBaseEn
// Default constructor needed for JPA entities.
}
/**
* PrePersist listener method for all {@link TenantAwareBaseEntity}
* entities.
*/
@PrePersist
void prePersist() {
// before persisting the entity check the current ID of the tenant by
// using the TenantAware
// service
final String currentTenant = SystemManagementHolder.getInstance().currentTenant();
if (currentTenant == null) {
throw new TenantNotExistException("Tenant "
+ TenantAwareHolder.getInstance().getTenantAware().getCurrentTenant()
+ " does not exists, cannot create entity " + this.getClass() + " with id " + super.getId());
}
setTenant(currentTenant.toUpperCase());
}
@Override
public String getTenant() {
return tenant;
@@ -72,11 +54,6 @@ public abstract class AbstractJpaTenantAwareBaseEntity extends AbstractJpaBaseEn
this.tenant = tenant;
}
@Override
public String toString() {
return "BaseEntity [id=" + super.getId() + "]";
}
/**
* Tenant aware entities extend the equals/hashcode strategy with the tenant
* name. That would allow for instance in a multi-schema based data
@@ -119,4 +96,27 @@ public abstract class AbstractJpaTenantAwareBaseEntity extends AbstractJpaBaseEn
return true;
}
@Override
public String toString() {
return "BaseEntity [id=" + super.getId() + "]";
}
/**
* PrePersist listener method for all {@link TenantAwareBaseEntity}
* entities.
*/
@PrePersist
void prePersist() {
// before persisting the entity check the current ID of the tenant by
// using the TenantAware
// service
final String currentTenant = SystemManagementHolder.getInstance().currentTenant();
if (currentTenant == null) {
throw new TenantNotExistException("Tenant "
+ TenantAwareHolder.getInstance().getTenantAware().getCurrentTenant()
+ " does not exists, cannot create entity " + this.getClass() + " with id " + super.getId());
}
setTenant(currentTenant.toUpperCase());
}
}

View File

@@ -9,14 +9,15 @@
*/
package org.eclipse.hawkbit.repository.jpa.model;
import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity;
import org.eclipse.hawkbit.repository.model.Type;
import java.io.Serial;
import jakarta.persistence.Column;
import jakarta.persistence.MappedSuperclass;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import java.io.Serial;
import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity;
import org.eclipse.hawkbit.repository.model.Type;
/**
* {@link TenantAwareBaseEntity} extension for all entities that are named in
@@ -49,10 +50,8 @@ public abstract class AbstractJpaTypeEntity extends AbstractJpaNamedEntity imple
/**
* Parameterized constructor.
*
* @param key
* of the {@link Type}
* @param colour
* of the {@link Type}
* @param key of the {@link Type}
* @param colour of the {@link Type}
*/
AbstractJpaTypeEntity(final String name, final String description, final String key, final String colour) {
super(name, description);
@@ -70,11 +69,11 @@ public abstract class AbstractJpaTypeEntity extends AbstractJpaNamedEntity imple
return colour;
}
public void setKey(final String key) {
this.key = key;
}
public void setColour(final String colour) {
this.colour = colour;
}
public void setKey(final String key) {
this.key = key;
}
}

View File

@@ -30,11 +30,11 @@ import org.eclipse.persistence.annotations.CascadeOnDelete;
/**
* Relation element between a {@link DistributionSetType} and its
* {@link SoftwareModuleType} elements.
*
*/
@Entity
@Table(name = "sp_ds_type_element")
public class DistributionSetTypeElement implements Serializable {
private static final long serialVersionUID = 1L;
@EmbeddedId
@@ -62,12 +62,9 @@ public class DistributionSetTypeElement implements Serializable {
/**
* Standard constructor.
*
* @param dsType
* of the element
* @param smType
* of the element
* @param mandatory
* to <code>true</code> if the {@link SoftwareModuleType} if
* @param dsType of the element
* @param smType of the element
* @param mandatory to <code>true</code> if the {@link SoftwareModuleType} if
* mandatory element in the {@link DistributionSet}.
*/
DistributionSetTypeElement(final JpaDistributionSetType dsType, final JpaSoftwareModuleType smType,
@@ -78,15 +75,15 @@ public class DistributionSetTypeElement implements Serializable {
this.mandatory = mandatory;
}
public boolean isMandatory() {
return mandatory;
}
DistributionSetTypeElement setMandatory(final boolean mandatory) {
this.mandatory = mandatory;
return this;
}
public boolean isMandatory() {
return mandatory;
}
public DistributionSetType getDsType() {
return dsType;
}
@@ -99,11 +96,6 @@ public class DistributionSetTypeElement implements Serializable {
return key;
}
@Override
public String toString() {
return "DistributionSetTypeElement [mandatory=" + mandatory + ", dsType=" + dsType + ", smType=" + smType + "]";
}
@Override
public int hashCode() {
final int prime = 31;
@@ -134,4 +126,9 @@ public class DistributionSetTypeElement implements Serializable {
return true;
}
@Override
public String toString() {
return "DistributionSetTypeElement [mandatory=" + mandatory + ", dsType=" + dsType + ", smType=" + smType + "]";
}
}

View File

@@ -19,6 +19,7 @@ import jakarta.persistence.Embeddable;
*/
@Embeddable
public class DistributionSetTypeElementCompositeKey implements Serializable {
private static final long serialVersionUID = 1L;
@Column(name = "distribution_set_type", nullable = false, updatable = false)
@@ -36,10 +37,8 @@ public class DistributionSetTypeElementCompositeKey implements Serializable {
/**
* Constructor.
*
* @param dsType
* in the key
* @param smType
* in the key
* @param dsType in the key
* @param smType in the key
*/
DistributionSetTypeElementCompositeKey(final JpaDistributionSetType dsType, final JpaSoftwareModuleType smType) {
this.dsType = dsType.getId();

View File

@@ -14,9 +14,9 @@ import java.io.Serializable;
/**
* The DistributionSet Metadata composite key which contains the meta data key
* and the ID of the DistributionSet itself.
*
*/
public final class DsMetadataCompositeKey implements Serializable {
private static final long serialVersionUID = 1L;
private String key;
@@ -28,10 +28,8 @@ public final class DsMetadataCompositeKey implements Serializable {
}
/**
* @param distributionSet
* the distribution set for this meta data
* @param key
* the key of the meta data
* @param distributionSet the distribution set for this meta data
* @param key the key of the meta data
*/
public DsMetadataCompositeKey(final Long distributionSet, final String key) {
this.distributionSet = distributionSet;

View File

@@ -31,8 +31,7 @@ public class EntityInterceptorListener {
/**
* Callback for lifecyle event <i>pre persist</i>.
*
* @param entity
* the JPA entity which this listener is associated with
* @param entity the JPA entity which this listener is associated with
*/
@PrePersist
public void prePersist(final Object entity) {
@@ -42,8 +41,7 @@ public class EntityInterceptorListener {
/**
* Callback for lifecyle event <i>post persist</i>.
*
* @param entity
* the JPA entity which this listener is associated with
* @param entity the JPA entity which this listener is associated with
*/
@PostPersist
public void postPersist(final Object entity) {
@@ -53,8 +51,7 @@ public class EntityInterceptorListener {
/**
* Callback for lifecyle event <i>post remove</i>.
*
* @param entity
* the JPA entity which this listener is associated with
* @param entity the JPA entity which this listener is associated with
*/
@PostRemove
public void postRemove(final Object entity) {
@@ -64,8 +61,7 @@ public class EntityInterceptorListener {
/**
* Callback for lifecyle event <i>pre remove</i>.
*
* @param entity
* the JPA entity which this listener is associated with
* @param entity the JPA entity which this listener is associated with
*/
@PreRemove
public void preRemove(final Object entity) {
@@ -75,8 +71,7 @@ public class EntityInterceptorListener {
/**
* Callback for lifecyle event <i>post load</i>.
*
* @param entity
* the JPA entity which this listener is associated with
* @param entity the JPA entity which this listener is associated with
*/
@PostLoad
public void postLoad(final Object entity) {
@@ -86,8 +81,7 @@ public class EntityInterceptorListener {
/**
* Callback for lifecyle event <i>pre update</i>.
*
* @param entity
* the JPA entity which this listener is associated with
* @param entity the JPA entity which this listener is associated with
*/
@PreUpdate
public void preUpdate(final Object entity) {
@@ -97,8 +91,7 @@ public class EntityInterceptorListener {
/**
* Callback for lifecyle event <i>post update</i>.
*
* @param entity
* the JPA entity which this listener is associated with
* @param entity the JPA entity which this listener is associated with
*/
@PostUpdate
public void postUpdate(final Object entity) {

View File

@@ -17,10 +17,17 @@ import org.eclipse.persistence.queries.UpdateObjectQuery;
/**
* Listens to change in property values of an entity and calls the corresponding
* {@link EventAwareEntity}.
*
*/
public class EntityPropertyChangeListener extends DescriptorEventAdapter {
@Override
public void postDelete(final DescriptorEvent event) {
final Object object = event.getObject();
if (isEventAwareEntity(object)) {
doNotifiy(() -> ((EventAwareEntity) object).fireDeleteEvent(event));
}
}
@Override
public void postInsert(final DescriptorEvent event) {
final Object object = event.getObject();
@@ -40,14 +47,6 @@ public class EntityPropertyChangeListener extends DescriptorEventAdapter {
}
@Override
public void postDelete(final DescriptorEvent event) {
final Object object = event.getObject();
if (isEventAwareEntity(object)) {
doNotifiy(() -> ((EventAwareEntity) object).fireDeleteEvent(event));
}
}
private static boolean isEventAwareEntity(final Object object) {
return object instanceof EventAwareEntity;
}

View File

@@ -67,6 +67,7 @@ import org.eclipse.persistence.descriptors.DescriptorEvent;
// sub entities
@SuppressWarnings("squid:S2160")
public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Action, EventAwareEntity {
private static final long serialVersionUID = 1L;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@@ -113,7 +114,7 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio
@ConversionValue(objectValue = "SCHEDULED", dataValue = "8"),
@ConversionValue(objectValue = "CANCEL_REJECTED", dataValue = "9"),
@ConversionValue(objectValue = "DOWNLOADED", dataValue = "10"),
@ConversionValue(objectValue = "WAIT_FOR_CONFIRMATION", dataValue = "11")})
@ConversionValue(objectValue = "WAIT_FOR_CONFIRMATION", dataValue = "11") })
@Convert("status")
@NotNull
private Status status;
@@ -157,10 +158,6 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio
this.distributionSet = (JpaDistributionSet) distributionSet;
}
public void setActive(final boolean active) {
this.active = active;
}
@Override
public Status getStatus() {
return status;
@@ -175,8 +172,8 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio
return active;
}
public void setActionType(final ActionType actionType) {
this.actionType = actionType;
public void setActive(final boolean active) {
this.active = active;
}
@Override
@@ -184,16 +181,8 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio
return actionType;
}
public List<ActionStatus> getActionStatus() {
if (actionStatus == null) {
return Collections.emptyList();
}
return Collections.unmodifiableList(actionStatus);
}
public void setTarget(final Target target) {
this.target = (JpaTarget) target;
public void setActionType(final ActionType actionType) {
this.actionType = actionType;
}
@Override
@@ -201,6 +190,10 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio
return target;
}
public void setTarget(final Target target) {
this.target = (JpaTarget) target;
}
@Override
public long getForcedTime() {
return forcedTime;
@@ -237,35 +230,6 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio
this.rollout = (JpaRollout) rollout;
}
@Override
public String toString() {
return "JpaAction [distributionSet=" + distributionSet.getId() + ", version=" + getOptLockRevision() + ", id="
+ getId() + ", actionType=" + getActionType() + ", weight=" + getWeight() + ", isActive=" + isActive()
+ ", createdAt=" + getCreatedAt() + ", lastModifiedAt=" + getLastModifiedAt() + ", status="
+ getStatus().name() + "]";
}
@Override
public void fireCreateEvent(final DescriptorEvent descriptorEvent) {
EventPublisherHolder.getInstance().getEventPublisher()
.publishEvent(new ActionCreatedEvent(this, BaseEntity.getIdOrNull(target),
BaseEntity.getIdOrNull(rollout), BaseEntity.getIdOrNull(rolloutGroup),
EventPublisherHolder.getInstance().getApplicationId()));
}
@Override
public void fireUpdateEvent(final DescriptorEvent descriptorEvent) {
EventPublisherHolder.getInstance().getEventPublisher()
.publishEvent(new ActionUpdatedEvent(this, BaseEntity.getIdOrNull(target),
BaseEntity.getIdOrNull(rollout), BaseEntity.getIdOrNull(rolloutGroup),
EventPublisherHolder.getInstance().getApplicationId()));
}
@Override
public void fireDeleteEvent(final DescriptorEvent descriptorEvent) {
// there is no action deletion
}
@Override
public String getMaintenanceWindowSchedule() {
return maintenanceWindowSchedule;
@@ -274,8 +238,7 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio
/**
* Sets the maintenance schedule.
*
* @param maintenanceWindowSchedule
* is a cron expression to be used for scheduling.
* @param maintenanceWindowSchedule is a cron expression to be used for scheduling.
*/
public void setMaintenanceWindowSchedule(final String maintenanceWindowSchedule) {
this.maintenanceWindowSchedule = maintenanceWindowSchedule;
@@ -289,8 +252,7 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio
/**
* Sets the maintenance window duration.
*
* @param maintenanceWindowDuration
* is the duration of an available maintenance schedule in
* @param maintenanceWindowDuration is the duration of an available maintenance schedule in
* HH:mm:ss format.
*/
public void setMaintenanceWindowDuration(final String maintenanceWindowDuration) {
@@ -305,8 +267,7 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio
/**
* Sets the time zone to be used for maintenance window.
*
* @param maintenanceWindowTimeZone
* is the time zone specified as +/-hh:mm offset from UTC for
* @param maintenanceWindowTimeZone is the time zone specified as +/-hh:mm offset from UTC for
* example +02:00 for CET summer time and +00:00 for UTC. The
* start time of a maintenance window calculated based on the
* cron expression is relative to this time zone.
@@ -315,24 +276,36 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio
this.maintenanceWindowTimeZone = maintenanceWindowTimeZone;
}
@Override
public String getExternalRef() {
return externalRef;
}
@Override
public void setExternalRef(final String externalRef) {
this.externalRef = externalRef;
}
@Override
public String getInitiatedBy() {
return initiatedBy;
}
public void setInitiatedBy(final String initiatedBy) {
this.initiatedBy = initiatedBy;
}
@Override
public Optional<Integer> getLastActionStatusCode() {
return Optional.ofNullable(lastActionStatusCode);
}
@Override
public Optional<ZonedDateTime> getMaintenanceWindowStartTime() {
return MaintenanceScheduleHelper.getNextMaintenanceWindow(maintenanceWindowSchedule, maintenanceWindowDuration,
maintenanceWindowTimeZone);
}
/**
* Returns the end time of next available or active maintenance window for
* the {@link Action} as {@link ZonedDateTime}. If a maintenance window is
* already active, the end time of currently active window is returned.
*
* @return the end time of window as { @link Optional<ZonedDateTime>}.
*/
private Optional<ZonedDateTime> getMaintenanceWindowEndTime() {
return getMaintenanceWindowStartTime()
.map(start -> start.plus(MaintenanceScheduleHelper.convertToISODuration(maintenanceWindowDuration)));
}
@Override
public boolean hasMaintenanceSchedule() {
return this.maintenanceWindowSchedule != null;
@@ -366,35 +339,60 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio
}
}
@Override
public void setExternalRef(final String externalRef) {
this.externalRef = externalRef;
}
@Override
public String getExternalRef() {
return externalRef;
}
public void setInitiatedBy(final String initiatedBy) {
this.initiatedBy = initiatedBy;
}
@Override
public String getInitiatedBy() {
return initiatedBy;
}
@Override
public Optional<Integer> getLastActionStatusCode() {
return Optional.ofNullable(lastActionStatusCode);
public boolean isWaitingConfirmation() {
return status == Status.WAIT_FOR_CONFIRMATION;
}
public void setLastActionStatusCode(final Integer lastActionStatusCode) {
this.lastActionStatusCode = lastActionStatusCode;
}
public boolean isWaitingConfirmation() {
return status == Status.WAIT_FOR_CONFIRMATION;
public List<ActionStatus> getActionStatus() {
if (actionStatus == null) {
return Collections.emptyList();
}
return Collections.unmodifiableList(actionStatus);
}
@Override
public String toString() {
return "JpaAction [distributionSet=" + distributionSet.getId() + ", version=" + getOptLockRevision() + ", id="
+ getId() + ", actionType=" + getActionType() + ", weight=" + getWeight() + ", isActive=" + isActive()
+ ", createdAt=" + getCreatedAt() + ", lastModifiedAt=" + getLastModifiedAt() + ", status="
+ getStatus().name() + "]";
}
@Override
public void fireCreateEvent(final DescriptorEvent descriptorEvent) {
EventPublisherHolder.getInstance().getEventPublisher()
.publishEvent(new ActionCreatedEvent(this, BaseEntity.getIdOrNull(target),
BaseEntity.getIdOrNull(rollout), BaseEntity.getIdOrNull(rolloutGroup),
EventPublisherHolder.getInstance().getApplicationId()));
}
@Override
public void fireUpdateEvent(final DescriptorEvent descriptorEvent) {
EventPublisherHolder.getInstance().getEventPublisher()
.publishEvent(new ActionUpdatedEvent(this, BaseEntity.getIdOrNull(target),
BaseEntity.getIdOrNull(rollout), BaseEntity.getIdOrNull(rolloutGroup),
EventPublisherHolder.getInstance().getApplicationId()));
}
@Override
public void fireDeleteEvent(final DescriptorEvent descriptorEvent) {
// there is no action deletion
}
/**
* Returns the end time of next available or active maintenance window for
* the {@link Action} as {@link ZonedDateTime}. If a maintenance window is
* already active, the end time of currently active window is returned.
*
* @return the end time of window as { @link Optional<ZonedDateTime>}.
*/
private Optional<ZonedDateTime> getMaintenanceWindowEndTime() {
return getMaintenanceWindowStartTime()
.map(start -> start.plus(MaintenanceScheduleHelper.convertToISODuration(maintenanceWindowDuration)));
}
}

View File

@@ -14,7 +14,6 @@ import java.util.Collections;
import java.util.List;
import java.util.Optional;
import jakarta.persistence.CollectionTable;
import jakarta.persistence.Column;
import jakarta.persistence.ConstraintMode;
@@ -50,6 +49,7 @@ import org.eclipse.persistence.annotations.ObjectTypeConverter;
// sub entities
@SuppressWarnings("squid:S2160")
public class JpaActionStatus extends AbstractJpaTenantAwareBaseEntity implements ActionStatus {
private static final int MESSAGE_ENTRY_LENGTH = 512;
private static final long serialVersionUID = 1L;
@@ -75,7 +75,7 @@ public class JpaActionStatus extends AbstractJpaTenantAwareBaseEntity implements
@ConversionValue(objectValue = "SCHEDULED", dataValue = "8"),
@ConversionValue(objectValue = "CANCEL_REJECTED", dataValue = "9"),
@ConversionValue(objectValue = "DOWNLOADED", dataValue = "10"),
@ConversionValue(objectValue = "WAIT_FOR_CONFIRMATION", dataValue = "11")})
@ConversionValue(objectValue = "WAIT_FOR_CONFIRMATION", dataValue = "11") })
@Convert("status")
@NotNull
private Status status;
@@ -93,12 +93,9 @@ public class JpaActionStatus extends AbstractJpaTenantAwareBaseEntity implements
/**
* Creates a new {@link ActionStatus} object.
*
* @param action
* the action for this action status
* @param status
* the status for this action status
* @param occurredAt
* the occurred timestamp
* @param action the action for this action status
* @param status the status for this action status
* @param occurredAt the occurred timestamp
*/
public JpaActionStatus(final Action action, final Status status, final long occurredAt) {
this.action = (JpaAction) action;
@@ -109,14 +106,10 @@ public class JpaActionStatus extends AbstractJpaTenantAwareBaseEntity implements
/**
* Creates a new {@link ActionStatus} object.
*
* @param action
* the action for this action status
* @param status
* the status for this action status
* @param occurredAt
* the occurred timestamp
* @param message
* the message which should be added to this action status
* @param action the action for this action status
* @param status the status for this action status
* @param occurredAt the occurred timestamp
* @param message the message which should be added to this action status
*/
public JpaActionStatus(final JpaAction action, final Status status, final long occurredAt, final String message) {
this.action = action;
@@ -128,10 +121,8 @@ public class JpaActionStatus extends AbstractJpaTenantAwareBaseEntity implements
/**
* Creates a new {@link ActionStatus} object.
*
* @param status
* the status for this action status
* @param occurredAt
* the occurred timestamp
* @param status the status for this action status
* @param occurredAt the occurred timestamp
*/
public JpaActionStatus(final Status status, final long occurredAt) {
this.status = status;
@@ -154,36 +145,6 @@ public class JpaActionStatus extends AbstractJpaTenantAwareBaseEntity implements
this.occurredAt = occurredAt;
}
public final void addMessage(final String message) {
if (message != null) {
if (messages == null) {
messages = new ArrayList<>((message.length() / MESSAGE_ENTRY_LENGTH) + 1);
}
if (message.length() > MESSAGE_ENTRY_LENGTH) {
// split
for (int off = 0; off < message.length();) {
final int end = off + MESSAGE_ENTRY_LENGTH;
if (end < message.length()) {
messages.add(message.substring(off, end));
} else {
messages.add(message.substring(off));
}
off = end;
}
} else {
messages.add(message);
}
}
}
public List<String> getMessages() {
if (messages == null) {
messages = Collections.emptyList();
}
return Collections.unmodifiableList(messages);
}
@Override
public Action getAction() {
return action;
@@ -209,4 +170,34 @@ public class JpaActionStatus extends AbstractJpaTenantAwareBaseEntity implements
public void setCode(final Integer code) {
this.code = code;
}
public final void addMessage(final String message) {
if (message != null) {
if (messages == null) {
messages = new ArrayList<>((message.length() / MESSAGE_ENTRY_LENGTH) + 1);
}
if (message.length() > MESSAGE_ENTRY_LENGTH) {
// split
for (int off = 0; off < message.length(); ) {
final int end = off + MESSAGE_ENTRY_LENGTH;
if (end < message.length()) {
messages.add(message.substring(off, end));
} else {
messages.add(message.substring(off));
}
off = end;
}
} else {
messages.add(message);
}
}
}
public List<String> getMessages() {
if (messages == null) {
messages = Collections.emptyList();
}
return Collections.unmodifiableList(messages);
}
}

View File

@@ -29,7 +29,6 @@ import org.eclipse.hawkbit.repository.model.SoftwareModule;
/**
* JPA implementation of {@link Artifact}.
*
*/
@Table(name = "sp_artifact", indexes = { @Index(name = "sp_idx_artifact_01", columnList = "tenant,software_module"),
@Index(name = "sp_idx_artifact_02", columnList = "tenant,sha1_hash"),
@@ -39,6 +38,7 @@ import org.eclipse.hawkbit.repository.model.SoftwareModule;
// sub entities
@SuppressWarnings("squid:S2160")
public class JpaArtifact extends AbstractJpaTenantAwareBaseEntity implements Artifact {
private static final long serialVersionUID = 1L;
@Column(name = "sha1_hash", length = 40, nullable = false, updatable = false)
@@ -74,12 +74,9 @@ public class JpaArtifact extends AbstractJpaTenantAwareBaseEntity implements Art
/**
* Constructs artifact.
*
* @param sha1Hash
* that is the link to the {@link AbstractDbArtifact} entity.
* @param filename
* that is used by {@link AbstractDbArtifact} store.
* @param softwareModule
* of this artifact
* @param sha1Hash that is the link to the {@link AbstractDbArtifact} entity.
* @param filename that is used by {@link AbstractDbArtifact} store.
* @param softwareModule of this artifact
*/
public JpaArtifact(@NotEmpty final String sha1Hash, @NotNull final String filename,
final SoftwareModule softwareModule) {
@@ -89,6 +86,16 @@ public class JpaArtifact extends AbstractJpaTenantAwareBaseEntity implements Art
this.softwareModule.addArtifact(this);
}
@Override
public String getFilename() {
return filename;
}
@Override
public SoftwareModule getSoftwareModule() {
return softwareModule;
}
@Override
public String getMd5Hash() {
return md5Hash;
@@ -104,19 +111,10 @@ public class JpaArtifact extends AbstractJpaTenantAwareBaseEntity implements Art
return sha256Hash;
}
public void setMd5Hash(final String md5Hash) {
this.md5Hash = md5Hash;
}
public void setSha1Hash(final String sha1Hash) {
this.sha1Hash = sha1Hash;
}
public void setSha256Hash(final String sha256Hash) {
this.sha256Hash = sha256Hash;
}
@Override
public long getSize() {
return size;
@@ -126,13 +124,11 @@ public class JpaArtifact extends AbstractJpaTenantAwareBaseEntity implements Art
this.size = size;
}
@Override
public SoftwareModule getSoftwareModule() {
return softwareModule;
public void setSha1Hash(final String sha1Hash) {
this.sha1Hash = sha1Hash;
}
@Override
public String getFilename() {
return filename;
public void setMd5Hash(final String md5Hash) {
this.md5Hash = md5Hash;
}
}

View File

@@ -9,11 +9,6 @@
*/
package org.eclipse.hawkbit.repository.jpa.model;
import org.eclipse.hawkbit.repository.model.AutoConfirmationStatus;
import org.eclipse.hawkbit.repository.model.NamedEntity;
import org.eclipse.hawkbit.repository.model.Target;
import org.springframework.util.StringUtils;
import jakarta.persistence.Column;
import jakarta.persistence.ConstraintMode;
import jakarta.persistence.Entity;
@@ -24,6 +19,11 @@ import jakarta.persistence.OneToOne;
import jakarta.persistence.Table;
import jakarta.validation.constraints.Size;
import org.eclipse.hawkbit.repository.model.AutoConfirmationStatus;
import org.eclipse.hawkbit.repository.model.NamedEntity;
import org.eclipse.hawkbit.repository.model.Target;
import org.springframework.util.StringUtils;
@Entity
@Table(name = "sp_target_conf_status")
public class JpaAutoConfirmationStatus extends AbstractJpaTenantAwareBaseEntity implements AutoConfirmationStatus {

View File

@@ -284,18 +284,6 @@ public class JpaDistributionSet extends AbstractJpaNamedVersionedEntity implemen
EventPublisherHolder.getInstance().getApplicationId()));
}
private void checkTypeCompatability(final SoftwareModule softwareModule) {
// we cannot allow that modules are added without a type defined
if (type == null) {
throw new DistributionSetTypeUndefinedException();
}
// check if it is allowed to such a module to this DS type
if (!type.containsModuleType(softwareModule.getType())) {
throw new UnsupportedSoftwareModuleForThisDistributionSetException();
}
}
private static void publishEventWithEventPublisher(final ApplicationEvent event) {
EventPublisherHolder.getInstance().getEventPublisher().publishEvent(event);
}
@@ -309,4 +297,16 @@ public class JpaDistributionSet extends AbstractJpaNamedVersionedEntity implemen
return changes.stream().anyMatch(changeRecord -> DELETED_PROPERTY.equals(changeRecord.getAttribute())
&& Boolean.parseBoolean(changeRecord.getNewValue().toString()));
}
private void checkTypeCompatability(final SoftwareModule softwareModule) {
// we cannot allow that modules are added without a type defined
if (type == null) {
throw new DistributionSetTypeUndefinedException();
}
// check if it is allowed to such a module to this DS type
if (!type.containsModuleType(softwareModule.getType())) {
throw new UnsupportedSoftwareModuleForThisDistributionSetException();
}
}
}

View File

@@ -24,12 +24,12 @@ import org.eclipse.hawkbit.repository.model.DistributionSetMetadata;
/**
* Meta data for {@link DistributionSet}.
*
*/
@IdClass(DsMetadataCompositeKey.class)
@Entity
@Table(name = "sp_ds_metadata")
public class JpaDistributionSetMetadata extends AbstractJpaMetaData implements DistributionSetMetadata {
private static final long serialVersionUID = 1L;
@Id
@@ -54,15 +54,15 @@ public class JpaDistributionSetMetadata extends AbstractJpaMetaData implements D
return new DsMetadataCompositeKey(distributionSet.getId(), getKey());
}
public void setDistributionSet(final DistributionSet distributionSet) {
this.distributionSet = (JpaDistributionSet) distributionSet;
}
@Override
public DistributionSet getDistributionSet() {
return distributionSet;
}
public void setDistributionSet(final DistributionSet distributionSet) {
this.distributionSet = (JpaDistributionSet) distributionSet;
}
@Override
public int hashCode() {
final int prime = 31;

View File

@@ -9,7 +9,6 @@
*/
package org.eclipse.hawkbit.repository.jpa.model;
import java.util.Collections;
import java.util.List;
import jakarta.persistence.Entity;
@@ -31,7 +30,6 @@ import org.eclipse.persistence.descriptors.DescriptorEvent;
/**
* A {@link DistributionSetTag} is used to describe DistributionSet attributes
* and use them also for filtering the DistributionSet list.
*
*/
@Entity
@Table(name = "sp_distributionset_tag", indexes = {
@@ -39,6 +37,7 @@ import org.eclipse.persistence.descriptors.DescriptorEvent;
@Index(name = "sp_idx_distribution_set_tag_01", columnList = "tenant,name") }, uniqueConstraints = @UniqueConstraint(columnNames = {
"name", "tenant" }, name = "uk_ds_tag"))
public class JpaDistributionSetTag extends JpaTag implements DistributionSetTag, EventAwareEntity {
private static final long serialVersionUID = 1L;
@CascadeOnDelete
@@ -48,12 +47,9 @@ public class JpaDistributionSetTag extends JpaTag implements DistributionSetTag,
/**
* Public constructor.
*
* @param name
* of the {@link DistributionSetTag}
* @param description
* of the {@link DistributionSetTag}
* @param colour
* of tag in UI
* @param name of the {@link DistributionSetTag}
* @param description of the {@link DistributionSetTag}
* @param colour of tag in UI
*/
public JpaDistributionSetTag(final String name, final String description, final String colour) {
super(name, description, colour);

View File

@@ -9,6 +9,24 @@
*/
package org.eclipse.hawkbit.repository.jpa.model;
import java.io.Serial;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.Index;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
import org.eclipse.hawkbit.repository.event.remote.DistributionSetTypeDeletedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetTypeCreatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetTypeUpdatedEvent;
@@ -22,27 +40,9 @@ import org.eclipse.persistence.annotations.CascadeOnDelete;
import org.eclipse.persistence.descriptors.DescriptorEvent;
import org.springframework.util.CollectionUtils;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.Index;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
import java.io.Serial;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
/**
* A distribution set type defines which software module types can or have to be
* {@link DistributionSet}.
*
*/
@Entity
@Table(name = "sp_distribution_set_type", indexes = {
@@ -77,12 +77,9 @@ public class JpaDistributionSetType extends AbstractJpaTypeEntity implements Dis
/**
* Standard constructor.
*
* @param key
* of the type (unique)
* @param name
* of the type (unique)
* @param description
* of the type
* @param key of the type (unique)
* @param name of the type (unique)
* @param description of the type
*/
public JpaDistributionSetType(final String key, final String name, final String description) {
this(key, name, description, null);
@@ -91,14 +88,10 @@ public class JpaDistributionSetType extends AbstractJpaTypeEntity implements Dis
/**
* Constructor.
*
* @param key
* of the type
* @param name
* of the type
* @param description
* of the type
* @param colour
* of the type. It will be null by default
* @param key of the type
* @param name of the type
* @param description of the type
* @param colour of the type. It will be null by default
*/
public JpaDistributionSetType(final String key, final String name, final String description, final String colour) {
super(name, description, key, colour);
@@ -144,15 +137,14 @@ public class JpaDistributionSetType extends AbstractJpaTypeEntity implements Dis
return new HashSet<>(((JpaDistributionSetType) dsType).elements).equals(elements);
}
private boolean isOneModuleListEmpty(final DistributionSetType dsType) {
return (!CollectionUtils.isEmpty(((JpaDistributionSetType) dsType).elements)
&& CollectionUtils.isEmpty(elements))
|| (CollectionUtils.isEmpty(((JpaDistributionSetType) dsType).elements)
&& !CollectionUtils.isEmpty(elements));
@Override
public boolean checkComplete(final DistributionSet distributionSet) {
final List<SoftwareModuleType> smTypes = distributionSet.getModules().stream().map(SoftwareModule::getType)
.distinct().collect(Collectors.toList());
if (smTypes.isEmpty()) {
return false;
}
private boolean areBothModuleListsEmpty(final DistributionSetType dsType) {
return CollectionUtils.isEmpty(((JpaDistributionSetType) dsType).elements) && CollectionUtils.isEmpty(elements);
return smTypes.containsAll(getMandatoryModuleTypes());
}
public JpaDistributionSetType addOptionalModuleType(final SoftwareModuleType smType) {
@@ -163,26 +155,6 @@ public class JpaDistributionSetType extends AbstractJpaTypeEntity implements Dis
return setModuleType(smType, true);
}
private JpaDistributionSetType setModuleType(final SoftwareModuleType smType, final boolean mandatory) {
if (elements == null) {
elements = new HashSet<>();
elements.add(new DistributionSetTypeElement(this, (JpaSoftwareModuleType) smType, mandatory));
return this;
}
// check if this was in the list before before
final Optional<DistributionSetTypeElement> existing = elements.stream()
.filter(element -> element.getSmType().getKey().equals(smType.getKey())).findAny();
if (existing.isPresent()) {
existing.get().setMandatory(mandatory);
} else {
elements.add(new DistributionSetTypeElement(this, (JpaSoftwareModuleType) smType, mandatory));
}
return this;
}
public JpaDistributionSetType removeModuleType(final Long smTypeId) {
if (elements == null) {
return this;
@@ -195,16 +167,6 @@ public class JpaDistributionSetType extends AbstractJpaTypeEntity implements Dis
return this;
}
@Override
public boolean checkComplete(final DistributionSet distributionSet) {
final List<SoftwareModuleType> smTypes = distributionSet.getModules().stream().map(SoftwareModule::getType)
.distinct().collect(Collectors.toList());
if (smTypes.isEmpty()) {
return false;
}
return smTypes.containsAll(getMandatoryModuleTypes());
}
public Set<DistributionSetTypeElement> getElements() {
if (elements == null) {
return Collections.emptySet();
@@ -235,4 +197,35 @@ public class JpaDistributionSetType extends AbstractJpaTypeEntity implements Dis
EventPublisherHolder.getInstance().getEventPublisher().publishEvent(new DistributionSetTypeDeletedEvent(
getTenant(), getId(), getClass(), EventPublisherHolder.getInstance().getApplicationId()));
}
private boolean isOneModuleListEmpty(final DistributionSetType dsType) {
return (!CollectionUtils.isEmpty(((JpaDistributionSetType) dsType).elements)
&& CollectionUtils.isEmpty(elements))
|| (CollectionUtils.isEmpty(((JpaDistributionSetType) dsType).elements)
&& !CollectionUtils.isEmpty(elements));
}
private boolean areBothModuleListsEmpty(final DistributionSetType dsType) {
return CollectionUtils.isEmpty(((JpaDistributionSetType) dsType).elements) && CollectionUtils.isEmpty(elements);
}
private JpaDistributionSetType setModuleType(final SoftwareModuleType smType, final boolean mandatory) {
if (elements == null) {
elements = new HashSet<>();
elements.add(new DistributionSetTypeElement(this, (JpaSoftwareModuleType) smType, mandatory));
return this;
}
// check if this was in the list before before
final Optional<DistributionSetTypeElement> existing = elements.stream()
.filter(element -> element.getSmType().getKey().equals(smType.getKey())).findAny();
if (existing.isPresent()) {
existing.get().setMandatory(mandatory);
} else {
elements.add(new DistributionSetTypeElement(this, (JpaSoftwareModuleType) smType, mandatory));
}
return this;
}
}

View File

@@ -52,7 +52,6 @@ import org.eclipse.persistence.sessions.changesets.ObjectChangeSet;
/**
* JPA implementation of a {@link Rollout}.
*
*/
@Entity
@Table(name = "sp_rollout", uniqueConstraints = @UniqueConstraint(columnNames = { "name",
@@ -149,6 +148,62 @@ public class JpaRollout extends AbstractJpaNamedEntity implements Rollout, Event
@Transient
private transient TotalTargetCountStatus totalTargetCountStatus;
public List<RolloutGroup> getRolloutGroups() {
if (rolloutGroups == null) {
return Collections.emptyList();
}
return Collections.unmodifiableList(rolloutGroups);
}
public long getLastCheck() {
return lastCheck;
}
public void setLastCheck(final long lastCheck) {
this.lastCheck = lastCheck;
}
// dynamic is null only for old rollouts - could be used for distinguishing
// old once from the other
public boolean isNewStyleTargetPercent() {
return dynamic != null;
}
@Override
public String toString() {
return "Rollout [ targetFilterQuery=" + targetFilterQuery + ", distributionSet=" + distributionSet + ", status="
+ status + ", lastCheck=" + lastCheck + ", getName()=" + getName() + ", getId()=" + getId() + "]";
}
@Override
public void fireCreateEvent(final DescriptorEvent descriptorEvent) {
EventPublisherHolder.getInstance().getEventPublisher()
.publishEvent(new RolloutCreatedEvent(this, EventPublisherHolder.getInstance().getApplicationId()));
}
@Override
public void fireUpdateEvent(final DescriptorEvent descriptorEvent) {
EventPublisherHolder.getInstance().getEventPublisher()
.publishEvent(new RolloutUpdatedEvent(this, EventPublisherHolder.getInstance().getApplicationId()));
if (isSoftDeleted(descriptorEvent)) {
EventPublisherHolder.getInstance().getEventPublisher().publishEvent(new RolloutDeletedEvent(getTenant(),
getId(), getClass(), EventPublisherHolder.getInstance().getApplicationId()));
}
}
@Override
public void fireDeleteEvent(final DescriptorEvent descriptorEvent) {
EventPublisherHolder.getInstance().getEventPublisher().publishEvent(new RolloutDeletedEvent(getTenant(),
getId(), getClass(), EventPublisherHolder.getInstance().getApplicationId()));
}
@Override
public boolean isDeleted() {
return deleted;
}
@Override
public DistributionSet getDistributionSet() {
return distributionSet;
@@ -158,14 +213,6 @@ public class JpaRollout extends AbstractJpaNamedEntity implements Rollout, Event
this.distributionSet = (JpaDistributionSet) distributionSet;
}
public List<RolloutGroup> getRolloutGroups() {
if (rolloutGroups == null) {
return Collections.emptyList();
}
return Collections.unmodifiableList(rolloutGroups);
}
@Override
public String getTargetFilterQuery() {
return targetFilterQuery;
@@ -184,23 +231,6 @@ public class JpaRollout extends AbstractJpaNamedEntity implements Rollout, Event
this.status = status;
}
public long getLastCheck() {
return lastCheck;
}
public void setLastCheck(final long lastCheck) {
this.lastCheck = lastCheck;
}
@Override
public Long getStartAt() {
return startAt;
}
public void setStartAt(final Long startAt) {
this.startAt = startAt;
}
@Override
public ActionType getActionType() {
return actionType;
@@ -215,40 +245,13 @@ public class JpaRollout extends AbstractJpaNamedEntity implements Rollout, Event
return forcedTime;
}
public void setForcedTime(final long forcedTime) {
this.forcedTime = forcedTime;
}
@Override
public Optional<Integer> getWeight() {
return Optional.ofNullable(weight);
public Long getStartAt() {
return startAt;
}
public void setWeight(final Integer weight) {
this.weight = weight;
}
@Override
public boolean isDynamic() {
return Boolean.TRUE.equals(dynamic);
}
public void setDynamic(final Boolean dynamic) {
this.dynamic = dynamic;
}
// dynamic is null only for old rollouts - could be used for distinguishing
// old once from the other
public boolean isNewStyleTargetPercent() {
return dynamic != null;
}
public Optional<String> getAccessControlContext() {
return Optional.ofNullable(accessControlContext);
}
public void setAccessControlContext(final String accessControlContext) {
this.accessControlContext = accessControlContext;
public void setStartAt(final Long startAt) {
this.startAt = startAt;
}
@Override
@@ -281,54 +284,6 @@ public class JpaRollout extends AbstractJpaNamedEntity implements Rollout, Event
this.totalTargetCountStatus = totalTargetCountStatus;
}
@Override
public String toString() {
return "Rollout [ targetFilterQuery=" + targetFilterQuery + ", distributionSet=" + distributionSet + ", status="
+ status + ", lastCheck=" + lastCheck + ", getName()=" + getName() + ", getId()=" + getId() + "]";
}
@Override
public void fireCreateEvent(final DescriptorEvent descriptorEvent) {
EventPublisherHolder.getInstance().getEventPublisher()
.publishEvent(new RolloutCreatedEvent(this, EventPublisherHolder.getInstance().getApplicationId()));
}
@Override
public void fireUpdateEvent(final DescriptorEvent descriptorEvent) {
EventPublisherHolder.getInstance().getEventPublisher()
.publishEvent(new RolloutUpdatedEvent(this, EventPublisherHolder.getInstance().getApplicationId()));
if (isSoftDeleted(descriptorEvent)) {
EventPublisherHolder.getInstance().getEventPublisher().publishEvent(new RolloutDeletedEvent(getTenant(),
getId(), getClass(), EventPublisherHolder.getInstance().getApplicationId()));
}
}
private static boolean isSoftDeleted(final DescriptorEvent event) {
final ObjectChangeSet changeSet = ((UpdateObjectQuery) event.getQuery()).getObjectChangeSet();
final List<DirectToFieldChangeRecord> changes = changeSet.getChanges().stream()
.filter(DirectToFieldChangeRecord.class::isInstance).map(DirectToFieldChangeRecord.class::cast)
.collect(Collectors.toList());
return changes.stream().anyMatch(changeRecord -> DELETED_PROPERTY.equals(changeRecord.getAttribute())
&& Boolean.parseBoolean(changeRecord.getNewValue().toString()));
}
@Override
public void fireDeleteEvent(final DescriptorEvent descriptorEvent) {
EventPublisherHolder.getInstance().getEventPublisher().publishEvent(new RolloutDeletedEvent(getTenant(),
getId(), getClass(), EventPublisherHolder.getInstance().getApplicationId()));
}
@Override
public boolean isDeleted() {
return deleted;
}
public void setDeleted(final boolean deleted) {
this.deleted = deleted;
}
@Override
public String getApprovalDecidedBy() {
return approvalDecidedBy;
@@ -343,7 +298,51 @@ public class JpaRollout extends AbstractJpaNamedEntity implements Rollout, Event
return approvalRemark;
}
@Override
public Optional<Integer> getWeight() {
return Optional.ofNullable(weight);
}
public void setWeight(final Integer weight) {
this.weight = weight;
}
@Override
public boolean isDynamic() {
return Boolean.TRUE.equals(dynamic);
}
public void setDynamic(final Boolean dynamic) {
this.dynamic = dynamic;
}
public Optional<String> getAccessControlContext() {
return Optional.ofNullable(accessControlContext);
}
public void setAccessControlContext(final String accessControlContext) {
this.accessControlContext = accessControlContext;
}
public void setApprovalRemark(final String approvalRemark) {
this.approvalRemark = approvalRemark;
}
public void setForcedTime(final long forcedTime) {
this.forcedTime = forcedTime;
}
public void setDeleted(final boolean deleted) {
this.deleted = deleted;
}
private static boolean isSoftDeleted(final DescriptorEvent event) {
final ObjectChangeSet changeSet = ((UpdateObjectQuery) event.getQuery()).getObjectChangeSet();
final List<DirectToFieldChangeRecord> changes = changeSet.getChanges().stream()
.filter(DirectToFieldChangeRecord.class::isInstance).map(DirectToFieldChangeRecord.class::cast)
.collect(Collectors.toList());
return changes.stream().anyMatch(changeRecord -> DELETED_PROPERTY.equals(changeRecord.getAttribute())
&& Boolean.parseBoolean(changeRecord.getNewValue().toString()));
}
}

View File

@@ -41,7 +41,6 @@ import org.eclipse.persistence.descriptors.DescriptorEvent;
/**
* JPA entity definition of persisting a group of an rollout.
*
*/
@Entity
@Table(name = "sp_rolloutgroup", uniqueConstraints = @UniqueConstraint(columnNames = { "name", "rollout",
@@ -146,14 +145,6 @@ public class JpaRolloutGroup extends AbstractJpaNamedEntity implements RolloutGr
this.status = status;
}
public List<RolloutTargetGroup> getRolloutTargetGroup() {
if (rolloutTargetGroup == null) {
return Collections.emptyList();
}
return Collections.unmodifiableList(rolloutTargetGroup);
}
@Override
public RolloutGroup getParent() {
return parent;
@@ -168,10 +159,6 @@ public class JpaRolloutGroup extends AbstractJpaNamedEntity implements RolloutGr
this.dynamic = dynamic;
}
public void setParent(final RolloutGroup parent) {
this.parent = (JpaRolloutGroup) parent;
}
@Override
public RolloutGroupSuccessCondition getSuccessCondition() {
return successCondition;
@@ -245,12 +232,15 @@ public class JpaRolloutGroup extends AbstractJpaNamedEntity implements RolloutGr
this.totalTargets = totalTargets;
}
public void setSuccessAction(final RolloutGroupSuccessAction successAction) {
this.successAction = successAction;
/**
* @return the totalTargetCountStatus
*/
@Override
public TotalTargetCountStatus getTotalTargetCountStatus() {
if (totalTargetCountStatus == null) {
totalTargetCountStatus = new TotalTargetCountStatus(Long.valueOf(totalTargets), rollout.getActionType());
}
public void setSuccessActionExp(final String successActionExp) {
this.successActionExp = successActionExp;
return totalTargetCountStatus;
}
@Override
@@ -267,38 +257,46 @@ public class JpaRolloutGroup extends AbstractJpaNamedEntity implements RolloutGr
return targetPercentage;
}
public void setConfirmationRequired(final boolean confirmationRequired) {
this.confirmationRequired = confirmationRequired;
}
@Override
public boolean isConfirmationRequired() {
return confirmationRequired;
}
public void setConfirmationRequired(final boolean confirmationRequired) {
this.confirmationRequired = confirmationRequired;
}
public void setTargetPercentage(final float targetPercentage) {
this.targetPercentage = targetPercentage;
}
/**
* @return the totalTargetCountStatus
*/
@Override
public TotalTargetCountStatus getTotalTargetCountStatus() {
if (totalTargetCountStatus == null) {
totalTargetCountStatus = new TotalTargetCountStatus(Long.valueOf(totalTargets), rollout.getActionType());
}
return totalTargetCountStatus;
}
/**
* @param totalTargetCountStatus
* the totalTargetCountStatus to set
* @param totalTargetCountStatus the totalTargetCountStatus to set
*/
public void setTotalTargetCountStatus(final TotalTargetCountStatus totalTargetCountStatus) {
this.totalTargetCountStatus = totalTargetCountStatus;
}
public void setSuccessActionExp(final String successActionExp) {
this.successActionExp = successActionExp;
}
public void setSuccessAction(final RolloutGroupSuccessAction successAction) {
this.successAction = successAction;
}
public void setParent(final RolloutGroup parent) {
this.parent = (JpaRolloutGroup) parent;
}
public List<RolloutTargetGroup> getRolloutTargetGroup() {
if (rolloutTargetGroup == null) {
return Collections.emptyList();
}
return Collections.unmodifiableList(rolloutTargetGroup);
}
@Override
public String toString() {
return "RolloutGroup [rollout=" + (rollout != null ? rollout.getId() : "") + ", status=" + status

View File

@@ -83,7 +83,8 @@ public class JpaSoftwareModule extends AbstractJpaNamedVersionedEntity implement
private JpaSoftwareModuleType type;
@CascadeOnDelete
@OneToMany(fetch = FetchType.LAZY, mappedBy = "softwareModule", cascade = { CascadeType.PERSIST }, targetEntity = JpaArtifact.class, orphanRemoval = true)
@OneToMany(fetch = FetchType.LAZY, mappedBy = "softwareModule", cascade = {
CascadeType.PERSIST }, targetEntity = JpaArtifact.class, orphanRemoval = true)
private List<JpaArtifact> artifacts;
@Setter
@@ -191,7 +192,8 @@ public class JpaSoftwareModule extends AbstractJpaNamedVersionedEntity implement
}
sb.delete(sb.length() - 2, sb.length());
throw new LockedException(JpaSoftwareModule.class, getId(), "DELETE", sb.toString());
};
}
;
}
this.deleted = deleted;
}

View File

@@ -25,12 +25,12 @@ import org.eclipse.hawkbit.repository.model.SoftwareModuleMetadata;
/**
* Metadata for {@link SoftwareModule}.
*
*/
@IdClass(SwMetadataCompositeKey.class)
@Entity
@Table(name = "sp_sw_metadata")
public class JpaSoftwareModuleMetadata extends AbstractJpaMetaData implements SoftwareModuleMetadata {
private static final long serialVersionUID = 1L;
@Id

View File

@@ -9,6 +9,8 @@
*/
package org.eclipse.hawkbit.repository.jpa.model;
import java.io.Serial;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Index;
@@ -23,11 +25,8 @@ import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
import org.eclipse.hawkbit.repository.model.helper.EventPublisherHolder;
import org.eclipse.persistence.descriptors.DescriptorEvent;
import java.io.Serial;
/**
* Type of a software modules.
*
*/
@Entity
@Table(name = "sp_software_module_type", indexes = {
@@ -53,14 +52,10 @@ public class JpaSoftwareModuleType extends AbstractJpaTypeEntity implements Soft
/**
* Constructor.
*
* @param key
* of the type
* @param name
* of the type
* @param description
* of the type
* @param maxAssignments
* assignments to a DS
* @param key of the type
* @param name of the type
* @param description of the type
* @param maxAssignments assignments to a DS
*/
public JpaSoftwareModuleType(final String key, final String name, final String description,
final int maxAssignments) {
@@ -70,16 +65,11 @@ public class JpaSoftwareModuleType extends AbstractJpaTypeEntity implements Soft
/**
* Constructor.
*
* @param key
* of the type
* @param name
* of the type
* @param description
* of the type
* @param maxAssignments
* assignments to a DS
* @param colour
* of the type. It will be null by default
* @param key of the type
* @param name of the type
* @param description of the type
* @param maxAssignments assignments to a DS
* @param colour of the type. It will be null by default
*/
public JpaSoftwareModuleType(final String key, final String name, final String description,
final int maxAssignments, final String colour) {
@@ -94,15 +84,15 @@ public class JpaSoftwareModuleType extends AbstractJpaTypeEntity implements Soft
// Default Constructor for JPA.
}
public void setMaxAssignments(final int maxAssignments) {
this.maxAssignments = maxAssignments;
}
@Override
public int getMaxAssignments() {
return maxAssignments;
}
public void setMaxAssignments(final int maxAssignments) {
this.maxAssignments = maxAssignments;
}
@Override
public boolean isDeleted() {
return deleted;

View File

@@ -10,7 +10,6 @@
package org.eclipse.hawkbit.repository.jpa.model;
import org.eclipse.hawkbit.repository.model.Statistic;
public interface JpaStatistic extends Statistic {

View File

@@ -18,13 +18,13 @@ import org.eclipse.hawkbit.repository.model.Tag;
/**
* A Tag can be used as describing and organizational meta information for any
* kind of entity.
*
*/
@MappedSuperclass
// exception squid:S2160 - BaseEntity equals/hashcode is handling correctly for
// sub entities
@SuppressWarnings("squid:S2160")
public class JpaTag extends AbstractJpaNamedEntity implements Tag {
private static final long serialVersionUID = 1L;
@Column(name = "colour", nullable = true, length = Tag.COLOUR_MAX_SIZE)
@@ -38,12 +38,9 @@ public class JpaTag extends AbstractJpaNamedEntity implements Tag {
/**
* Public constructor.
*
* @param name
* of the {@link Tag}
* @param description
* of the {@link Tag}
* @param colour
* of tag in UI
* @param name of the {@link Tag}
* @param description of the {@link Tag}
* @param colour of tag in UI
*/
public JpaTag(final String name, final String description, final String colour) {
super(name, description);

View File

@@ -218,91 +218,25 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw
return assignedDistributionSet;
}
/**
* @param assignedDistributionSet Distribution set
*/
public void setAssignedDistributionSet(final DistributionSet assignedDistributionSet) {
this.assignedDistributionSet = (JpaDistributionSet) assignedDistributionSet;
}
@Override
public String getControllerId() {
return controllerId;
}
/**
* @return tags
*/
public Set<TargetTag> getTags() {
if (tags == null) {
return Collections.emptySet();
}
return Collections.unmodifiableSet(tags);
}
/**
* @return rollouts target group
*/
public List<RolloutTargetGroup> getRolloutTargetGroup() {
if (rolloutTargetGroup == null) {
return Collections.emptyList();
}
return Collections.unmodifiableList(rolloutTargetGroup);
}
/**
* @param tag
* to be added
*/
public void addTag(final TargetTag tag) {
if (tags == null) {
tags = new HashSet<>();
}
tags.add(tag);
}
/**
* @param tag
* the tag to be removed from the target
*/
public void removeTag(final TargetTag tag) {
if (tags != null) {
tags.remove(tag);
}
}
/**
* @param assignedDistributionSet
* Distribution set
*/
public void setAssignedDistributionSet(final DistributionSet assignedDistributionSet) {
this.assignedDistributionSet = (JpaDistributionSet) assignedDistributionSet;
}
/**
* @param controllerId
* Controller ID
* @param controllerId Controller ID
*/
public void setControllerId(final String controllerId) {
this.controllerId = controllerId;
}
/**
* @return list of action
*/
public List<Action> getActions() {
if (actions == null) {
return Collections.emptyList();
}
return Collections.unmodifiableList(actions);
}
/**
* @param action
* Action
*/
public void addAction(final Action action) {
if (actions == null) {
actions = new ArrayList<>();
}
actions.add((JpaAction) action);
}
/**
* @return the securityToken if the current security context contains the
* necessary permission {@link SpPermission#READ_TARGET_SEC_TOKEN}
@@ -321,8 +255,7 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw
}
/**
* @param securityToken
* token value
* @param securityToken token value
*/
public void setSecurityToken(final String securityToken) {
this.securityToken = securityToken;
@@ -344,6 +277,26 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw
}
}
@Override
public Long getLastTargetQuery() {
return lastTargetQuery;
}
@Override
public Long getInstallationDate() {
return installationDate;
}
@Override
public TargetUpdateStatus getUpdateStatus() {
return updateStatus;
}
@Override
public TargetType getTargetType() {
return targetType;
}
/**
* @return the poll time which holds the last poll time of the target, the
* next poll time and the overdue time. In case the
@@ -370,33 +323,113 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw
}
@Override
public Long getLastTargetQuery() {
return lastTargetQuery;
}
@Override
public Long getInstallationDate() {
return installationDate;
}
@Override
public TargetUpdateStatus getUpdateStatus() {
return updateStatus;
}
@Override
public TargetType getTargetType() {
return targetType;
public boolean isRequestControllerAttributes() {
return requestControllerAttributes;
}
/**
* @param type
* Target type
* @param requestControllerAttributes Attributes
*/
public void setRequestControllerAttributes(final boolean requestControllerAttributes) {
this.requestControllerAttributes = requestControllerAttributes;
}
/**
* @param type Target type
*/
public void setTargetType(final TargetType type) {
this.targetType = type;
}
/**
* @param updateStatus Status
*/
public void setUpdateStatus(final TargetUpdateStatus updateStatus) {
this.updateStatus = updateStatus;
}
/**
* @param installationDate installation date
*/
public void setInstallationDate(final Long installationDate) {
this.installationDate = installationDate;
}
/**
* @param lastTargetQuery last query ID
*/
public void setLastTargetQuery(final Long lastTargetQuery) {
this.lastTargetQuery = lastTargetQuery;
}
/**
* @param address Address
*/
public void setAddress(final String address) {
this.address = address;
}
/**
* @return tags
*/
public Set<TargetTag> getTags() {
if (tags == null) {
return Collections.emptySet();
}
return Collections.unmodifiableSet(tags);
}
/**
* @return rollouts target group
*/
public List<RolloutTargetGroup> getRolloutTargetGroup() {
if (rolloutTargetGroup == null) {
return Collections.emptyList();
}
return Collections.unmodifiableList(rolloutTargetGroup);
}
/**
* @param tag to be added
*/
public void addTag(final TargetTag tag) {
if (tags == null) {
tags = new HashSet<>();
}
tags.add(tag);
}
/**
* @param tag the tag to be removed from the target
*/
public void removeTag(final TargetTag tag) {
if (tags != null) {
tags.remove(tag);
}
}
/**
* @return list of action
*/
public List<Action> getActions() {
if (actions == null) {
return Collections.emptyList();
}
return Collections.unmodifiableList(actions);
}
/**
* @param action Action
*/
public void addAction(final Action action) {
if (actions == null) {
actions = new ArrayList<>();
}
actions.add((JpaAction) action);
}
/**
* @return Distribution set
*/
@@ -404,6 +437,13 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw
return installedDistributionSet;
}
/**
* @param installedDistributionSet Distribution set
*/
public void setInstalledDistributionSet(final JpaDistributionSet installedDistributionSet) {
this.installedDistributionSet = installedDistributionSet;
}
/**
* @return controller attributes
*/
@@ -411,11 +451,6 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw
return controllerAttributes;
}
@Override
public boolean isRequestControllerAttributes() {
return requestControllerAttributes;
}
/**
* @return target metadata
*/
@@ -433,65 +468,12 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw
+ "]";
}
/**
* @param address
* Address
*/
public void setAddress(final String address) {
this.address = address;
}
/**
* @param lastTargetQuery
* last query ID
*/
public void setLastTargetQuery(final Long lastTargetQuery) {
this.lastTargetQuery = lastTargetQuery;
}
/**
* @param installationDate
* installation date
*/
public void setInstallationDate(final Long installationDate) {
this.installationDate = installationDate;
}
/**
* @param installedDistributionSet
* Distribution set
*/
public void setInstalledDistributionSet(final JpaDistributionSet installedDistributionSet) {
this.installedDistributionSet = installedDistributionSet;
}
/**
* @param updateStatus
* Status
*/
public void setUpdateStatus(final TargetUpdateStatus updateStatus) {
this.updateStatus = updateStatus;
}
/**
* @param requestControllerAttributes
* Attributes
*/
public void setRequestControllerAttributes(final boolean requestControllerAttributes) {
this.requestControllerAttributes = requestControllerAttributes;
}
@Override
public void fireCreateEvent(final DescriptorEvent descriptorEvent) {
EventPublisherHolder.getInstance().getEventPublisher()
.publishEvent(new TargetCreatedEvent(this, EventPublisherHolder.getInstance().getApplicationId()));
}
@Override
public List<String> getUpdateIgnoreFields() {
return TARGET_UPDATE_EVENT_IGNORE_FIELDS;
}
@Override
public void fireUpdateEvent(final DescriptorEvent descriptorEvent) {
EventPublisherHolder.getInstance().getEventPublisher()
@@ -504,4 +486,9 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw
.publishEvent(new TargetDeletedEvent(getTenant(), getId(), getControllerId(), address,
getClass(), EventPublisherHolder.getInstance().getApplicationId()));
}
@Override
public List<String> getUpdateIgnoreFields() {
return TARGET_UPDATE_EVENT_IGNORE_FIELDS;
}
}

View File

@@ -39,7 +39,6 @@ import org.eclipse.persistence.descriptors.DescriptorEvent;
/**
* Stored target filter.
*
*/
@Entity
@Table(name = "sp_target_filter_query", uniqueConstraints = @UniqueConstraint(columnNames = { "name",
@@ -49,6 +48,7 @@ import org.eclipse.persistence.descriptors.DescriptorEvent;
@SuppressWarnings("squid:S2160")
public class JpaTargetFilterQuery extends AbstractJpaTenantAwareBaseEntity
implements TargetFilterQuery, EventAwareEntity {
private static final long serialVersionUID = 1L;
@Column(name = "name", length = NamedEntity.NAME_MAX_SIZE, nullable = false)
@@ -94,18 +94,12 @@ public class JpaTargetFilterQuery extends AbstractJpaTenantAwareBaseEntity
/**
* Construct a Target filter query with auto assign distribution set
*
* @param name
* of the {@link TargetFilterQuery}.
* @param query
* of the {@link TargetFilterQuery}.
* @param autoAssignDistributionSet
* of the {@link TargetFilterQuery}.
* @param autoAssignActionType
* of the {@link TargetFilterQuery}.
* @param autoAssignWeight
* of the {@link TargetFilterQuery}.
* @param confirmationRequired
* of the {@link TargetFilterQuery}.
* @param name of the {@link TargetFilterQuery}.
* @param query of the {@link TargetFilterQuery}.
* @param autoAssignDistributionSet of the {@link TargetFilterQuery}.
* @param autoAssignActionType of the {@link TargetFilterQuery}.
* @param autoAssignWeight of the {@link TargetFilterQuery}.
* @param confirmationRequired of the {@link TargetFilterQuery}.
*/
public JpaTargetFilterQuery(final String name, final String query, final DistributionSet autoAssignDistributionSet,
final ActionType autoAssignActionType, final Integer autoAssignWeight, final boolean confirmationRequired) {

View File

@@ -24,12 +24,12 @@ import org.eclipse.hawkbit.repository.model.TargetMetadata;
/**
* Meta data for {@link Target}.
*
*/
@IdClass(TargetMetadataCompositeKey.class)
@Entity
@Table(name = "sp_target_metadata")
public class JpaTargetMetadata extends AbstractJpaMetaData implements TargetMetadata {
private static final long serialVersionUID = 1L;
@Id
@@ -44,10 +44,8 @@ public class JpaTargetMetadata extends AbstractJpaMetaData implements TargetMeta
/**
* Creates a single metadata entry with the given key and value.
*
* @param key
* of the meta data entry
* @param value
* of the meta data entry
* @param key of the meta data entry
* @param value of the meta data entry
*/
public JpaTargetMetadata(final String key, final String value) {
super(key, value);
@@ -57,12 +55,9 @@ public class JpaTargetMetadata extends AbstractJpaMetaData implements TargetMeta
* Creates a single metadata entry with the given key and value for the
* given {@link Target}.
*
* @param key
* of the meta data entry
* @param value
* of the meta data entry
* @param target
* the meta data entry is associated with
* @param key of the meta data entry
* @param value of the meta data entry
* @param target the meta data entry is associated with
*/
public JpaTargetMetadata(final String key, final String value, final Target target) {
super(key, value);
@@ -73,21 +68,13 @@ public class JpaTargetMetadata extends AbstractJpaMetaData implements TargetMeta
return new TargetMetadataCompositeKey(target.getId(), getKey());
}
public void setTarget(final Target target) {
this.target = (JpaTarget) target;
}
@Override
public Target getTarget() {
return target;
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + ((target == null) ? 0 : target.hashCode());
return result;
public void setTarget(final Target target) {
this.target = (JpaTarget) target;
}
@Override
@@ -107,4 +94,12 @@ public class JpaTargetMetadata extends AbstractJpaMetaData implements TargetMeta
}
return true;
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + ((target == null) ? 0 : target.hashCode());
return result;
}
}

Some files were not shown because too many files have changed in this diff Show More