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 # 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,155 +9,155 @@
SPDX-License-Identifier: EPL-2.0 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"> 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> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.eclipse.hawkbit</groupId> <groupId>org.eclipse.hawkbit</groupId>
<version>${revision}</version> <version>${revision}</version>
<artifactId>hawkbit-repository</artifactId> <artifactId>hawkbit-repository</artifactId>
</parent> </parent>
<artifactId>hawkbit-repository-jpa</artifactId> <artifactId>hawkbit-repository-jpa</artifactId>
<name>hawkBit :: Repository :: JPA Implementation</name> <name>hawkBit :: Repository :: JPA Implementation</name>
<properties> <properties>
<apt.source.dir>${project.build.directory}/generated-sources/apt/</apt.source.dir> <apt.source.dir>${project.build.directory}/generated-sources/apt/</apt.source.dir>
</properties> </properties>
<dependencies> <dependencies>
<!-- Hawkbit --> <!-- Hawkbit -->
<dependency> <dependency>
<groupId>org.eclipse.hawkbit</groupId> <groupId>org.eclipse.hawkbit</groupId>
<artifactId>hawkbit-repository-api</artifactId> <artifactId>hawkbit-repository-api</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.eclipse.hawkbit</groupId> <groupId>org.eclipse.hawkbit</groupId>
<artifactId>hawkbit-repository-core</artifactId> <artifactId>hawkbit-repository-core</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.eclipse.hawkbit</groupId> <groupId>org.eclipse.hawkbit</groupId>
<artifactId>hawkbit-artifact-repository-filesystem</artifactId> <artifactId>hawkbit-artifact-repository-filesystem</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework</groupId> <groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId> <artifactId>spring-core</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId> <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.eclipse.persistence</groupId> <groupId>org.eclipse.persistence</groupId>
<artifactId>org.eclipse.persistence.jpa</artifactId> <artifactId>org.eclipse.persistence.jpa</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework.security</groupId> <groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId> <artifactId>spring-security-core</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.flywaydb</groupId> <groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId> <artifactId>flyway-core</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.flywaydb</groupId> <groupId>org.flywaydb</groupId>
<artifactId>flyway-mysql</artifactId> <artifactId>flyway-mysql</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>cz.jirutka.rsql</groupId> <groupId>cz.jirutka.rsql</groupId>
<artifactId>rsql-parser</artifactId> <artifactId>rsql-parser</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.jsoup</groupId> <groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId> <artifactId>jsoup</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>javax.xml.bind</groupId> <groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId> <artifactId>jaxb-api</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.hibernate.validator</groupId> <groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId> <artifactId>hibernate-validator</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.commons</groupId> <groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId> <artifactId>commons-collections4</artifactId>
</dependency> </dependency>
<!-- Test --> <!-- Test -->
<dependency> <dependency>
<groupId>org.eclipse.hawkbit</groupId> <groupId>org.eclipse.hawkbit</groupId>
<artifactId>hawkbit-repository-test</artifactId> <artifactId>hawkbit-repository-test</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>javax.el</groupId> <groupId>javax.el</groupId>
<artifactId>javax.el-api</artifactId> <artifactId>javax.el-api</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>io.qameta.allure</groupId> <groupId>io.qameta.allure</groupId>
<artifactId>allure-junit5</artifactId> <artifactId>allure-junit5</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
</dependencies> </dependencies>
<build> <build>
<plugins> <plugins>
<!-- Static weaver for EclipseLink --> <!-- Static weaver for EclipseLink -->
<plugin> <plugin>
<groupId>com.ethlo.persistence.tools</groupId> <groupId>com.ethlo.persistence.tools</groupId>
<artifactId>eclipselink-maven-plugin</artifactId> <artifactId>eclipselink-maven-plugin</artifactId>
<executions> <executions>
<execution> <execution>
<id>modelgen</id> <id>modelgen</id>
<phase>generate-sources</phase> <phase>generate-sources</phase>
<goals> <goals>
<goal>modelgen</goal> <goal>modelgen</goal>
</goals> </goals>
</execution> </execution>
<execution> <execution>
<phase>process-classes</phase> <phase>process-classes</phase>
<goals> <goals>
<goal>weave</goal> <goal>weave</goal>
</goals> </goals>
</execution> </execution>
</executions> </executions>
<configuration> <configuration>
<basePackage>org.eclipse.hawkbit.repository.jpa.model</basePackage> <basePackage>org.eclipse.hawkbit.repository.jpa.model</basePackage>
</configuration> </configuration>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>org.eclipse.persistence</groupId> <groupId>org.eclipse.persistence</groupId>
<artifactId>org.eclipse.persistence.jpa</artifactId> <artifactId>org.eclipse.persistence.jpa</artifactId>
<version>${eclipselink.version}</version> <version>${eclipselink.version}</version>
</dependency> </dependency>
</dependencies> </dependencies>
</plugin> </plugin>
<plugin> <plugin>
<!-- Should be placed here below 'eclipselink-maven-plugin' as the plugin should be executed --> <!-- Should be placed here below 'eclipselink-maven-plugin' as the plugin should be executed -->
<!-- after the code generation and the order of the plugins in the pom is relevant for maven --> <!-- after the code generation and the order of the plugins in the pom is relevant for maven -->
<groupId>org.codehaus.mojo</groupId> <groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId> <artifactId>build-helper-maven-plugin</artifactId>
<executions> <executions>
<execution> <execution>
<id>add-source</id> <id>add-source</id>
<phase>generate-sources</phase> <phase>generate-sources</phase>
<goals> <goals>
<goal>add-source</goal> <goal>add-source</goal>
</goals> </goals>
<configuration> <configuration>
<sources> <sources>
<source>${apt.source.dir}</source> <source>${apt.source.dir}</source>
</sources> </sources>
</configuration> </configuration>
</execution> </execution>
</executions> </executions>
</plugin> </plugin>
</plugins> </plugins>
</build> </build>
</project> </project>

View File

