Code format hawkbit-repository-jpa (#1928)

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

View File

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

View File

@@ -9,155 +9,155 @@
SPDX-License-Identifier: EPL-2.0
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.eclipse.hawkbit</groupId>
<version>${revision}</version>
<artifactId>hawkbit-repository</artifactId>
</parent>
<artifactId>hawkbit-repository-jpa</artifactId>
<name>hawkBit :: Repository :: JPA Implementation</name>
<properties>
<apt.source.dir>${project.build.directory}/generated-sources/apt/</apt.source.dir>
</properties>
<dependencies>
<!-- Hawkbit -->
<dependency>
<groupId>org.eclipse.hawkbit</groupId>
<artifactId>hawkbit-repository-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.hawkbit</groupId>
<artifactId>hawkbit-repository-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.hawkbit</groupId>
<artifactId>hawkbit-artifact-repository-filesystem</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>org.eclipse.persistence.jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-mysql</artifactId>
</dependency>
<dependency>
<groupId>cz.jirutka.rsql</groupId>
<artifactId>rsql-parser</artifactId>
</dependency>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
</dependency>
<!-- Test -->
<dependency>
<groupId>org.eclipse.hawkbit</groupId>
<artifactId>hawkbit-repository-test</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.el</groupId>
<artifactId>javax.el-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.qameta.allure</groupId>
<artifactId>allure-junit5</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Static weaver for EclipseLink -->
<plugin>
<groupId>com.ethlo.persistence.tools</groupId>
<artifactId>eclipselink-maven-plugin</artifactId>
<executions>
<execution>
<id>modelgen</id>
<phase>generate-sources</phase>
<goals>
<goal>modelgen</goal>
</goals>
</execution>
<execution>
<phase>process-classes</phase>
<goals>
<goal>weave</goal>
</goals>
</execution>
</executions>
<configuration>
<basePackage>org.eclipse.hawkbit.repository.jpa.model</basePackage>
</configuration>
<dependencies>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>org.eclipse.persistence.jpa</artifactId>
<version>${eclipselink.version}</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<!-- 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 -->
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<executions>
<execution>
<id>add-source</id>
<phase>generate-sources</phase>
<goals>
<goal>add-source</goal>
</goals>
<configuration>
<sources>
<source>${apt.source.dir}</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.eclipse.hawkbit</groupId>
<version>${revision}</version>
<artifactId>hawkbit-repository</artifactId>
</parent>
<artifactId>hawkbit-repository-jpa</artifactId>
<name>hawkBit :: Repository :: JPA Implementation</name>
<properties>
<apt.source.dir>${project.build.directory}/generated-sources/apt/</apt.source.dir>
</properties>
<dependencies>
<!-- Hawkbit -->
<dependency>
<groupId>org.eclipse.hawkbit</groupId>
<artifactId>hawkbit-repository-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.hawkbit</groupId>
<artifactId>hawkbit-repository-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.hawkbit</groupId>
<artifactId>hawkbit-artifact-repository-filesystem</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>org.eclipse.persistence.jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-mysql</artifactId>
</dependency>
<dependency>
<groupId>cz.jirutka.rsql</groupId>
<artifactId>rsql-parser</artifactId>
</dependency>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
</dependency>
<!-- Test -->
<dependency>
<groupId>org.eclipse.hawkbit</groupId>
<artifactId>hawkbit-repository-test</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.el</groupId>
<artifactId>javax.el-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.qameta.allure</groupId>
<artifactId>allure-junit5</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Static weaver for EclipseLink -->
<plugin>
<groupId>com.ethlo.persistence.tools</groupId>
<artifactId>eclipselink-maven-plugin</artifactId>
<executions>
<execution>
<id>modelgen</id>
<phase>generate-sources</phase>
<goals>
<goal>modelgen</goal>
</goals>
</execution>
<execution>
<phase>process-classes</phase>
<goals>
<goal>weave</goal>
</goals>
</execution>
</executions>
<configuration>
<basePackage>org.eclipse.hawkbit.repository.jpa.model</basePackage>
</configuration>
<dependencies>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>org.eclipse.persistence.jpa</artifactId>
<version>${eclipselink.version}</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<!-- 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 -->
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<executions>
<execution>
<id>add-source</id>
<phase>generate-sources</phase>
<goals>
<goal>add-source</goal>
</goals>
<configuration>
<sources>
<source>${apt.source.dir}</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -19,13 +19,12 @@ import org.springframework.stereotype.Service;
* used by spring caching framework to resolve the key-generator. The
* key-generator must registered as bean so spring can resolve the key-generator
* by its name.
*
*
* 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 by the implemented interfaces. So introduce a single interface for
* the {@link JpaSystemManagement} implementation to allow it to register the
* key-generator bean.
*
*/
@FunctionalInterface
public interface CurrentTenantCacheKeyGenerator {
@@ -33,7 +32,7 @@ public interface CurrentTenantCacheKeyGenerator {
/**
* Bean declaration to register a {@code currentTenantKeyGenerator} bean
* which is used by the caching framework.
*
*
* @return the {@link KeyGenerator} to be used to cache the values of the
* 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
* repository factories by using a {@link BaseRepositoryTypeProvider}
* implementation, allows injecting different base repository implementations based on repository type
*
*
* @param <T>
* @param <S>
* @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
* interface.
*
* @param repositoryInterface
* must not be {@literal null}.
* @param repositoryInterface must not be {@literal null}.
*/
public CustomBaseRepositoryFactoryBean(final Class<? extends T> repositoryInterface) {
super(repositoryInterface);

View File

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

View File

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

View File

@@ -22,10 +22,10 @@ import org.springframework.orm.jpa.vendor.EclipseLinkJpaDialect;
/**
* {@link EclipseLinkJpaDialect} with additional exception translation
* mechanisms based on {@link SQLStateSQLExceptionTranslator}.
*
*
* There are multiple variations of exceptions coming out of persistence
* provider:
*
*
* <p>
* 1) {@link PersistenceException}s that can be mapped by
* {@link EclipseLinkJpaDialect} into corresponding {@link DataAccessException}.
@@ -47,9 +47,9 @@ import org.springframework.orm.jpa.vendor.EclipseLinkJpaDialect;
* <p>
* 3.b.) the cause is not an {@link SQLException} and as a result cannot be
* mapped.
*
*/
public class HawkBitEclipseLinkJpaDialect extends EclipseLinkJpaDialect {
private static final long serialVersionUID = 1L;
private static final SQLStateSQLExceptionTranslator SQLSTATE_EXCEPTION_TRANSLATOR = new SQLStateSQLExceptionTranslator();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -32,7 +32,7 @@ import org.eclipse.hawkbit.repository.model.TenantConfigurationValue;
* 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
* CANCELLED or ERROR).
*
*
* The cleanup task can be enabled /disabled and configured on a per tenant
* basis.
*/
@@ -49,11 +49,9 @@ public class AutoActionCleanup implements CleanupTask {
/**
* Constructs the action cleanup handler.
*
* @param deploymentMgmt
* The {@link DeploymentManagement} to operate on.
* @param configMgmt
* The {@link TenantConfigurationManagement} service.
*
* @param deploymentMgmt The {@link DeploymentManagement} to operate on.
* @param configMgmt The {@link TenantConfigurationManagement} service.
*/
public AutoActionCleanup(final DeploymentManagement deploymentMgmt,
final TenantConfigurationManagement configMgmt) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -23,7 +23,7 @@ import org.springframework.transaction.support.TransactionSynchronizationManager
@Slf4j
public class AfterTransactionCommitDefaultServiceExecutor extends TransactionSynchronizationAdapter
implements AfterTransactionCommitExecutor {
private static final ThreadLocal<List<Runnable>> THREAD_LOCAL_RUNNABLES = new ThreadLocal<>();
@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
// Exception squid:S1217 - we want to run this synchronous
@SuppressWarnings("squid:S1217")
@@ -62,12 +70,4 @@ public class AfterTransactionCommitDefaultServiceExecutor extends TransactionSyn
runnable.run();
}
@Override
@SuppressWarnings({ "squid:S1217" })
public void afterCompletion(final int status) {
final String transactionStatus = status == STATUS_COMMITTED ? "COMMITTED" : "ROLLEDBACK";
log.debug("Transaction completed after commit with status {}", transactionStatus);
THREAD_LOCAL_RUNNABLES.remove();
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -65,9 +65,9 @@ public class JpaConfirmationManagement extends JpaActionManagement implements Co
* Constructor
*/
public JpaConfirmationManagement(final TargetRepository targetRepository,
final ActionRepository actionRepository, final ActionStatusRepository actionStatusRepository,
final RepositoryProperties repositoryProperties, final QuotaManagement quotaManagement,
final EntityFactory entityFactory) {
final ActionRepository actionRepository, final ActionStatusRepository actionStatusRepository,
final RepositoryProperties repositoryProperties, final QuotaManagement quotaManagement,
final EntityFactory entityFactory) {
super(actionRepository, actionStatusRepository, quotaManagement, repositoryProperties);
this.targetRepository = targetRepository;
this.entityFactory = entityFactory;
@@ -96,7 +96,7 @@ public class JpaConfirmationManagement extends JpaActionManagement implements Co
final AutoConfirmationStatus autoConfStatus = updatedTarget.getAutoConfirmationStatus();
if (autoConfStatus == 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);
log.error("message");
throw new IllegalStateException(message);
@@ -159,17 +159,20 @@ public class JpaConfirmationManagement extends JpaActionManagement implements Co
return addActionStatus((JpaActionStatusCreate) statusCreate);
}
private ActionStatusCreate createConfirmationActionStatus(final long actionId, final Integer code,
final Collection<String> messages) {
final ActionStatusCreate statusCreate = entityFactory.actionStatus().create(actionId);
if (!CollectionUtils.isEmpty(messages)) {
statusCreate.messages(messages);
@Override
@Transactional
public void deactivateAutoConfirmation(String controllerId) {
log.debug("Deactivate auto confirmation for controllerId '{}'", controllerId);
final JpaTarget target = getTargetByControllerIdAndThrowIfNotFound(controllerId);
target.setAutoConfirmationStatus(null);
targetRepository.save(target);
}
@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) {
@@ -188,6 +191,19 @@ public class JpaConfirmationManagement extends JpaActionManagement implements Co
}
}
private ActionStatusCreate createConfirmationActionStatus(final long actionId, final Integer code,
final Collection<String> messages) {
final ActionStatusCreate statusCreate = entityFactory.actionStatus().create(actionId);
if (!CollectionUtils.isEmpty(messages)) {
statusCreate.messages(messages);
}
if (code != null) {
statusCreate.code(code);
statusCreate.message(String.format(CONFIRMATION_CODE_MSG_PREFIX, code));
}
return statusCreate;
}
private List<Action> giveConfirmationForActiveActions(final AutoConfirmationStatus autoConfirmationStatus) {
final Target target = autoConfirmationStatus.getTarget();
return findActiveActionsHavingStatus(target.getControllerId(), Status.WAIT_FOR_CONFIRMATION).stream()
@@ -218,24 +234,8 @@ public class JpaConfirmationManagement extends JpaActionManagement implements Co
return actionRepository.save(action);
}
@Override
@Transactional
public void deactivateAutoConfirmation(String controllerId) {
log.debug("Deactivate auto confirmation for controllerId '{}'", controllerId);
final JpaTarget target = getTargetByControllerIdAndThrowIfNotFound(controllerId);
target.setAutoConfirmationStatus(null);
targetRepository.save(target);
}
private JpaTarget getTargetByControllerIdAndThrowIfNotFound(final String controllerId) {
return targetRepository.findOne(TargetSpecifications.hasControllerId(controllerId))
.orElseThrow(() -> new EntityNotFoundException(Target.class, controllerId));
}
@Override
protected void onActionStatusUpdate(final Status updatedActionStatus, final JpaAction action) {
if (updatedActionStatus == Status.RUNNING && action.isActive()) {
action.setStatus(Status.RUNNING);
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -44,7 +44,6 @@ import org.springframework.validation.annotation.Validated;
/**
* JPA implementation of {@link SoftwareModuleTypeManagement}.
*
*/
@Transactional(readOnly = true)
@Validated
@@ -68,6 +67,37 @@ public class JpaSoftwareModuleTypeManagement implements SoftwareModuleTypeManage
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
@Transactional
@Retryable(include = {
@@ -84,46 +114,11 @@ public class JpaSoftwareModuleTypeManagement implements SoftwareModuleTypeManage
return softwareModuleTypeRepository.save(type);
}
@Override
public Page<SoftwareModuleType> findByRsql(final Pageable pageable, final String rsqlParam) {
return JpaManagementHelper.findAllWithCountBySpec(softwareModuleTypeRepository, pageable,
List.of(
RSQLUtility.buildRsqlSpecification(rsqlParam, SoftwareModuleTypeFields.class,
virtualPropertyReplacer, database),
SoftwareModuleTypeSpecification.isNotDeleted()));
}
@Override
public Slice<SoftwareModuleType> findAll(final Pageable pageable) {
return JpaManagementHelper.findAllWithoutCountBySpec(softwareModuleTypeRepository, pageable,
List.of(SoftwareModuleTypeSpecification.isNotDeleted()));
}
@Override
public long count() {
return softwareModuleTypeRepository.count(SoftwareModuleTypeSpecification.isNotDeleted());
}
@Override
public Optional<SoftwareModuleType> getByKey(final String key) {
return softwareModuleTypeRepository.findByKey(key);
}
@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
@Transactional
@Retryable(include = {
@@ -135,17 +130,6 @@ public class JpaSoftwareModuleTypeManagement implements SoftwareModuleTypeManage
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
@Transactional
@Retryable(include = {
@@ -161,14 +145,29 @@ public class JpaSoftwareModuleTypeManagement implements SoftwareModuleTypeManage
return Collections.unmodifiableList(softwareModuleTypeRepository.findAllById(ids));
}
@Override
public boolean exists(final long id) {
return softwareModuleTypeRepository.existsById(id);
}
@Override
public Optional<SoftwareModuleType> get(final long id) {
return softwareModuleTypeRepository.findById(id).map(SoftwareModuleType.class::cast);
}
@Override
public boolean exists(final long id) {
return softwareModuleTypeRepository.existsById(id);
public Slice<SoftwareModuleType> findAll(final Pageable pageable) {
return JpaManagementHelper.findAllWithoutCountBySpec(softwareModuleTypeRepository, pageable,
List.of(SoftwareModuleTypeSpecification.isNotDeleted()));
}
@Override
public Page<SoftwareModuleType> findByRsql(final Pageable pageable, final String rsqlParam) {
return JpaManagementHelper.findAllWithCountBySpec(softwareModuleTypeRepository, pageable,
List.of(
RSQLUtility.buildRsqlSpecification(rsqlParam, SoftwareModuleTypeFields.class,
virtualPropertyReplacer, database),
SoftwareModuleTypeSpecification.isNotDeleted()));
}
private void delete(JpaSoftwareModuleType toDelete) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -67,8 +67,8 @@ import org.springframework.context.ApplicationEvent;
@Entity
@Table(name = "sp_distribution_set", uniqueConstraints = {
@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_prim", columnList = "tenant,id") })
@Index(name = "sp_idx_distribution_set_01", columnList = "tenant,deleted,complete"),
@Index(name = "sp_idx_distribution_set_prim", columnList = "tenant,id") })
@NamedEntityGraph(name = "DistributionSet.detail", attributeNodes = { @NamedAttributeNode("modules"),
@NamedAttributeNode("tags"), @NamedAttributeNode("type") })
// 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)
@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 = "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;
@CascadeOnDelete
@@ -284,18 +284,6 @@ public class JpaDistributionSet extends AbstractJpaNamedVersionedEntity implemen
EventPublisherHolder.getInstance().getApplicationId()));
}
private void checkTypeCompatability(final SoftwareModule softwareModule) {
// we cannot allow that modules are added without a type defined
if (type == null) {
throw new DistributionSetTypeUndefinedException();
}
// check if it is allowed to such a module to this DS type
if (!type.containsModuleType(softwareModule.getType())) {
throw new UnsupportedSoftwareModuleForThisDistributionSetException();
}
}
private static void publishEventWithEventPublisher(final ApplicationEvent event) {
EventPublisherHolder.getInstance().getEventPublisher().publishEvent(event);
}
@@ -309,4 +297,16 @@ public class JpaDistributionSet extends AbstractJpaNamedVersionedEntity implemen
return changes.stream().anyMatch(changeRecord -> DELETED_PROPERTY.equals(changeRecord.getAttribute())
&& Boolean.parseBoolean(changeRecord.getNewValue().toString()));
}
private void checkTypeCompatability(final SoftwareModule softwareModule) {
// we cannot allow that modules are added without a type defined
if (type == null) {
throw new DistributionSetTypeUndefinedException();
}
// check if it is allowed to such a module to this DS type
if (!type.containsModuleType(softwareModule.getType())) {
throw new UnsupportedSoftwareModuleForThisDistributionSetException();
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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