@@ -19,13 +19,12 @@ import org.springframework.stereotype.Service;
* used by spring caching framework to resolve the key-generator. The * used by spring caching framework to resolve the key-generator. The
* key-generator must registered as bean so spring can resolve the key-generator * key-generator must registered as bean so spring can resolve the key-generator
* by its name. * by its name.
* *
* When using the {@link Service} annotation e.g. by {@link JpaSystemManagement} * When using the {@link Service} annotation e.g. by {@link JpaSystemManagement}
* the bean registration must be declared by the interface due spring registers * the bean registration must be declared by the interface due spring registers
* the bean by the implemented interfaces. So introduce a single interface for * the bean by the implemented interfaces. So introduce a single interface for
* the {@link JpaSystemManagement} implementation to allow it to register the * the {@link JpaSystemManagement} implementation to allow it to register the
* key-generator bean. * key-generator bean.
*
*/ */
@FunctionalInterface @FunctionalInterface
public interface CurrentTenantCacheKeyGenerator { public interface CurrentTenantCacheKeyGenerator {
@@ -33,7 +32,7 @@ public interface CurrentTenantCacheKeyGenerator {
/** /**
* Bean declaration to register a {@code currentTenantKeyGenerator} bean * Bean declaration to register a {@code currentTenantKeyGenerator} bean
* which is used by the caching framework. * which is used by the caching framework.
* *
* @return the {@link KeyGenerator} to be used to cache the values of the * @return the {@link KeyGenerator} to be used to cache the values of the
* current used tenant in the {@link JpaSystemManagement} * current used tenant in the {@link JpaSystemManagement}
*/ */

View File

@@ -21,7 +21,7 @@ import org.springframework.data.repository.core.support.RepositoryFactorySupport
* A {@link JpaRepositoryFactoryBean} extension that allow injection of custom * A {@link JpaRepositoryFactoryBean} extension that allow injection of custom
* repository factories by using a {@link BaseRepositoryTypeProvider} * repository factories by using a {@link BaseRepositoryTypeProvider}
* implementation, allows injecting different base repository implementations based on repository type * implementation, allows injecting different base repository implementations based on repository type
* *
* @param <T> * @param <T>
* @param <S> * @param <S>
* @param <ID> * @param <ID>
@@ -36,8 +36,7 @@ public class CustomBaseRepositoryFactoryBean<T extends Repository<S, ID>, S, ID>
* Creates a new {@link JpaRepositoryFactoryBean} for the given repository * Creates a new {@link JpaRepositoryFactoryBean} for the given repository
* interface. * interface.
* *
* @param repositoryInterface * @param repositoryInterface must not be {@literal null}.
* must not be {@literal null}.
*/ */
public CustomBaseRepositoryFactoryBean(final Class<? extends T> repositoryInterface) { public CustomBaseRepositoryFactoryBean(final Class<? extends T> repositoryInterface) {
super(repositoryInterface); super(repositoryInterface);

View File

@@ -57,6 +57,30 @@ public class DefaultRolloutApprovalStrategy implements RolloutApprovalStrategy {
return isApprovalEnabled() && hasNoApproveRolloutPermission(getActorAuthorities(rollout)); 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() { private boolean isApprovalEnabled() {
return systemSecurityContext.runAsSystem(() -> tenantConfigurationManagement return systemSecurityContext.runAsSystem(() -> tenantConfigurationManagement
.getConfigurationValue(TenantConfigurationKey.ROLLOUT_APPROVAL_ENABLED, Boolean.class).getValue()); .getConfigurationValue(TenantConfigurationKey.ROLLOUT_APPROVAL_ENABLED, Boolean.class).getValue());
@@ -78,28 +102,4 @@ public class DefaultRolloutApprovalStrategy implements RolloutApprovalStrategy {
return ((User) getCurrentAuthentication().getPrincipal()).getAuthorities().stream() return ((User) getCurrentAuthentication().getPrincipal()).getAuthorities().stream()
.map(GrantedAuthority::getAuthority).toList(); .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

@@ -27,63 +27,56 @@ public interface EntityInterceptor {
/** /**
* Callback for the {@link PrePersist} lifecycle event. * Callback for the {@link PrePersist} lifecycle event.
* *
* @param entity * @param entity the model entity
* the model entity
*/ */
default void prePersist(final Object entity) { default void prePersist(final Object entity) {
} }
/** /**
* Callback for the {@link PostPersist} lifecycle event. * Callback for the {@link PostPersist} lifecycle event.
* *
* @param entity * @param entity the model entity
* the model entity
*/ */
default void postPersist(final Object entity) { default void postPersist(final Object entity) {
} }
/** /**
* Callback for the {@link PostRemove} lifecycle event. * Callback for the {@link PostRemove} lifecycle event.
* *
* @param entity * @param entity the model entity
* the model entity
*/ */
default void postRemove(final Object entity) { default void postRemove(final Object entity) {
} }
/** /**
* Callback for the {@link PreRemove} lifecycle event. * Callback for the {@link PreRemove} lifecycle event.
* *
* @param entity * @param entity the model entity
* the model entity
*/ */
default void preRemove(final Object entity) { default void preRemove(final Object entity) {
} }
/** /**
* Callback for the {@link PostLoad} lifecycle event. * Callback for the {@link PostLoad} lifecycle event.
* *
* @param entity * @param entity the model entity
* the model entity
*/ */
default void postLoad(final Object entity) { default void postLoad(final Object entity) {
} }
/** /**
* Callback for the {@link PreUpdate} lifecycle event. * Callback for the {@link PreUpdate} lifecycle event.
* *
* @param entity * @param entity the model entity
* the model entity
*/ */
default void preUpdate(final Object entity) { default void preUpdate(final Object entity) {
} }
/** /**
* Callback for the {@link PostUpdate} lifecycle event. * Callback for the {@link PostUpdate} lifecycle event.
* *
* @param entity * @param entity the model entity
* the model entity
*/ */
default void postUpdate(final Object entity) { default void postUpdate(final Object entity) {
} }

View File

@@ -22,10 +22,10 @@ import org.springframework.orm.jpa.vendor.EclipseLinkJpaDialect;
/** /**
* {@link EclipseLinkJpaDialect} with additional exception translation * {@link EclipseLinkJpaDialect} with additional exception translation
* mechanisms based on {@link SQLStateSQLExceptionTranslator}. * mechanisms based on {@link SQLStateSQLExceptionTranslator}.
* *
* There are multiple variations of exceptions coming out of persistence * There are multiple variations of exceptions coming out of persistence
* provider: * provider:
* *
* <p> * <p>
* 1) {@link PersistenceException}s that can be mapped by * 1) {@link PersistenceException}s that can be mapped by
* {@link EclipseLinkJpaDialect} into corresponding {@link DataAccessException}. * {@link EclipseLinkJpaDialect} into corresponding {@link DataAccessException}.
@@ -47,9 +47,9 @@ import org.springframework.orm.jpa.vendor.EclipseLinkJpaDialect;
* <p> * <p>
* 3.b.) the cause is not an {@link SQLException} and as a result cannot be * 3.b.) the cause is not an {@link SQLException} and as a result cannot be
* mapped. * mapped.
*
*/ */
public class HawkBitEclipseLinkJpaDialect extends EclipseLinkJpaDialect { public class HawkBitEclipseLinkJpaDialect extends EclipseLinkJpaDialect {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private static final SQLStateSQLExceptionTranslator SQLSTATE_EXCEPTION_TRANSLATOR = new SQLStateSQLExceptionTranslator(); 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.JpaRolloutGroupBuilder;
import org.eclipse.hawkbit.repository.jpa.builder.JpaSoftwareModuleTypeBuilder; import org.eclipse.hawkbit.repository.jpa.builder.JpaSoftwareModuleTypeBuilder;
import org.eclipse.hawkbit.repository.jpa.builder.JpaTagBuilder; 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.JpaDistributionSetMetadata;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetMetadata; import org.eclipse.hawkbit.repository.jpa.model.JpaTargetMetadata;
import org.eclipse.hawkbit.repository.model.MetaData; import org.eclipse.hawkbit.repository.model.MetaData;
@@ -36,7 +35,6 @@ import org.springframework.validation.annotation.Validated;
/** /**
* JPA Implementation of {@link EntityFactory}. * JPA Implementation of {@link EntityFactory}.
*
*/ */
@Validated @Validated
public class JpaEntityFactory implements EntityFactory { public class JpaEntityFactory implements EntityFactory {
@@ -65,6 +63,16 @@ public class JpaEntityFactory implements EntityFactory {
@Autowired @Autowired
private TargetTypeBuilder targetTypeBuilder; private TargetTypeBuilder targetTypeBuilder;
@Override
public ActionStatusBuilder actionStatus() {
return new JpaActionStatusBuilder();
}
@Override
public DistributionSetBuilder distributionSet() {
return distributionSetBuilder;
}
@Override @Override
public MetaData generateDsMetadata(final String key, final String value) { public MetaData generateDsMetadata(final String key, final String value) {
return new JpaDistributionSetMetadata(key, StringUtils.trimWhitespace(value)); return new JpaDistributionSetMetadata(key, StringUtils.trimWhitespace(value));
@@ -76,23 +84,8 @@ public class JpaEntityFactory implements EntityFactory {
} }
@Override @Override
public DistributionSetTypeBuilder distributionSetType() { public SoftwareModuleMetadataBuilder softwareModuleMetadata() {
return distributionSetTypeBuilder; return softwareModuleMetadataBuilder;
}
@Override
public DistributionSetBuilder distributionSet() {
return distributionSetBuilder;
}
@Override
public TargetBuilder target() {
return targetBuilder;
}
@Override
public TargetTypeBuilder targetType() {
return targetTypeBuilder;
} }
@Override @Override
@@ -101,8 +94,18 @@ public class JpaEntityFactory implements EntityFactory {
} }
@Override @Override
public TargetFilterQueryBuilder targetFilterQuery() { public RolloutGroupBuilder rolloutGroup() {
return targetFilterQueryBuilder; return new JpaRolloutGroupBuilder();
}
@Override
public DistributionSetTypeBuilder distributionSetType() {
return distributionSetTypeBuilder;
}
@Override
public RolloutBuilder rollout() {
return rolloutBuilder;
} }
@Override @Override
@@ -116,23 +119,18 @@ public class JpaEntityFactory implements EntityFactory {
} }
@Override @Override
public ActionStatusBuilder actionStatus() { public TargetBuilder target() {
return new JpaActionStatusBuilder(); return targetBuilder;
} }
@Override @Override
public RolloutBuilder rollout() { public TargetTypeBuilder targetType() {
return rolloutBuilder; return targetTypeBuilder;
} }
@Override @Override
public RolloutGroupBuilder rolloutGroup() { public TargetFilterQueryBuilder targetFilterQuery() {
return new JpaRolloutGroupBuilder(); return targetFilterQueryBuilder;
}
@Override
public SoftwareModuleMetadataBuilder softwareModuleMetadata() {
return softwareModuleMetadataBuilder;
} }
} }

View File

@@ -15,10 +15,7 @@ import java.util.Optional;
import jakarta.persistence.EntityManager; 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.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.repository.NoCountSliceRepository;
import org.eclipse.hawkbit.repository.jpa.specifications.SpecificationsBuilder; import org.eclipse.hawkbit.repository.jpa.specifications.SpecificationsBuilder;
import org.springframework.data.domain.Page; 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 * A collection of static helper methods for the management classes
*/ */
public final class JpaManagementHelper { public final class JpaManagementHelper {
private JpaManagementHelper() { private JpaManagementHelper() {
} }

View File

@@ -9,6 +9,8 @@
*/ */
package org.eclipse.hawkbit.repository.jpa; 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.InvocationTargetException;
import java.lang.reflect.Proxy; import java.lang.reflect.Proxy;
import java.util.Arrays; import java.util.Arrays;
@@ -73,8 +75,6 @@ import org.springframework.data.domain.Sort.Direction;
import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionException; import org.springframework.transaction.TransactionException;
import static org.eclipse.hawkbit.repository.jpa.builder.JpaRolloutGroupCreate.addSuccessAndErrorConditionsAndActions;
/** /**
* A Jpa implementation of {@link RolloutExecutor} * 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, private static final List<Status> DOWNLOAD_ONLY_ACTION_TERMINATION_STATUSES = Arrays.asList(Status.ERROR,
Status.FINISHED, Status.CANCELED, Status.DOWNLOADED); Status.FINISHED, Status.CANCELED, Status.DOWNLOADED);
private static final Comparator<RolloutGroup> DESC_COMP = Comparator.comparingLong(RolloutGroup::getId).reversed();
private final ActionRepository actionRepository; private final ActionRepository actionRepository;
private final RolloutGroupRepository rolloutGroupRepository; private final RolloutGroupRepository rolloutGroupRepository;
private final RolloutTargetGroupRepository rolloutTargetGroupRepository; private final RolloutTargetGroupRepository rolloutTargetGroupRepository;
private final RolloutRepository rolloutRepository; private final RolloutRepository rolloutRepository;
private final TargetManagement targetManagement; private final TargetManagement targetManagement;
private final DeploymentManagement deploymentManagement; private final DeploymentManagement deploymentManagement;
private final RolloutGroupManagement rolloutGroupManagement; private final RolloutGroupManagement rolloutGroupManagement;
private final RolloutManagement rolloutManagement; private final RolloutManagement rolloutManagement;
private final QuotaManagement quotaManagement; private final QuotaManagement quotaManagement;
private final RolloutGroupEvaluationManager evaluationManager; private final RolloutGroupEvaluationManager evaluationManager;
private final RolloutApprovalStrategy rolloutApprovalStrategy; private final RolloutApprovalStrategy rolloutApprovalStrategy;
private final EntityManager entityManager; private final EntityManager entityManager;
private final PlatformTransactionManager txManager; private final PlatformTransactionManager txManager;
private final AfterTransactionCommitExecutor afterCommit; private final AfterTransactionCommitExecutor afterCommit;
private final EventPublisherHolder eventPublisherHolder; private final EventPublisherHolder eventPublisherHolder;
private final TenantAware tenantAware; private final TenantAware tenantAware;
private final RepositoryProperties repositoryProperties; private final RepositoryProperties repositoryProperties;
private final Map<Long, AtomicLong> lastDynamicGroupFill = new ConcurrentHashMap<>();
public JpaRolloutExecutor( public JpaRolloutExecutor(
final ActionRepository actionRepository, final RolloutGroupRepository rolloutGroupRepository, final ActionRepository actionRepository, final RolloutGroupRepository rolloutGroupRepository,
@@ -158,48 +155,48 @@ public class JpaRolloutExecutor implements RolloutExecutor {
log.debug("Processing rollout {}", rollout.getId()); log.debug("Processing rollout {}", rollout.getId());
switch (rollout.getStatus()) { switch (rollout.getStatus()) {
case CREATING: case CREATING:
handleCreateRollout((JpaRollout) rollout); handleCreateRollout((JpaRollout) rollout);
break; break;
case READY: case READY:
handleReadyRollout(rollout); handleReadyRollout(rollout);
break; break;
case STARTING: case STARTING:
// the lastModifiedBy user is probably the user that has actually called the rollout start (unless overridden) - not the creator // the lastModifiedBy user is probably the user that has actually called the rollout start (unless overridden) - not the creator
SpringSecurityAuditorAware.setAuditorOverride(rollout.getLastModifiedBy()); SpringSecurityAuditorAware.setAuditorOverride(rollout.getLastModifiedBy());
try { try {
handleStartingRollout(rollout); handleStartingRollout(rollout);
} finally { } finally {
// clear, ALWAYS, the set auditor override // clear, ALWAYS, the set auditor override
SpringSecurityAuditorAware.clearAuditorOverride(); SpringSecurityAuditorAware.clearAuditorOverride();
} }
break; break;
case RUNNING: case RUNNING:
handleRunningRollout((JpaRollout) rollout); handleRunningRollout((JpaRollout) rollout);
break; break;
case STOPPING: case STOPPING:
// the lastModifiedBy user is probably the user that has actually called the rollout stop (unless overridden) - not the creator // the lastModifiedBy user is probably the user that has actually called the rollout stop (unless overridden) - not the creator
SpringSecurityAuditorAware.setAuditorOverride(rollout.getLastModifiedBy()); SpringSecurityAuditorAware.setAuditorOverride(rollout.getLastModifiedBy());
try { try {
handleStopRollout((JpaRollout) rollout); handleStopRollout((JpaRollout) rollout);
} finally { } finally {
// clear, ALWAYS, the set auditor override // clear, ALWAYS, the set auditor override
SpringSecurityAuditorAware.clearAuditorOverride(); SpringSecurityAuditorAware.clearAuditorOverride();
} }
break; break;
case DELETING: case DELETING:
// the lastModifiedBy user is probably the user that has actually called the rollout delete (unless overridden) - not the creator // the lastModifiedBy user is probably the user that has actually called the rollout delete (unless overridden) - not the creator
SpringSecurityAuditorAware.setAuditorOverride(rollout.getLastModifiedBy()); SpringSecurityAuditorAware.setAuditorOverride(rollout.getLastModifiedBy());
try { try {
handleDeleteRollout((JpaRollout) rollout); handleDeleteRollout((JpaRollout) rollout);
} finally { } finally {
// clear, ALWAYS, the set auditor override // clear, ALWAYS, the set auditor override
SpringSecurityAuditorAware.clearAuditorOverride(); SpringSecurityAuditorAware.clearAuditorOverride();
} }
break; break;
default: default:
log.error("Rollout in status {} not supposed to be handled!", rollout.getStatus()); log.error("Rollout in status {} not supposed to be handled!", rollout.getStatus());
break; break;
} }
log.debug("Rollout {} processed", rollout.getId()); log.debug("Rollout {} processed", rollout.getId());
@@ -312,9 +309,9 @@ public class JpaRolloutExecutor implements RolloutExecutor {
rolloutGroupRepository.findByRolloutAndStatusNotIn(rollout, rolloutGroupRepository.findByRolloutAndStatusNotIn(rollout,
Arrays.asList(RolloutGroupStatus.FINISHED, RolloutGroupStatus.ERROR)).forEach(rolloutGroup -> { Arrays.asList(RolloutGroupStatus.FINISHED, RolloutGroupStatus.ERROR)).forEach(rolloutGroup -> {
rolloutGroup.setStatus(RolloutGroupStatus.FINISHED); rolloutGroup.setStatus(RolloutGroupStatus.FINISHED);
rolloutGroupRepository.save(rolloutGroup); rolloutGroupRepository.save(rolloutGroup);
}); });
rollout.setStatus(RolloutStatus.FINISHED); rollout.setStatus(RolloutStatus.FINISHED);
rolloutRepository.save(rollout); rolloutRepository.save(rollout);
@@ -417,7 +414,6 @@ public class JpaRolloutExecutor implements RolloutExecutor {
return groupsActiveLeft == 0; return groupsActiveLeft == 0;
} }
private static final Comparator<RolloutGroup> DESC_COMP = Comparator.comparingLong(RolloutGroup::getId).reversed();
private void executeLatestRolloutGroup(final JpaRollout rollout) { private void executeLatestRolloutGroup(final JpaRollout rollout) {
// was - rolloutGroupRepository.findByRolloutAndStatusNotOrderByIdDesc(rollout, RolloutGroupStatus.SCHEDULED); // was - rolloutGroupRepository.findByRolloutAndStatusNotOrderByIdDesc(rollout, RolloutGroupStatus.SCHEDULED);
final List<JpaRolloutGroup> latestRolloutGroup = rollout.getRolloutGroups().stream() 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 // so the evaluation to use total targets to properly
private RolloutGroup evalProxy(final RolloutGroup group) { private RolloutGroup evalProxy(final RolloutGroup group) {
if (group.isDynamic()) { 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( return (RolloutGroup) Proxy.newProxyInstance(
RolloutGroup.class.getClassLoader(), RolloutGroup.class.getClassLoader(),
new Class<?>[] {RolloutGroup.class}, new Class<?>[] { RolloutGroup.class },
(proxy, method, args) -> { (proxy, method, args) -> {
if ("getTotalTargets".equals(method.getName())) { if ("getTotalTargets".equals(method.getName())) {
return expected; return expected;
@@ -601,14 +597,14 @@ public class JpaRolloutExecutor implements RolloutExecutor {
long targetsInGroupFilter; long targetsInGroupFilter;
if (!RolloutHelper.isRolloutRetried(rollout.getTargetFilterQuery())) { if (!RolloutHelper.isRolloutRetried(rollout.getTargetFilterQuery())) {
targetsInGroupFilter = DeploymentHelper.runInNewTransaction(txManager, targetsInGroupFilter = DeploymentHelper.runInNewTransaction(txManager,
"countAllTargetsByTargetFilterQueryAndNotInRolloutGroups", "countAllTargetsByTargetFilterQueryAndNotInRolloutGroups",
count -> targetManagement.countByRsqlAndNotInRolloutGroupsAndCompatibleAndUpdatable(readyGroups, count -> targetManagement.countByRsqlAndNotInRolloutGroupsAndCompatibleAndUpdatable(readyGroups,
groupTargetFilter, rollout.getDistributionSet().getType())); groupTargetFilter, rollout.getDistributionSet().getType()));
} else { } else {
targetsInGroupFilter = DeploymentHelper.runInNewTransaction(txManager, targetsInGroupFilter = DeploymentHelper.runInNewTransaction(txManager,
"countByFailedRolloutAndNotInRolloutGroupsAndCompatible", "countByFailedRolloutAndNotInRolloutGroupsAndCompatible",
count -> targetManagement.countByFailedRolloutAndNotInRolloutGroups(readyGroups, count -> targetManagement.countByFailedRolloutAndNotInRolloutGroups(readyGroups,
RolloutHelper.getIdFromRetriedTargetFilter(rollout.getTargetFilterQuery()))); RolloutHelper.getIdFromRetriedTargetFilter(rollout.getTargetFilterQuery())));
} }
final double percentFromTheRest; final double percentFromTheRest;
@@ -677,7 +673,6 @@ public class JpaRolloutExecutor implements RolloutExecutor {
}); });
} }
private final Map<Long, AtomicLong> lastDynamicGroupFill = new ConcurrentHashMap<>();
// return if group change is made // return if group change is made
private boolean fillDynamicRolloutGroupsWithTargets(final JpaRollout rollout) { private boolean fillDynamicRolloutGroupsWithTargets(final JpaRollout rollout) {
final AtomicLong lastFill = lastDynamicGroupFill.computeIfAbsent(rollout.getId(), id -> new AtomicLong(0)); 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); RolloutHelper.verifyRolloutInStatus(rollout, RolloutStatus.RUNNING);
final List<RolloutGroup> rolloutGroups = rollout.getRolloutGroups(); 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(); final long currentlyInGroup = group.getTotalTargets();
if (currentlyInGroup >= expectedInGroup || group.getStatus() == RolloutGroupStatus.FINISHED) { if (currentlyInGroup >= expectedInGroup || group.getStatus() == RolloutGroupStatus.FINISHED) {
// the last one is full. create new and start filling it on the next iteration // 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; 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 { try {
RolloutHelper.verifyRolloutGroupAmount(groupCount + 1, quotaManagement); RolloutHelper.verifyRolloutGroupAmount(groupCount + 1, quotaManagement);
} catch (final AssignmentQuotaExceededException e) { } catch (final AssignmentQuotaExceededException e) {
@@ -755,7 +751,8 @@ public class JpaRolloutExecutor implements RolloutExecutor {
final JpaRolloutGroup group = new JpaRolloutGroup(); final JpaRolloutGroup group = new JpaRolloutGroup();
final String lastGroupWithoutSuffix = "group-" + groupCount; 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; final String nameAndDesc = "group-" + (groupCount + 1) + suffix;
group.setName(nameAndDesc); group.setName(nameAndDesc);
group.setDescription(nameAndDesc); group.setDescription(nameAndDesc);

View File

@@ -37,17 +37,12 @@ public class JpaRolloutHandler implements RolloutHandler {
/** /**
* Constructor * Constructor
* *
* @param tenantAware * @param tenantAware the {@link TenantAware} bean holding the tenant information
* the {@link TenantAware} bean holding the tenant information * @param rolloutManagement to fetch rollout related information from the datasource
* @param rolloutManagement * @param rolloutExecutor to trigger executions for a specific rollout
* to fetch rollout related information from the datasource * @param lockRegistry to lock processes
* @param rolloutExecutor * @param txManager transaction manager interface
* 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, public JpaRolloutHandler(final TenantAware tenantAware, final RolloutManagement rolloutManagement,
final RolloutExecutor rolloutExecutor, final LockRegistry lockRegistry, final RolloutExecutor rolloutExecutor, final LockRegistry lockRegistry,
@@ -83,8 +78,9 @@ public class JpaRolloutHandler implements RolloutHandler {
try { try {
handleRolloutInNewTransaction(rolloutId, handlerId); handleRolloutInNewTransaction(rolloutId, handlerId);
} catch (final Throwable throwable) { } 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."); log.debug("Finished handling of the rollouts.");
} finally { } finally {
if (log.isTraceEnabled()) { if (log.isTraceEnabled()) {
@@ -107,17 +103,17 @@ public class JpaRolloutHandler implements RolloutHandler {
// auditor is retrieved and set on transaction commit // auditor is retrieved and set on transaction commit
// if not overridden, the system user will be the auditor // if not overridden, the system user will be the auditor
rollout.getAccessControlContext().ifPresentOrElse( rollout.getAccessControlContext().ifPresentOrElse(
context -> // has stored context - executes it with it context -> // has stored context - executes it with it
contextAware.runInContext( contextAware.runInContext(
context, context,
() -> rolloutExecutor.execute(rollout)), () -> rolloutExecutor.execute(rollout)),
() -> // has no stored context - executes it in the tenant & user scope () -> // has no stored context - executes it in the tenant & user scope
contextAware.runAsTenantAsUser( contextAware.runAsTenantAsUser(
contextAware.getCurrentTenant(), contextAware.getCurrentTenant(),
rollout.getCreatedBy(), () -> { rollout.getCreatedBy(), () -> {
rolloutExecutor.execute(rollout); rolloutExecutor.execute(rollout);
return null; return null;
}) })
); );
}, },
() -> log.error("Could not retrieve rollout with id {}. Will not continue with execution.", () -> log.error("Could not retrieve rollout with id {}. Will not continue with execution.",

View File

@@ -14,8 +14,9 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import jakarta.persistence.EntityManager;
import javax.sql.DataSource; import javax.sql.DataSource;
import jakarta.persistence.EntityManager;
import jakarta.validation.Validation; import jakarta.validation.Validation;
import org.eclipse.hawkbit.ContextAware; 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.DistributionSetRepository;
import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetTagRepository; import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetTagRepository;
import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetTypeRepository; 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.LocalArtifactRepository;
import org.eclipse.hawkbit.repository.jpa.repository.RolloutGroupRepository; import org.eclipse.hawkbit.repository.jpa.repository.RolloutGroupRepository;
import org.eclipse.hawkbit.repository.jpa.repository.RolloutRepository; import org.eclipse.hawkbit.repository.jpa.repository.RolloutRepository;
import org.eclipse.hawkbit.repository.jpa.repository.RolloutTargetGroupRepository; 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.SoftwareModuleMetadataRepository;
import org.eclipse.hawkbit.repository.jpa.repository.SoftwareModuleRepository; import org.eclipse.hawkbit.repository.jpa.repository.SoftwareModuleRepository;
import org.eclipse.hawkbit.repository.jpa.repository.SoftwareModuleTypeRepository; 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.SystemManagementHolder;
import org.eclipse.hawkbit.repository.model.helper.SystemSecurityContextHolder; import org.eclipse.hawkbit.repository.model.helper.SystemSecurityContextHolder;
import org.eclipse.hawkbit.repository.model.helper.TenantConfigurationManagementHolder; 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.RsqlValidationOracle;
import org.eclipse.hawkbit.repository.rsql.RsqlVisitorFactory; 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.repository.rsql.VirtualPropertyReplacer;
import org.eclipse.hawkbit.security.HawkbitSecurityProperties; import org.eclipse.hawkbit.security.HawkbitSecurityProperties;
import org.eclipse.hawkbit.security.SecurityTokenGenerator; import org.eclipse.hawkbit.security.SecurityTokenGenerator;
@@ -202,7 +203,6 @@ import org.springframework.validation.beanvalidation.MethodValidationPostProcess
/** /**
* General configuration for hawkBit's Repository. * General configuration for hawkBit's Repository.
*
*/ */
@EnableJpaRepositories(value = "org.eclipse.hawkbit.repository.jpa.repository", repositoryFactoryBeanClass = CustomBaseRepositoryFactoryBean.class) @EnableJpaRepositories(value = "org.eclipse.hawkbit.repository.jpa.repository", repositoryFactoryBeanClass = CustomBaseRepositoryFactoryBean.class)
@EnableTransactionManagement @EnableTransactionManagement
@@ -223,10 +223,110 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
super(dataSource, properties, jtaTransactionManagerProvider); 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 @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
PauseRolloutGroupAction pauseRolloutGroupAction(final RolloutManagement rolloutManagement, PauseRolloutGroupAction pauseRolloutGroupAction(final RolloutManagement rolloutManagement,
final RolloutGroupRepository rolloutGroupRepository, final SystemSecurityContext systemSecurityContext) { final RolloutGroupRepository rolloutGroupRepository, final SystemSecurityContext systemSecurityContext) {
return new PauseRolloutGroupAction(rolloutManagement, rolloutGroupRepository, systemSecurityContext); return new PauseRolloutGroupAction(rolloutManagement, rolloutGroupRepository, systemSecurityContext);
} }
@@ -299,10 +399,8 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
} }
/** /**
* @param distributionSetTypeManagement * @param distributionSetTypeManagement to loading the {@link DistributionSetType}
* to loading the {@link DistributionSetType} * @param softwareManagement for loading {@link DistributionSet#getModules()}
* @param softwareManagement
* for loading {@link DistributionSet#getModules()}
* @return DistributionSetBuilder bean * @return DistributionSetBuilder bean
*/ */
@Bean @Bean
@@ -317,8 +415,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
} }
/** /**
* @param dsTypeManagement * @param dsTypeManagement for loading {@link TargetType#getCompatibleDistributionSetTypes()}
* for loading {@link TargetType#getCompatibleDistributionSetTypes()}
* @return TargetTypeBuilder bean * @return TargetTypeBuilder bean
*/ */
@Bean @Bean
@@ -333,9 +430,8 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
} }
/** /**
* @param softwareModuleTypeManagement * @param softwareModuleTypeManagement for loading {@link DistributionSetType#getMandatoryModuleTypes()}
* for loading {@link DistributionSetType#getMandatoryModuleTypes()} * and {@link DistributionSetType#getOptionalModuleTypes()}
* and {@link DistributionSetType#getOptionalModuleTypes()}
* @return DistributionSetTypeBuilder bean * @return DistributionSetTypeBuilder bean
*/ */
@Bean @Bean
@@ -345,8 +441,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
} }
/** /**
* @param softwareModuleTypeManagement * @param softwareModuleTypeManagement for loading {@link SoftwareModule#getType()}
* for loading {@link SoftwareModule#getType()}
* @return SoftwareModuleBuilder bean * @return SoftwareModuleBuilder bean
*/ */
@Bean @Bean
@@ -355,8 +450,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
} }
/** /**
* @param distributionSetManagement * @param distributionSetManagement for loading {@link Rollout#getDistributionSet()}
* for loading {@link Rollout#getDistributionSet()}
* @return RolloutBuilder bean * @return RolloutBuilder bean
*/ */
@Bean @Bean
@@ -365,9 +459,8 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
} }
/** /**
* @param distributionSetManagement * @param distributionSetManagement for loading
* for loading * {@link TargetFilterQuery#getAutoAssignDistributionSet()}
* {@link TargetFilterQuery#getAutoAssignDistributionSet()}
* @return TargetFilterQueryBuilder bean * @return TargetFilterQueryBuilder bean
*/ */
@Bean @Bean
@@ -434,7 +527,6 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
} }
/** /**
*
* @return the singleton instance of the * @return the singleton instance of the
* {@link AfterTransactionCommitExecutorHolder} * {@link AfterTransactionCommitExecutorHolder}
*/ */
@@ -443,23 +535,6 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
return AfterTransactionCommitExecutorHolder.getInstance(); 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 * @return {@link ExceptionMappingAspectHandler} aspect bean
*/ */
@@ -468,51 +543,6 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
return new ExceptionMappingAspectHandler(); 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. * {@link JpaSystemManagement} bean.
* *
@@ -614,13 +644,13 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
TargetManagement targetManagement(final EntityManager entityManager, final QuotaManagement quotaManagement, TargetManagement targetManagement(final EntityManager entityManager, final QuotaManagement quotaManagement,
final TargetRepository targetRepository, final TargetMetadataRepository targetMetadataRepository, final TargetRepository targetRepository, final TargetMetadataRepository targetMetadataRepository,
final RolloutGroupRepository rolloutGroupRepository, final RolloutGroupRepository rolloutGroupRepository,
final TargetFilterQueryRepository targetFilterQueryRepository, final TargetFilterQueryRepository targetFilterQueryRepository,
final TargetTypeRepository targetTypeRepository, final TargetTagRepository targetTagRepository, final TargetTypeRepository targetTypeRepository, final TargetTagRepository targetTagRepository,
final EventPublisherHolder eventPublisherHolder, final TenantAware tenantAware, final EventPublisherHolder eventPublisherHolder, final TenantAware tenantAware,
final VirtualPropertyReplacer virtualPropertyReplacer, final VirtualPropertyReplacer virtualPropertyReplacer,
final JpaProperties properties, final DistributionSetManagement distributionSetManagement) { final JpaProperties properties, final DistributionSetManagement distributionSetManagement) {
return new JpaTargetManagement(entityManager, distributionSetManagement, quotaManagement, targetRepository, return new JpaTargetManagement(entityManager, distributionSetManagement, quotaManagement, targetRepository,
targetTypeRepository, targetMetadataRepository, rolloutGroupRepository, targetFilterQueryRepository, targetTypeRepository, targetMetadataRepository, rolloutGroupRepository, targetFilterQueryRepository,
targetTagRepository, eventPublisherHolder, tenantAware, virtualPropertyReplacer, targetTagRepository, eventPublisherHolder, tenantAware, virtualPropertyReplacer,
@@ -630,19 +660,12 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
/** /**
* {@link JpaTargetFilterQueryManagement} bean. * {@link JpaTargetFilterQueryManagement} bean.
* *
* @param targetFilterQueryRepository * @param targetFilterQueryRepository holding {@link TargetFilterQuery} entities
* holding {@link TargetFilterQuery} entities * @param targetManagement managing {@link Target} entities
* @param targetManagement * @param virtualPropertyReplacer for RSQL handling
* managing {@link Target} entities * @param distributionSetManagement for auto assign DS access
* @param virtualPropertyReplacer * @param quotaManagement to access quotas
* for RSQL handling * @param properties JPA properties
* @param distributionSetManagement
* for auto assign DS access
* @param quotaManagement
* to access quotas
* @param properties
* JPA properties
*
* @return a new {@link TargetFilterQueryManagement} * @return a new {@link TargetFilterQueryManagement}
*/ */
@Bean @Bean
@@ -659,7 +682,6 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
tenantConfigurationManagement, repositoryProperties, systemSecurityContext, contextAware, auditorAware); tenantConfigurationManagement, repositoryProperties, systemSecurityContext, contextAware, auditorAware);
} }
/** /**
* {@link JpaTargetTagManagement} bean. * {@link JpaTargetTagManagement} bean.
* *
@@ -792,10 +814,10 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
RolloutGroupManagement rolloutGroupManagement(final RolloutGroupRepository rolloutGroupRepository, RolloutGroupManagement rolloutGroupManagement(final RolloutGroupRepository rolloutGroupRepository,
final RolloutRepository rolloutRepository, final ActionRepository actionRepository, final RolloutRepository rolloutRepository, final ActionRepository actionRepository,
final TargetRepository targetRepository, final EntityManager entityManager, final TargetRepository targetRepository, final EntityManager entityManager,
final VirtualPropertyReplacer virtualPropertyReplacer, final RolloutStatusCache rolloutStatusCache, final VirtualPropertyReplacer virtualPropertyReplacer, final RolloutStatusCache rolloutStatusCache,
final JpaProperties properties) { final JpaProperties properties) {
return new JpaRolloutGroupManagement(rolloutGroupRepository, rolloutRepository, actionRepository, return new JpaRolloutGroupManagement(rolloutGroupRepository, rolloutRepository, actionRepository,
targetRepository, entityManager, virtualPropertyReplacer, rolloutStatusCache, properties.getDatabase()); targetRepository, entityManager, virtualPropertyReplacer, rolloutStatusCache, properties.getDatabase());
} }
@@ -808,15 +830,16 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
DeploymentManagement deploymentManagement(final EntityManager entityManager, DeploymentManagement deploymentManagement(final EntityManager entityManager,
final ActionRepository actionRepository, final ActionRepository actionRepository,
final DistributionSetManagement distributionSetManagement, final TargetRepository targetRepository, final DistributionSetManagement distributionSetManagement, final TargetRepository targetRepository,
final ActionStatusRepository actionStatusRepository, final AuditorAware<String> auditorProvider, final ActionStatusRepository actionStatusRepository, final AuditorAware<String> auditorProvider,
final EventPublisherHolder eventPublisherHolder, final AfterTransactionCommitExecutor afterCommit, final EventPublisherHolder eventPublisherHolder, final AfterTransactionCommitExecutor afterCommit,
final VirtualPropertyReplacer virtualPropertyReplacer, final PlatformTransactionManager txManager, final VirtualPropertyReplacer virtualPropertyReplacer, final PlatformTransactionManager txManager,
final TenantConfigurationManagement tenantConfigurationManagement, final QuotaManagement quotaManagement, final TenantConfigurationManagement tenantConfigurationManagement, final QuotaManagement quotaManagement,
final SystemSecurityContext systemSecurityContext, final TenantAware tenantAware, final AuditorAware<String> auditorAware, final SystemSecurityContext systemSecurityContext, final TenantAware tenantAware, final AuditorAware<String> auditorAware,
final JpaProperties properties, final RepositoryProperties repositoryProperties) { 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, eventPublisherHolder, afterCommit, virtualPropertyReplacer, txManager, tenantConfigurationManagement,
quotaManagement, systemSecurityContext, tenantAware, auditorAware, properties.getDatabase(), repositoryProperties); quotaManagement, systemSecurityContext, tenantAware, auditorAware, properties.getDatabase(), repositoryProperties);
} }
@@ -880,10 +903,8 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
/** /**
* {@link EventEntityManager} bean. * {@link EventEntityManager} bean.
* *
* @param aware * @param aware the tenant aware
* the tenant aware * @param entityManager the entity manager
* @param entityManager
* the entity manager
* @return a new {@link EventEntityManager} * @return a new {@link EventEntityManager}
*/ */
@Bean @Bean
@@ -895,14 +916,10 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
/** /**
* {@link AutoAssignChecker} bean. * {@link AutoAssignChecker} bean.
* *
* @param targetFilterQueryManagement * @param targetFilterQueryManagement to get all target filter queries
* to get all target filter queries * @param targetManagement to get targets
* @param targetManagement * @param deploymentManagement to assign distribution sets to targets
* to get targets * @param transactionManager to run transactions
* @param deploymentManagement
* to assign distribution sets to targets
* @param transactionManager
* to run transactions
* @return a new {@link AutoAssignChecker} * @return a new {@link AutoAssignChecker}
*/ */
@Bean @Bean
@@ -920,14 +937,10 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
* Note: does not activate in test profile, otherwise it is hard to test the * Note: does not activate in test profile, otherwise it is hard to test the
* auto assign functionality. * auto assign functionality.
* *
* @param systemManagement * @param systemManagement to find all tenants
* to find all tenants * @param systemSecurityContext to run as system
* @param systemSecurityContext * @param autoAssignExecutor to run a check as tenant
* to run as system * @param lockRegistry to lock the tenant for auto assignment
* @param autoAssignExecutor
* to run a check as tenant
* @param lockRegistry
* to lock the tenant for auto assignment
* @return a new {@link AutoAssignChecker} * @return a new {@link AutoAssignChecker}
*/ */
@Bean @Bean
@@ -945,11 +958,8 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
/** /**
* {@link AutoActionCleanup} bean. * {@link AutoActionCleanup} bean.
* *
* @param deploymentManagement * @param deploymentManagement Deployment management service
* Deployment management service * @param configManagement Tenant configuration service
* @param configManagement
* Tenant configuration service
*
* @return a new {@link AutoActionCleanup} bean * @return a new {@link AutoActionCleanup} bean
*/ */
@Bean @Bean
@@ -961,15 +971,10 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
/** /**
* {@link AutoCleanupScheduler} bean. * {@link AutoCleanupScheduler} bean.
* *
* @param systemManagement * @param systemManagement to find all tenants
* to find all tenants * @param systemSecurityContext to run as system
* @param systemSecurityContext * @param lockRegistry to lock the tenant for auto assignment
* to run as system * @param cleanupTasks a list of cleanup tasks
* @param lockRegistry
* to lock the tenant for auto assignment
* @param cleanupTasks
* a list of cleanup tasks
*
* @return a new {@link AutoCleanupScheduler} bean * @return a new {@link AutoCleanupScheduler} bean
*/ */
@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 * Note: does not activate in test profile, otherwise it is hard to test the
* rollout handling functionality. * rollout handling functionality.
* *
* @param systemManagement * @param systemManagement to find all tenants
* to find all tenants * @param rolloutHandler to run the rollout handler
* @param rolloutHandler * @param systemSecurityContext to run as system
* to run the rollout handler
* @param systemSecurityContext
* to run as system
* @return a new {@link RolloutScheduler} bean. * @return a new {@link RolloutScheduler} bean.
*/ */
@Bean @Bean
@@ -1069,40 +1071,4 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
ArtifactEncryptionService artifactEncryptionService() { ArtifactEncryptionService artifactEncryptionService() {
return ArtifactEncryptionService.getInstance(); 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 { public class SystemManagementCacheKeyGenerator implements CurrentTenantCacheKeyGenerator {
private final ThreadLocal<String> createInitialTenant = new ThreadLocal<>();
@Autowired @Autowired
private TenantAware tenantAware; 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 @Override
@Bean @Bean
public KeyGenerator currentTenantKeyGenerator() { public KeyGenerator currentTenantKeyGenerator() {
@@ -55,7 +39,7 @@ public class SystemManagementCacheKeyGenerator implements CurrentTenantCacheKeyG
/** /**
* Get the tenant which overwrites the actual tenant used by the * Get the tenant which overwrites the actual tenant used by the
* {@linkplain #currentTenantKeyGenerator()}. * {@linkplain #currentTenantKeyGenerator()}.
* *
* @return A present optional in case that there is a tenant in the progress of * @return A present optional in case that there is a tenant in the progress of
* creation. * creation.
*/ */
@@ -66,9 +50,8 @@ public class SystemManagementCacheKeyGenerator implements CurrentTenantCacheKeyG
/** /**
* Overwrite the tenant used by the key generator in case that the tenant is in * Overwrite the tenant used by the key generator in case that the tenant is in
* the process of creation. * the process of creation.
* *
* @param tenant * @param tenant the tenant which should be used instead of the actual one.
* the tenant which should be used instead of the actual one.
*/ */
public void setTenantInCreation(@NotNull String tenant) { public void setTenantInCreation(@NotNull String tenant) {
createInitialTenant.set(Objects.requireNonNull(tenant)); createInitialTenant.set(Objects.requireNonNull(tenant));
@@ -81,4 +64,20 @@ public class SystemManagementCacheKeyGenerator implements CurrentTenantCacheKeyG
public void removeTenantInCreation() { public void removeTenantInCreation() {
createInitialTenant.remove(); 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. * {@link KeyGenerator} for tenant related caches.
*
*/ */
@Service @Service
public class TenantKeyGenerator implements KeyGenerator { public class TenantKeyGenerator implements KeyGenerator {

View File

@@ -24,7 +24,7 @@ import org.springframework.lang.Nullable;
* additional restrictions (e.g. entity based) could be applied. * additional restrictions (e.g. entity based) could be applied.
* <p/> * <p/>
* <b>Note</b>: Experimental, only * <b>Note</b>: Experimental, only
* *
* @param <T> the domain type the repository manages * @param <T> the domain type the repository manages
*/ */
public interface AccessController<T> { public interface AccessController<T> {
@@ -33,16 +33,15 @@ public interface AccessController<T> {
* Introduce a new specification to limit the access to a specific entity. * Introduce a new specification to limit the access to a specific entity.
* *
* @return a new specification limiting the access, if empty no access restrictions * @return a new specification limiting the access, if empty no access restrictions
* are to be applied * are to be applied
*/ */
Optional<Specification<T>> getAccessRules(Operation operation); Optional<Specification<T>> getAccessRules(Operation operation);
/** /**
* Append the resource limitation on an already existing specification. * Append the resource limitation on an already existing specification.
* *
* @param specification * @param specification is the root specification which needs to be appended by the
* is the root specification which needs to be appended by the * resource limitation
* resource limitation
* @return a new appended specification * @return a new appended specification
*/ */
@Nullable @Nullable
@@ -55,18 +54,17 @@ public interface AccessController<T> {
/** /**
* Verify if the given {@link Operation} is permitted for the provided entity. * Verify if the given {@link Operation} is permitted for the provided entity.
* *
* @throws InsufficientPermissionException * @throws InsufficientPermissionException if operation is not permitted for given entities
* if operation is not permitted for given entities
*/ */
void assertOperationAllowed(final Operation operation, final T entity) throws InsufficientPermissionException; void assertOperationAllowed(final Operation operation, final T entity) throws InsufficientPermissionException;
/** /**
* Verify if the given {@link Operation} is permitted for the provided entities. * Verify if the given {@link Operation} is permitted for the provided entities.
* *
* @throws InsufficientPermissionException * @throws InsufficientPermissionException if operation is not permitted for given entities
* 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) { for (final T entity : entities) {
assertOperationAllowed(operation, entity); assertOperationAllowed(operation, entity);
} }

View File

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

View File

@@ -56,15 +56,11 @@ public abstract class AbstractAutoAssignExecutor implements AutoAssignExecutor {
/** /**
* Constructor * Constructor
* *
* @param targetFilterQueryManagement * @param targetFilterQueryManagement to get all target filter queries
* to get all target filter queries * @param deploymentManagement to assign distribution sets to targets
* @param deploymentManagement * @param transactionManager to run transactions
* to assign distribution sets to targets * @param contextAware to handle the context
* @param transactionManager
* to run transactions
* @param contextAware
* to handle the context
*/ */
protected AbstractAutoAssignExecutor(final TargetFilterQueryManagement targetFilterQueryManagement, protected AbstractAutoAssignExecutor(final TargetFilterQueryManagement targetFilterQueryManagement,
final DeploymentManagement deploymentManagement, final PlatformTransactionManager transactionManager, final DeploymentManagement deploymentManagement, final PlatformTransactionManager transactionManager,
@@ -75,6 +71,12 @@ public abstract class AbstractAutoAssignExecutor implements AutoAssignExecutor {
this.contextAware = contextAware; this.contextAware = contextAware;
} }
protected static String getAutoAssignmentInitiatedBy(final TargetFilterQuery targetFilterQuery) {
return StringUtils.hasText(targetFilterQuery.getAutoAssignInitiatedBy())
? targetFilterQuery.getAutoAssignInitiatedBy()
: targetFilterQuery.getCreatedBy();
}
protected DeploymentManagement getDeploymentManagement() { protected DeploymentManagement getDeploymentManagement() {
return deploymentManagement; return deploymentManagement;
} }
@@ -100,17 +102,17 @@ public abstract class AbstractAutoAssignExecutor implements AutoAssignExecutor {
filterQueries.forEach(filterQuery -> { filterQueries.forEach(filterQuery -> {
try { try {
filterQuery.getAccessControlContext().ifPresentOrElse( filterQuery.getAccessControlContext().ifPresentOrElse(
context -> // has stored context - executes it with it context -> // has stored context - executes it with it
contextAware.runInContext( contextAware.runInContext(
context, context,
() -> consumer.accept(filterQuery)), () -> consumer.accept(filterQuery)),
() -> // has no stored context - executes it in the tenant & user scope () -> // has no stored context - executes it in the tenant & user scope
contextAware.runAsTenantAsUser( contextAware.runAsTenantAsUser(
contextAware.getCurrentTenant(), contextAware.getCurrentTenant(),
getAutoAssignmentInitiatedBy(filterQuery), () -> { getAutoAssignmentInitiatedBy(filterQuery), () -> {
consumer.accept(filterQuery); consumer.accept(filterQuery);
return null; return null;
}) })
); );
} catch (final RuntimeException ex) { } catch (final RuntimeException ex) {
log.debug( log.debug(
@@ -128,11 +130,9 @@ public abstract class AbstractAutoAssignExecutor implements AutoAssignExecutor {
/** /**
* Runs target assignments within a dedicated transaction for a given list of * Runs target assignments within a dedicated transaction for a given list of
* controllerIDs * controllerIDs
* *
* @param targetFilterQuery * @param targetFilterQuery the target filter query
* the target filter query * @param controllerIds the controllerIDs
* @param controllerIds
* the controllerIDs
* @return count of targets * @return count of targets
*/ */
protected int runTransactionalAssignment(final TargetFilterQuery targetFilterQuery, 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 * Creates a list of {@link DeploymentRequest} for given list of controllerIds
* and {@link TargetFilterQuery} * and {@link TargetFilterQuery}
* *
* @param controllerIds * @param controllerIds list of controllerIds
* list of controllerIds * @param filterQuery the query the targets have to match
* @param filterQuery
* the query the targets have to match
* @return list of deployment request * @return list of deployment request
*/ */
protected List<DeploymentRequest> mapToDeploymentRequests(final List<String> controllerIds, protected List<DeploymentRequest> mapToDeploymentRequests(final List<String> controllerIds,
@@ -179,10 +177,4 @@ public abstract class AbstractAutoAssignExecutor implements AutoAssignExecutor {
.setConfirmationRequired(filterQuery.isConfirmationRequired()).build()) .setConfirmationRequired(filterQuery.isConfirmationRequired()).build())
.toList(); .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 * Instantiates a new auto assign checker
* *
* @param targetFilterQueryManagement * @param targetFilterQueryManagement to get all target filter queries
* to get all target filter queries * @param targetManagement to get targets
* @param targetManagement * @param deploymentManagement to assign distribution sets to targets
* to get targets * @param transactionManager to run transactions
* @param deploymentManagement * @param contextAware to handle the context
* to assign distribution sets to targets
* @param transactionManager
* to run transactions
* @param contextAware
* to handle the context
*/ */
public AutoAssignChecker(final TargetFilterQueryManagement targetFilterQueryManagement, public AutoAssignChecker(final TargetFilterQueryManagement targetFilterQueryManagement,
final TargetManagement targetManagement, final DeploymentManagement deploymentManagement, final TargetManagement targetManagement, final DeploymentManagement deploymentManagement,
@@ -83,8 +78,7 @@ public class AutoAssignChecker extends AbstractAutoAssignExecutor {
* them. Catches PersistenceException and own exceptions derived from * them. Catches PersistenceException and own exceptions derived from
* AbstractServerRtException * AbstractServerRtException
* *
* @param targetFilterQuery * @param targetFilterQuery the target filter query
* the target filter query
*/ */
private void checkByTargetFilterQueryAndAssignDS(final TargetFilterQuery targetFilterQuery) { private void checkByTargetFilterQueryAndAssignDS(final TargetFilterQuery targetFilterQuery) {
log.debug("Auto assign check call for tenant {} and target filter query id {} started", log.debug("Auto assign check call for tenant {} and target filter query id {} started",

View File

@@ -23,7 +23,7 @@ import org.springframework.scheduling.annotation.Scheduled;
*/ */
@Slf4j @Slf4j
public class AutoAssignScheduler { public class AutoAssignScheduler {
private static final String PROP_SCHEDULER_DELAY_PLACEHOLDER = "${hawkbit.autoassign.scheduler.fixedDelay:2000}"; private static final String PROP_SCHEDULER_DELAY_PLACEHOLDER = "${hawkbit.autoassign.scheduler.fixedDelay:2000}";
private final SystemManagement systemManagement; private final SystemManagement systemManagement;
@@ -36,15 +36,11 @@ public class AutoAssignScheduler {
/** /**
* Instantiates a new AutoAssignScheduler * Instantiates a new AutoAssignScheduler
* *
* @param systemManagement * @param systemManagement to find all tenants
* to find all tenants * @param systemSecurityContext to run as system
* @param systemSecurityContext * @param autoAssignExecutor to run a check as tenant
* to run as system * @param lockRegistry to acquire a lock per tenant
* @param autoAssignExecutor
* to run a check as tenant
* @param lockRegistry
* to acquire a lock per tenant
*/ */
public AutoAssignScheduler(final SystemManagement systemManagement, public AutoAssignScheduler(final SystemManagement systemManagement,
final SystemSecurityContext systemSecurityContext, final AutoAssignExecutor autoAssignExecutor, final SystemSecurityContext systemSecurityContext, final AutoAssignExecutor autoAssignExecutor,

View File

@@ -32,7 +32,7 @@ import org.eclipse.hawkbit.repository.model.TenantConfigurationValue;
* actions which are in a certain {@link Action.Status}. It is recommended to * actions which are in a certain {@link Action.Status}. It is recommended to
* only clean up actions which have terminated already (i.e. actions in status * only clean up actions which have terminated already (i.e. actions in status
* CANCELLED or ERROR). * CANCELLED or ERROR).
* *
* The cleanup task can be enabled /disabled and configured on a per tenant * The cleanup task can be enabled /disabled and configured on a per tenant
* basis. * basis.
*/ */
@@ -49,11 +49,9 @@ public class AutoActionCleanup implements CleanupTask {
/** /**
* Constructs the action cleanup handler. * Constructs the action cleanup handler.
* *
* @param deploymentMgmt * @param deploymentMgmt The {@link DeploymentManagement} to operate on.
* The {@link DeploymentManagement} to operate on. * @param configMgmt The {@link TenantConfigurationManagement} service.
* @param configMgmt
* The {@link TenantConfigurationManagement} service.
*/ */
public AutoActionCleanup(final DeploymentManagement deploymentMgmt, public AutoActionCleanup(final DeploymentManagement deploymentMgmt,
final TenantConfigurationManagement configMgmt) { final TenantConfigurationManagement configMgmt) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -18,6 +18,51 @@ import org.eclipse.hawkbit.repository.model.RolloutGroupConditions;
public class JpaRolloutGroupCreate extends AbstractRolloutGroupCreate<RolloutGroupCreate> public class JpaRolloutGroupCreate extends AbstractRolloutGroupCreate<RolloutGroupCreate>
implements 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 @Override
public JpaRolloutGroup build() { public JpaRolloutGroup build() {
final JpaRolloutGroup group = new JpaRolloutGroup(); final JpaRolloutGroup group = new JpaRolloutGroup();
@@ -40,60 +85,4 @@ public class JpaRolloutGroupCreate extends AbstractRolloutGroupCreate<RolloutGro
return group; 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}. * Builder implementation for {@link SoftwareModule}.
*
*/ */
public class JpaSoftwareModuleBuilder implements SoftwareModuleBuilder { public class JpaSoftwareModuleBuilder implements SoftwareModuleBuilder {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -23,7 +23,7 @@ import org.springframework.transaction.support.TransactionSynchronizationManager
@Slf4j @Slf4j
public class AfterTransactionCommitDefaultServiceExecutor extends TransactionSynchronizationAdapter public class AfterTransactionCommitDefaultServiceExecutor extends TransactionSynchronizationAdapter
implements AfterTransactionCommitExecutor { implements AfterTransactionCommitExecutor {
private static final ThreadLocal<List<Runnable>> THREAD_LOCAL_RUNNABLES = new ThreadLocal<>(); private static final ThreadLocal<List<Runnable>> THREAD_LOCAL_RUNNABLES = new ThreadLocal<>();
@Override @Override
@@ -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 @Override
// Exception squid:S1217 - we want to run this synchronous // Exception squid:S1217 - we want to run this synchronous
@SuppressWarnings("squid:S1217") @SuppressWarnings("squid:S1217")
@@ -62,12 +70,4 @@ public class AfterTransactionCommitDefaultServiceExecutor extends TransactionSyn
runnable.run(); 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; package org.eclipse.hawkbit.repository.jpa.executor;
/** /**
*
* A interface to register a runnable, which will be executed after a successful * A interface to register a runnable, which will be executed after a successful
* spring transaction. * spring transaction.
*
*/ */
@FunctionalInterface @FunctionalInterface
public interface AfterTransactionCommitExecutor { public interface AfterTransactionCommitExecutor {
@@ -21,9 +19,8 @@ public interface AfterTransactionCommitExecutor {
/** /**
* Register a runnable which will be executed after a successful spring * Register a runnable which will be executed after a successful spring
* transaction. * transaction.
* *
* @param runnable * @param runnable the after commit runnable
* the after commit runnable
*/ */
void afterCommit(Runnable runnable); void afterCommit(Runnable runnable);
} }

View File

@@ -17,6 +17,8 @@ import java.util.Set;
import java.util.function.BooleanSupplier; import java.util.function.BooleanSupplier;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import jakarta.persistence.criteria.JoinType;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.repository.QuotaManagement; import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.RepositoryConstants; 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.CollectionUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import jakarta.persistence.criteria.JoinType;
/** /**
* {@link DistributionSet} to {@link Target} assignment strategy as utility for * {@link DistributionSet} to {@link Target} assignment strategy as utility for
* {@link JpaDeploymentManagement}. * {@link JpaDeploymentManagement}.
@@ -68,10 +68,10 @@ public abstract class AbstractDsAssignmentStrategy {
private final RepositoryProperties repositoryProperties; private final RepositoryProperties repositoryProperties;
AbstractDsAssignmentStrategy(final TargetRepository targetRepository, AbstractDsAssignmentStrategy(final TargetRepository targetRepository,
final AfterTransactionCommitExecutor afterCommit, final EventPublisherHolder eventPublisherHolder, final AfterTransactionCommitExecutor afterCommit, final EventPublisherHolder eventPublisherHolder,
final ActionRepository actionRepository, final ActionStatusRepository actionStatusRepository, final ActionRepository actionRepository, final ActionStatusRepository actionStatusRepository,
final QuotaManagement quotaManagement, final BooleanSupplier multiAssignmentsConfig, final QuotaManagement quotaManagement, final BooleanSupplier multiAssignmentsConfig,
final BooleanSupplier confirmationFlowConfig, final RepositoryProperties repositoryProperties) { final BooleanSupplier confirmationFlowConfig, final RepositoryProperties repositoryProperties) {
this.targetRepository = targetRepository; this.targetRepository = targetRepository;
this.afterCommit = afterCommit; this.afterCommit = afterCommit;
this.eventPublisherHolder = eventPublisherHolder; this.eventPublisherHolder = eventPublisherHolder;
@@ -83,63 +83,47 @@ public abstract class AbstractDsAssignmentStrategy {
this.repositoryProperties = repositoryProperties; this.repositoryProperties = repositoryProperties;
} }
/** public JpaAction createTargetAction(final String initiatedBy, final TargetWithActionType targetWithActionType,
* Find targets to be considered for assignment. final List<JpaTarget> targets, final JpaDistributionSet set) {
* final Optional<JpaTarget> optTarget = targets.stream()
* @param controllerIDs .filter(t -> t.getControllerId().equals(targetWithActionType.getControllerId())).findFirst();
* 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);
/** // create the action
* return optTarget.map(target -> {
* @param set assertActionsPerTargetQuota(target, 1);
* @param targets final JpaAction actionForTarget = new JpaAction();
*/ actionForTarget.setActionType(targetWithActionType.getActionType());
abstract void sendTargetUpdatedEvents(final DistributionSet set, final List<JpaTarget> targets); 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) {
* Update status and DS fields of given target. final JpaActionStatus actionStatus = new JpaActionStatus();
* actionStatus.setAction(action);
* @param distributionSet actionStatus.setOccurredAt(action.getCreatedAt());
* to set
* @param targetIds
* to change
* @param currentUser
* for auditing
*/
abstract void setAssignedDistributionSetAndTargetStatus(final JpaDistributionSet distributionSet,
final List<List<Long>> targetIds, final String currentUser);
/** if (StringUtils.hasText(actionMessage)) {
* Cancels actions that can be canceled (i.e. actionStatus.addMessage(actionMessage);
* {@link DistributionSet#isRequiredMigrationStep() is <code>false</code>}) } else {
* as a result of the new assignment and returns all {@link Target}s where actionStatus.addMessage(getActionMessage(action));
* 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);
/** return actionStatus;
* 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);
protected void sendTargetUpdatedEvent(final JpaTarget target) { protected void sendTargetUpdatedEvent(final JpaTarget target) {
afterCommit.afterCommit(() -> eventPublisherHolder.getEventPublisher() afterCommit.afterCommit(() -> eventPublisherHolder.getEventPublisher()
@@ -172,7 +156,7 @@ public abstract class AbstractDsAssignmentStrategy {
// document that the status has been retrieved // document that the status has been retrieved
actionStatusRepository.save(new JpaActionStatus(action, Status.CANCELING, System.currentTimeMillis(), actionStatusRepository.save(new JpaActionStatus(action, Status.CANCELING, System.currentTimeMillis(),
RepositoryConstants.SERVER_MESSAGE_PREFIX + "cancel obsolete action due to new update")); RepositoryConstants.SERVER_MESSAGE_PREFIX + "cancel obsolete action due to new update"));
actionRepository.save(action); actionRepository.save(action);
return action.getTarget().getId(); return action.getTarget().getId();
@@ -223,65 +207,70 @@ public abstract class AbstractDsAssignmentStrategy {
* Sends the {@link CancelTargetAssignmentEvent} for a specific action to * Sends the {@link CancelTargetAssignmentEvent} for a specific action to
* the eventPublisher. * the eventPublisher.
* *
* @param action * @param action the action of the assignment
* the action of the assignment
*/ */
protected void cancelAssignDistributionSetEvent(final Action action) { protected void cancelAssignDistributionSetEvent(final Action action) {
afterCommit.afterCommit(() -> eventPublisherHolder.getEventPublisher().publishEvent( afterCommit.afterCommit(() -> eventPublisherHolder.getEventPublisher().publishEvent(
new CancelTargetAssignmentEvent(action, eventPublisherHolder.getApplicationId()))); new CancelTargetAssignmentEvent(action, eventPublisherHolder.getApplicationId())));
} }
private void cancelAssignDistributionSetEvent(final List<Action> actions) { protected boolean isMultiAssignmentsEnabled() {
if (CollectionUtils.isEmpty(actions)) { return multiAssignmentsConfig.getAsBoolean();
return;
}
final String tenant = actions.get(0).getTenant();
afterCommit.afterCommit(() -> eventPublisherHolder.getEventPublisher()
.publishEvent(new CancelTargetAssignmentEvent(tenant,
actions, eventPublisherHolder.getApplicationId())));
} }
public JpaAction createTargetAction(final String initiatedBy, final TargetWithActionType targetWithActionType, protected boolean isConfirmationFlowEnabled() {
final List<JpaTarget> targets, final JpaDistributionSet set) { return confirmationFlowConfig.getAsBoolean();
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(); * Find targets to be considered for assignment.
actionStatus.setAction(action); *
actionStatus.setOccurredAt(action.getCreatedAt()); * @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);
if (StringUtils.hasText(actionMessage)) { /**
actionStatus.addMessage(actionMessage); * @param set
} else { * @param targets
actionStatus.addMessage(getActionMessage(action)); */
} abstract void sendTargetUpdatedEvents(final DistributionSet set, final List<JpaTarget> targets);
return actionStatus; /**
} * 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) { private static String getActionMessage(final Action action) {
final RolloutGroup rolloutGroup = action.getRolloutGroup(); final RolloutGroup rolloutGroup = action.getRolloutGroup();
@@ -292,18 +281,20 @@ public abstract class AbstractDsAssignmentStrategy {
} }
return String.format("Assignment initiated by user '%s'", action.getInitiatedBy()); return String.format("Assignment initiated by user '%s'", action.getInitiatedBy());
} }
private void cancelAssignDistributionSetEvent(final List<Action> actions) {
if (CollectionUtils.isEmpty(actions)) {
return;
}
final String tenant = actions.get(0).getTenant();
afterCommit.afterCommit(() -> eventPublisherHolder.getEventPublisher()
.publishEvent(new CancelTargetAssignmentEvent(tenant,
actions, eventPublisherHolder.getApplicationId())));
}
private void assertActionsPerTargetQuota(final Target target, final int requested) { private void assertActionsPerTargetQuota(final Target target, final int requested) {
final int quota = quotaManagement.getMaxActionsPerTarget(); final int quota = quotaManagement.getMaxActionsPerTarget();
QuotaHelper.assertAssignmentQuota(target.getId(), requested, quota, Action.class, Target.class, QuotaHelper.assertAssignmentQuota(target.getId(), requested, quota, Action.class, Target.class,
actionRepository::countByTargetId); 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; 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 lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.repository.QuotaManagement; import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.RepositoryProperties; 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.PageRequest;
import org.springframework.data.domain.Sort; 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 * Implements utility methods for managing {@link Action}s
*/ */
@@ -56,6 +56,54 @@ public class JpaActionManagement {
this.repositoryProperties = repositoryProperties; 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, List<Action> findActiveActionsWithHighestWeightConsideringDefault(final String controllerId,
final int maxActionCount) { final int maxActionCount) {
final List<Action> actions = new ArrayList<>(); final List<Action> actions = new ArrayList<>();
@@ -84,23 +132,8 @@ public class JpaActionManagement {
return actions.stream().sorted(actionImportance).limit(maxActionCount).collect(Collectors.toList()); return actions.stream().sorted(actionImportance).limit(maxActionCount).collect(Collectors.toList());
} }
protected List<JpaAction> findActiveActionsHavingStatus(final String controllerId, final Action.Status status) { private static boolean isIntermediateStatus(final JpaActionStatus actionStatus) {
return actionRepository.findAll( return FINISHED != actionStatus.getStatus() && ERROR != actionStatus.getStatus();
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;
} }
/** /**
@@ -117,25 +150,12 @@ public class JpaActionManagement {
final boolean isAllowedByRepositoryConfiguration = intermediateStatus && !repositoryProperties.isRejectActionStatusForClosedAction(); 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 //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; 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}. * Sets {@link TargetUpdateStatus} based on given {@link ActionStatus}.
*/ */
@@ -152,24 +172,4 @@ public class JpaActionManagement {
action.setLastActionStatusCode(actionStatus.getCode().orElse(null)); action.setLastActionStatusCode(actionStatus.getCode().orElse(null));
return actionRepository.save(action); 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.io.InputStream;
import java.util.Optional; import java.util.Optional;
import jakarta.persistence.EntityManager;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.artifact.repository.ArtifactRepository; import org.eclipse.hawkbit.artifact.repository.ArtifactRepository;
import org.eclipse.hawkbit.artifact.repository.ArtifactStoreException; 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.transaction.support.TransactionSynchronizationManager;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import jakarta.persistence.EntityManager;
/** /**
* JPA based {@link ArtifactManagement} implementation. * JPA based {@link ArtifactManagement} implementation.
*/ */
@@ -92,6 +92,11 @@ public class JpaArtifactManagement implements ArtifactManagement {
this.tenantAware = tenantAware; this.tenantAware = tenantAware;
} }
@Override
public long count() {
return localArtifactRepository.count();
}
@Override @Override
@Transactional @Transactional
@Retryable(include = { @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 @Override
@Transactional @Transactional
@Retryable(include = { @Retryable(include = {
@@ -222,7 +143,7 @@ public class JpaArtifactManagement implements ArtifactManagement {
// clearArtifactBinary checks (unconditionally) software module UPDATE access // clearArtifactBinary checks (unconditionally) software module UPDATE access
softwareModuleRepository.getAccessController().ifPresent(accessController -> softwareModuleRepository.getAccessController().ifPresent(accessController ->
accessController.assertOperationAllowed(AccessController.Operation.UPDATE, accessController.assertOperationAllowed(AccessController.Operation.UPDATE,
(JpaSoftwareModule) toDelete.getSoftwareModule())); (JpaSoftwareModule) toDelete.getSoftwareModule()));
((JpaSoftwareModule) toDelete.getSoftwareModule()).removeArtifact(toDelete); ((JpaSoftwareModule) toDelete.getSoftwareModule()).removeArtifact(toDelete);
softwareModuleRepository.save((JpaSoftwareModule) toDelete.getSoftwareModule()); softwareModuleRepository.save((JpaSoftwareModule) toDelete.getSoftwareModule());
@@ -268,11 +189,6 @@ public class JpaArtifactManagement implements ArtifactManagement {
return localArtifactRepository.count(ArtifactSpecifications.bySoftwareModuleId(softwareModuleId)); return localArtifactRepository.count(ArtifactSpecifications.bySoftwareModuleId(softwareModuleId));
} }
@Override
public long count() {
return localArtifactRepository.count();
}
@Override @Override
public Optional<DbArtifact> loadArtifactBinary(final String sha1Hash, final long softwareModuleId, public Optional<DbArtifact> loadArtifactBinary(final String sha1Hash, final long softwareModuleId,
final boolean isEncrypted) { final boolean isEncrypted) {
@@ -293,6 +209,91 @@ public class JpaArtifactManagement implements ArtifactManagement {
return Optional.empty(); 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) { private DbArtifact wrapInEncryptionAwareDbArtifact(final long softwareModuleId, final DbArtifact dbArtifact) {
if (dbArtifact == null) { if (dbArtifact == null) {
return null; return null;

View File

@@ -65,9 +65,9 @@ public class JpaConfirmationManagement extends JpaActionManagement implements Co
* Constructor * Constructor
*/ */
public JpaConfirmationManagement(final TargetRepository targetRepository, public JpaConfirmationManagement(final TargetRepository targetRepository,
final ActionRepository actionRepository, final ActionStatusRepository actionStatusRepository, final ActionRepository actionRepository, final ActionStatusRepository actionStatusRepository,
final RepositoryProperties repositoryProperties, final QuotaManagement quotaManagement, final RepositoryProperties repositoryProperties, final QuotaManagement quotaManagement,
final EntityFactory entityFactory) { final EntityFactory entityFactory) {
super(actionRepository, actionStatusRepository, quotaManagement, repositoryProperties); super(actionRepository, actionStatusRepository, quotaManagement, repositoryProperties);
this.targetRepository = targetRepository; this.targetRepository = targetRepository;
this.entityFactory = entityFactory; this.entityFactory = entityFactory;
@@ -96,7 +96,7 @@ public class JpaConfirmationManagement extends JpaActionManagement implements Co
final AutoConfirmationStatus autoConfStatus = updatedTarget.getAutoConfirmationStatus(); final AutoConfirmationStatus autoConfStatus = updatedTarget.getAutoConfirmationStatus();
if (autoConfStatus == null) { if (autoConfStatus == null) {
final String message = String.format("Persisted auto confirmation status is null. " final String message = String.format("Persisted auto confirmation status is null. "
+ "Cannot proceed with giving confirmations for active actions for device %s with initiator %s.", + "Cannot proceed with giving confirmations for active actions for device %s with initiator %s.",
controllerId, initiator); controllerId, initiator);
log.error("message"); log.error("message");
throw new IllegalStateException(message); throw new IllegalStateException(message);
@@ -159,17 +159,20 @@ public class JpaConfirmationManagement extends JpaActionManagement implements Co
return addActionStatus((JpaActionStatusCreate) statusCreate); return addActionStatus((JpaActionStatusCreate) statusCreate);
} }
private ActionStatusCreate createConfirmationActionStatus(final long actionId, final Integer code, @Override
final Collection<String> messages) { @Transactional
final ActionStatusCreate statusCreate = entityFactory.actionStatus().create(actionId); public void deactivateAutoConfirmation(String controllerId) {
if (!CollectionUtils.isEmpty(messages)) { log.debug("Deactivate auto confirmation for controllerId '{}'", controllerId);
statusCreate.messages(messages); final JpaTarget target = getTargetByControllerIdAndThrowIfNotFound(controllerId);
target.setAutoConfirmationStatus(null);
targetRepository.save(target);
}
@Override
protected void onActionStatusUpdate(final Status updatedActionStatus, final JpaAction action) {
if (updatedActionStatus == Status.RUNNING && action.isActive()) {
action.setStatus(Status.RUNNING);
} }
if (code != null) {
statusCreate.code(code);
statusCreate.message(String.format(CONFIRMATION_CODE_MSG_PREFIX, code));
}
return statusCreate;
} }
private static void assertActionCanAcceptFeedback(final Action action) { 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) { private List<Action> giveConfirmationForActiveActions(final AutoConfirmationStatus autoConfirmationStatus) {
final Target target = autoConfirmationStatus.getTarget(); final Target target = autoConfirmationStatus.getTarget();
return findActiveActionsHavingStatus(target.getControllerId(), Status.WAIT_FOR_CONFIRMATION).stream() return findActiveActionsHavingStatus(target.getControllerId(), Status.WAIT_FOR_CONFIRMATION).stream()
@@ -218,24 +234,8 @@ public class JpaConfirmationManagement extends JpaActionManagement implements Co
return actionRepository.save(action); 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) { private JpaTarget getTargetByControllerIdAndThrowIfNotFound(final String controllerId) {
return targetRepository.findOne(TargetSpecifications.hasControllerId(controllerId)) return targetRepository.findOne(TargetSpecifications.hasControllerId(controllerId))
.orElseThrow(() -> new EntityNotFoundException(Target.class, 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; + ACTION_PAGE_LIMIT;
private static final EnumMap<Database, String> QUERY_DELETE_ACTIONS_BY_STATE_AND_LAST_MODIFIED; 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 EntityManager entityManager;
private final DistributionSetManagement distributionSetManagement; private final DistributionSetManagement distributionSetManagement;
private final TargetRepository targetRepository; private final TargetRepository targetRepository;
@@ -152,6 +142,15 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
private final Database database; private final Database database;
private final RetryTemplate retryTemplate; 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, public JpaDeploymentManagement(final EntityManager entityManager, final ActionRepository actionRepository,
final DistributionSetManagement distributionSetManagement, final TargetRepository targetRepository, final DistributionSetManagement distributionSetManagement, final TargetRepository targetRepository,
final ActionStatusRepository actionStatusRepository, final AuditorAware<String> auditorProvider, final ActionStatusRepository actionStatusRepository, final AuditorAware<String> auditorProvider,
@@ -183,9 +182,18 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
@Override @Override
@Transactional(isolation = Isolation.READ_COMMITTED) @Transactional(isolation = Isolation.READ_COMMITTED)
public List<DistributionSetAssignmentResult> offlineAssignedDistributionSets( public List<DistributionSetAssignmentResult> assignDistributionSets(
final Collection<Entry<String, Long>> assignments) { final List<DeploymentRequest> deploymentRequests) {
return offlineAssignedDistributionSets(assignments,tenantAware.getCurrentUsername()); 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 @Override
@@ -205,18 +213,422 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
@Override @Override
@Transactional(isolation = Isolation.READ_COMMITTED) @Transactional(isolation = Isolation.READ_COMMITTED)
public List<DistributionSetAssignmentResult> assignDistributionSets( public List<DistributionSetAssignmentResult> offlineAssignedDistributionSets(
final List<DeploymentRequest> deploymentRequests) { final Collection<Entry<String, Long>> assignments) {
return assignDistributionSets(tenantAware.getCurrentUsername(), deploymentRequests, null); return offlineAssignedDistributionSets(assignments, tenantAware.getCurrentUsername());
} }
@Override @Override
@Transactional(isolation = Isolation.READ_COMMITTED) @Transactional(isolation = Isolation.READ_COMMITTED)
public List<DistributionSetAssignmentResult> assignDistributionSets(final String initiatedBy, @Retryable(include = {
final List<DeploymentRequest> deploymentRequests, final String actionMessage) { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
WeightValidationHelper.usingContext(systemSecurityContext, tenantConfigurationManagement) public Action cancelAction(final long actionId) {
.validate(deploymentRequests); log.debug("cancelAction({})", actionId);
return assignDistributionSets(initiatedBy, deploymentRequests, actionMessage, onlineDsAssignmentStrategy);
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, private List<DistributionSetAssignmentResult> assignDistributionSets(final String initiatedBy,
@@ -327,12 +739,6 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
return deploymentRequests; 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, private DistributionSetAssignmentResult assignDistributionSetToTargetsWithRetry(final String initiatedBy,
final Long dsId, final Collection<TargetWithActionType> targetsWithActionType, final String actionMessage, final Long dsId, final Collection<TargetWithActionType> targetsWithActionType, final String actionMessage,
final AbstractDsAssignmentStrategy assignmentStrategy) { final AbstractDsAssignmentStrategy assignmentStrategy) {
@@ -355,21 +761,14 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
* to {@link TargetUpdateStatus#IN_SYNC} <br/> * to {@link TargetUpdateStatus#IN_SYNC} <br/>
* D. does not send a {@link TargetAssignDistributionSetEvent}.<br/> * D. does not send a {@link TargetAssignDistributionSetEvent}.<br/>
* *
* @param initiatedBy * @param initiatedBy the username of the user who initiated the assignment
* the username of the user who initiated the assignment * @param dsId the ID of the distribution set to assign
* @param dsId * @param targetsWithActionType a list of all targets and their action type
* the ID of the distribution set to assign * @param actionMessage an optional message to be written into the action status
* @param targetsWithActionType * @param assignmentStrategy the assignment strategy (online /offline)
* 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 * @return the assignment result
* * @throws IncompleteDistributionSetException if mandatory {@link SoftwareModuleType} are not assigned as
* @throws IncompleteDistributionSetException * define by the {@link DistributionSetType}.
* if mandatory {@link SoftwareModuleType} are not assigned as
* define by the {@link DistributionSetType}.
*/ */
private DistributionSetAssignmentResult assignDistributionSetToTargets(final String initiatedBy, final Long dsId, private DistributionSetAssignmentResult assignDistributionSetToTargets(final String initiatedBy, final Long dsId,
final Collection<TargetWithActionType> targetsWithActionType, final String actionMessage, final Collection<TargetWithActionType> targetsWithActionType, final String actionMessage,
@@ -377,7 +776,7 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
final JpaDistributionSet distributionSet = final JpaDistributionSet distributionSet =
(JpaDistributionSet) distributionSetManagement.getValidAndComplete(dsId); (JpaDistributionSet) distributionSetManagement.getValidAndComplete(dsId);
if (((JpaDistributionSetManagement)distributionSetManagement).isImplicitLockApplicable(distributionSet)) { if (((JpaDistributionSetManagement) distributionSetManagement).isImplicitLockApplicable(distributionSet)) {
// without new transaction DS changed event is not thrown // without new transaction DS changed event is not thrown
DeploymentHelper.runInNewTransaction(txManager, "Implicit lock", status -> { DeploymentHelper.runInNewTransaction(txManager, "Implicit lock", status -> {
distributionSetManagement.lock(distributionSet.getId()); distributionSetManagement.lock(distributionSet.getId());
@@ -446,24 +845,6 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
return new ArrayList<>(assignedActions.values()); 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) { private void enforceMaxAssignmentsPerRequest(final int requestedActions) {
QuotaHelper.assertAssignmentRequestSizeQuota(requestedActions, QuotaHelper.assertAssignmentRequestSizeQuota(requestedActions,
quotaManagement.getMaxTargetDistributionSetAssignmentsPerManualAssignment()); 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, private void setAssignedDistributionSetAndTargetUpdateStatus(final AbstractDsAssignmentStrategy assignmentStrategy,
final JpaDistributionSet set, final List<List<Long>> targetIdsChunks) { final JpaDistributionSet set, final List<List<Long>> targetIdsChunks) {
final String currentUser = auditorProvider.getCurrentAuditor().orElse(null); final String currentUser = auditorProvider.getCurrentAuditor().orElse(null);
@@ -581,112 +944,6 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
assignmentStrategy.sendTargetUpdatedEvents(set, targets); 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) { private JpaAction closeActionIfSetWasAlreadyAssigned(final JpaAction action) {
if (isMultiAssignmentsEnabled()) { if (isMultiAssignmentsEnabled()) {
return action; return action;
@@ -764,221 +1021,6 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
actionStatusRepository.save(actionStatus); 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() { private boolean isMultiAssignmentsEnabled() {
return TenantConfigHelper.usingContext(systemSecurityContext, tenantConfigurationManagement) return TenantConfigHelper.usingContext(systemSecurityContext, tenantConfigurationManagement)
.isMultiAssignmentsEnabled(); .isMultiAssignmentsEnabled();
@@ -994,54 +1036,6 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
.runAsSystem(() -> tenantConfigurationManagement.getConfigurationValue(key, valueType).getValue()); .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) { private void assertTargetReadAllowed(final Long targetId) {
if (!targetRepository.existsById(targetId)) { if (!targetRepository.existsById(targetId)) {
throw new EntityNotFoundException(Target.class, 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, private void invalidateDistributionSetsInTransaction(final DistributionSetInvalidation distributionSetInvalidation,
final String tenant) { final String tenant) {
DeploymentHelper.runInNewTransaction(txManager, tenant + "-invalidateDS", status -> { 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) { private long countRolloutsForInvalidation(final Collection<Long> setIds) {
return setIds.stream().mapToLong(rolloutManagement::countByDistributionSetIdAndRolloutIsStoppable).sum(); 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.builder.JpaTagCreate;
import org.eclipse.hawkbit.repository.jpa.configuration.Constants; import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetTag; 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.DistributionSetRepository;
import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetTagRepository; import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetTagRepository;
import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility; import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility;
@@ -49,7 +48,6 @@ import org.springframework.validation.annotation.Validated;
/** /**
* JPA implementation of {@link TargetTagManagement}. * JPA implementation of {@link TargetTagManagement}.
*
*/ */
@Transactional(readOnly = true) @Transactional(readOnly = true)
@Validated @Validated
@@ -76,22 +74,11 @@ public class JpaDistributionSetTagManagement implements DistributionSetTagManage
@Transactional @Transactional
@Retryable(include = { @Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public DistributionSetTag update(final TagUpdate u) { public List<DistributionSetTag> create(final Collection<TagCreate> dst) {
final GenericTagUpdate update = (GenericTagUpdate) u; final List<JpaDistributionSetTag> toCreate = dst.stream().map(JpaTagCreate.class::cast)
.map(JpaTagCreate::buildDistributionSetTag).toList();
final JpaDistributionSetTag tag = distributionSetTagRepository.findById(update.getId()) return Collections
.orElseThrow(() -> new EntityNotFoundException(DistributionSetTag.class, update.getId())); .unmodifiableList(distributionSetTagRepository.saveAll(AccessController.Operation.CREATE, toCreate));
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);
} }
@Override @Override
@@ -107,47 +94,30 @@ public class JpaDistributionSetTagManagement implements DistributionSetTagManage
@Transactional @Transactional
@Retryable(include = { @Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public List<DistributionSetTag> create(final Collection<TagCreate> dst) { public DistributionSetTag update(final TagUpdate u) {
final List<JpaDistributionSetTag> toCreate = dst.stream().map(JpaTagCreate.class::cast) final GenericTagUpdate update = (GenericTagUpdate) u;
.map(JpaTagCreate::buildDistributionSetTag).toList();
return Collections final JpaDistributionSetTag tag = distributionSetTagRepository.findById(update.getId())
.unmodifiableList(distributionSetTagRepository.saveAll(AccessController.Operation.CREATE, toCreate)); .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 @Override
@Transactional @Transactional
@Retryable(include = { @Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public void delete(final String tagName) { public void delete(final long id) {
final JpaDistributionSetTag dsTag = distributionSetTagRepository distributionSetTagRepository.deleteById(id);
.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)));
} }
@Override @Override
@@ -170,26 +140,54 @@ public class JpaDistributionSetTagManagement implements DistributionSetTagManage
return Collections.unmodifiableList(distributionSetTagRepository.findAllById(ids)); return Collections.unmodifiableList(distributionSetTagRepository.findAllById(ids));
} }
@Override
public boolean exists(final long id) {
return distributionSetTagRepository.existsById(id);
}
@Override @Override
public Optional<DistributionSetTag> get(final long id) { public Optional<DistributionSetTag> get(final long id) {
return distributionSetTagRepository.findById(id).map(DistributionSetTag.class::cast); 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 @Override
@Transactional @Transactional
@Retryable(include = { @Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public void delete(final long id) { public void delete(final String tagName) {
distributionSetTagRepository.deleteById(id); final JpaDistributionSetTag dsTag = distributionSetTagRepository
.findOne(DistributionSetTagSpecifications.byName(tagName))
.orElseThrow(() -> new EntityNotFoundException(DistributionSetTag.class, tagName));
distributionSetTagRepository.delete(dsTag);
} }
@Override @Override
public boolean exists(final long id) { public Optional<DistributionSetTag> getByName(final String name) {
return distributionSetTagRepository.existsById(id); return distributionSetTagRepository.findByNameEquals(name);
} }
@Override @Override
public long count() { public Page<DistributionSetTag> findByDistributionSet(final Pageable pageable, final long distributionSetId) {
return distributionSetTagRepository.count(); 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}. * JPA implementation of {@link DistributionSetTypeManagement}.
*
*/ */
@Transactional(readOnly = true) @Transactional(readOnly = true)
@Validated @Validated
@@ -92,6 +91,72 @@ public class JpaDistributionSetTypeManagement implements DistributionSetTypeMana
this.quotaManagement = quotaManagement; 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 @Override
@Transactional @Transactional
@Retryable(include = { @Retryable(include = {
@@ -130,15 +195,66 @@ public class JpaDistributionSetTypeManagement implements DistributionSetTypeMana
return distributionSetTypeRepository.save(type); return distributionSetTypeRepository.save(type);
} }
private void addModuleTypes(final Collection<Long> currentSmTypeIds, final Collection<Long> updatedSmTypeIds, @Override
final Function<SoftwareModuleType, JpaDistributionSetType> addModuleTypeCallback) { public long count() {
final Set<Long> smTypeIdsToAdd = updatedSmTypeIds.stream().filter(id -> !currentSmTypeIds.contains(id)) return distributionSetTypeRepository.count(DistributionSetTypeSpecification.isNotDeleted());
.collect(Collectors.toSet()); }
if (!CollectionUtils.isEmpty(smTypeIdsToAdd)) {
softwareModuleTypeRepository.findAllById(smTypeIdsToAdd).forEach(addModuleTypeCallback::apply); @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, private static void removeModuleTypes(final Collection<Long> currentSmTypeIds,
final Collection<Long> updatedSmTypeIds, final Collection<Long> updatedSmTypeIds,
final LongFunction<JpaDistributionSetType> removeModuleTypeCallback) { final LongFunction<JpaDistributionSetType> removeModuleTypeCallback) {
@@ -149,22 +265,17 @@ public class JpaDistributionSetTypeManagement implements DistributionSetTypeMana
} }
} }
@Override private static boolean hasModuleChanges(final GenericDistributionSetTypeUpdate update) {
@Transactional return update.getOptional().isPresent() || update.getMandatory().isPresent();
@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 private void addModuleTypes(final Collection<Long> currentSmTypeIds, final Collection<Long> updatedSmTypeIds,
@Transactional final Function<SoftwareModuleType, JpaDistributionSetType> addModuleTypeCallback) {
@Retryable(include = { final Set<Long> smTypeIdsToAdd = updatedSmTypeIds.stream().filter(id -> !currentSmTypeIds.contains(id))
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) .collect(Collectors.toSet());
public DistributionSetType assignOptionalSoftwareModuleTypes(final long id, if (!CollectionUtils.isEmpty(smTypeIdsToAdd)) {
final Collection<Long> softwareModulesTypeIds) { softwareModuleTypeRepository.findAllById(smTypeIdsToAdd).forEach(addModuleTypeCallback::apply);
return assignSoftwareModuleTypes(id, softwareModulesTypeIds, false); }
} }
private DistributionSetType assignSoftwareModuleTypes( private DistributionSetType assignSoftwareModuleTypes(
@@ -189,14 +300,10 @@ public class JpaDistributionSetTypeManagement implements DistributionSetTypeMana
/** /**
* Enforces the quota specifying the maximum number of * Enforces the quota specifying the maximum number of
* {@link SoftwareModuleType}s per {@link DistributionSetType}. * {@link SoftwareModuleType}s per {@link DistributionSetType}.
* *
* @param id * @param id of the distribution set type
* of the distribution set type * @param requested number of software module types to check
* @param requested * @throws AssignmentQuotaExceededException if the software module type quota is exceeded
* 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) { private void assertSoftwareModuleTypeQuota(final long id, final int requested) {
QuotaHelper.assertAssignmentQuota(id, requested, QuotaHelper.assertAssignmentQuota(id, requested,
@@ -204,79 +311,6 @@ public class JpaDistributionSetTypeManagement implements DistributionSetTypeMana
DistributionSetType.class, distributionSetTypeRepository::countSmTypesById); 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) { private void unassignDsTypeFromTargetTypes(final long typeId) {
final List<JpaTargetType> targetTypesByDsType = targetTypeRepository.findByDsType(typeId); final List<JpaTargetType> targetTypesByDsType = targetTypeRepository.findByDsType(typeId);
targetTypesByDsType.forEach(targetType -> { 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) { private JpaDistributionSetType findDistributionSetTypeAndThrowExceptionIfNotFound(final Long setId) {
return (JpaDistributionSetType) get(setId) return (JpaDistributionSetType) get(setId)
.orElseThrow(() -> new EntityNotFoundException(DistributionSetType.class, 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) { private void checkDistributionSetTypeNotAssigned(final Long id) {
if (distributionSetRepository.countByTypeId(id) > 0) { if (distributionSetRepository.countByTypeId(id) > 0) {
throw new EntityReadOnlyException(String.format( throw new EntityReadOnlyException(String.format(
"Distribution set type %s is already assigned to distribution sets and cannot be changed!", id)); "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

@@ -91,10 +91,10 @@ public class JpaRolloutGroupManagement implements RolloutGroupManagement {
private final Database database; private final Database database;
public JpaRolloutGroupManagement(final RolloutGroupRepository rolloutGroupRepository, public JpaRolloutGroupManagement(final RolloutGroupRepository rolloutGroupRepository,
final RolloutRepository rolloutRepository, final ActionRepository actionRepository, final RolloutRepository rolloutRepository, final ActionRepository actionRepository,
final TargetRepository targetRepository, final EntityManager entityManager, final TargetRepository targetRepository, final EntityManager entityManager,
final VirtualPropertyReplacer virtualPropertyReplacer, final RolloutStatusCache rolloutStatusCache, final VirtualPropertyReplacer virtualPropertyReplacer, final RolloutStatusCache rolloutStatusCache,
final Database database) { final Database database) {
this.rolloutGroupRepository = rolloutGroupRepository; this.rolloutGroupRepository = rolloutGroupRepository;
this.rolloutRepository = rolloutRepository; this.rolloutRepository = rolloutRepository;
this.actionRepository = actionRepository; this.actionRepository = actionRepository;
@@ -105,37 +105,6 @@ public class JpaRolloutGroupManagement implements RolloutGroupManagement {
this.database = database; 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 @Override
public Page<RolloutGroup> findByRolloutWithDetailedStatus(final Pageable pageable, final long rolloutId) { public Page<RolloutGroup> findByRolloutWithDetailedStatus(final Pageable pageable, final long rolloutId) {
throwEntityNotFoundExceptionIfRolloutDoesNotExist(rolloutId); throwEntityNotFoundExceptionIfRolloutDoesNotExist(rolloutId);
@@ -154,7 +123,7 @@ public class JpaRolloutGroupManagement implements RolloutGroupManagement {
for (final JpaRolloutGroup rolloutGroup : rolloutGroups) { for (final JpaRolloutGroup rolloutGroup : rolloutGroups) {
final TotalTargetCountStatus totalTargetCountStatus = new TotalTargetCountStatus( final TotalTargetCountStatus totalTargetCountStatus = new TotalTargetCountStatus(
allStatesForRollout.get(rolloutGroup.getId()), (long)rolloutGroup.getTotalTargets(), allStatesForRollout.get(rolloutGroup.getId()), (long) rolloutGroup.getTotalTargets(),
rolloutGroup.getRollout().getActionType()); rolloutGroup.getRollout().getActionType());
rolloutGroup.setTotalTargetCountStatus(totalTargetCountStatus); rolloutGroup.setTotalTargetCountStatus(totalTargetCountStatus);
} }
@@ -162,6 +131,49 @@ public class JpaRolloutGroupManagement implements RolloutGroupManagement {
return JpaManagementHelper.convertPage(rolloutGroups, pageable); 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 @Override
public Page<RolloutGroup> findByRolloutAndRsqlWithDetailedStatus(final Pageable pageable, final long rolloutId, public Page<RolloutGroup> findByRolloutAndRsqlWithDetailedStatus(final Pageable pageable, final long rolloutId,
final String rsqlParam) { final String rsqlParam) {
@@ -189,6 +201,53 @@ public class JpaRolloutGroupManagement implements RolloutGroupManagement {
return JpaManagementHelper.convertPage(rolloutGroups, pageable); 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 @Override
public Optional<RolloutGroup> getWithDetailedStatus(final long rolloutGroupId) { public Optional<RolloutGroup> getWithDetailedStatus(final long rolloutGroupId) {
final Optional<RolloutGroup> rolloutGroup = get(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) { private Map<Long, List<TotalTargetCountActionStatus>> getStatusCountItemForRolloutGroup(final List<Long> groupIds) {
final Map<Long, List<TotalTargetCountActionStatus>> fromCache = rolloutStatusCache final Map<Long, List<TotalTargetCountActionStatus>> fromCache = rolloutStatusCache
.getRolloutGroupStatus(groupIds); .getRolloutGroupStatus(groupIds);
@@ -235,68 +316,6 @@ public class JpaRolloutGroupManagement implements RolloutGroupManagement {
return fromCache; 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, private Predicate getRolloutGroupTargetWithRolloutGroupJoinCondition(final long rolloutGroupId,
final CriteriaBuilder cb, final Root<RolloutTargetGroup> targetRoot) { final CriteriaBuilder cb, final Root<RolloutTargetGroup> targetRoot) {
return cb.equal(targetRoot.get(RolloutTargetGroup_.rolloutGroup).get(JpaRolloutGroup_.id), // return cb.equal(targetRoot.get(RolloutTargetGroup_.rolloutGroup).get(JpaRolloutGroup_.id), //
@@ -327,29 +346,10 @@ public class JpaRolloutGroupManagement implements RolloutGroupManagement {
}).collect(Collectors.toList()); }).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) { private void throwExceptionIfRolloutGroupDoesNotExist(final Long rolloutGroupId) {
if (!rolloutGroupRepository.existsById(rolloutGroupId)) { if (!rolloutGroupRepository.existsById(rolloutGroupId)) {
throw new EntityNotFoundException(RolloutGroup.class, 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.ConstraintDeclarationException;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import jakarta.validation.ValidationException; import jakarta.validation.ValidationException;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.ContextAware;
import org.eclipse.hawkbit.repository.DistributionSetManagement; import org.eclipse.hawkbit.repository.DistributionSetManagement;
import org.eclipse.hawkbit.repository.QuotaManagement; import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.RepositoryProperties; 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.RolloutStatusCache;
import org.eclipse.hawkbit.repository.TargetManagement; import org.eclipse.hawkbit.repository.TargetManagement;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement; import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.ContextAware;
import org.eclipse.hawkbit.repository.builder.DynamicRolloutGroupTemplate; import org.eclipse.hawkbit.repository.builder.DynamicRolloutGroupTemplate;
import org.eclipse.hawkbit.repository.builder.GenericRolloutUpdate; import org.eclipse.hawkbit.repository.builder.GenericRolloutUpdate;
import org.eclipse.hawkbit.repository.builder.RolloutCreate; 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, private static final List<RolloutStatus> ROLLOUT_STATUS_STOPPABLE = Arrays.asList(RolloutStatus.RUNNING,
RolloutStatus.CREATING, RolloutStatus.PAUSED, RolloutStatus.READY, RolloutStatus.STARTING, RolloutStatus.CREATING, RolloutStatus.PAUSED, RolloutStatus.READY, RolloutStatus.STARTING,
RolloutStatus.WAITING_FOR_APPROVAL, RolloutStatus.APPROVAL_DENIED); 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 TargetManagement targetManagement;
private final DistributionSetManagement distributionSetManagement; private final DistributionSetManagement distributionSetManagement;
private final VirtualPropertyReplacer virtualPropertyReplacer; private final VirtualPropertyReplacer virtualPropertyReplacer;
@@ -144,6 +119,22 @@ public class JpaRolloutManagement implements RolloutManagement {
private final EventPublisherHolder eventPublisherHolder; private final EventPublisherHolder eventPublisherHolder;
private final ContextAware contextAware; private final ContextAware contextAware;
private final Database database; 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, public JpaRolloutManagement(final TargetManagement targetManagement,
final DistributionSetManagement distributionSetManagement, final EventPublisherHolder eventPublisherHolder, final DistributionSetManagement distributionSetManagement, final EventPublisherHolder eventPublisherHolder,
@@ -163,34 +154,29 @@ public class JpaRolloutManagement implements RolloutManagement {
this.contextAware = contextAware; this.contextAware = contextAware;
} }
@Override public static String createRolloutLockKey(final String tenant) {
public Page<Rollout> findAll(final Pageable pageable, final boolean deleted) { return tenant + "-rollout";
return JpaManagementHelper.findAllWithCountBySpec(rolloutRepository, pageable, Collections }
.singletonList(RolloutSpecification.isDeletedWithDistributionSet(deleted, pageable.getSort())));
public void publishRolloutGroupCreatedEventAfterCommit(final RolloutGroup group, final Rollout rollout) {
afterCommit.afterCommit(() -> eventPublisherHolder.getEventPublisher().publishEvent(
new RolloutGroupCreatedEvent(group, rollout.getId(), eventPublisherHolder.getApplicationId())));
} }
@Override @Override
public Page<Rollout> findByRsql(final Pageable pageable, final String rsqlParam, final boolean deleted) { public long count() {
final List<Specification<JpaRollout>> specList = new ArrayList<>(2); return rolloutRepository.count(
specList.add( RolloutSpecification.isDeletedWithDistributionSet(false, Sort.by(Direction.DESC, JpaRollout_.ID)));
RSQLUtility.buildRsqlSpecification(rsqlParam, RolloutFields.class, virtualPropertyReplacer, database));
specList.add(RolloutSpecification.isDeletedWithDistributionSet(deleted, pageable.getSort()));
return JpaManagementHelper.findAllWithCountBySpec(rolloutRepository, pageable, specList);
} }
@Override @Override
public Optional<Rollout> get(final long rolloutId) { public long countByFilters(final String searchText) {
return rolloutRepository.findById(rolloutId).map(Rollout.class::cast); return rolloutRepository.count(RolloutSpecification.likeName(searchText, false));
} }
@Override @Override
@Transactional public long countByDistributionSetIdAndRolloutIsStoppable(final long setId) {
@Retryable(include = { return rolloutRepository.countByDistributionSetIdAndStatusIn(setId, ROLLOUT_STATUS_STOPPABLE);
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 @Override
@@ -204,7 +190,7 @@ public class JpaRolloutManagement implements RolloutManagement {
} else if (amountGroup == 0) { } else if (amountGroup == 0) {
if (dynamicRolloutGroupTemplate == null) { if (dynamicRolloutGroupTemplate == null) {
throw new ValidationException( throw new ValidationException(
"When amount of groups is 0, the rollouts shall be dynamic and a dynamic group template must be provided"); "When amount of groups is 0, the rollouts shall be dynamic and a dynamic group template must be provided");
} }
} else { } else {
RolloutHelper.verifyRolloutGroupAmount(amountGroup, quotaManagement); RolloutHelper.verifyRolloutGroupAmount(amountGroup, quotaManagement);
@@ -219,6 +205,15 @@ public class JpaRolloutManagement implements RolloutManagement {
return createRolloutGroups(amountGroup, conditions, savedRollout, confirmationRequired, dynamicRolloutGroupTemplate); 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 @Override
@Transactional @Transactional
@Retryable(include = { @Retryable(include = {
@@ -234,6 +229,314 @@ public class JpaRolloutManagement implements RolloutManagement {
return createRolloutGroups(groups, conditions, savedRollout); 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) { private JpaRollout createRollout(final JpaRollout rollout, final boolean pureDynamic) {
WeightValidationHelper.usingContext(systemSecurityContext, tenantConfigurationManagement).validate(rollout); WeightValidationHelper.usingContext(systemSecurityContext, tenantConfigurationManagement).validate(rollout);
final JpaDistributionSet distributionSet = (JpaDistributionSet) rollout.getDistributionSet(); final JpaDistributionSet distributionSet = (JpaDistributionSet) rollout.getDistributionSet();
@@ -259,7 +562,7 @@ public class JpaRolloutManagement implements RolloutManagement {
rollout.setTotalTargets(totalTargets); rollout.setTotalTargets(totalTargets);
} }
if (((JpaDistributionSetManagement)distributionSetManagement).isImplicitLockApplicable(distributionSet)) { if (((JpaDistributionSetManagement) distributionSetManagement).isImplicitLockApplicable(distributionSet)) {
distributionSetManagement.lock(distributionSet.getId()); distributionSetManagement.lock(distributionSet.getId());
} }
@@ -391,253 +694,11 @@ public class JpaRolloutManagement implements RolloutManagement {
return rolloutRepository.save(savedRollout); 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) { private JpaRollout getRolloutOrThrowExceptionIfNotFound(final Long rolloutId) {
return rolloutRepository.findById(rolloutId) return rolloutRepository.findById(rolloutId)
.orElseThrow(() -> new EntityNotFoundException(Rollout.class, 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) { private Map<Long, List<TotalTargetCountActionStatus>> getStatusCountItemForRollout(final List<Long> rollouts) {
if (rollouts.isEmpty()) { if (rollouts.isEmpty()) {
return null; return null;
@@ -662,60 +723,32 @@ public class JpaRolloutManagement implements RolloutManagement {
return fromCache; 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 * Enforces the quota defining the maximum number of {@link Target}s per
* {@link RolloutGroup}. * {@link RolloutGroup}.
* *
* @param requested * @param requested number of targets to check
* number of targets to check
*/ */
private void assertTargetsPerRolloutGroupQuota(final long requested) { private void assertTargetsPerRolloutGroupQuota(final long requested) {
final int quota = quotaManagement.getMaxTargetsPerRolloutGroup(); final int quota = quotaManagement.getMaxTargetsPerRolloutGroup();
QuotaHelper.assertAssignmentQuota(requested, quota, Target.class, RolloutGroup.class); 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, private RolloutGroupsValidation validateTargetsInGroups(final List<RolloutGroup> groups, final String baseFilter,
final long totalTargets, final Long dsTypeId) { final long totalTargets, final Long dsTypeId) {
final List<Long> groupTargetCounts = new ArrayList<>(groups.size()); final List<Long> groupTargetCounts = new ArrayList<>(groups.size());
Map<String, Long> targetFilterCounts; Map<String, Long> targetFilterCounts;
if (!RolloutHelper.isRolloutRetried(baseFilter)) { if (!RolloutHelper.isRolloutRetried(baseFilter)) {
targetFilterCounts = groups.stream() targetFilterCounts = groups.stream()
.map(group -> RolloutHelper.getGroupTargetFilter(baseFilter, group)).distinct() .map(group -> RolloutHelper.getGroupTargetFilter(baseFilter, group)).distinct()
.collect(Collectors.toMap(Function.identity(), .collect(Collectors.toMap(Function.identity(),
groupTargetFilter -> targetManagement.countByRsqlAndCompatible(groupTargetFilter, dsTypeId))); groupTargetFilter -> targetManagement.countByRsqlAndCompatible(groupTargetFilter, dsTypeId)));
} else { } else {
targetFilterCounts = groups.stream() targetFilterCounts = groups.stream()
.map(group -> RolloutHelper.getGroupTargetFilter(baseFilter, group)).distinct() .map(group -> RolloutHelper.getGroupTargetFilter(baseFilter, group)).distinct()
.collect(Collectors.toMap(Function.identity(), .collect(Collectors.toMap(Function.identity(),
groupTargetFilter -> targetManagement.countByFailedInRollout( groupTargetFilter -> targetManagement.countByFailedInRollout(
RolloutHelper.getIdFromRetriedTargetFilter(baseFilter), dsTypeId))); RolloutHelper.getIdFromRetriedTargetFilter(baseFilter), dsTypeId)));
} }
long unusedTargetsCount = 0; long unusedTargetsCount = 0;
@@ -786,51 +819,6 @@ public class JpaRolloutManagement implements RolloutManagement {
return totalTargets - validation.getTargetsInGroups(); 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) { private TargetCount calculateTargets(final String targetFilter, final Long createdAt, final Long dsTypeId) {
String baseFilter; String baseFilter;
long totalTargets; long totalTargets;
@@ -839,7 +827,7 @@ public class JpaRolloutManagement implements RolloutManagement {
totalTargets = targetManagement.countByRsqlAndCompatible(baseFilter, dsTypeId); totalTargets = targetManagement.countByRsqlAndCompatible(baseFilter, dsTypeId);
} else { } else {
totalTargets = targetManagement.countByFailedInRollout( totalTargets = targetManagement.countByFailedInRollout(
RolloutHelper.getIdFromRetriedTargetFilter(targetFilter), dsTypeId); RolloutHelper.getIdFromRetriedTargetFilter(targetFilter), dsTypeId);
baseFilter = targetFilter; baseFilter = targetFilter;
} }

View File

@@ -22,15 +22,6 @@ import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import jakarta.persistence.EntityManager; 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.ArtifactEncryptionService;
import org.eclipse.hawkbit.repository.ArtifactManagement; 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.builder.SoftwareModuleUpdate;
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException; import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; 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.JpaManagementHelper;
import org.eclipse.hawkbit.repository.jpa.acm.AccessController; import org.eclipse.hawkbit.repository.jpa.acm.AccessController;
import org.eclipse.hawkbit.repository.jpa.builder.JpaSoftwareModuleCreate; import org.eclipse.hawkbit.repository.jpa.builder.JpaSoftwareModuleCreate;
import org.eclipse.hawkbit.repository.jpa.builder.JpaSoftwareModuleMetadataCreate; import org.eclipse.hawkbit.repository.jpa.builder.JpaSoftwareModuleMetadataCreate;
import org.eclipse.hawkbit.repository.jpa.configuration.Constants; 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.JpaSoftwareModule;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModuleMetadata; import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModuleMetadata;
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.specifications.SoftwareModuleSpecification;
import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper; import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper;
import org.eclipse.hawkbit.repository.model.Artifact; 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.DistributionSet;
import org.eclipse.hawkbit.repository.model.SoftwareModule; import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.SoftwareModuleMetadata; 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.PageRequest;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice; 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.domain.Specification;
import org.springframework.data.jpa.repository.query.QueryUtils;
import org.springframework.orm.jpa.vendor.Database; import org.springframework.orm.jpa.vendor.Database;
import org.springframework.retry.annotation.Backoff; import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable; import org.springframework.retry.annotation.Retryable;
@@ -133,23 +117,20 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement {
@Transactional @Transactional
@Retryable(include = { @Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public SoftwareModule update(final SoftwareModuleUpdate u) { public List<SoftwareModule> create(final Collection<SoftwareModuleCreate> swModules) {
final GenericSoftwareModuleUpdate update = (GenericSoftwareModuleUpdate) u; final List<JpaSoftwareModule> modulesToCreate = swModules.stream().map(JpaSoftwareModuleCreate.class::cast)
.map(JpaSoftwareModuleCreate::build).toList();
final JpaSoftwareModule module = softwareModuleRepository.findById(update.getId()) final List<SoftwareModule> createdModules = Collections
.orElseThrow(() -> new EntityNotFoundException(SoftwareModule.class, update.getId())); .unmodifiableList(softwareModuleRepository.saveAll(AccessController.Operation.CREATE, modulesToCreate));
update.getDescription().ifPresent(module::setDescription); if (createdModules.stream().anyMatch(SoftwareModule::isEncrypted)) {
update.getVendor().ifPresent(module::setVendor); entityManager.flush();
createdModules.stream().filter(SoftwareModule::isEncrypted).map(SoftwareModule::getId)
// lock/unlock ONLY if locked flag is present! .forEach(encryptedModuleId -> ArtifactEncryptionService.getInstance()
if (Boolean.TRUE.equals(update.locked())) { .addSoftwareModuleEncryptionSecrets(encryptedModuleId));
module.lock();
} else if (Boolean.FALSE.equals(update.locked())) {
module.unlock();
} }
return createdModules;
return softwareModuleRepository.save(module);
} }
@Override @Override
@@ -173,61 +154,36 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement {
@Transactional @Transactional
@Retryable(include = { @Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public List<SoftwareModule> create(final Collection<SoftwareModuleCreate> swModules) { public SoftwareModule update(final SoftwareModuleUpdate u) {
final List<JpaSoftwareModule> modulesToCreate = swModules.stream().map(JpaSoftwareModuleCreate.class::cast) final GenericSoftwareModuleUpdate update = (GenericSoftwareModuleUpdate) u;
.map(JpaSoftwareModuleCreate::build).toList();
final List<SoftwareModule> createdModules = Collections final JpaSoftwareModule module = softwareModuleRepository.findById(update.getId())
.unmodifiableList(softwareModuleRepository.saveAll(AccessController.Operation.CREATE, modulesToCreate)); .orElseThrow(() -> new EntityNotFoundException(SoftwareModule.class, update.getId()));
if (createdModules.stream().anyMatch(SoftwareModule::isEncrypted)) { update.getDescription().ifPresent(module::setDescription);
entityManager.flush(); update.getVendor().ifPresent(module::setVendor);
createdModules.stream().filter(SoftwareModule::isEncrypted).map(SoftwareModule::getId)
.forEach(encryptedModuleId -> ArtifactEncryptionService.getInstance() // lock/unlock ONLY if locked flag is present!
.addSoftwareModuleEncryptionSecrets(encryptedModuleId)); if (Boolean.TRUE.equals(update.locked())) {
module.lock();
} else if (Boolean.FALSE.equals(update.locked())) {
module.unlock();
} }
return createdModules;
return softwareModuleRepository.save(module);
} }
@Override @Override
public Slice<SoftwareModule> findByType(final Pageable pageable, final long typeId) { public long count() {
assertSoftwareModuleTypeExists(typeId); return softwareModuleRepository.count(SoftwareModuleSpecification.isNotDeleted());
return JpaManagementHelper.findAllWithoutCountBySpec(
softwareModuleRepository,
pageable,
List.of(
SoftwareModuleSpecification.equalType(typeId),
SoftwareModuleSpecification.isNotDeleted()));
} }
@Override @Override
public Optional<SoftwareModule> get(final long id) { @Transactional
return softwareModuleRepository.findById(id).map(SoftwareModule.class::cast); @Retryable(include = {
} ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public void delete(final long id) {
@Override delete(List.of(id));
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());
}
} }
@Override @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 @Override
public Slice<SoftwareModule> findAll(final Pageable pageable) { public Slice<SoftwareModule> findAll(final Pageable pageable) {
return JpaManagementHelper.findAllWithoutCountBySpec(softwareModuleRepository, pageable, List.of( return JpaManagementHelper.findAllWithoutCountBySpec(softwareModuleRepository, pageable, List.of(
@@ -286,11 +257,6 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement {
SoftwareModuleSpecification.fetchType())); SoftwareModuleSpecification.fetchType()));
} }
@Override
public long count() {
return softwareModuleRepository.count(SoftwareModuleSpecification.isNotDeleted());
}
@Override @Override
public Page<SoftwareModule> findByRsql(final Pageable pageable, final String rsqlParam) { public Page<SoftwareModule> findByRsql(final Pageable pageable, final String rsqlParam) {
return JpaManagementHelper.findAllWithCountBySpec(softwareModuleRepository, pageable, List.of( return JpaManagementHelper.findAllWithCountBySpec(softwareModuleRepository, pageable, List.of(
@@ -299,70 +265,6 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement {
SoftwareModuleSpecification.isNotDeleted())); 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 @Override
@Transactional @Transactional
@Retryable(include = { @Retryable(include = {
@@ -400,56 +302,21 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement {
return Collections.emptyList(); 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 @Override
@Transactional @Transactional
@Retryable(include = { @Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public SoftwareModuleMetadata updateMetaData(final SoftwareModuleMetadataUpdate u) { public SoftwareModuleMetadata createMetaData(final SoftwareModuleMetadataCreate c) {
final GenericSoftwareModuleMetadataUpdate update = (GenericSoftwareModuleMetadataUpdate) u; final JpaSoftwareModuleMetadataCreate create = (JpaSoftwareModuleMetadataCreate) c;
final Long id = create.getSoftwareModuleId();
// check if exists otherwise throw entity not found exception assertSoftwareModuleExists(id);
final JpaSoftwareModuleMetadata metadata = (JpaSoftwareModuleMetadata) getMetaDataBySoftwareModuleId( assertMetaDataQuota(id, 1);
update.getSoftwareModuleId(), update.getKey())
.orElseThrow(() -> new EntityNotFoundException(SoftwareModuleMetadata.class,
update.getSoftwareModuleId(), update.getKey()));
update.getValue().ifPresent(metadata::setValue); // touch to update revision and last modified timestamp
update.isTargetVisible().ifPresent(metadata::setTargetVisible); JpaManagementHelper.touch(entityManager, softwareModuleRepository, (JpaSoftwareModule) get(id)
.orElseThrow(() -> new EntityNotFoundException(SoftwareModule.class, id)));
JpaManagementHelper.touch(entityManager, softwareModuleRepository, return saveMetadata(create);
(JpaSoftwareModule) metadata.getSoftwareModule());
return softwareModuleMetadataRepository.save(metadata);
} }
@Override @Override
@@ -466,14 +333,61 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement {
} }
@Override @Override
public Page<SoftwareModuleMetadata> findMetaDataByRsql(final Pageable pageable, final long id, public Page<SoftwareModule> findByAssignedTo(final Pageable pageable, final long distributionSetId) {
final String rsqlParam) { 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); assertSoftwareModuleExists(id);
final List<Specification<JpaSoftwareModuleMetadata>> specList = Arrays return softwareModuleMetadataRepository.findById(new SwMetadataCompositeKey(id, key))
.asList(RSQLUtility.buildRsqlSpecification(rsqlParam, SoftwareModuleMetadataFields.class, .map(SoftwareModuleMetadata.class::cast);
virtualPropertyReplacer, database), metadataBySoftwareModuleIdSpec(id));
return JpaManagementHelper.findAllWithCountBySpec(softwareModuleMetadataRepository, pageable, specList);
} }
@Override @Override
@@ -492,11 +406,35 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement {
} }
@Override @Override
public Optional<SoftwareModuleMetadata> getMetaDataBySoftwareModuleId(final long id, final String key) { public Page<SoftwareModuleMetadata> findMetaDataBySoftwareModuleIdAndTargetVisible(final Pageable pageable,
final long id) {
assertSoftwareModuleExists(id); assertSoftwareModuleExists(id);
return softwareModuleMetadataRepository.findById(new SwMetadataCompositeKey(id, key)) return JpaManagementHelper.convertPage(softwareModuleMetadataRepository.findBySoftwareModuleIdAndTargetVisible(
.map(SoftwareModuleMetadata.class::cast); 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 @Override
@@ -531,22 +469,71 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement {
@Transactional @Transactional
@Retryable(include = { @Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public void delete(final long id) { public SoftwareModuleMetadata updateMetaData(final SoftwareModuleMetadataUpdate u) {
delete(List.of(id)); 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 private static Stream<JpaSoftwareModuleMetadataCreate> createJpaMetadataCreateStream(
public boolean exists(final long id) { final Collection<SoftwareModuleMetadataCreate> create) {
return softwareModuleRepository.existsById(id); return create.stream().map(JpaSoftwareModuleMetadataCreate.class::cast);
} }
@Override private static Specification<JpaSoftwareModuleMetadata> metadataBySoftwareModuleIdSpec(final long id) {
public Page<SoftwareModuleMetadata> findMetaDataBySoftwareModuleIdAndTargetVisible(final Pageable pageable, return (root, query, cb) -> cb
final long id) { .equal(root.get(JpaSoftwareModuleMetadata_.softwareModule).get(JpaSoftwareModule_.id), id);
assertSoftwareModuleExists(id); }
return JpaManagementHelper.convertPage(softwareModuleMetadataRepository.findBySoftwareModuleIdAndTargetVisible( private void deleteGridFsArtifacts(final JpaSoftwareModule swModule) {
PageRequest.of(0, RepositoryConstants.MAX_META_DATA_COUNT), id, true), pageable); 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) { private void assertSoftwareModuleExists(final Long id) {
@@ -566,9 +553,4 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement {
throw new EntityNotFoundException(DistributionSet.class, distributionSetId); 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}. * JPA implementation of {@link SoftwareModuleTypeManagement}.
*
*/ */
@Transactional(readOnly = true) @Transactional(readOnly = true)
@Validated @Validated
@@ -68,6 +67,37 @@ public class JpaSoftwareModuleTypeManagement implements SoftwareModuleTypeManage
this.database = database; this.database = database;
} }
@Override
public Optional<SoftwareModuleType> getByKey(final String key) {
return softwareModuleTypeRepository.findByKey(key);
}
@Override
public Optional<SoftwareModuleType> getByName(final String name) {
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 = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public SoftwareModuleType create(final SoftwareModuleTypeCreate c) {
final JpaSoftwareModuleTypeCreate create = (JpaSoftwareModuleTypeCreate) c;
return softwareModuleTypeRepository.save(AccessController.Operation.CREATE, create.build());
}
@Override @Override
@Transactional @Transactional
@Retryable(include = { @Retryable(include = {
@@ -84,46 +114,11 @@ public class JpaSoftwareModuleTypeManagement implements SoftwareModuleTypeManage
return softwareModuleTypeRepository.save(type); 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 @Override
public long count() { public long count() {
return softwareModuleTypeRepository.count(SoftwareModuleTypeSpecification.isNotDeleted()); return softwareModuleTypeRepository.count(SoftwareModuleTypeSpecification.isNotDeleted());
} }
@Override
public Optional<SoftwareModuleType> getByKey(final String key) {
return softwareModuleTypeRepository.findByKey(key);
}
@Override
public Optional<SoftwareModuleType> getByName(final String name) {
return softwareModuleTypeRepository.findByName(name);
}
@Override
@Transactional
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public SoftwareModuleType create(final SoftwareModuleTypeCreate c) {
final JpaSoftwareModuleTypeCreate create = (JpaSoftwareModuleTypeCreate) c;
return softwareModuleTypeRepository.save(AccessController.Operation.CREATE, create.build());
}
@Override @Override
@Transactional @Transactional
@Retryable(include = { @Retryable(include = {
@@ -135,17 +130,6 @@ public class JpaSoftwareModuleTypeManagement implements SoftwareModuleTypeManage
delete(toDelete); delete(toDelete);
} }
@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 @Override
@Transactional @Transactional
@Retryable(include = { @Retryable(include = {
@@ -161,14 +145,29 @@ public class JpaSoftwareModuleTypeManagement implements SoftwareModuleTypeManage
return Collections.unmodifiableList(softwareModuleTypeRepository.findAllById(ids)); return Collections.unmodifiableList(softwareModuleTypeRepository.findAllById(ids));
} }
@Override
public boolean exists(final long id) {
return softwareModuleTypeRepository.existsById(id);
}
@Override @Override
public Optional<SoftwareModuleType> get(final long id) { public Optional<SoftwareModuleType> get(final long id) {
return softwareModuleTypeRepository.findById(id).map(SoftwareModuleType.class::cast); return softwareModuleTypeRepository.findById(id).map(SoftwareModuleType.class::cast);
} }
@Override @Override
public boolean exists(final long id) { public Slice<SoftwareModuleType> findAll(final Pageable pageable) {
return softwareModuleTypeRepository.existsById(id); 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) { private void delete(JpaSoftwareModuleType toDelete) {

View File

@@ -74,81 +74,57 @@ import org.springframework.validation.annotation.Validated;
public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, SystemManagement { public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, SystemManagement {
private static final int MAX_TENANTS_QUERY = 1000; private static final int MAX_TENANTS_QUERY = 1000;
private final String countArtifactQuery;
private final String countSoftwareModulesQuery;
@Autowired @Autowired
private EntityManager entityManager; private EntityManager entityManager;
@Autowired @Autowired
private TargetRepository targetRepository; private TargetRepository targetRepository;
@Autowired @Autowired
private TargetFilterQueryRepository targetFilterQueryRepository; private TargetFilterQueryRepository targetFilterQueryRepository;
@Autowired @Autowired
private DistributionSetRepository distributionSetRepository; private DistributionSetRepository distributionSetRepository;
@Autowired @Autowired
private SoftwareModuleRepository softwareModuleRepository; private SoftwareModuleRepository softwareModuleRepository;
@Autowired @Autowired
private TenantMetaDataRepository tenantMetaDataRepository; private TenantMetaDataRepository tenantMetaDataRepository;
@Autowired @Autowired
private DistributionSetTypeRepository distributionSetTypeRepository; private DistributionSetTypeRepository distributionSetTypeRepository;
@Autowired @Autowired
private SoftwareModuleTypeRepository softwareModuleTypeRepository; private SoftwareModuleTypeRepository softwareModuleTypeRepository;
@Autowired @Autowired
private TargetTagRepository targetTagRepository; private TargetTagRepository targetTagRepository;
@Autowired @Autowired
private TargetTypeRepository targetTypeRepository; private TargetTypeRepository targetTypeRepository;
@Autowired @Autowired
private DistributionSetTagRepository distributionSetTagRepository; private DistributionSetTagRepository distributionSetTagRepository;
@Autowired @Autowired
private TenantConfigurationRepository tenantConfigurationRepository; private TenantConfigurationRepository tenantConfigurationRepository;
@Autowired @Autowired
private RolloutRepository rolloutRepository; private RolloutRepository rolloutRepository;
@Autowired @Autowired
private TenantAware tenantAware; private TenantAware tenantAware;
@Autowired @Autowired
private TenantStatsManagement systemStatsManagement; private TenantStatsManagement systemStatsManagement;
@Autowired @Autowired
private TenancyCacheManager cacheManager; private TenancyCacheManager cacheManager;
@Autowired @Autowired
private SystemManagementCacheKeyGenerator currentTenantCacheKeyGenerator; private SystemManagementCacheKeyGenerator currentTenantCacheKeyGenerator;
@Autowired @Autowired
private SystemSecurityContext systemSecurityContext; private SystemSecurityContext systemSecurityContext;
@Autowired @Autowired
private PlatformTransactionManager txManager; private PlatformTransactionManager txManager;
@Autowired @Autowired
private RolloutStatusCache rolloutStatusCache; private RolloutStatusCache rolloutStatusCache;
@Autowired @Autowired
private ArtifactRepository artifactRepository; private ArtifactRepository artifactRepository;
@Autowired @Autowired
private RepositoryProperties repositoryProperties; private RepositoryProperties repositoryProperties;
private final String countArtifactQuery;
private final String countSoftwareModulesQuery;
/** /**
* Constructor. * Constructor.
* *
* @param properties * @param properties properties to get the underlying database
* properties to get the underlying database
*/ */
public JpaSystemManagement(final JpaProperties properties) { public JpaSystemManagement(final JpaProperties properties) {
@@ -159,48 +135,6 @@ public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, Syst
+ isDeleted; + 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 @Override
@Transactional(propagation = Propagation.SUPPORTS) @Transactional(propagation = Propagation.SUPPORTS)
public KeyGenerator currentTenantKeyGenerator() { public KeyGenerator currentTenantKeyGenerator() {
@@ -208,18 +142,13 @@ public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, Syst
} }
@Override @Override
public TenantMetaData createTenantMetadata(final String tenant) { @Cacheable(value = "currentTenant", keyGenerator = "currentTenantKeyGenerator", cacheManager = "directCacheManager", unless = "#result == null")
final TenantMetaData result = tenantMetaDataRepository.findByTenantIgnoreCase(tenant); public String currentTenant() {
// Create if it does not exist return currentTenantCacheKeyGenerator.getTenantInCreation().orElseGet(() -> {
if (result == null) { final TenantMetaData findByTenant = tenantMetaDataRepository
return createTenantMetadata0(tenant); .findByTenantIgnoreCase(tenantAware.getCurrentTenant());
} return findByTenant != null ? findByTenant.getTenant() : null;
return result; });
}
@Override
public Page<String> findTenants(final Pageable pageable) {
return tenantMetaDataRepository.findTenants(pageable);
} }
@Override @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 @Override
public TenantMetaData getTenantMetadata() { public TenantMetaData getTenantMetadata() {
final String tenant = tenantAware.getCurrentTenant(); final String tenant = tenantAware.getCurrentTenant();
@@ -270,13 +263,13 @@ public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, Syst
} }
@Override @Override
@Cacheable(value = "currentTenant", keyGenerator = "currentTenantKeyGenerator", cacheManager = "directCacheManager", unless = "#result == null") public TenantMetaData createTenantMetadata(final String tenant) {
public String currentTenant() { final TenantMetaData result = tenantMetaDataRepository.findByTenantIgnoreCase(tenant);
return currentTenantCacheKeyGenerator.getTenantInCreation().orElseGet(() -> { // Create if it does not exist
final TenantMetaData findByTenant = tenantMetaDataRepository if (result == null) {
.findByTenantIgnoreCase(tenantAware.getCurrentTenant()); return createTenantMetadata0(tenant);
return findByTenant != null ? findByTenant.getTenant() : null; }
}); return result;
} }
@Override @Override
@@ -292,6 +285,20 @@ public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, Syst
return tenantMetaDataRepository.save(data); 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() { private DistributionSetType createStandardSoftwareDataSetup() {
final SoftwareModuleType app = softwareModuleTypeRepository final SoftwareModuleType app = softwareModuleTypeRepository
.save(new JpaSoftwareModuleType(org.eclipse.hawkbit.repository.Constants.SMT_DEFAULT_APP_KEY, .save(new JpaSoftwareModuleType(org.eclipse.hawkbit.repository.Constants.SMT_DEFAULT_APP_KEY,
@@ -318,38 +325,7 @@ public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, Syst
.save(new JpaDistributionSetType(org.eclipse.hawkbit.repository.Constants.DST_DEFAULT_OS_WITH_APPS_KEY, .save(new JpaDistributionSetType(org.eclipse.hawkbit.repository.Constants.DST_DEFAULT_OS_WITH_APPS_KEY,
org.eclipse.hawkbit.repository.Constants.DST_DEFAULT_OS_WITH_APPS_NAME, org.eclipse.hawkbit.repository.Constants.DST_DEFAULT_OS_WITH_APPS_NAME,
"Default type with Firmware/OS and optional app(s).").addMandatoryModuleType(os) "Default type with Firmware/OS and optional app(s).").addMandatoryModuleType(os)
.addOptionalModuleType(app)); .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) { private TenantMetaData createTenantMetadata0(final String tenant) {
@@ -371,8 +347,7 @@ public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, Syst
* {@link MultiTenantJpaTransactionManager} is called again and set the * {@link MultiTenantJpaTransactionManager} is called again and set the
* tenant for this transaction. * tenant for this transaction.
* *
* @param tenant * @param tenant the tenant to be created
* the tenant to be created
* @return the initial created {@link TenantMetaData} * @return the initial created {@link TenantMetaData}
*/ */
private TenantMetaData createInitialTenantMetaData(final String tenant) { private TenantMetaData createInitialTenantMetaData(final String tenant) {

View File

@@ -16,7 +16,9 @@ import java.util.Optional;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import cz.jirutka.rsql.parser.RSQLParserException;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.ContextAware;
import org.eclipse.hawkbit.repository.DistributionSetManagement; import org.eclipse.hawkbit.repository.DistributionSetManagement;
import org.eclipse.hawkbit.repository.QuotaManagement; import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.RepositoryProperties; 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.TargetFilterQueryManagement;
import org.eclipse.hawkbit.repository.TargetManagement; import org.eclipse.hawkbit.repository.TargetManagement;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement; import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.ContextAware;
import org.eclipse.hawkbit.repository.builder.AutoAssignDistributionSetUpdate; import org.eclipse.hawkbit.repository.builder.AutoAssignDistributionSetUpdate;
import org.eclipse.hawkbit.repository.builder.GenericTargetFilterQueryUpdate; import org.eclipse.hawkbit.repository.builder.GenericTargetFilterQueryUpdate;
import org.eclipse.hawkbit.repository.builder.TargetFilterQueryCreate; 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.util.ObjectUtils;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import cz.jirutka.rsql.parser.RSQLParserException;
/** /**
* JPA implementation of {@link TargetFilterQueryManagement}. * JPA implementation of {@link TargetFilterQueryManagement}.
*/ */
@@ -140,6 +139,17 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme
targetFilterQueryRepository.deleteById(targetFilterQueryId); 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 @Override
public Slice<TargetFilterQuery> findAll(final Pageable pageable) { public Slice<TargetFilterQuery> findAll(final Pageable pageable) {
return JpaManagementHelper.findAllWithoutCountBySpec(targetFilterQueryRepository, pageable, null); return JpaManagementHelper.findAllWithoutCountBySpec(targetFilterQueryRepository, pageable, null);
@@ -179,7 +189,7 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme
public Page<TargetFilterQuery> findByRsql(final Pageable pageable, final String rsqlFilter) { public Page<TargetFilterQuery> findByRsql(final Pageable pageable, final String rsqlFilter) {
final List<Specification<JpaTargetFilterQuery>> specList = !ObjectUtils.isEmpty(rsqlFilter) final List<Specification<JpaTargetFilterQuery>> specList = !ObjectUtils.isEmpty(rsqlFilter)
? Collections.singletonList(RSQLUtility.buildRsqlSpecification(rsqlFilter, ? Collections.singletonList(RSQLUtility.buildRsqlSpecification(rsqlFilter,
TargetFilterQueryFields.class, virtualPropertyReplacer, database)) TargetFilterQueryFields.class, virtualPropertyReplacer, database))
: Collections.emptyList(); : Collections.emptyList();
return JpaManagementHelper.findAllWithCountBySpec(targetFilterQueryRepository, pageable, specList); return JpaManagementHelper.findAllWithCountBySpec(targetFilterQueryRepository, pageable, specList);
@@ -225,13 +235,13 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme
} }
@Override @Override
public Optional<TargetFilterQuery> getByName(final String targetFilterQueryName) { public Optional<TargetFilterQuery> get(final long targetFilterQueryId) {
return targetFilterQueryRepository.findByName(targetFilterQueryName); return targetFilterQueryRepository.findById(targetFilterQueryId).map(TargetFilterQuery.class::cast);
} }
@Override @Override
public Optional<TargetFilterQuery> get(final long targetFilterQueryId) { public Optional<TargetFilterQuery> getByName(final String targetFilterQueryName) {
return targetFilterQueryRepository.findById(targetFilterQueryId).map(TargetFilterQuery.class::cast); return targetFilterQueryRepository.findByName(targetFilterQueryName);
} }
@Override @Override
@@ -279,7 +289,7 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme
final JpaDistributionSet distributionSet = (JpaDistributionSet) distributionSetManagement final JpaDistributionSet distributionSet = (JpaDistributionSet) distributionSetManagement
.getValidAndComplete(update.getDsId()); .getValidAndComplete(update.getDsId());
if (((JpaDistributionSetManagement)distributionSetManagement).isImplicitLockApplicable(distributionSet)) { if (((JpaDistributionSetManagement) distributionSetManagement).isImplicitLockApplicable(distributionSet)) {
distributionSetManagement.lock(distributionSet.getId()); distributionSetManagement.lock(distributionSet.getId());
} }
@@ -298,9 +308,11 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme
return targetFilterQueryRepository.save(targetFilterQuery); return targetFilterQueryRepository.save(targetFilterQuery);
} }
private boolean isConfirmationFlowEnabled() { @Override
return TenantConfigHelper.usingContext(systemSecurityContext, tenantConfigurationManagement) @Transactional
.isConfirmationFlowEnabled(); 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) { private static ActionType sanitizeAutoAssignActionType(final ActionType actionType) {
@@ -315,32 +327,19 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme
return actionType; return actionType;
} }
private boolean isConfirmationFlowEnabled() {
return TenantConfigHelper.usingContext(systemSecurityContext, tenantConfigurationManagement)
.isConfirmationFlowEnabled();
}
private JpaTargetFilterQuery findTargetFilterQueryOrThrowExceptionIfNotFound(final Long queryId) { private JpaTargetFilterQuery findTargetFilterQueryOrThrowExceptionIfNotFound(final Long queryId) {
return targetFilterQueryRepository.findById(queryId) return targetFilterQueryRepository.findById(queryId)
.orElseThrow(() -> new EntityNotFoundException(TargetFilterQuery.class, 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) { private void assertMaxTargetsQuota(final String query, final String filterName, final long dsId) {
QuotaHelper.assertAssignmentQuota(filterName, QuotaHelper.assertAssignmentQuota(filterName,
targetManagement.countByRsqlAndNonDSAndCompatibleAndUpdatable(dsId, query), targetManagement.countByRsqlAndNonDSAndCompatibleAndUpdatable(dsId, query),
quotaManagement.getMaxTargetsPerAutoAssignment(), Target.class, TargetFilterQuery.class, null); 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}. * JPA implementation of {@link TargetTagManagement}.
*
*/ */
@Transactional(readOnly = true) @Transactional(readOnly = true)
@Validated @Validated
@@ -62,8 +61,8 @@ public class JpaTargetTagManagement implements TargetTagManagement {
} }
@Override @Override
public Optional<TargetTag> getByName(final String name) { public long count() {
return targetTagRepository.findByNameEquals(name); return targetTagRepository.count();
} }
@Override @Override
@@ -94,8 +93,13 @@ public class JpaTargetTagManagement implements TargetTagManagement {
public void delete(final String targetTagName) { public void delete(final String targetTagName) {
targetTagRepository.delete( targetTagRepository.delete(
targetTagRepository targetTagRepository
.findOne(((root, query, cb) -> cb.equal(root.get(JpaTargetTag_.name), targetTagName))) .findOne(((root, query, cb) -> cb.equal(root.get(JpaTargetTag_.name), targetTagName)))
.orElseThrow(() -> new EntityNotFoundException(TargetTag.class, targetTagName))); .orElseThrow(() -> new EntityNotFoundException(TargetTag.class, targetTagName)));
}
@Override
public Page<TargetTag> findAll(final Pageable pageable) {
return JpaManagementHelper.findAllWithCountBySpec(targetTagRepository, pageable, null);
} }
@Override @Override
@@ -105,8 +109,18 @@ public class JpaTargetTagManagement implements TargetTagManagement {
} }
@Override @Override
public long count() { public Optional<TargetTag> getByName(final String name) {
return targetTagRepository.count(); 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 @Override
@@ -125,19 +139,4 @@ public class JpaTargetTagManagement implements TargetTagManagement {
return targetTagRepository.save(tag); 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}. * JPA implementation of {@link TargetTypeManagement}.
*
*/ */
@Transactional(readOnly = true) @Transactional(readOnly = true)
@Validated @Validated
@@ -68,14 +67,10 @@ public class JpaTargetTypeManagement implements TargetTypeManagement {
/** /**
* Constructor * Constructor
* *
* @param targetTypeRepository * @param targetTypeRepository Target type repository
* Target type repository * @param targetRepository Target repository
* @param targetRepository * @param virtualPropertyReplacer replacer
* Target repository * @param database database
* @param virtualPropertyReplacer
* replacer
* @param database
* database
*/ */
public JpaTargetTypeManagement(final TargetTypeRepository targetTypeRepository, public JpaTargetTypeManagement(final TargetTypeRepository targetTypeRepository,
final TargetRepository targetRepository, final DistributionSetTypeRepository distributionSetTypeRepository, final TargetRepository targetRepository, final DistributionSetTypeRepository distributionSetTypeRepository,
@@ -152,7 +147,7 @@ public class JpaTargetTypeManagement implements TargetTypeManagement {
return JpaManagementHelper.findAllWithCountBySpec(targetTypeRepository, pageable, return JpaManagementHelper.findAllWithCountBySpec(targetTypeRepository, pageable,
List.of( List.of(
RSQLUtility.buildRsqlSpecification( RSQLUtility.buildRsqlSpecification(
rsqlParam, TargetTypeFields.class, virtualPropertyReplacer,database))); rsqlParam, TargetTypeFields.class, virtualPropertyReplacer, database)));
} }
@Override @Override
@@ -236,13 +231,9 @@ public class JpaTargetTypeManagement implements TargetTypeManagement {
* Enforces the quota specifying the maximum number of * Enforces the quota specifying the maximum number of
* {@link DistributionSetType}s per {@link TargetType}. * {@link DistributionSetType}s per {@link TargetType}.
* *
* @param id * @param id of the target type
* of the target type * @param requested number of distribution set types to check
* @param requested * @throws AssignmentQuotaExceededException if the software module type quota is exceeded
* 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) { private void assertDistributionSetTypeQuota(final long id, final int requested) {
QuotaHelper.assertAssignmentQuota(id, requested, quotaManagement.getMaxDistributionSetTypesPerTargetType(), QuotaHelper.assertAssignmentQuota(id, requested, quotaManagement.getMaxDistributionSetTypesPerTargetType(),

View File

@@ -58,76 +58,18 @@ import org.springframework.validation.annotation.Validated;
@Validated @Validated
public class JpaTenantConfigurationManagement implements TenantConfigurationManagement { public class JpaTenantConfigurationManagement implements TenantConfigurationManagement {
private static final ConfigurableConversionService conversionService = new DefaultConversionService();
@Autowired @Autowired
private TenantConfigurationRepository tenantConfigurationRepository; private TenantConfigurationRepository tenantConfigurationRepository;
@Autowired @Autowired
private TenantConfigurationProperties tenantConfigurationProperties; private TenantConfigurationProperties tenantConfigurationProperties;
@Autowired @Autowired
private ApplicationContext applicationContext; private ApplicationContext applicationContext;
@Autowired @Autowired
private CacheManager cacheManager; private CacheManager cacheManager;
@Autowired @Autowired
private AfterTransactionCommitExecutor afterCommitExecutor; 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 @Override
@CacheEvict(value = "tenantConfiguration", key = "#configurationKeyName") @CacheEvict(value = "tenantConfiguration", key = "#configurationKeyName")
@Transactional @Transactional
@@ -154,6 +96,85 @@ public class JpaTenantConfigurationManagement implements TenantConfigurationMana
return addOrUpdateConfiguration0(configurations); 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) { private <T extends Serializable> Map<String, TenantConfigurationValue<T>> addOrUpdateConfiguration0(Map<String, T> configurations) {
List<JpaTenantConfiguration> configurationList = new ArrayList<>(); List<JpaTenantConfiguration> configurationList = new ArrayList<>();
configurations.forEach((configurationKeyName, value) -> { configurations.forEach((configurationKeyName, value) -> {
@@ -187,9 +208,9 @@ public class JpaTenantConfigurationManagement implements TenantConfigurationMana
JpaTenantConfiguration::getKey, JpaTenantConfiguration::getKey,
updatedTenantConfiguration -> { updatedTenantConfiguration -> {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked") final Class<T> clazzT = (Class<T>) configurations.get(updatedTenantConfiguration.getKey())
final Class<T> clazzT = (Class<T>) configurations.get(updatedTenantConfiguration.getKey()).getClass(); .getClass();
return TenantConfigurationValue.<T>builder().global(false) return TenantConfigurationValue.<T> builder().global(false)
.createdBy(updatedTenantConfiguration.getCreatedBy()) .createdBy(updatedTenantConfiguration.getCreatedBy())
.createdAt(updatedTenantConfiguration.getCreatedAt()) .createdAt(updatedTenantConfiguration.getCreatedAt())
.lastModifiedAt(updatedTenantConfiguration.getLastModifiedAt()) .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( private <T extends Serializable> TenantConfigurationValue<T> buildTenantConfigurationValueByKey(
final TenantConfigurationKey configurationKey, final Class<T> propertyType, final TenantConfigurationKey configurationKey, final Class<T> propertyType,
final TenantConfiguration tenantConfiguration) { final TenantConfiguration tenantConfiguration) {
@@ -247,14 +242,10 @@ public class JpaTenantConfigurationManagement implements TenantConfigurationMana
/** /**
* Asserts that the requested configuration value change is allowed. Throws * Asserts that the requested configuration value change is allowed. Throws
* a {@link TenantConfigurationValueChangeNotAllowedException} otherwise. * a {@link TenantConfigurationValueChangeNotAllowedException} otherwise.
* *
* @param key * @param key The configuration key.
* The configuration key. * @param valueChange The configuration to be validated.
* @param valueChange * @throws TenantConfigurationValueChangeNotAllowedException if the requested configuration change is not allowed.
* The configuration to be validated.
*
* @throws TenantConfigurationValueChangeNotAllowedException
* if the requested configuration change is not allowed.
*/ */
private void assertValueChangeIsAllowed(final String key, final JpaTenantConfiguration valueChange) { private void assertValueChangeIsAllowed(final String key, final JpaTenantConfiguration valueChange) {
assertMultiAssignmentsValueChange(key, valueChange); assertMultiAssignmentsValueChange(key, valueChange);
@@ -280,7 +271,7 @@ public class JpaTenantConfigurationManagement implements TenantConfigurationMana
} }
if (MULTI_ASSIGNMENTS_ENABLED.equals(key) && Boolean.parseBoolean(valueChange.getValue())) { if (MULTI_ASSIGNMENTS_ENABLED.equals(key) && Boolean.parseBoolean(valueChange.getValue())) {
JpaTenantConfiguration batchConfig = tenantConfigurationRepository.findByKey(BATCH_ASSIGNMENTS_ENABLED); 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 " + log.debug("The Multi-Assignments '{}' feature cannot be enabled as it contradicts with " +
"The Batch-Assignments feature, which is already enabled .", key); "The Batch-Assignments feature, which is already enabled .", key);
throw new TenantConfigurationValueChangeNotAllowedException(); throw new TenantConfigurationValueChangeNotAllowedException();

View File

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

View File

@@ -44,27 +44,18 @@ import org.eclipse.hawkbit.repository.model.helper.EventPublisherHolder;
/** /**
* AbstractDsAssignmentStrategy for offline assignments, i.e. not managed by * AbstractDsAssignmentStrategy for offline assignments, i.e. not managed by
* hawkBit. * hawkBit.
*
*/ */
public class OfflineDsAssignmentStrategy extends AbstractDsAssignmentStrategy { public class OfflineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
OfflineDsAssignmentStrategy(final TargetRepository targetRepository, OfflineDsAssignmentStrategy(final TargetRepository targetRepository,
final AfterTransactionCommitExecutor afterCommit, final EventPublisherHolder eventPublisherHolder, final AfterTransactionCommitExecutor afterCommit, final EventPublisherHolder eventPublisherHolder,
final ActionRepository actionRepository, final ActionStatusRepository actionStatusRepository, final ActionRepository actionRepository, final ActionStatusRepository actionStatusRepository,
final QuotaManagement quotaManagement, final BooleanSupplier multiAssignmentsConfig, final QuotaManagement quotaManagement, final BooleanSupplier multiAssignmentsConfig,
final BooleanSupplier confirmationFlowConfig, final RepositoryProperties repositoryProperties) { final BooleanSupplier confirmationFlowConfig, final RepositoryProperties repositoryProperties) {
super(targetRepository, afterCommit, eventPublisherHolder, actionRepository, actionStatusRepository, super(targetRepository, afterCommit, eventPublisherHolder, actionRepository, actionStatusRepository,
quotaManagement, multiAssignmentsConfig, confirmationFlowConfig, repositoryProperties); 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 @Override
public List<JpaTarget> findTargetsForAssignment(final List<String> controllerIDs, final long setId) { public List<JpaTarget> findTargetsForAssignment(final List<String> controllerIDs, final long setId) {
final Function<List<String>, List<JpaTarget>> mapper; final Function<List<String>, List<JpaTarget>> mapper;
@@ -80,13 +71,11 @@ public class OfflineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
} }
@Override @Override
public Set<Long> cancelActiveActions(final List<List<Long>> targetIds) { public void sendTargetUpdatedEvents(final DistributionSet set, final List<JpaTarget> targets) {
return Collections.emptySet(); targets.forEach(target -> {
} target.setUpdateStatus(TargetUpdateStatus.IN_SYNC);
sendTargetUpdatedEvent(target);
@Override });
public void closeActiveActions(final List<List<Long>> targetIds) {
// Not supported by offline case
} }
@Override @Override
@@ -94,7 +83,8 @@ public class OfflineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
final JpaDistributionSet set, final List<List<Long>> targetIds, final String currentUser) { final JpaDistributionSet set, final List<List<Long>> targetIds, final String currentUser) {
final long now = System.currentTimeMillis(); final long now = System.currentTimeMillis();
targetIds.forEach(targetIdsChunk -> { 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!"); throw new InsufficientPermissionException("No update access to all targets!");
} }
targetRepository.setAssignedAndInstalledDistributionSetAndUpdateStatus( 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 @Override
public JpaAction createTargetAction(final String initiatedBy, final TargetWithActionType targetWithActionType, public JpaAction createTargetAction(final String initiatedBy, final TargetWithActionType targetWithActionType,
final List<JpaTarget> targets, final JpaDistributionSet set) { final List<JpaTarget> targets, final JpaDistributionSet set) {
@@ -135,14 +145,4 @@ public class OfflineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
return result; 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,46 +47,18 @@ import org.springframework.util.CollectionUtils;
/** /**
* AbstractDsAssignmentStrategy for online assignments, i.e. managed by hawkBit. * AbstractDsAssignmentStrategy for online assignments, i.e. managed by hawkBit.
*
*/ */
public class OnlineDsAssignmentStrategy extends AbstractDsAssignmentStrategy { public class OnlineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
OnlineDsAssignmentStrategy(final TargetRepository targetRepository, OnlineDsAssignmentStrategy(final TargetRepository targetRepository,
final AfterTransactionCommitExecutor afterCommit, final EventPublisherHolder eventPublisherHolder, final AfterTransactionCommitExecutor afterCommit, final EventPublisherHolder eventPublisherHolder,
final ActionRepository actionRepository, final ActionStatusRepository actionStatusRepository, final ActionRepository actionRepository, final ActionStatusRepository actionStatusRepository,
final QuotaManagement quotaManagement, final BooleanSupplier multiAssignmentsConfig, final QuotaManagement quotaManagement, final BooleanSupplier multiAssignmentsConfig,
final BooleanSupplier confirmationFlowConfig, final RepositoryProperties repositoryProperties) { final BooleanSupplier confirmationFlowConfig, final RepositoryProperties repositoryProperties) {
super(targetRepository, afterCommit, eventPublisherHolder, actionRepository, actionStatusRepository, super(targetRepository, afterCommit, eventPublisherHolder, actionRepository, actionStatusRepository,
quotaManagement, multiAssignmentsConfig, confirmationFlowConfig, repositoryProperties); 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) { public void sendDeploymentEvents(final long distributionSetId, final List<Action> actions) {
if (isMultiAssignmentsEnabled()) { if (isMultiAssignmentsEnabled()) {
sendDeploymentEvent(actions); sendDeploymentEvent(actions);
@@ -114,14 +86,11 @@ public class OnlineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
} }
@Override @Override
public Set<Long> cancelActiveActions(final List<List<Long>> targetIds) { public void sendTargetUpdatedEvents(final DistributionSet set, final List<JpaTarget> targets) {
return targetIds.stream().map(this::overrideObsoleteUpdateActions).flatMap(Collection::stream) targets.forEach(target -> {
.collect(Collectors.toSet()); target.setUpdateStatus(TargetUpdateStatus.PENDING);
} sendTargetUpdatedEvent(target);
});
@Override
public void closeActiveActions(final List<List<Long>> targetIds) {
targetIds.forEach(this::closeObsoleteUpdateActions);
} }
@Override @Override
@@ -129,7 +98,8 @@ public class OnlineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
final String currentUser) { final String currentUser) {
final long now = System.currentTimeMillis(); final long now = System.currentTimeMillis();
targetIds.forEach(targetIdsChunk -> { 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!"); throw new InsufficientPermissionException("No update access to all targets!");
} }
targetRepository.setAssignedDistributionSetAndUpdateStatus(TargetUpdateStatus.PENDING, 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 @Override
public JpaAction createTargetAction(final String initiatedBy, final TargetWithActionType targetWithActionType, public JpaAction createTargetAction(final String initiatedBy, final TargetWithActionType targetWithActionType,
final List<JpaTarget> targets, final JpaDistributionSet set) { 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) { private void sendMultiActionCancelEvent(final Action action) {
sendMultiActionCancelEvent(action.getTenant(), Collections.singletonList(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 * Helper to fire a {@link MultiActionCancelEvent}. This method may only be
* called if the Multi-Assignments feature is enabled. * called if the Multi-Assignments feature is enabled.
* *
* @param tenant * @param tenant the event is scoped to
* the event is scoped to * @param actions assigned to the targets
* @param actions
* assigned to the targets
*/ */
private void sendMultiActionCancelEvent(final String tenant, final List<Action> actions) { private void sendMultiActionCancelEvent(final String tenant, final List<Action> actions) {
afterCommit.afterCommit(() -> eventPublisherHolder.getEventPublisher() afterCommit.afterCommit(() -> eventPublisherHolder.getEventPublisher()
@@ -238,28 +250,12 @@ public class OnlineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
* Helper to fire a {@link MultiActionAssignEvent}. This method may only be * Helper to fire a {@link MultiActionAssignEvent}. This method may only be
* called if the Multi-Assignments feature is enabled. * called if the Multi-Assignments feature is enabled.
* *
* @param tenant * @param tenant the event is scoped to
* the event is scoped to * @param actions assigned to the targets
* @param actions
* assigned to the targets
*/ */
private void sendMultiActionAssignEvent(final String tenant, final List<Action> actions) { private void sendMultiActionAssignEvent(final String tenant, final List<Action> actions) {
afterCommit.afterCommit(() -> eventPublisherHolder.getEventPublisher() afterCommit.afterCommit(() -> eventPublisherHolder.getEventPublisher()
.publishEvent(new MultiActionAssignEvent(tenant, eventPublisherHolder.getApplicationId(), actions))); .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. * Holder of the base attributes common to all entities.
*
*/ */
@MappedSuperclass @MappedSuperclass
@Access(AccessType.FIELD) @Access(AccessType.FIELD)
@EntityListeners({ AuditingEntityListener.class, EntityPropertyChangeListener.class, EntityInterceptorListener.class }) @EntityListeners({ AuditingEntityListener.class, EntityPropertyChangeListener.class, EntityInterceptorListener.class })
public abstract class AbstractJpaBaseEntity implements BaseEntity { 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 @Id
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id") @Column(name = "id")
@@ -88,6 +87,34 @@ public abstract class AbstractJpaBaseEntity implements BaseEntity {
return lastModifiedBy; 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 @CreatedBy
public void setCreatedBy(final String createdBy) { public void setCreatedBy(final String createdBy) {
if (isController()) { 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 @Override
public Long getId() { public Long getId() {
return id; return id;
} }
@Override
public String toString() {
return this.getClass().getSimpleName() + " [id=" + id + "]";
}
public void setId(final Long id) { public void setId(final Long id) {
this.id = id; this.id = id;
} }
@@ -169,7 +155,7 @@ public abstract class AbstractJpaBaseEntity implements BaseEntity {
* Defined equals/hashcode strategy for the repository in general is that an * Defined equals/hashcode strategy for the repository in general is that an
* entity is equal if it has the same {@link #getId()} and * entity is equal if it has the same {@link #getId()} and
* {@link #getOptLockRevision()} and class. * {@link #getOptLockRevision()} and class.
* *
* @see java.lang.Object#hashCode() * @see java.lang.Object#hashCode()
*/ */
@Override @Override
@@ -188,7 +174,7 @@ public abstract class AbstractJpaBaseEntity implements BaseEntity {
* Defined equals/hashcode strategy for the repository in general is that an * Defined equals/hashcode strategy for the repository in general is that an
* entity is equal if it has the same {@link #getId()} and * entity is equal if it has the same {@link #getId()} and
* {@link #getOptLockRevision()} and class. * {@link #getOptLockRevision()} and class.
* *
* @see java.lang.Object#equals(java.lang.Object) * @see java.lang.Object#equals(java.lang.Object)
*/ */
@Override @Override
@@ -213,4 +199,17 @@ public abstract class AbstractJpaBaseEntity implements BaseEntity {
return optLockRevision == other.optLockRevision; 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. * Meta data for entities.
*
*/ */
@MappedSuperclass @MappedSuperclass
public abstract class AbstractJpaMetaData implements MetaData { public abstract class AbstractJpaMetaData implements MetaData {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@Id @Id
@@ -66,6 +66,11 @@ public abstract class AbstractJpaMetaData implements MetaData {
this.value = value; this.value = value;
} }
@Override
public int hashCode() {
return Objects.hash(key, value);
}
@Override @Override
public boolean equals(final Object o) { public boolean equals(final Object o) {
if (this == o) if (this == o)
@@ -75,9 +80,4 @@ public abstract class AbstractJpaMetaData implements MetaData {
final AbstractJpaMetaData that = (AbstractJpaMetaData) o; final AbstractJpaMetaData that = (AbstractJpaMetaData) o;
return Objects.equals(key, that.key) && Objects.equals(value, that.value); 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; package org.eclipse.hawkbit.repository.jpa.model;
import java.io.Serial;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.persistence.MappedSuperclass; import jakarta.persistence.MappedSuperclass;
import jakarta.validation.constraints.NotNull; 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.NamedEntity;
import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity; import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity;
import java.io.Serial;
/** /**
* {@link TenantAwareBaseEntity} extension for all entities that are named in * {@link TenantAwareBaseEntity} extension for all entities that are named in
* addition to their technical ID. * addition to their technical ID.
@@ -51,10 +51,8 @@ public abstract class AbstractJpaNamedEntity extends AbstractJpaTenantAwareBaseE
/** /**
* Parameterized constructor. * Parameterized constructor.
* *
* @param name * @param name of the {@link NamedEntity}
* of the {@link NamedEntity} * @param description of the {@link NamedEntity}
* @param description
* of the {@link NamedEntity}
*/ */
AbstractJpaNamedEntity(final String name, final String description) { AbstractJpaNamedEntity(final String name, final String description) {
this.name = name; this.name = name;
@@ -71,11 +69,11 @@ public abstract class AbstractJpaNamedEntity extends AbstractJpaTenantAwareBaseE
return name; return name;
} }
public void setDescription(final String description) {
this.description = description;
}
public void setName(final String name) { public void setName(final String name) {
this.name = 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. * Extension for {@link NamedEntity} that are versioned.
*
*/ */
@MappedSuperclass @MappedSuperclass
// exception squid:S2160 - BaseEntity equals/hashcode is handling correctly for // exception squid:S2160 - BaseEntity equals/hashcode is handling correctly for
// sub entities // sub entities
@SuppressWarnings("squid:S2160") @SuppressWarnings("squid:S2160")
public abstract class AbstractJpaNamedVersionedEntity extends AbstractJpaNamedEntity implements NamedVersionedEntity { public abstract class AbstractJpaNamedVersionedEntity extends AbstractJpaNamedEntity implements NamedVersionedEntity {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@Column(name = "version", nullable = false, length = NamedVersionedEntity.VERSION_MAX_SIZE) @Column(name = "version", nullable = false, length = NamedVersionedEntity.VERSION_MAX_SIZE)
@@ -36,10 +36,8 @@ public abstract class AbstractJpaNamedVersionedEntity extends AbstractJpaNamedEn
/** /**
* parameterized constructor. * parameterized constructor.
* *
* @param name * @param name of the entity
* of the entity * @param version of the entity
* @param version
* of the entity
* @param description * @param description
*/ */
AbstractJpaNamedVersionedEntity(final String name, final String version, final String 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. * Holder of the base attributes common to all tenant aware entities.
*
*/ */
@MappedSuperclass @MappedSuperclass
@TenantDiscriminatorColumn(name = "tenant", length = 40) @TenantDiscriminatorColumn(name = "tenant", length = 40)
@Multitenant(MultitenantType.SINGLE_TABLE) @Multitenant(MultitenantType.SINGLE_TABLE)
public abstract class AbstractJpaTenantAwareBaseEntity extends AbstractJpaBaseEntity implements TenantAwareBaseEntity { public abstract class AbstractJpaTenantAwareBaseEntity extends AbstractJpaBaseEntity implements TenantAwareBaseEntity {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@Column(name = "tenant", nullable = false, insertable = false, updatable = false, length = 40) @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. // 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 @Override
public String getTenant() { public String getTenant() {
return tenant; return tenant;
@@ -72,17 +54,12 @@ public abstract class AbstractJpaTenantAwareBaseEntity extends AbstractJpaBaseEn
this.tenant = tenant; this.tenant = tenant;
} }
@Override
public String toString() {
return "BaseEntity [id=" + super.getId() + "]";
}
/** /**
* Tenant aware entities extend the equals/hashcode strategy with the tenant * Tenant aware entities extend the equals/hashcode strategy with the tenant
* name. That would allow for instance in a multi-schema based data * name. That would allow for instance in a multi-schema based data
* separation setup to have the same primary key for different entities of * separation setup to have the same primary key for different entities of
* different tenants. * different tenants.
* *
* @see org.eclipse.hawkbit.repository.model.BaseEntity#hashCode() * @see org.eclipse.hawkbit.repository.model.BaseEntity#hashCode()
*/ */
@Override @Override
@@ -98,7 +75,7 @@ public abstract class AbstractJpaTenantAwareBaseEntity extends AbstractJpaBaseEn
* name. That would allow for instance in a multi-schema based data * name. That would allow for instance in a multi-schema based data
* separation setup to have the same primary key for different entities of * separation setup to have the same primary key for different entities of
* different tenants. * different tenants.
* *
* @see org.eclipse.hawkbit.repository.model.BaseEntity#equals(java.lang.Object) * @see org.eclipse.hawkbit.repository.model.BaseEntity#equals(java.lang.Object)
*/ */
@Override @Override
@@ -119,4 +96,27 @@ public abstract class AbstractJpaTenantAwareBaseEntity extends AbstractJpaBaseEn
return true; 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; package org.eclipse.hawkbit.repository.jpa.model;
import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity; import java.io.Serial;
import org.eclipse.hawkbit.repository.model.Type;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.persistence.MappedSuperclass; import jakarta.persistence.MappedSuperclass;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size; 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 * {@link TenantAwareBaseEntity} extension for all entities that are named in
@@ -49,10 +50,8 @@ public abstract class AbstractJpaTypeEntity extends AbstractJpaNamedEntity imple
/** /**
* Parameterized constructor. * Parameterized constructor.
* *
* @param key * @param key of the {@link Type}
* of the {@link Type} * @param colour of the {@link Type}
* @param colour
* of the {@link Type}
*/ */
AbstractJpaTypeEntity(final String name, final String description, final String key, final String colour) { AbstractJpaTypeEntity(final String name, final String description, final String key, final String colour) {
super(name, description); super(name, description);
@@ -70,11 +69,11 @@ public abstract class AbstractJpaTypeEntity extends AbstractJpaNamedEntity imple
return colour; return colour;
} }
public void setKey(final String key) {
this.key = key;
}
public void setColour(final String colour) { public void setColour(final String colour) {
this.colour = 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 * Relation element between a {@link DistributionSetType} and its
* {@link SoftwareModuleType} elements. * {@link SoftwareModuleType} elements.
*
*/ */
@Entity @Entity
@Table(name = "sp_ds_type_element") @Table(name = "sp_ds_type_element")
public class DistributionSetTypeElement implements Serializable { public class DistributionSetTypeElement implements Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@EmbeddedId @EmbeddedId
@@ -62,13 +62,10 @@ public class DistributionSetTypeElement implements Serializable {
/** /**
* Standard constructor. * Standard constructor.
* *
* @param dsType * @param dsType of the element
* of the element * @param smType of the element
* @param smType * @param mandatory to <code>true</code> if the {@link SoftwareModuleType} if
* of the element * mandatory element in the {@link DistributionSet}.
* @param mandatory
* to <code>true</code> if the {@link SoftwareModuleType} if
* mandatory element in the {@link DistributionSet}.
*/ */
DistributionSetTypeElement(final JpaDistributionSetType dsType, final JpaSoftwareModuleType smType, DistributionSetTypeElement(final JpaDistributionSetType dsType, final JpaSoftwareModuleType smType,
final boolean mandatory) { final boolean mandatory) {
@@ -78,15 +75,15 @@ public class DistributionSetTypeElement implements Serializable {
this.mandatory = mandatory; this.mandatory = mandatory;
} }
public boolean isMandatory() {
return mandatory;
}
DistributionSetTypeElement setMandatory(final boolean mandatory) { DistributionSetTypeElement setMandatory(final boolean mandatory) {
this.mandatory = mandatory; this.mandatory = mandatory;
return this; return this;
} }
public boolean isMandatory() {
return mandatory;
}
public DistributionSetType getDsType() { public DistributionSetType getDsType() {
return dsType; return dsType;
} }
@@ -99,11 +96,6 @@ public class DistributionSetTypeElement implements Serializable {
return key; return key;
} }
@Override
public String toString() {
return "DistributionSetTypeElement [mandatory=" + mandatory + ", dsType=" + dsType + ", smType=" + smType + "]";
}
@Override @Override
public int hashCode() { public int hashCode() {
final int prime = 31; final int prime = 31;
@@ -134,4 +126,9 @@ public class DistributionSetTypeElement implements Serializable {
return true; 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 @Embeddable
public class DistributionSetTypeElementCompositeKey implements Serializable { public class DistributionSetTypeElementCompositeKey implements Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@Column(name = "distribution_set_type", nullable = false, updatable = false) @Column(name = "distribution_set_type", nullable = false, updatable = false)
@@ -36,10 +37,8 @@ public class DistributionSetTypeElementCompositeKey implements Serializable {
/** /**
* Constructor. * Constructor.
* *
* @param dsType * @param dsType in the key
* in the key * @param smType in the key
* @param smType
* in the key
*/ */
DistributionSetTypeElementCompositeKey(final JpaDistributionSetType dsType, final JpaSoftwareModuleType smType) { DistributionSetTypeElementCompositeKey(final JpaDistributionSetType dsType, final JpaSoftwareModuleType smType) {
this.dsType = dsType.getId(); 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 * The DistributionSet Metadata composite key which contains the meta data key
* and the ID of the DistributionSet itself. * and the ID of the DistributionSet itself.
*
*/ */
public final class DsMetadataCompositeKey implements Serializable { public final class DsMetadataCompositeKey implements Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private String key; private String key;
@@ -28,10 +28,8 @@ public final class DsMetadataCompositeKey implements Serializable {
} }
/** /**
* @param distributionSet * @param distributionSet the distribution set for this meta data
* the distribution set for this meta data * @param key the key of the meta data
* @param key
* the key of the meta data
*/ */
public DsMetadataCompositeKey(final Long distributionSet, final String key) { public DsMetadataCompositeKey(final Long distributionSet, final String key) {
this.distributionSet = distributionSet; this.distributionSet = distributionSet;

View File

@@ -31,8 +31,7 @@ public class EntityInterceptorListener {
/** /**
* Callback for lifecyle event <i>pre persist</i>. * Callback for lifecyle event <i>pre persist</i>.
* *
* @param entity * @param entity the JPA entity which this listener is associated with
* the JPA entity which this listener is associated with
*/ */
@PrePersist @PrePersist
public void prePersist(final Object entity) { public void prePersist(final Object entity) {
@@ -42,8 +41,7 @@ public class EntityInterceptorListener {
/** /**
* Callback for lifecyle event <i>post persist</i>. * Callback for lifecyle event <i>post persist</i>.
* *
* @param entity * @param entity the JPA entity which this listener is associated with
* the JPA entity which this listener is associated with
*/ */
@PostPersist @PostPersist
public void postPersist(final Object entity) { public void postPersist(final Object entity) {
@@ -53,8 +51,7 @@ public class EntityInterceptorListener {
/** /**
* Callback for lifecyle event <i>post remove</i>. * Callback for lifecyle event <i>post remove</i>.
* *
* @param entity * @param entity the JPA entity which this listener is associated with
* the JPA entity which this listener is associated with
*/ */
@PostRemove @PostRemove
public void postRemove(final Object entity) { public void postRemove(final Object entity) {
@@ -64,8 +61,7 @@ public class EntityInterceptorListener {
/** /**
* Callback for lifecyle event <i>pre remove</i>. * Callback for lifecyle event <i>pre remove</i>.
* *
* @param entity * @param entity the JPA entity which this listener is associated with
* the JPA entity which this listener is associated with
*/ */
@PreRemove @PreRemove
public void preRemove(final Object entity) { public void preRemove(final Object entity) {
@@ -75,8 +71,7 @@ public class EntityInterceptorListener {
/** /**
* Callback for lifecyle event <i>post load</i>. * Callback for lifecyle event <i>post load</i>.
* *
* @param entity * @param entity the JPA entity which this listener is associated with
* the JPA entity which this listener is associated with
*/ */
@PostLoad @PostLoad
public void postLoad(final Object entity) { public void postLoad(final Object entity) {
@@ -86,8 +81,7 @@ public class EntityInterceptorListener {
/** /**
* Callback for lifecyle event <i>pre update</i>. * Callback for lifecyle event <i>pre update</i>.
* *
* @param entity * @param entity the JPA entity which this listener is associated with
* the JPA entity which this listener is associated with
*/ */
@PreUpdate @PreUpdate
public void preUpdate(final Object entity) { public void preUpdate(final Object entity) {
@@ -97,8 +91,7 @@ public class EntityInterceptorListener {
/** /**
* Callback for lifecyle event <i>post update</i>. * Callback for lifecyle event <i>post update</i>.
* *
* @param entity * @param entity the JPA entity which this listener is associated with
* the JPA entity which this listener is associated with
*/ */
@PostUpdate @PostUpdate
public void postUpdate(final Object entity) { 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 * Listens to change in property values of an entity and calls the corresponding
* {@link EventAwareEntity}. * {@link EventAwareEntity}.
*
*/ */
public class EntityPropertyChangeListener extends DescriptorEventAdapter { 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 @Override
public void postInsert(final DescriptorEvent event) { public void postInsert(final DescriptorEvent event) {
final Object object = event.getObject(); 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) { private static boolean isEventAwareEntity(final Object object) {
return object instanceof EventAwareEntity; return object instanceof EventAwareEntity;
} }

View File

@@ -22,21 +22,21 @@ public interface EventAwareEntity {
/** /**
* Fired for the Entity creation. * Fired for the Entity creation.
* *
* @param descriptorEvent * @param descriptorEvent
*/ */
void fireCreateEvent(DescriptorEvent descriptorEvent); void fireCreateEvent(DescriptorEvent descriptorEvent);
/** /**
* Fired for the Entity updation. * Fired for the Entity updation.
* *
* @param descriptorEvent * @param descriptorEvent
*/ */
void fireUpdateEvent(DescriptorEvent descriptorEvent); void fireUpdateEvent(DescriptorEvent descriptorEvent);
/** /**
* Fired for the Entity deletion. * Fired for the Entity deletion.
* *
* @param descriptorEvent * @param descriptorEvent
*/ */
void fireDeleteEvent(DescriptorEvent descriptorEvent); void fireDeleteEvent(DescriptorEvent descriptorEvent);

View File

@@ -67,6 +67,7 @@ import org.eclipse.persistence.descriptors.DescriptorEvent;
// sub entities // sub entities
@SuppressWarnings("squid:S2160") @SuppressWarnings("squid:S2160")
public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Action, EventAwareEntity { public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Action, EventAwareEntity {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@ManyToOne(fetch = FetchType.LAZY, optional = false) @ManyToOne(fetch = FetchType.LAZY, optional = false)
@@ -113,7 +114,7 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio
@ConversionValue(objectValue = "SCHEDULED", dataValue = "8"), @ConversionValue(objectValue = "SCHEDULED", dataValue = "8"),
@ConversionValue(objectValue = "CANCEL_REJECTED", dataValue = "9"), @ConversionValue(objectValue = "CANCEL_REJECTED", dataValue = "9"),
@ConversionValue(objectValue = "DOWNLOADED", dataValue = "10"), @ConversionValue(objectValue = "DOWNLOADED", dataValue = "10"),
@ConversionValue(objectValue = "WAIT_FOR_CONFIRMATION", dataValue = "11")}) @ConversionValue(objectValue = "WAIT_FOR_CONFIRMATION", dataValue = "11") })
@Convert("status") @Convert("status")
@NotNull @NotNull
private Status status; private Status status;
@@ -157,10 +158,6 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio
this.distributionSet = (JpaDistributionSet) distributionSet; this.distributionSet = (JpaDistributionSet) distributionSet;
} }
public void setActive(final boolean active) {
this.active = active;
}
@Override @Override
public Status getStatus() { public Status getStatus() {
return status; return status;
@@ -175,8 +172,8 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio
return active; return active;
} }
public void setActionType(final ActionType actionType) { public void setActive(final boolean active) {
this.actionType = actionType; this.active = active;
} }
@Override @Override
@@ -184,16 +181,8 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio
return actionType; return actionType;
} }
public List<ActionStatus> getActionStatus() { public void setActionType(final ActionType actionType) {
if (actionStatus == null) { this.actionType = actionType;
return Collections.emptyList();
}
return Collections.unmodifiableList(actionStatus);
}
public void setTarget(final Target target) {
this.target = (JpaTarget) target;
} }
@Override @Override
@@ -201,6 +190,10 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio
return target; return target;
} }
public void setTarget(final Target target) {
this.target = (JpaTarget) target;
}
@Override @Override
public long getForcedTime() { public long getForcedTime() {
return forcedTime; return forcedTime;
@@ -237,35 +230,6 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio
this.rollout = (JpaRollout) rollout; 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 @Override
public String getMaintenanceWindowSchedule() { public String getMaintenanceWindowSchedule() {
return maintenanceWindowSchedule; return maintenanceWindowSchedule;
@@ -274,8 +238,7 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio
/** /**
* Sets the maintenance schedule. * Sets the maintenance schedule.
* *
* @param maintenanceWindowSchedule * @param maintenanceWindowSchedule is a cron expression to be used for scheduling.
* is a cron expression to be used for scheduling.
*/ */
public void setMaintenanceWindowSchedule(final String maintenanceWindowSchedule) { public void setMaintenanceWindowSchedule(final String maintenanceWindowSchedule) {
this.maintenanceWindowSchedule = maintenanceWindowSchedule; this.maintenanceWindowSchedule = maintenanceWindowSchedule;
@@ -289,9 +252,8 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio
/** /**
* Sets the maintenance window duration. * Sets the maintenance window duration.
* *
* @param maintenanceWindowDuration * @param maintenanceWindowDuration is the duration of an available maintenance schedule in
* is the duration of an available maintenance schedule in * HH:mm:ss format.
* HH:mm:ss format.
*/ */
public void setMaintenanceWindowDuration(final String maintenanceWindowDuration) { public void setMaintenanceWindowDuration(final String maintenanceWindowDuration) {
this.maintenanceWindowDuration = maintenanceWindowDuration; this.maintenanceWindowDuration = maintenanceWindowDuration;
@@ -305,34 +267,45 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio
/** /**
* Sets the time zone to be used for maintenance window. * Sets the time zone to be used for maintenance window.
* *
* @param maintenanceWindowTimeZone * @param maintenanceWindowTimeZone is the time zone specified as +/-hh:mm offset from UTC for
* 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
* example +02:00 for CET summer time and +00:00 for UTC. The * start time of a maintenance window calculated based on the
* start time of a maintenance window calculated based on the * cron expression is relative to this time zone.
* cron expression is relative to this time zone.
*/ */
public void setMaintenanceWindowTimeZone(final String maintenanceWindowTimeZone) { public void setMaintenanceWindowTimeZone(final String maintenanceWindowTimeZone) {
this.maintenanceWindowTimeZone = maintenanceWindowTimeZone; 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 @Override
public Optional<ZonedDateTime> getMaintenanceWindowStartTime() { public Optional<ZonedDateTime> getMaintenanceWindowStartTime() {
return MaintenanceScheduleHelper.getNextMaintenanceWindow(maintenanceWindowSchedule, maintenanceWindowDuration, return MaintenanceScheduleHelper.getNextMaintenanceWindow(maintenanceWindowSchedule, maintenanceWindowDuration,
maintenanceWindowTimeZone); 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 @Override
public boolean hasMaintenanceSchedule() { public boolean hasMaintenanceSchedule() {
return this.maintenanceWindowSchedule != null; return this.maintenanceWindowSchedule != null;
@@ -366,35 +339,60 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio
} }
} }
@Override public boolean isWaitingConfirmation() {
public void setExternalRef(final String externalRef) { return status == Status.WAIT_FOR_CONFIRMATION;
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 void setLastActionStatusCode(final Integer lastActionStatusCode) { public void setLastActionStatusCode(final Integer lastActionStatusCode) {
this.lastActionStatusCode = lastActionStatusCode; this.lastActionStatusCode = lastActionStatusCode;
} }
public boolean isWaitingConfirmation() { public List<ActionStatus> getActionStatus() {
return status == Status.WAIT_FOR_CONFIRMATION; 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.List;
import java.util.Optional; import java.util.Optional;
import jakarta.persistence.CollectionTable; import jakarta.persistence.CollectionTable;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.persistence.ConstraintMode; import jakarta.persistence.ConstraintMode;
@@ -50,6 +49,7 @@ import org.eclipse.persistence.annotations.ObjectTypeConverter;
// sub entities // sub entities
@SuppressWarnings("squid:S2160") @SuppressWarnings("squid:S2160")
public class JpaActionStatus extends AbstractJpaTenantAwareBaseEntity implements ActionStatus { public class JpaActionStatus extends AbstractJpaTenantAwareBaseEntity implements ActionStatus {
private static final int MESSAGE_ENTRY_LENGTH = 512; private static final int MESSAGE_ENTRY_LENGTH = 512;
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@@ -75,7 +75,7 @@ public class JpaActionStatus extends AbstractJpaTenantAwareBaseEntity implements
@ConversionValue(objectValue = "SCHEDULED", dataValue = "8"), @ConversionValue(objectValue = "SCHEDULED", dataValue = "8"),
@ConversionValue(objectValue = "CANCEL_REJECTED", dataValue = "9"), @ConversionValue(objectValue = "CANCEL_REJECTED", dataValue = "9"),
@ConversionValue(objectValue = "DOWNLOADED", dataValue = "10"), @ConversionValue(objectValue = "DOWNLOADED", dataValue = "10"),
@ConversionValue(objectValue = "WAIT_FOR_CONFIRMATION", dataValue = "11")}) @ConversionValue(objectValue = "WAIT_FOR_CONFIRMATION", dataValue = "11") })
@Convert("status") @Convert("status")
@NotNull @NotNull
private Status status; private Status status;
@@ -89,16 +89,13 @@ public class JpaActionStatus extends AbstractJpaTenantAwareBaseEntity implements
@Column(name = "code", nullable = true, updatable = false) @Column(name = "code", nullable = true, updatable = false)
private Integer code; private Integer code;
/** /**
* Creates a new {@link ActionStatus} object. * Creates a new {@link ActionStatus} object.
* *
* @param action * @param action the action for this action status
* the action for this action status * @param status the status for this action status
* @param status * @param occurredAt the occurred timestamp
* the status for this action status
* @param occurredAt
* the occurred timestamp
*/ */
public JpaActionStatus(final Action action, final Status status, final long occurredAt) { public JpaActionStatus(final Action action, final Status status, final long occurredAt) {
this.action = (JpaAction) action; this.action = (JpaAction) action;
@@ -109,14 +106,10 @@ public class JpaActionStatus extends AbstractJpaTenantAwareBaseEntity implements
/** /**
* Creates a new {@link ActionStatus} object. * Creates a new {@link ActionStatus} object.
* *
* @param action * @param action the action for this action status
* the action for this action status * @param status the status for this action status
* @param status * @param occurredAt the occurred timestamp
* the status for this action status * @param message the message which should be added to 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) { public JpaActionStatus(final JpaAction action, final Status status, final long occurredAt, final String message) {
this.action = action; this.action = action;
@@ -128,10 +121,8 @@ public class JpaActionStatus extends AbstractJpaTenantAwareBaseEntity implements
/** /**
* Creates a new {@link ActionStatus} object. * Creates a new {@link ActionStatus} object.
* *
* @param status * @param status the status for this action status
* the status for this action status * @param occurredAt the occurred timestamp
* @param occurredAt
* the occurred timestamp
*/ */
public JpaActionStatus(final Status status, final long occurredAt) { public JpaActionStatus(final Status status, final long occurredAt) {
this.status = status; this.status = status;
@@ -154,36 +145,6 @@ public class JpaActionStatus extends AbstractJpaTenantAwareBaseEntity implements
this.occurredAt = occurredAt; 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 @Override
public Action getAction() { public Action getAction() {
return action; return action;
@@ -209,4 +170,34 @@ public class JpaActionStatus extends AbstractJpaTenantAwareBaseEntity implements
public void setCode(final Integer code) { public void setCode(final Integer code) {
this.code = 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}. * JPA implementation of {@link Artifact}.
*
*/ */
@Table(name = "sp_artifact", indexes = { @Index(name = "sp_idx_artifact_01", columnList = "tenant,software_module"), @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"), @Index(name = "sp_idx_artifact_02", columnList = "tenant,sha1_hash"),
@@ -39,6 +38,7 @@ import org.eclipse.hawkbit.repository.model.SoftwareModule;
// sub entities // sub entities
@SuppressWarnings("squid:S2160") @SuppressWarnings("squid:S2160")
public class JpaArtifact extends AbstractJpaTenantAwareBaseEntity implements Artifact { public class JpaArtifact extends AbstractJpaTenantAwareBaseEntity implements Artifact {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@Column(name = "sha1_hash", length = 40, nullable = false, updatable = false) @Column(name = "sha1_hash", length = 40, nullable = false, updatable = false)
@@ -74,12 +74,9 @@ public class JpaArtifact extends AbstractJpaTenantAwareBaseEntity implements Art
/** /**
* Constructs artifact. * Constructs artifact.
* *
* @param sha1Hash * @param sha1Hash that is the link to the {@link AbstractDbArtifact} entity.
* that is the link to the {@link AbstractDbArtifact} entity. * @param filename that is used by {@link AbstractDbArtifact} store.
* @param filename * @param softwareModule of this artifact
* that is used by {@link AbstractDbArtifact} store.
* @param softwareModule
* of this artifact
*/ */
public JpaArtifact(@NotEmpty final String sha1Hash, @NotNull final String filename, public JpaArtifact(@NotEmpty final String sha1Hash, @NotNull final String filename,
final SoftwareModule softwareModule) { final SoftwareModule softwareModule) {
@@ -89,6 +86,16 @@ public class JpaArtifact extends AbstractJpaTenantAwareBaseEntity implements Art
this.softwareModule.addArtifact(this); this.softwareModule.addArtifact(this);
} }
@Override
public String getFilename() {
return filename;
}
@Override
public SoftwareModule getSoftwareModule() {
return softwareModule;
}
@Override @Override
public String getMd5Hash() { public String getMd5Hash() {
return md5Hash; return md5Hash;
@@ -104,19 +111,10 @@ public class JpaArtifact extends AbstractJpaTenantAwareBaseEntity implements Art
return sha256Hash; 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) { public void setSha256Hash(final String sha256Hash) {
this.sha256Hash = sha256Hash; this.sha256Hash = sha256Hash;
} }
@Override @Override
public long getSize() { public long getSize() {
return size; return size;
@@ -126,13 +124,11 @@ public class JpaArtifact extends AbstractJpaTenantAwareBaseEntity implements Art
this.size = size; this.size = size;
} }
@Override public void setSha1Hash(final String sha1Hash) {
public SoftwareModule getSoftwareModule() { this.sha1Hash = sha1Hash;
return softwareModule;
} }
@Override public void setMd5Hash(final String md5Hash) {
public String getFilename() { this.md5Hash = md5Hash;
return filename;
} }
} }

View File

@@ -9,11 +9,6 @@
*/ */
package org.eclipse.hawkbit.repository.jpa.model; 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.Column;
import jakarta.persistence.ConstraintMode; import jakarta.persistence.ConstraintMode;
import jakarta.persistence.Entity; import jakarta.persistence.Entity;
@@ -24,6 +19,11 @@ import jakarta.persistence.OneToOne;
import jakarta.persistence.Table; import jakarta.persistence.Table;
import jakarta.validation.constraints.Size; 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 @Entity
@Table(name = "sp_target_conf_status") @Table(name = "sp_target_conf_status")
public class JpaAutoConfirmationStatus extends AbstractJpaTenantAwareBaseEntity implements AutoConfirmationStatus { public class JpaAutoConfirmationStatus extends AbstractJpaTenantAwareBaseEntity implements AutoConfirmationStatus {
@@ -81,11 +81,11 @@ public class JpaAutoConfirmationStatus extends AbstractJpaTenantAwareBaseEntity
// https://docs.oracle.com/en/java/javase/17/text-blocks/index.html#normalization-of-line-terminators // https://docs.oracle.com/en/java/javase/17/text-blocks/index.html#normalization-of-line-terminators
// nevertheless of the end of line of the file (\r\n, \n or \r) the result will contains \n // nevertheless of the end of line of the file (\r\n, \n or \r) the result will contains \n
return """ return """
Assignment automatically confirmed by initiator '%s'.\040 Assignment automatically confirmed by initiator '%s'.\040
Auto confirmation activated by system user: '%s'\040 Auto confirmation activated by system user: '%s'\040
Remark: %s""".formatted(formattedInitiator, createdByRolloutsUser, remarkMessage); Remark: %s""".formatted(formattedInitiator, createdByRolloutsUser, remarkMessage);
} }
@Override @Override

View File

@@ -67,8 +67,8 @@ import org.springframework.context.ApplicationEvent;
@Entity @Entity
@Table(name = "sp_distribution_set", uniqueConstraints = { @Table(name = "sp_distribution_set", uniqueConstraints = {
@UniqueConstraint(columnNames = { "name", "version", "tenant" }, name = "uk_distrib_set") }, indexes = { @UniqueConstraint(columnNames = { "name", "version", "tenant" }, name = "uk_distrib_set") }, indexes = {
@Index(name = "sp_idx_distribution_set_01", columnList = "tenant,deleted,complete"), @Index(name = "sp_idx_distribution_set_01", columnList = "tenant,deleted,complete"),
@Index(name = "sp_idx_distribution_set_prim", columnList = "tenant,id") }) @Index(name = "sp_idx_distribution_set_prim", columnList = "tenant,id") })
@NamedEntityGraph(name = "DistributionSet.detail", attributeNodes = { @NamedAttributeNode("modules"), @NamedEntityGraph(name = "DistributionSet.detail", attributeNodes = { @NamedAttributeNode("modules"),
@NamedAttributeNode("tags"), @NamedAttributeNode("type") }) @NamedAttributeNode("tags"), @NamedAttributeNode("type") })
// exception squid:S2160 - BaseEntity equals/hashcode is handling correctly for // exception squid:S2160 - BaseEntity equals/hashcode is handling correctly for
@@ -90,7 +90,7 @@ public class JpaDistributionSet extends AbstractJpaNamedVersionedEntity implemen
@ManyToMany(targetEntity = JpaSoftwareModule.class, fetch = FetchType.LAZY) @ManyToMany(targetEntity = JpaSoftwareModule.class, fetch = FetchType.LAZY)
@JoinTable(name = "sp_ds_module", joinColumns = { @JoinTable(name = "sp_ds_module", joinColumns = {
@JoinColumn(name = "ds_id", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_ds_module_ds")) }, inverseJoinColumns = { @JoinColumn(name = "ds_id", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_ds_module_ds")) }, inverseJoinColumns = {
@JoinColumn(name = "module_id", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_ds_module_module")) }) @JoinColumn(name = "module_id", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_ds_module_module")) })
private Set<SoftwareModule> modules; private Set<SoftwareModule> modules;
@CascadeOnDelete @CascadeOnDelete
@@ -284,18 +284,6 @@ public class JpaDistributionSet extends AbstractJpaNamedVersionedEntity implemen
EventPublisherHolder.getInstance().getApplicationId())); 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) { private static void publishEventWithEventPublisher(final ApplicationEvent event) {
EventPublisherHolder.getInstance().getEventPublisher().publishEvent(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()) return changes.stream().anyMatch(changeRecord -> DELETED_PROPERTY.equals(changeRecord.getAttribute())
&& Boolean.parseBoolean(changeRecord.getNewValue().toString())); && 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}. * Meta data for {@link DistributionSet}.
*
*/ */
@IdClass(DsMetadataCompositeKey.class) @IdClass(DsMetadataCompositeKey.class)
@Entity @Entity
@Table(name = "sp_ds_metadata") @Table(name = "sp_ds_metadata")
public class JpaDistributionSetMetadata extends AbstractJpaMetaData implements DistributionSetMetadata { public class JpaDistributionSetMetadata extends AbstractJpaMetaData implements DistributionSetMetadata {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@Id @Id
@@ -54,15 +54,15 @@ public class JpaDistributionSetMetadata extends AbstractJpaMetaData implements D
return new DsMetadataCompositeKey(distributionSet.getId(), getKey()); return new DsMetadataCompositeKey(distributionSet.getId(), getKey());
} }
public void setDistributionSet(final DistributionSet distributionSet) {
this.distributionSet = (JpaDistributionSet) distributionSet;
}
@Override @Override
public DistributionSet getDistributionSet() { public DistributionSet getDistributionSet() {
return distributionSet; return distributionSet;
} }
public void setDistributionSet(final DistributionSet distributionSet) {
this.distributionSet = (JpaDistributionSet) distributionSet;
}
@Override @Override
public int hashCode() { public int hashCode() {
final int prime = 31; final int prime = 31;

View File

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

View File

@@ -9,6 +9,24 @@
*/ */
package org.eclipse.hawkbit.repository.jpa.model; 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.DistributionSetTypeDeletedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetTypeCreatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetTypeCreatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetTypeUpdatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetTypeUpdatedEvent;
@@ -22,34 +40,16 @@ import org.eclipse.persistence.annotations.CascadeOnDelete;
import org.eclipse.persistence.descriptors.DescriptorEvent; import org.eclipse.persistence.descriptors.DescriptorEvent;
import org.springframework.util.CollectionUtils; 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 * A distribution set type defines which software module types can or have to be
* {@link DistributionSet}. * {@link DistributionSet}.
*
*/ */
@Entity @Entity
@Table(name = "sp_distribution_set_type", indexes = { @Table(name = "sp_distribution_set_type", indexes = {
@Index(name = "sp_idx_distribution_set_type_01", columnList = "tenant,deleted"), @Index(name = "sp_idx_distribution_set_type_01", columnList = "tenant,deleted"),
@Index(name = "sp_idx_distribution_set_type_prim", columnList = "tenant,id") }, uniqueConstraints = { @Index(name = "sp_idx_distribution_set_type_prim", columnList = "tenant,id") }, uniqueConstraints = {
@UniqueConstraint(columnNames = { "name", "tenant" }, name = "uk_dst_name"), @UniqueConstraint(columnNames = { "name", "tenant" }, name = "uk_dst_name"),
@UniqueConstraint(columnNames = { "type_key", "tenant" }, name = "uk_dst_key") }) @UniqueConstraint(columnNames = { "type_key", "tenant" }, name = "uk_dst_key") })
// exception squid:S2160 - BaseEntity equals/hashcode is handling correctly for // exception squid:S2160 - BaseEntity equals/hashcode is handling correctly for
// sub entities // sub entities
@SuppressWarnings("squid:S2160") @SuppressWarnings("squid:S2160")
@@ -77,12 +77,9 @@ public class JpaDistributionSetType extends AbstractJpaTypeEntity implements Dis
/** /**
* Standard constructor. * Standard constructor.
* *
* @param key * @param key of the type (unique)
* of the type (unique) * @param name of the type (unique)
* @param name * @param description of the type
* of the type (unique)
* @param description
* of the type
*/ */
public JpaDistributionSetType(final String key, final String name, final String description) { public JpaDistributionSetType(final String key, final String name, final String description) {
this(key, name, description, null); this(key, name, description, null);
@@ -91,14 +88,10 @@ public class JpaDistributionSetType extends AbstractJpaTypeEntity implements Dis
/** /**
* Constructor. * Constructor.
* *
* @param key * @param key of the type
* of the type * @param name of the type
* @param name * @param description of the type
* of the type * @param colour of the type. It will be null by default
* @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) { public JpaDistributionSetType(final String key, final String name, final String description, final String colour) {
super(name, description, key, 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); return new HashSet<>(((JpaDistributionSetType) dsType).elements).equals(elements);
} }
private boolean isOneModuleListEmpty(final DistributionSetType dsType) { @Override
return (!CollectionUtils.isEmpty(((JpaDistributionSetType) dsType).elements) public boolean checkComplete(final DistributionSet distributionSet) {
&& CollectionUtils.isEmpty(elements)) final List<SoftwareModuleType> smTypes = distributionSet.getModules().stream().map(SoftwareModule::getType)
|| (CollectionUtils.isEmpty(((JpaDistributionSetType) dsType).elements) .distinct().collect(Collectors.toList());
&& !CollectionUtils.isEmpty(elements)); if (smTypes.isEmpty()) {
} return false;
}
private boolean areBothModuleListsEmpty(final DistributionSetType dsType) { return smTypes.containsAll(getMandatoryModuleTypes());
return CollectionUtils.isEmpty(((JpaDistributionSetType) dsType).elements) && CollectionUtils.isEmpty(elements);
} }
public JpaDistributionSetType addOptionalModuleType(final SoftwareModuleType smType) { public JpaDistributionSetType addOptionalModuleType(final SoftwareModuleType smType) {
@@ -163,26 +155,6 @@ public class JpaDistributionSetType extends AbstractJpaTypeEntity implements Dis
return setModuleType(smType, true); 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) { public JpaDistributionSetType removeModuleType(final Long smTypeId) {
if (elements == null) { if (elements == null) {
return this; return this;
@@ -195,16 +167,6 @@ public class JpaDistributionSetType extends AbstractJpaTypeEntity implements Dis
return this; 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() { public Set<DistributionSetTypeElement> getElements() {
if (elements == null) { if (elements == null) {
return Collections.emptySet(); return Collections.emptySet();
@@ -235,4 +197,35 @@ public class JpaDistributionSetType extends AbstractJpaTypeEntity implements Dis
EventPublisherHolder.getInstance().getEventPublisher().publishEvent(new DistributionSetTypeDeletedEvent( EventPublisherHolder.getInstance().getEventPublisher().publishEvent(new DistributionSetTypeDeletedEvent(
getTenant(), getId(), getClass(), EventPublisherHolder.getInstance().getApplicationId())); 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}. * JPA implementation of a {@link Rollout}.
*
*/ */
@Entity @Entity
@Table(name = "sp_rollout", uniqueConstraints = @UniqueConstraint(columnNames = { "name", @Table(name = "sp_rollout", uniqueConstraints = @UniqueConstraint(columnNames = { "name",
@@ -149,6 +148,62 @@ public class JpaRollout extends AbstractJpaNamedEntity implements Rollout, Event
@Transient @Transient
private transient TotalTargetCountStatus totalTargetCountStatus; 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 @Override
public DistributionSet getDistributionSet() { public DistributionSet getDistributionSet() {
return distributionSet; return distributionSet;
@@ -158,14 +213,6 @@ public class JpaRollout extends AbstractJpaNamedEntity implements Rollout, Event
this.distributionSet = (JpaDistributionSet) distributionSet; this.distributionSet = (JpaDistributionSet) distributionSet;
} }
public List<RolloutGroup> getRolloutGroups() {
if (rolloutGroups == null) {
return Collections.emptyList();
}
return Collections.unmodifiableList(rolloutGroups);
}
@Override @Override
public String getTargetFilterQuery() { public String getTargetFilterQuery() {
return targetFilterQuery; return targetFilterQuery;
@@ -184,23 +231,6 @@ public class JpaRollout extends AbstractJpaNamedEntity implements Rollout, Event
this.status = status; 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 @Override
public ActionType getActionType() { public ActionType getActionType() {
return actionType; return actionType;
@@ -215,40 +245,13 @@ public class JpaRollout extends AbstractJpaNamedEntity implements Rollout, Event
return forcedTime; return forcedTime;
} }
public void setForcedTime(final long forcedTime) {
this.forcedTime = forcedTime;
}
@Override @Override
public Optional<Integer> getWeight() { public Long getStartAt() {
return Optional.ofNullable(weight); return startAt;
} }
public void setWeight(final Integer weight) { public void setStartAt(final Long startAt) {
this.weight = weight; this.startAt = startAt;
}
@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;
} }
@Override @Override
@@ -281,54 +284,6 @@ public class JpaRollout extends AbstractJpaNamedEntity implements Rollout, Event
this.totalTargetCountStatus = totalTargetCountStatus; 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 @Override
public String getApprovalDecidedBy() { public String getApprovalDecidedBy() {
return approvalDecidedBy; return approvalDecidedBy;
@@ -343,7 +298,51 @@ public class JpaRollout extends AbstractJpaNamedEntity implements Rollout, Event
return approvalRemark; 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) { public void setApprovalRemark(final String approvalRemark) {
this.approvalRemark = 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. * JPA entity definition of persisting a group of an rollout.
*
*/ */
@Entity @Entity
@Table(name = "sp_rolloutgroup", uniqueConstraints = @UniqueConstraint(columnNames = { "name", "rollout", @Table(name = "sp_rolloutgroup", uniqueConstraints = @UniqueConstraint(columnNames = { "name", "rollout",
@@ -121,7 +120,7 @@ public class JpaRolloutGroup extends AbstractJpaNamedEntity implements RolloutGr
@Column(name = "target_percentage") @Column(name = "target_percentage")
private float targetPercentage = 100; private float targetPercentage = 100;
@Column(name = "confirmation_required") @Column(name = "confirmation_required")
private boolean confirmationRequired; private boolean confirmationRequired;
@@ -146,14 +145,6 @@ public class JpaRolloutGroup extends AbstractJpaNamedEntity implements RolloutGr
this.status = status; this.status = status;
} }
public List<RolloutTargetGroup> getRolloutTargetGroup() {
if (rolloutTargetGroup == null) {
return Collections.emptyList();
}
return Collections.unmodifiableList(rolloutTargetGroup);
}
@Override @Override
public RolloutGroup getParent() { public RolloutGroup getParent() {
return parent; return parent;
@@ -168,10 +159,6 @@ public class JpaRolloutGroup extends AbstractJpaNamedEntity implements RolloutGr
this.dynamic = dynamic; this.dynamic = dynamic;
} }
public void setParent(final RolloutGroup parent) {
this.parent = (JpaRolloutGroup) parent;
}
@Override @Override
public RolloutGroupSuccessCondition getSuccessCondition() { public RolloutGroupSuccessCondition getSuccessCondition() {
return successCondition; return successCondition;
@@ -245,12 +232,15 @@ public class JpaRolloutGroup extends AbstractJpaNamedEntity implements RolloutGr
this.totalTargets = totalTargets; this.totalTargets = totalTargets;
} }
public void setSuccessAction(final RolloutGroupSuccessAction successAction) { /**
this.successAction = successAction; * @return the totalTargetCountStatus
} */
@Override
public void setSuccessActionExp(final String successActionExp) { public TotalTargetCountStatus getTotalTargetCountStatus() {
this.successActionExp = successActionExp; if (totalTargetCountStatus == null) {
totalTargetCountStatus = new TotalTargetCountStatus(Long.valueOf(totalTargets), rollout.getActionType());
}
return totalTargetCountStatus;
} }
@Override @Override
@@ -267,38 +257,46 @@ public class JpaRolloutGroup extends AbstractJpaNamedEntity implements RolloutGr
return targetPercentage; return targetPercentage;
} }
public void setConfirmationRequired(final boolean confirmationRequired) {
this.confirmationRequired = confirmationRequired;
}
@Override @Override
public boolean isConfirmationRequired() { public boolean isConfirmationRequired() {
return confirmationRequired; return confirmationRequired;
} }
public void setConfirmationRequired(final boolean confirmationRequired) {
this.confirmationRequired = confirmationRequired;
}
public void setTargetPercentage(final float targetPercentage) { public void setTargetPercentage(final float targetPercentage) {
this.targetPercentage = targetPercentage; this.targetPercentage = targetPercentage;
} }
/** /**
* @return the totalTargetCountStatus * @param totalTargetCountStatus the totalTargetCountStatus to set
*/
@Override
public TotalTargetCountStatus getTotalTargetCountStatus() {
if (totalTargetCountStatus == null) {
totalTargetCountStatus = new TotalTargetCountStatus(Long.valueOf(totalTargets), rollout.getActionType());
}
return totalTargetCountStatus;
}
/**
* @param totalTargetCountStatus
* the totalTargetCountStatus to set
*/ */
public void setTotalTargetCountStatus(final TotalTargetCountStatus totalTargetCountStatus) { public void setTotalTargetCountStatus(final TotalTargetCountStatus totalTargetCountStatus) {
this.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 @Override
public String toString() { public String toString() {
return "RolloutGroup [rollout=" + (rollout != null ? rollout.getId() : "") + ", status=" + status return "RolloutGroup [rollout=" + (rollout != null ? rollout.getId() : "") + ", status=" + status

View File

@@ -62,9 +62,9 @@ import org.eclipse.persistence.sessions.changesets.ObjectChangeSet;
@Entity @Entity
@Table(name = "sp_base_software_module", uniqueConstraints = @UniqueConstraint(columnNames = { "module_type", "name", @Table(name = "sp_base_software_module", uniqueConstraints = @UniqueConstraint(columnNames = { "module_type", "name",
"version", "tenant" }, name = "uk_base_sw_mod"), indexes = { "version", "tenant" }, name = "uk_base_sw_mod"), indexes = {
@Index(name = "sp_idx_base_sw_module_01", columnList = "tenant,deleted,name,version"), @Index(name = "sp_idx_base_sw_module_01", columnList = "tenant,deleted,name,version"),
@Index(name = "sp_idx_base_sw_module_02", columnList = "tenant,deleted,module_type"), @Index(name = "sp_idx_base_sw_module_02", columnList = "tenant,deleted,module_type"),
@Index(name = "sp_idx_base_sw_module_prim", columnList = "tenant,id") }) @Index(name = "sp_idx_base_sw_module_prim", columnList = "tenant,id") })
@NamedEntityGraph(name = "SoftwareModule.artifacts", attributeNodes = { @NamedAttributeNode("artifacts") }) @NamedEntityGraph(name = "SoftwareModule.artifacts", attributeNodes = { @NamedAttributeNode("artifacts") })
// exception squid:S2160 - BaseEntity equals/hashcode is handling correctly for // exception squid:S2160 - BaseEntity equals/hashcode is handling correctly for
// sub entities // sub entities
@@ -83,7 +83,8 @@ public class JpaSoftwareModule extends AbstractJpaNamedVersionedEntity implement
private JpaSoftwareModuleType type; private JpaSoftwareModuleType type;
@CascadeOnDelete @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; private List<JpaArtifact> artifacts;
@Setter @Setter
@@ -191,7 +192,8 @@ public class JpaSoftwareModule extends AbstractJpaNamedVersionedEntity implement
} }
sb.delete(sb.length() - 2, sb.length()); sb.delete(sb.length() - 2, sb.length());
throw new LockedException(JpaSoftwareModule.class, getId(), "DELETE", sb.toString()); throw new LockedException(JpaSoftwareModule.class, getId(), "DELETE", sb.toString());
}; }
;
} }
this.deleted = deleted; this.deleted = deleted;
} }

View File

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

View File

@@ -9,6 +9,8 @@
*/ */
package org.eclipse.hawkbit.repository.jpa.model; package org.eclipse.hawkbit.repository.jpa.model;
import java.io.Serial;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.persistence.Entity; import jakarta.persistence.Entity;
import jakarta.persistence.Index; import jakarta.persistence.Index;
@@ -23,18 +25,15 @@ import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
import org.eclipse.hawkbit.repository.model.helper.EventPublisherHolder; import org.eclipse.hawkbit.repository.model.helper.EventPublisherHolder;
import org.eclipse.persistence.descriptors.DescriptorEvent; import org.eclipse.persistence.descriptors.DescriptorEvent;
import java.io.Serial;
/** /**
* Type of a software modules. * Type of a software modules.
*
*/ */
@Entity @Entity
@Table(name = "sp_software_module_type", indexes = { @Table(name = "sp_software_module_type", indexes = {
@Index(name = "sp_idx_software_module_type_01", columnList = "tenant,deleted"), @Index(name = "sp_idx_software_module_type_01", columnList = "tenant,deleted"),
@Index(name = "sp_idx_software_module_type_prim", columnList = "tenant,id") }, uniqueConstraints = { @Index(name = "sp_idx_software_module_type_prim", columnList = "tenant,id") }, uniqueConstraints = {
@UniqueConstraint(columnNames = { "type_key", "tenant" }, name = "uk_smt_type_key"), @UniqueConstraint(columnNames = { "type_key", "tenant" }, name = "uk_smt_type_key"),
@UniqueConstraint(columnNames = { "name", "tenant" }, name = "uk_smt_name") }) @UniqueConstraint(columnNames = { "name", "tenant" }, name = "uk_smt_name") })
// exception squid:S2160 - BaseEntity equals/hashcode is handling correctly for // exception squid:S2160 - BaseEntity equals/hashcode is handling correctly for
// sub entities // sub entities
@SuppressWarnings("squid:S2160") @SuppressWarnings("squid:S2160")
@@ -53,14 +52,10 @@ public class JpaSoftwareModuleType extends AbstractJpaTypeEntity implements Soft
/** /**
* Constructor. * Constructor.
* *
* @param key * @param key of the type
* of the type * @param name of the type
* @param name * @param description of the type
* of the type * @param maxAssignments assignments to a DS
* @param description
* of the type
* @param maxAssignments
* assignments to a DS
*/ */
public JpaSoftwareModuleType(final String key, final String name, final String description, public JpaSoftwareModuleType(final String key, final String name, final String description,
final int maxAssignments) { final int maxAssignments) {
@@ -70,16 +65,11 @@ public class JpaSoftwareModuleType extends AbstractJpaTypeEntity implements Soft
/** /**
* Constructor. * Constructor.
* *
* @param key * @param key of the type
* of the type * @param name of the type
* @param name * @param description of the type
* of the type * @param maxAssignments assignments to a DS
* @param description * @param colour of the type. It will be null by default
* 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, public JpaSoftwareModuleType(final String key, final String name, final String description,
final int maxAssignments, final String colour) { final int maxAssignments, final String colour) {
@@ -94,15 +84,15 @@ public class JpaSoftwareModuleType extends AbstractJpaTypeEntity implements Soft
// Default Constructor for JPA. // Default Constructor for JPA.
} }
public void setMaxAssignments(final int maxAssignments) {
this.maxAssignments = maxAssignments;
}
@Override @Override
public int getMaxAssignments() { public int getMaxAssignments() {
return maxAssignments; return maxAssignments;
} }
public void setMaxAssignments(final int maxAssignments) {
this.maxAssignments = maxAssignments;
}
@Override @Override
public boolean isDeleted() { public boolean isDeleted() {
return deleted; return deleted;

View File

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

View File

@@ -77,7 +77,7 @@ import org.eclipse.persistence.descriptors.DescriptorEvent;
@Index(name = "sp_idx_target_04", columnList = "tenant,created_at"), @Index(name = "sp_idx_target_04", columnList = "tenant,created_at"),
@Index(name = "sp_idx_target_05", columnList = "tenant,last_modified_at"), @Index(name = "sp_idx_target_05", columnList = "tenant,last_modified_at"),
@Index(name = "sp_idx_target_prim", columnList = "tenant,id") }, uniqueConstraints = @UniqueConstraint(columnNames = { @Index(name = "sp_idx_target_prim", columnList = "tenant,id") }, uniqueConstraints = @UniqueConstraint(columnNames = {
"controller_id", "tenant" }, name = "uk_tenant_controller_id")) "controller_id", "tenant" }, name = "uk_tenant_controller_id"))
// exception squid:S2160 - BaseEntity equals/hashcode is handling correctly for // exception squid:S2160 - BaseEntity equals/hashcode is handling correctly for
// sub entities // sub entities
@SuppressWarnings("squid:S2160") @SuppressWarnings("squid:S2160")
@@ -218,91 +218,25 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw
return assignedDistributionSet; return assignedDistributionSet;
} }
/**
* @param assignedDistributionSet Distribution set
*/
public void setAssignedDistributionSet(final DistributionSet assignedDistributionSet) {
this.assignedDistributionSet = (JpaDistributionSet) assignedDistributionSet;
}
@Override @Override
public String getControllerId() { public String getControllerId() {
return controllerId; return controllerId;
} }
/** /**
* @return tags * @param controllerId Controller ID
*/
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
*/ */
public void setControllerId(final String controllerId) { public void setControllerId(final String controllerId) {
this.controllerId = 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 * @return the securityToken if the current security context contains the
* necessary permission {@link SpPermission#READ_TARGET_SEC_TOKEN} * necessary permission {@link SpPermission#READ_TARGET_SEC_TOKEN}
@@ -321,8 +255,7 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw
} }
/** /**
* @param securityToken * @param securityToken token value
* token value
*/ */
public void setSecurityToken(final String securityToken) { public void setSecurityToken(final String securityToken) {
this.securityToken = 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 * @return the poll time which holds the last poll time of the target, the
* next poll time and the overdue time. In case the * next poll time and the overdue time. In case the
@@ -366,37 +319,117 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw
} }
public void setAutoConfirmationStatus(final JpaAutoConfirmationStatus autoConfirmationStatus) { public void setAutoConfirmationStatus(final JpaAutoConfirmationStatus autoConfirmationStatus) {
this.autoConfirmationStatus = autoConfirmationStatus; this.autoConfirmationStatus = autoConfirmationStatus;
} }
@Override @Override
public Long getLastTargetQuery() { public boolean isRequestControllerAttributes() {
return lastTargetQuery; return requestControllerAttributes;
}
@Override
public Long getInstallationDate() {
return installationDate;
}
@Override
public TargetUpdateStatus getUpdateStatus() {
return updateStatus;
}
@Override
public TargetType getTargetType() {
return targetType;
} }
/** /**
* @param type * @param requestControllerAttributes Attributes
* Target type */
public void setRequestControllerAttributes(final boolean requestControllerAttributes) {
this.requestControllerAttributes = requestControllerAttributes;
}
/**
* @param type Target type
*/ */
public void setTargetType(final TargetType type) { public void setTargetType(final TargetType type) {
this.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 * @return Distribution set
*/ */
@@ -404,6 +437,13 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw
return installedDistributionSet; return installedDistributionSet;
} }
/**
* @param installedDistributionSet Distribution set
*/
public void setInstalledDistributionSet(final JpaDistributionSet installedDistributionSet) {
this.installedDistributionSet = installedDistributionSet;
}
/** /**
* @return controller attributes * @return controller attributes
*/ */
@@ -411,11 +451,6 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw
return controllerAttributes; return controllerAttributes;
} }
@Override
public boolean isRequestControllerAttributes() {
return requestControllerAttributes;
}
/** /**
* @return target metadata * @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 @Override
public void fireCreateEvent(final DescriptorEvent descriptorEvent) { public void fireCreateEvent(final DescriptorEvent descriptorEvent) {
EventPublisherHolder.getInstance().getEventPublisher() EventPublisherHolder.getInstance().getEventPublisher()
.publishEvent(new TargetCreatedEvent(this, EventPublisherHolder.getInstance().getApplicationId())); .publishEvent(new TargetCreatedEvent(this, EventPublisherHolder.getInstance().getApplicationId()));
} }
@Override
public List<String> getUpdateIgnoreFields() {
return TARGET_UPDATE_EVENT_IGNORE_FIELDS;
}
@Override @Override
public void fireUpdateEvent(final DescriptorEvent descriptorEvent) { public void fireUpdateEvent(final DescriptorEvent descriptorEvent) {
EventPublisherHolder.getInstance().getEventPublisher() EventPublisherHolder.getInstance().getEventPublisher()
@@ -504,4 +486,9 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw
.publishEvent(new TargetDeletedEvent(getTenant(), getId(), getControllerId(), address, .publishEvent(new TargetDeletedEvent(getTenant(), getId(), getControllerId(), address,
getClass(), EventPublisherHolder.getInstance().getApplicationId())); 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. * Stored target filter.
*
*/ */
@Entity @Entity
@Table(name = "sp_target_filter_query", uniqueConstraints = @UniqueConstraint(columnNames = { "name", @Table(name = "sp_target_filter_query", uniqueConstraints = @UniqueConstraint(columnNames = { "name",
@@ -49,6 +48,7 @@ import org.eclipse.persistence.descriptors.DescriptorEvent;
@SuppressWarnings("squid:S2160") @SuppressWarnings("squid:S2160")
public class JpaTargetFilterQuery extends AbstractJpaTenantAwareBaseEntity public class JpaTargetFilterQuery extends AbstractJpaTenantAwareBaseEntity
implements TargetFilterQuery, EventAwareEntity { implements TargetFilterQuery, EventAwareEntity {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@Column(name = "name", length = NamedEntity.NAME_MAX_SIZE, nullable = false) @Column(name = "name", length = NamedEntity.NAME_MAX_SIZE, nullable = false)
@@ -93,19 +93,13 @@ public class JpaTargetFilterQuery extends AbstractJpaTenantAwareBaseEntity
/** /**
* Construct a Target filter query with auto assign distribution set * Construct a Target filter query with auto assign distribution set
* *
* @param name * @param name of the {@link TargetFilterQuery}.
* of the {@link TargetFilterQuery}. * @param query of the {@link TargetFilterQuery}.
* @param query * @param autoAssignDistributionSet of the {@link TargetFilterQuery}.
* of the {@link TargetFilterQuery}. * @param autoAssignActionType of the {@link TargetFilterQuery}.
* @param autoAssignDistributionSet * @param autoAssignWeight of the {@link TargetFilterQuery}.
* of the {@link TargetFilterQuery}. * @param confirmationRequired 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, public JpaTargetFilterQuery(final String name, final String query, final DistributionSet autoAssignDistributionSet,
final ActionType autoAssignActionType, final Integer autoAssignWeight, final boolean confirmationRequired) { final ActionType autoAssignActionType, final Integer autoAssignWeight, final boolean confirmationRequired) {

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