Hibernate support (#2147)

* Hibernate support

---------

Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
Avgustin Marinov
2024-12-16 16:08:07 +02:00
committed by GitHub
parent af50e8c938
commit db3ac7f2dd
51 changed files with 1397 additions and 704 deletions

View File

@@ -23,32 +23,65 @@
<properties>
<apt.source.dir>${project.build.directory}/generated-sources/apt/</apt.source.dir>
<jpa.vendor>eclipselink</jpa.vendor>
</properties>
<profiles>
<profile>
<id>eclipselink</id>
<activation>
<property>
<!-- default, if not set - eclipse link -->
<name>!jpa.vendor</name>
</property>
</activation>
<dependencies>
<dependency>
<groupId>org.eclipse.hawkbit</groupId>
<artifactId>hawkbit-repository-jpa-eclipselink</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</profile>
<profile>
<id>hibernate</id>
<activation>
<property>
<name>jpa.vendor</name>
<value>hibernate</value>
</property>
</activation>
<dependencies>
<dependency>
<groupId>org.eclipse.hawkbit</groupId>
<artifactId>hawkbit-repository-jpa-hibernate</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</profile>
</profiles>
<dependencies>
<!-- Hawkbit -->
<dependency>
<groupId>org.eclipse.hawkbit</groupId>
<artifactId>hawkbit-repository-api</artifactId>
<artifactId>hawkbit-repository-jpa-api</artifactId>
<version>${project.version}</version>
</dependency>
<!-- Static class generation -->
<dependency>
<groupId>org.eclipse.hawkbit</groupId>
<artifactId>hawkbit-repository-core</artifactId>
<version>${project.version}</version>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-jpamodelgen</artifactId>
<optional>true</optional>
</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>
@@ -70,13 +103,6 @@
<artifactId>commons-collections4</artifactId>
</dependency>
<!-- Static class generation -->
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-jpamodelgen</artifactId>
<optional>true</optional>
</dependency>
<!-- Test -->
<dependency>
<groupId>org.eclipse.hawkbit</groupId>
@@ -90,30 +116,4 @@
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<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

@@ -1,84 +0,0 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.repository.jpa;
import jakarta.persistence.PostLoad;
import jakarta.persistence.PostPersist;
import jakarta.persistence.PostRemove;
import jakarta.persistence.PostUpdate;
import jakarta.persistence.PrePersist;
import jakarta.persistence.PreRemove;
import jakarta.persistence.PreUpdate;
/**
* Interface for the entity interceptor lifecycle.
*/
// Exception squid:EmptyStatementUsageCheck - don't want to force users to
// implement all methods
@SuppressWarnings("squid:EmptyStatementUsageCheck")
public interface EntityInterceptor {
/**
* Callback for the {@link PrePersist} lifecycle event.
*
* @param entity the model entity
*/
default void prePersist(final Object entity) {
}
/**
* Callback for the {@link PostPersist} lifecycle event.
*
* @param entity the model entity
*/
default void postPersist(final Object entity) {
}
/**
* Callback for the {@link PostRemove} lifecycle event.
*
* @param entity the model entity
*/
default void postRemove(final Object entity) {
}
/**
* Callback for the {@link PreRemove} lifecycle event.
*
* @param entity the model entity
*/
default void preRemove(final Object entity) {
}
/**
* Callback for the {@link PostLoad} lifecycle event.
*
* @param entity the model entity
*/
default void postLoad(final Object entity) {
}
/**
* Callback for the {@link PreUpdate} lifecycle event.
*
* @param entity the model entity
*/
default void preUpdate(final Object entity) {
}
/**
* Callback for the {@link PostUpdate} lifecycle event.
*
* @param entity the model entity
*/
default void postUpdate(final Object entity) {
}
}

View File

@@ -1,102 +0,0 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.repository.jpa;
import java.io.Serial;
import java.sql.SQLException;
import jakarta.persistence.PersistenceException;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.support.SQLStateSQLExceptionTranslator;
import org.springframework.lang.NonNull;
import org.springframework.orm.jpa.JpaSystemException;
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}.
* <p>
* 2) {@link PersistenceException}s that could not be mapped by
* {@link EclipseLinkJpaDialect} directly but instead are wrapped into
* {@link JpaSystemException}.
* <p>
* 2.a) here the wrapped exception's causes might be an {@link SQLException}
* which might be mappable by {@link SQLStateSQLExceptionTranslator} or
* <p>
* 2.b.) the wrapped exception's causes due not contain an {@link SQLException}
* and as a result cannot be mapped.
* <p>
* 3) A {@link RuntimeException} that is no {@link PersistenceException}.
* <p>
* 3.a) here a cause might be an {@link SQLException} which might be mappable by
* {@link SQLStateSQLExceptionTranslator} or
* <p>
* 3.b.) the cause is not an {@link SQLException} and as a result cannot be
* mapped.
*/
public class HawkbitEclipseLinkJpaDialect extends EclipseLinkJpaDialect {
@Serial
private static final long serialVersionUID = 1L;
private static final SQLStateSQLExceptionTranslator SQLSTATE_EXCEPTION_TRANSLATOR = new SQLStateSQLExceptionTranslator();
@Override
public DataAccessException translateExceptionIfPossible(@NonNull final RuntimeException ex) {
final DataAccessException dataAccessException = super.translateExceptionIfPossible(ex);
if (dataAccessException == null) {
return searchAndTranslateSqlException(ex);
}
return translateJpaSystemExceptionIfPossible(dataAccessException);
}
private static DataAccessException translateJpaSystemExceptionIfPossible(
final DataAccessException accessException) {
if (!(accessException instanceof JpaSystemException)) {
return accessException;
}
final DataAccessException sqlException = searchAndTranslateSqlException(accessException);
if (sqlException == null) {
return accessException;
}
return sqlException;
}
private static DataAccessException searchAndTranslateSqlException(final RuntimeException ex) {
final SQLException sqlException = findSqlException(ex);
if (sqlException == null) {
return null;
}
return SQLSTATE_EXCEPTION_TRANSLATOR.translate("", null, sqlException);
}
private static SQLException findSqlException(final RuntimeException jpaSystemException) {
Throwable exception = jpaSystemException;
do {
final Throwable cause = exception.getCause();
if (cause instanceof SQLException) {
return (SQLException) cause;
}
exception = cause;
} while (exception != null);
return null;
}
}

View File

@@ -1,55 +0,0 @@
/**
* Copyright (c) 2024 Contributors to the Eclipse Foundation
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.repository.jpa;
import java.util.Collection;
import java.util.List;
import java.util.stream.IntStream;
import jakarta.persistence.Query;
import lombok.NoArgsConstructor;
@NoArgsConstructor(access = lombok.AccessLevel.PRIVATE)
public class Jpa {
public enum JpaVendor {
ECLIPSELINK,
HIBERNATE // NOT SUPPORTED!
}
public static final JpaVendor JPA_VENDOR = JpaVendor.ECLIPSELINK;
public static char NATIVE_QUERY_PARAMETER_PREFIX = switch (JPA_VENDOR) {
case ECLIPSELINK -> '?';
case HIBERNATE -> ':';
};
public static <T> String formatNativeQueryInClause(final String name, final List<T> list) {
return switch (Jpa.JPA_VENDOR) {
case ECLIPSELINK -> formatEclipseLinkNativeQueryInClause(IntStream.range(0, list.size()).mapToObj(i -> name + "_" + i).toList());
case HIBERNATE -> ":" + name;
};
}
public static <T> void setNativeQueryInParameter(final Query deleteQuery, final String name, final List<T> list) {
if (Jpa.JPA_VENDOR == Jpa.JpaVendor.ECLIPSELINK) {
for (int i = 0, len = list.size(); i < len; i++) {
deleteQuery.setParameter(name + "_" + i, list.get(i));
}
} else if (Jpa.JPA_VENDOR == Jpa.JpaVendor.HIBERNATE) {
deleteQuery.setParameter(name, list);
}
}
private static String formatEclipseLinkNativeQueryInClause(final Collection<String> elements) {
return "?" + String.join(",?", elements);
}
}

View File

@@ -9,14 +9,10 @@
*/
package org.eclipse.hawkbit.repository.jpa;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ScheduledExecutorService;
import javax.sql.DataSource;
import jakarta.persistence.EntityManager;
import jakarta.validation.Validation;
@@ -81,7 +77,6 @@ import org.eclipse.hawkbit.repository.jpa.builder.JpaSoftwareModuleMetadataBuild
import org.eclipse.hawkbit.repository.jpa.builder.JpaTargetBuilder;
import org.eclipse.hawkbit.repository.jpa.builder.JpaTargetFilterQueryBuilder;
import org.eclipse.hawkbit.repository.jpa.builder.JpaTargetTypeBuilder;
import org.eclipse.hawkbit.repository.jpa.configuration.MultiTenantJpaTransactionManager;
import org.eclipse.hawkbit.repository.jpa.event.JpaEventEntityManager;
import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitDefaultServiceExecutor;
import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor;
@@ -166,10 +161,7 @@ import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.eclipse.hawkbit.tenancy.UserAuthoritiesResolver;
import org.eclipse.hawkbit.utils.TenantConfigHelper;
import org.eclipse.persistence.config.PersistenceUnitProperties;
import org.hibernate.validator.BaseHibernateValidatorConfiguration;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
@@ -177,9 +169,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.JpaBaseConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.boot.autoconfigure.transaction.TransactionManagerCustomizers;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@@ -191,14 +181,10 @@ import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.integration.support.locks.LockRegistry;
import org.springframework.lang.NonNull;
import org.springframework.orm.jpa.vendor.AbstractJpaVendorAdapter;
import org.springframework.orm.jpa.vendor.EclipseLinkJpaDialect;
import org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter;
import org.springframework.retry.annotation.EnableRetry;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.jta.JtaTransactionManager;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
/**
@@ -213,15 +199,9 @@ import org.springframework.validation.beanvalidation.MethodValidationPostProcess
@EnableRetry
@EntityScan("org.eclipse.hawkbit.repository.jpa.model")
@PropertySource("classpath:/hawkbit-jpa-defaults.properties")
@Import({ RepositoryDefaultConfiguration.class, DataSourceAutoConfiguration.class,
SystemManagementCacheKeyGenerator.class })
@Import({ JpaConfiguration.class, RepositoryDefaultConfiguration.class, DataSourceAutoConfiguration.class, SystemManagementCacheKeyGenerator.class })
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
protected RepositoryApplicationConfiguration(final DataSource dataSource, final JpaProperties properties,
final ObjectProvider<JtaTransactionManager> jtaTransactionManagerProvider) {
super(dataSource, properties, jtaTransactionManagerProvider);
}
public class RepositoryApplicationConfiguration {
/**
* Defines the validation processor bean.
@@ -233,58 +213,14 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
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());
processor.setValidator(Validation.byDefaultProvider()
.configure()
.addProperty(org.hibernate.validator.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,

View File

@@ -1,53 +0,0 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.repository.jpa.configuration;
import java.io.Serial;
import java.util.Objects;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.transaction.Transaction;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.eclipse.persistence.config.PersistenceUnitProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.jpa.EntityManagerHolder;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.transaction.TransactionDefinition;
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 {
@Serial
private static final long serialVersionUID = 1L;
@Autowired
private transient TenantAware tenantAware;
@Override
protected void doBegin(final Object transaction, final TransactionDefinition definition) {
super.doBegin(transaction, definition);
final String currentTenant = tenantAware.getCurrentTenant();
if (currentTenant != null) {
final EntityManagerFactory emFactory = Objects.requireNonNull(getEntityManagerFactory());
final EntityManagerHolder emHolder = Objects.requireNonNull(
(EntityManagerHolder) TransactionSynchronizationManager.getResource(emFactory),
"No EntityManagerHolder provided by TransactionSynchronizationManager");
final EntityManager em = emHolder.getEntityManager();
em.setProperty(PersistenceUnitProperties.MULTITENANT_PROPERTY_DEFAULT, currentTenant.toUpperCase());
}
}
}

View File

@@ -1,73 +0,0 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.repository.jpa.executor;
import java.util.ArrayList;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
/**
* A Service which calls register runnable. This runnables will be executed after a successful spring transaction commit.
* The class is thread safe.
*/
@Slf4j
public class AfterTransactionCommitDefaultServiceExecutor implements AfterTransactionCommitExecutor {
private static class TransactionSynchronizationImpl implements TransactionSynchronization {
private final List<Runnable> afterCommitRunnables = new ArrayList<>();
@Override
// Exception squid:S1217 - Is aspectJ proxy
@SuppressWarnings({ "squid:S1217" })
public void afterCommit() {
log.debug("Transaction successfully committed, executing {} runnables", afterCommitRunnables.size());
for (final Runnable afterCommitRunnable : afterCommitRunnables) {
log.debug("Executing runnable {}", afterCommitRunnable);
try {
afterCommitRunnable.run();
} catch (final RuntimeException e) {
log.error("Failed to execute runnable {}", afterCommitRunnable, e);
}
}
}
@Override
@SuppressWarnings({ "squid:S1217" })
public void afterCompletion(final int status) {
log.debug("Transaction completed after commit with status {}", status == STATUS_COMMITTED ? "COMMITTED" : "ROLLEDBACK");
}
private void afterCommit(final Runnable runnable) {
afterCommitRunnables.add(runnable);
}
}
@Override
// Exception squid:S1217 - we want to run this synchronous
@SuppressWarnings("squid:S1217")
public void afterCommit(final Runnable runnable) {
log.debug("Submitting new runnable {} to run after transaction commit", runnable);
if (TransactionSynchronizationManager.isSynchronizationActive()) {
TransactionSynchronizationManager.getSynchronizations().stream().filter(TransactionSynchronizationImpl.class::isInstance)
.map(TransactionSynchronizationImpl.class::cast).findAny().orElseGet(() -> {
final TransactionSynchronizationImpl newTS = new TransactionSynchronizationImpl();
TransactionSynchronizationManager.registerSynchronization(newTS);
return newTS;
}).afterCommit(runnable);
} else {
log.info("Transaction synchronization is NOT ACTIVE/ INACTIVE. Executing right now runnable {}", runnable);
runnable.run();
}
}
}

View File

@@ -1,26 +0,0 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.repository.jpa.executor;
/**
* A interface to register a runnable, which will be executed after a successful
* spring transaction.
*/
@FunctionalInterface
public interface AfterTransactionCommitExecutor {
/**
* Register a runnable which will be executed after a successful spring
* transaction.
*
* @param runnable the after commit runnable
*/
void afterCommit(Runnable runnable);
}

View File

@@ -23,7 +23,6 @@ import org.eclipse.hawkbit.artifact.repository.HashNotMatchException;
import org.eclipse.hawkbit.artifact.repository.model.AbstractDbArtifact;
import org.eclipse.hawkbit.artifact.repository.model.DbArtifact;
import org.eclipse.hawkbit.artifact.repository.model.DbArtifactHash;
import org.eclipse.hawkbit.im.authentication.SpPermission;
import org.eclipse.hawkbit.repository.ArtifactEncryptionService;
import org.eclipse.hawkbit.repository.ArtifactManagement;
import org.eclipse.hawkbit.repository.QuotaManagement;
@@ -36,7 +35,6 @@ import org.eclipse.hawkbit.repository.exception.InvalidMD5HashException;
import org.eclipse.hawkbit.repository.exception.InvalidSHA1HashException;
import org.eclipse.hawkbit.repository.exception.InvalidSHA256HashException;
import org.eclipse.hawkbit.repository.jpa.EncryptionAwareDbArtifact;
import org.eclipse.hawkbit.repository.jpa.Jpa;
import org.eclipse.hawkbit.repository.jpa.JpaManagementHelper;
import org.eclipse.hawkbit.repository.jpa.acm.AccessController;
import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
@@ -53,17 +51,13 @@ import org.eclipse.hawkbit.repository.model.Artifact;
import org.eclipse.hawkbit.repository.model.ArtifactUpload;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.ConcurrencyFailureException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.validation.annotation.Validated;
/**

View File

@@ -36,7 +36,6 @@ import jakarta.validation.constraints.NotEmpty;
import org.apache.commons.collections4.ListUtils;
import org.eclipse.hawkbit.repository.DistributionSetManagement;
import org.eclipse.hawkbit.repository.FilterParams;
import org.eclipse.hawkbit.repository.OffsetBasedPageRequest;
import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.TargetFields;
import org.eclipse.hawkbit.repository.TargetManagement;
@@ -90,7 +89,6 @@ import org.springframework.dao.ConcurrencyFailureException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.retry.annotation.Backoff;

View File

@@ -1,213 +0,0 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.repository.jpa.model;
import java.io.Serial;
import jakarta.persistence.Access;
import jakarta.persistence.AccessType;
import jakarta.persistence.Column;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.MappedSuperclass;
import jakarta.persistence.PostPersist;
import jakarta.persistence.PostRemove;
import jakarta.persistence.PostUpdate;
import jakarta.persistence.Version;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.eclipse.hawkbit.repository.jpa.model.helper.AfterTransactionCommitExecutorHolder;
import org.eclipse.hawkbit.repository.model.BaseEntity;
import org.eclipse.hawkbit.tenancy.TenantAwareAuthenticationDetails;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import org.springframework.security.core.context.SecurityContextHolder;
/**
* Base hawkBit entity class containing the common attributes.
*/
@NoArgsConstructor(access = AccessLevel.PROTECTED) // Default constructor needed for JPA entities.
@MappedSuperclass
@EntityListeners({ AuditingEntityListener.class, EntityInterceptorListener.class })
public abstract class AbstractJpaBaseEntity implements BaseEntity {
protected static final int USERNAME_FIELD_LENGTH = 64;
@Serial
private static final long serialVersionUID = 1L;
@Setter // should be used just for test purposes
@Getter
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;
@Setter // should be used just for test purposes
@Getter
@Version
@Column(name = "optlock_revision")
private int optLockRevision;
// Audit fields. use property access to ensure that setters will be called and checked for modification
// (touch implementation depends on setLastModifiedAt(1).
@Column(name = "created_by", updatable = false, nullable = false, length = USERNAME_FIELD_LENGTH)
private String createdBy;
@Column(name = "created_at", updatable = false, nullable = false)
private long createdAt;
@Column(name = "last_modified_by", nullable = false, length = USERNAME_FIELD_LENGTH)
private String lastModifiedBy;
@Column(name = "last_modified_at", nullable = false)
private long lastModifiedAt;
@CreatedBy
public void setCreatedBy(final String createdBy) {
this.createdBy = createdBy;
}
// maybe needed to have correct createdBy value in the database
@Access(AccessType.PROPERTY)
public String getCreatedBy() {
return createdBy;
}
@CreatedDate
public void setCreatedAt(final long createdAt) {
this.createdAt = createdAt;
}
// property access to make entity manager to detect touch
@Access(AccessType.PROPERTY)
public long getCreatedAt() {
return createdAt;
}
@LastModifiedBy
public void setLastModifiedBy(final String lastModifiedBy) {
if (this.lastModifiedBy != null && isController()) {
// initialized and controller = doesn't update
return;
}
this.lastModifiedBy = lastModifiedBy;
}
// property access to make entity manager to detect touch
@Access(AccessType.PROPERTY)
public String getLastModifiedBy() {
return lastModifiedBy == null ? createdBy : lastModifiedBy;
}
@LastModifiedDate
public void setLastModifiedAt(final long lastModifiedAt) {
if (this.lastModifiedAt != 0 && isController()) {
// initialized and controller = doesn't update
return;
}
this.lastModifiedAt = lastModifiedAt;
}
// property access to make entity manager to detect touch
@Access(AccessType.PROPERTY)
public long getLastModifiedAt() {
return lastModifiedAt == 0 ? createdAt : lastModifiedAt;
}
/**
* 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.
*/
@Override
// Exception squid:S864 - generated code
@SuppressWarnings({ "squid:S864" })
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (id == null ? 0 : id.hashCode());
result = prime * result + optLockRevision;
result = prime * result + this.getClass().getName().hashCode();
return result;
}
/**
* 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.
*/
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(this.getClass().isInstance(obj))) {
return false;
}
final AbstractJpaBaseEntity other = (AbstractJpaBaseEntity) obj;
final Long id = getId();
final Long otherId = other.getId();
if (id == null) {
if (otherId != null) {
return false;
}
} else if (!id.equals(otherId)) {
return false;
}
return getOptLockRevision() == other.getOptLockRevision();
}
@Override
public String toString() {
return this.getClass().getSimpleName() + " [id=" + id + "]";
}
@PostPersist
public void postInsert() {
if (this instanceof EventAwareEntity eventAwareEntity) {
doNotify(eventAwareEntity::fireCreateEvent);
}
}
@PostUpdate
public void postUpdate() {
if (this instanceof EventAwareEntity eventAwareEntity) {
doNotify(eventAwareEntity::fireUpdateEvent);
}
}
@PostRemove
public void postDelete() {
if (this instanceof EventAwareEntity eventAwareEntity) {
doNotify(eventAwareEntity::fireDeleteEvent);
}
}
protected static void doNotify(final Runnable runnable) {
// fire events onl AFTER transaction commit
AfterTransactionCommitExecutorHolder.getInstance().getAfterCommit().afterCommit(runnable);
}
private boolean isController() {
return SecurityContextHolder.getContext().getAuthentication() != null
&& SecurityContextHolder.getContext().getAuthentication()
.getDetails() instanceof TenantAwareAuthenticationDetails tenantAwareDetails
&& tenantAwareDetails.isController();
}
}

View File

@@ -1,103 +0,0 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.repository.jpa.model;
import java.io.Serial;
import java.util.Objects;
import jakarta.persistence.Column;
import jakarta.persistence.MappedSuperclass;
import jakarta.persistence.PrePersist;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.eclipse.hawkbit.repository.exception.TenantNotExistException;
import org.eclipse.hawkbit.repository.jpa.model.helper.TenantAwareHolder;
import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity;
import org.eclipse.persistence.annotations.Multitenant;
import org.eclipse.persistence.annotations.MultitenantType;
import org.eclipse.persistence.annotations.TenantDiscriminatorColumn;
/**
* Holder of the base attributes common to all tenant aware entities.
*/
@NoArgsConstructor(access = AccessLevel.PROTECTED) // Default constructor needed for JPA entities.
@Setter
@Getter
@MappedSuperclass
// Eclipse link MultiTenant support
@TenantDiscriminatorColumn(name = "tenant", length = 40)
@Multitenant(MultitenantType.SINGLE_TABLE)
public abstract class AbstractJpaTenantAwareBaseEntity extends AbstractJpaBaseEntity implements TenantAwareBaseEntity {
@Serial
private static final long serialVersionUID = 1L;
@Column(name = "tenant", nullable = false, insertable = false, updatable = false, length = 40)
@Size(min = 1, max = 40)
@NotNull
private String tenant;
/**
* 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.
*/
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + (tenant == null ? 0 : tenant.hashCode());
return result;
}
/**
* 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.
*/
@Override
// exception squid:S2259 - obj is checked for null in super
@SuppressWarnings("squid:S2259")
public boolean equals(final Object obj) {
if (!super.equals(obj)) {
return false;
}
final AbstractJpaTenantAwareBaseEntity other = (AbstractJpaTenantAwareBaseEntity) obj;
return Objects.equals(getTenant(), other.getTenant());
}
@Override
public String toString() {
return "BaseEntity [id=" + super.getId() + "]";
}
/**
* PrePersist listener method for all {@link TenantAwareBaseEntity} entities.
*
* // TODO - check if the tenant support should set tenant from context
* // TODO - should we check if tenant exists in the system? Note: seems it's not good to work with db in the listener
*/
@PrePersist
void prePersist() {
// before persisting the entity check the current ID of the tenant by using the TenantAware service
final String currentTenant = TenantAwareHolder.getInstance().getTenantAware().getCurrentTenant();
if (currentTenant == null) {
throw new TenantNotExistException(
String.format(
"Tenant %s does not exists, cannot create entity %s with id %d",
TenantAwareHolder.getInstance().getTenantAware().getCurrentTenant(), getClass(), getId()));
}
setTenant(currentTenant.toUpperCase());
}
}

View File

@@ -1,103 +0,0 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.repository.jpa.model;
import java.util.function.Consumer;
import jakarta.persistence.PostLoad;
import jakarta.persistence.PostPersist;
import jakarta.persistence.PostRemove;
import jakarta.persistence.PostUpdate;
import jakarta.persistence.PrePersist;
import jakarta.persistence.PreRemove;
import jakarta.persistence.PreUpdate;
import org.eclipse.hawkbit.repository.jpa.EntityInterceptor;
import org.eclipse.hawkbit.repository.jpa.model.helper.EntityInterceptorHolder;
/**
* Entity listener which calls the callback's of all registered entity interceptors.
*/
public class EntityInterceptorListener {
/**
* Callback for lifecyle event <i>pre persist</i>.
*
* @param entity the JPA entity which this listener is associated with
*/
@PrePersist
public void prePersist(final Object entity) {
notifyAll(interceptor -> interceptor.prePersist(entity));
}
/**
* Callback for lifecyle event <i>post persist</i>.
*
* @param entity the JPA entity which this listener is associated with
*/
@PostPersist
public void postPersist(final Object entity) {
notifyAll(interceptor -> interceptor.postPersist(entity));
}
/**
* Callback for lifecyle event <i>post remove</i>.
*
* @param entity the JPA entity which this listener is associated with
*/
@PostRemove
public void postRemove(final Object entity) {
notifyAll(interceptor -> interceptor.postRemove(entity));
}
/**
* Callback for lifecyle event <i>pre remove</i>.
*
* @param entity the JPA entity which this listener is associated with
*/
@PreRemove
public void preRemove(final Object entity) {
notifyAll(interceptor -> interceptor.preRemove(entity));
}
/**
* Callback for lifecyle event <i>post load</i>.
*
* @param entity the JPA entity which this listener is associated with
*/
@PostLoad
public void postLoad(final Object entity) {
notifyAll(interceptor -> interceptor.postLoad(entity));
}
/**
* Callback for lifecyle event <i>pre update</i>.
*
* @param entity the JPA entity which this listener is associated with
*/
@PreUpdate
public void preUpdate(final Object entity) {
notifyAll(interceptor -> interceptor.preUpdate(entity));
}
/**
* Callback for lifecyle event <i>post update</i>.
*
* @param entity the JPA entity which this listener is associated with
*/
@PostUpdate
public void postUpdate(final Object entity) {
notifyAll(interceptor -> interceptor.postUpdate(entity));
}
private static void notifyAll(final Consumer<? super EntityInterceptor> action) {
EntityInterceptorHolder.getInstance().getEntityInterceptors().forEach(action);
}
}

View File

@@ -1,32 +0,0 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.repository.jpa.model;
/**
* Interfaces which can be implemented by entities to be called when the entity should fire an event because the entity has been created,
* updated or deleted.
*/
public interface EventAwareEntity {
/**
* Fired for the Entity creation.
*/
void fireCreateEvent();
/**
* Fired for the Entity update.
*/
void fireUpdateEvent();
/**
* Fired for the Entity deletion.
*/
void fireDeleteEvent();
}

View File

@@ -19,8 +19,6 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import jakarta.persistence.Access;
import jakarta.persistence.AccessType;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.ConstraintMode;
@@ -37,7 +35,6 @@ import jakarta.persistence.NamedEntityGraph;
import jakarta.persistence.NamedEntityGraphs;
import jakarta.persistence.NamedSubgraph;
import jakarta.persistence.OneToMany;
import jakarta.persistence.PostUpdate;
import jakarta.persistence.Table;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
@@ -56,7 +53,6 @@ import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.helper.EventPublisherHolder;
import org.springframework.data.annotation.CreatedDate;
/**
* JPA implementation of {@link Action}.

View File

@@ -16,7 +16,6 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import jakarta.persistence.CascadeType;
@@ -27,7 +26,6 @@ import jakarta.persistence.Convert;
import jakarta.persistence.Converter;
import jakarta.persistence.ElementCollection;
import jakarta.persistence.Entity;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.FetchType;
import jakarta.persistence.ForeignKey;
import jakarta.persistence.Index;
@@ -67,11 +65,6 @@ import org.eclipse.hawkbit.repository.model.helper.EventPublisherHolder;
import org.eclipse.hawkbit.repository.model.helper.SystemSecurityContextHolder;
import org.eclipse.hawkbit.repository.model.helper.TenantConfigurationManagementHolder;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.persistence.descriptors.DescriptorEvent;
import org.eclipse.persistence.descriptors.DescriptorEventAdapter;
import org.eclipse.persistence.queries.UpdateObjectQuery;
import org.hibernate.event.spi.PreUpdateEvent;
import org.hibernate.event.spi.PreUpdateEventListener;
/**
* JPA implementation of {@link Target}.
@@ -88,7 +81,6 @@ import org.hibernate.event.spi.PreUpdateEventListener;
uniqueConstraints = @UniqueConstraint(columnNames = { "controller_id", "tenant" }, name = "uk_tenant_controller_id"))
// exception squid:S2160 - BaseEntity equals/hashcode is handling correctly for sub entities
@SuppressWarnings("squid:S2160")
@EntityListeners({ JpaTarget.EntityPropertyChangeListener.class }) // add listener to the listeners declared into suppers
@Slf4j
public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAwareEntity {
@@ -330,40 +322,4 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw
), null);
}
}
/**
* Listens to updates on {@link JpaTarget} entities, Filtering out updates that only change the "lastTargetQuery" or "address" fields.
*/
public static class EntityPropertyChangeListener extends DescriptorEventAdapter implements PreUpdateEventListener {
private static final List<String> TARGET_UPDATE_EVENT_IGNORE_FIELDS = List.of(
"lastTargetQuery", "address", // actual to be skipped
"optLockRevision", "lastModifiedAt", "lastModifiedBy" // system to be skipped
);
@Override
public void postUpdate(final DescriptorEvent event) {
final Object object = event.getObject();
if (((UpdateObjectQuery) event.getQuery()).getObjectChangeSet().getChangedAttributeNames().stream()
.anyMatch(field -> !TARGET_UPDATE_EVENT_IGNORE_FIELDS.contains(field))) {
doNotify(() -> ((EventAwareEntity) object).fireUpdateEvent());
}
}
@Override
public boolean onPreUpdate(final PreUpdateEvent event) {
final Object[] oldState = event.getOldState();
final Object[] newState = event.getState();
for (int i = 0; i < newState.length; i++) {
if (!Objects.equals(oldState[i], newState[i])) {
final String attribute = event.getPersister().getAttributeMapping(i).getAttributeName();
if (!TARGET_UPDATE_EVENT_IGNORE_FIELDS.contains(attribute)) {
doNotify(() -> ((EventAwareEntity) event.getEntity()).fireUpdateEvent());
break;
}
}
}
return false;
}
}
}

View File

@@ -1,49 +0,0 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.repository.jpa.model.helper;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor;
import org.springframework.beans.factory.annotation.Autowired;
/**
* A singleton bean which holds the {@link AfterTransactionCommitExecutor} to provide it to in beans not instantiated by spring e.g. JPA
* entities which cannot be autowired.
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class AfterTransactionCommitExecutorHolder {
private static final AfterTransactionCommitExecutorHolder SINGLETON = new AfterTransactionCommitExecutorHolder();
@Autowired
private AfterTransactionCommitExecutor afterCommit;
/**
* @return the cache manager holder singleton instance
*/
public static AfterTransactionCommitExecutorHolder getInstance() {
return SINGLETON;
}
/**
* @return the afterCommit
*/
public AfterTransactionCommitExecutor getAfterCommit() {
return afterCommit;
}
/**
* @param afterCommit the afterCommit to set
*/
public void setAfterCommit(final AfterTransactionCommitExecutor afterCommit) {
this.afterCommit = afterCommit;
}
}

View File

@@ -1,43 +0,0 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.repository.jpa.model.helper;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.hawkbit.repository.jpa.EntityInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
/**
* A singleton bean which holds the {@link EntityInterceptor} to have all
* interceptors in spring beans.
*/
public final class EntityInterceptorHolder {
private static final EntityInterceptorHolder SINGLETON = new EntityInterceptorHolder();
@Autowired(required = false)
private final List<EntityInterceptor> entityInterceptors = new ArrayList<>();
private EntityInterceptorHolder() {
}
/**
* @return the entity intreceptor holder singleton instance
*/
public static EntityInterceptorHolder getInstance() {
return SINGLETON;
}
public List<EntityInterceptor> getEntityInterceptors() {
return entityInterceptors;
}
}

View File

@@ -1,49 +0,0 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.repository.jpa.model.helper;
import org.eclipse.hawkbit.security.SecurityTokenGenerator;
import org.springframework.beans.factory.annotation.Autowired;
/**
* A singleton bean which holds the {@link SecurityTokenGenerator} and make it
* accessible to beans which are not managed by spring, e.g. JPA entities.
*/
public final class SecurityTokenGeneratorHolder {
private static final SecurityTokenGeneratorHolder INSTANCE = new SecurityTokenGeneratorHolder();
@Autowired
private SecurityTokenGenerator securityTokenGenerator;
/**
* private constructor.
*/
private SecurityTokenGeneratorHolder() {
}
/**
* @return a singleton instance of the security token generator holder.
*/
public static SecurityTokenGeneratorHolder getInstance() {
return INSTANCE;
}
/**
* delegates to {@link SecurityTokenGenerator#generateToken()}.
*
* @return the result {@link SecurityTokenGenerator#generateToken()}
*/
public String generateToken() {
return securityTokenGenerator.generateToken();
}
}

View File

@@ -1,42 +0,0 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.repository.jpa.model.helper;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.springframework.beans.factory.annotation.Autowired;
/**
* A singleton bean which holds {@link TenantAware} service and makes it
* accessible to beans which are not managed by spring, e.g. JPA entities.
*/
public final class TenantAwareHolder {
private static final TenantAwareHolder INSTANCE = new TenantAwareHolder();
@Autowired
private TenantAware tenantAware;
private TenantAwareHolder() {
}
/**
* @return the singleton {@link TenantAwareHolder} instance
*/
public static TenantAwareHolder getInstance() {
return INSTANCE;
}
/**
* @return the {@link TenantAware} service
*/
public TenantAware getTenantAware() {
return tenantAware;
}
}

View File

@@ -34,7 +34,6 @@ import org.eclipse.hawkbit.repository.rsql.SuggestToken;
import org.eclipse.hawkbit.repository.rsql.SuggestionContext;
import org.eclipse.hawkbit.repository.rsql.SyntaxErrorContext;
import org.eclipse.hawkbit.repository.rsql.ValidationOracleContext;
import org.eclipse.persistence.exceptions.ConversionException;
import org.springframework.orm.jpa.JpaSystemException;
import org.springframework.util.CollectionUtils;
@@ -75,8 +74,12 @@ public class RsqlParserValidationOracle implements RsqlValidationOracle {
} catch (final RSQLParameterUnsupportedFieldException | IllegalArgumentException ex) {
errorContext.setErrorMessage(getCustomMessage(ex.getMessage(), null));
log.trace("Illegal argument on parsing :", ex);
} catch (@SuppressWarnings("squid:S1166") final ConversionException | JpaSystemException e) {
} catch (final JpaSystemException e) {
// noop
} catch (final RuntimeException e) {
if (!"org.eclipse.persistence.exceptions.ConversionException".equals(e.getClass().getName())) {
throw e;
}
}
return context;
}

View File

@@ -1,48 +0,0 @@
/**
* Copyright (c) 2024 Contributors to the Eclipse Foundation
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.repository.jpa.utils;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import jakarta.persistence.AttributeConverter;
public class MapAttributeConverter<JAVA_TYPE extends Enum<JAVA_TYPE>, DB_TYPE> implements AttributeConverter<JAVA_TYPE, DB_TYPE> {
private final Map<JAVA_TYPE, DB_TYPE> javaToDbMap;
private final Map<DB_TYPE, JAVA_TYPE> dbToJavaMap;
private final DB_TYPE nullMapping;
protected MapAttributeConverter(final Map<JAVA_TYPE, DB_TYPE> javaToDbMap, final DB_TYPE nullMapping) {
this.javaToDbMap = javaToDbMap;
this.nullMapping = nullMapping;
this.dbToJavaMap = javaToDbMap.entrySet().stream().collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey));
if (javaToDbMap.size() != dbToJavaMap.size()) {
throw new IllegalArgumentException("Duplicate values in javaToDbMap");
}
}
@Override
public DB_TYPE convertToDatabaseColumn(final JAVA_TYPE attribute) {
if (attribute == null) {
return nullMapping;
}
return javaToDbMap.get(attribute);
}
@Override
public JAVA_TYPE convertToEntityAttribute(final DB_TYPE dbData) {
if (Objects.equals(dbData, nullMapping)) {
return null;
}
return dbToJavaMap.get(dbData);
}
}

View File

@@ -1,88 +0,0 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.repository.jpa;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.sql.SQLException;
import jakarta.persistence.OptimisticLockException;
import jakarta.persistence.PersistenceException;
import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import io.qameta.allure.Story;
import org.junit.jupiter.api.Test;
import org.springframework.dao.ConcurrencyFailureException;
import org.springframework.dao.UncategorizedDataAccessException;
/**
* Mapping tests for {@link HawkbitEclipseLinkJpaDialect}.
*/
@Feature("Unit Tests - Repository")
@Story("Exception handling")
public class HawkBitEclipseLinkJpaDialectTest {
private final HawkbitEclipseLinkJpaDialect hawkBitEclipseLinkJpaDialectUnderTest = new HawkbitEclipseLinkJpaDialect();
@Test
@Description("Use Case: PersistenceException that can be mapped by EclipseLinkJpaDialect into corresponding DataAccessException.")
public void jpaOptimisticLockExceptionIsConcurrencyFailureException() {
assertThat(
hawkBitEclipseLinkJpaDialectUnderTest.translateExceptionIfPossible(mock(OptimisticLockException.class)))
.isInstanceOf(ConcurrencyFailureException.class);
}
@Test
@Description("Use Case: PersistenceException that could not be mapped by EclipseLinkJpaDialect directly but "
+ "instead is wrapped into JpaSystemException. Cause of PersistenceException is an SQLException.")
public void jpaSystemExceptionWithSqlDeadLockExceptionIsConcurrencyFailureException() {
final PersistenceException persEception = mock(PersistenceException.class);
when(persEception.getCause()).thenReturn(new SQLException("simulated transaction ER_LOCK_DEADLOCK", "40001"));
assertThat(hawkBitEclipseLinkJpaDialectUnderTest.translateExceptionIfPossible(persEception))
.isInstanceOf(ConcurrencyFailureException.class);
}
@Test
@Description("Use Case: PersistenceException that could not be mapped by EclipseLinkJpaDialect directly but instead is wrapped"
+ " into JpaSystemException. Cause of PersistenceException is not an SQLException.")
public void jpaSystemExceptionWithNumberFormatExceptionIsNull() {
final PersistenceException persEception = mock(PersistenceException.class);
when(persEception.getCause()).thenReturn(new NumberFormatException());
assertThat(hawkBitEclipseLinkJpaDialectUnderTest.translateExceptionIfPossible(persEception))
.isInstanceOf(UncategorizedDataAccessException.class);
}
@Test
@Description("Use Case: RuntimeException that could not be mapped by EclipseLinkJpaDialect directly. Cause of "
+ "RuntimeException is an SQLException.")
public void runtimeExceptionWithSqlDeadLockExceptionIsConcurrencyFailureException() {
final RuntimeException persEception = mock(RuntimeException.class);
when(persEception.getCause()).thenReturn(new SQLException("simulated transaction ER_LOCK_DEADLOCK", "40001"));
assertThat(hawkBitEclipseLinkJpaDialectUnderTest.translateExceptionIfPossible(persEception))
.isInstanceOf(ConcurrencyFailureException.class);
}
@Test
@Description("Use Case: RuntimeException that could not be mapped by EclipseLinkJpaDialect directly. Cause of "
+ "RuntimeException is not an SQLException.")
public void runtimeExceptionWithNumberFormatExceptionIsNull() {
final RuntimeException persEception = mock(RuntimeException.class);
when(persEception.getCause()).thenReturn(new NumberFormatException());
assertThat(hawkBitEclipseLinkJpaDialectUnderTest.translateExceptionIfPossible(persEception)).isNull();
}
}

View File

@@ -37,8 +37,7 @@ class ActionTest extends AbstractJpaIntegrationTest {
@Test
@Description("Ensures that timeforced moded switch from soft to forces after defined timeframe.")
void timeforcedHitNewHasCodeIsGenerated() {
void timeForcedHitNewHasCodeIsGenerated() {
// current time + 1 seconds
final long sleepTime = 1000;
final long timeForceTimeAt = System.currentTimeMillis() + sleepTime;
@@ -48,8 +47,7 @@ class ActionTest extends AbstractJpaIntegrationTest {
assertThat(timeforcedAction.isForcedOrTimeForced()).isFalse();
// wait until timeforce time is hit
Awaitility.await().atMost(Duration.ofSeconds(2)).pollInterval(Duration.ofMillis(100))
.until(timeforcedAction::isForcedOrTimeForced);
Awaitility.await().atMost(Duration.ofSeconds(2)).pollInterval(Duration.ofMillis(100)).until(timeforcedAction::isForcedOrTimeForced);
}
@Test
@@ -57,12 +55,11 @@ class ActionTest extends AbstractJpaIntegrationTest {
void testActionTypeConvert() {
final long id = createAction().getId();
for (final ActionType actionType : ActionType.values()) {
final JpaAction action = actionRepository
.findById(id).orElseThrow(() -> new IllegalStateException("Action not found"));
final JpaAction action = actionRepository.findById(id).orElseThrow(() -> new IllegalStateException("Action not found"));
action.setActionType(actionType);
actionRepository.save(action);
assertThat(actionRepository.findById(id).orElseThrow(() -> new IllegalStateException("Action not found"))
.getActionType()).isEqualTo(actionType);
assertThat(actionRepository.findById(id).orElseThrow(() -> new IllegalStateException("Action not found")).getActionType())
.isEqualTo(actionType);
}
}
@@ -71,12 +68,11 @@ class ActionTest extends AbstractJpaIntegrationTest {
void testStatusConvert() {
final long id = createAction().getId();
for (final Status status : Status.values()) {
final JpaAction action = actionRepository
.findById(id).orElseThrow(() -> new IllegalStateException("Action not found"));
final JpaAction action = actionRepository.findById(id).orElseThrow(() -> new IllegalStateException("Action not found"));
action.setStatus(status);
actionRepository.save(action);
assertThat(actionRepository.findById(id).orElseThrow(() -> new IllegalStateException("Action not found"))
.getStatus()).isEqualTo(status);
assertThat(actionRepository.findById(id).orElseThrow(() -> new IllegalStateException("Action not found")).getStatus())
.isEqualTo(status);
}
}
@@ -113,4 +109,4 @@ class ActionTest extends AbstractJpaIntegrationTest {
action.setActive(true);
return actionRepository.save(action);
}
}
}

View File

@@ -16,7 +16,6 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
@@ -36,7 +35,6 @@ import org.eclipse.hawkbit.artifact.repository.model.DbArtifact;
import org.eclipse.hawkbit.artifact.repository.model.DbArtifactHash;
import org.eclipse.hawkbit.im.authentication.SpPermission;
import org.eclipse.hawkbit.repository.ArtifactManagement;
import org.eclipse.hawkbit.repository.event.remote.SoftwareModuleDeletedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleCreatedEvent;
import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException;
import org.eclipse.hawkbit.repository.exception.FileSizeQuotaExceededException;
@@ -47,7 +45,6 @@ import org.eclipse.hawkbit.repository.exception.InvalidSHA256HashException;
import org.eclipse.hawkbit.repository.exception.StorageQuotaExceededException;
import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest;
import org.eclipse.hawkbit.repository.jpa.RandomGeneratedInputStream;
import org.eclipse.hawkbit.repository.jpa.model.JpaArtifact;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule;
import org.eclipse.hawkbit.repository.model.Artifact;
import org.eclipse.hawkbit.repository.model.ArtifactUpload;
@@ -73,53 +70,46 @@ class ArtifactManagementTest extends AbstractJpaIntegrationTest {
final SoftwareModule module = testdataFactory.createSoftwareModuleOs();
assertThat(artifactManagement.get(NOT_EXIST_IDL)).isNotPresent();
assertThat(artifactManagement.getByFilenameAndSoftwareModule(NOT_EXIST_ID, module.getId()).isPresent())
.isFalse();
assertThat(artifactManagement.getByFilenameAndSoftwareModule(NOT_EXIST_ID, module.getId())).isEmpty();
assertThat(artifactManagement.findFirstBySHA1(NOT_EXIST_ID)).isNotPresent();
assertThat(artifactManagement.loadArtifactBinary(NOT_EXIST_ID, module.getId(), module.isEncrypted()))
.isNotPresent();
assertThat(artifactManagement.loadArtifactBinary(NOT_EXIST_ID, module.getId(), module.isEncrypted())).isEmpty();
}
@Test
@Description("Verifies that management queries react as specfied on calls for non existing entities "
+ " by means of throwing EntityNotFoundException.")
@ExpectEvents({ @Expect(type = SoftwareModuleDeletedEvent.class, count = 0) })
void entityQueriesReferringToNotExistingEntitiesThrowsException() throws URISyntaxException {
@Description("Verifies that management queries react as specfied on calls for non existing entities by means of " +
"throwing EntityNotFoundException.")
@ExpectEvents()
void entityQueriesReferringToNotExistingEntitiesThrowsException() {
final String artifactData = "test";
final int artifactSize = artifactData.length();
verifyThrownExceptionBy(
() -> artifactManagement.create(new ArtifactUpload(IOUtils.toInputStream(artifactData, "UTF-8"),
NOT_EXIST_IDL, "xxx", null, null, null, false, null, artifactSize)),
"SoftwareModule");
() -> artifactManagement.create(new ArtifactUpload(
IOUtils.toInputStream(artifactData, "UTF-8"),
NOT_EXIST_IDL, "xxx", null, null, null, false, null, artifactSize)), "SoftwareModule");
verifyThrownExceptionBy(
() -> artifactManagement.create(new ArtifactUpload(IOUtils.toInputStream(artifactData, "UTF-8"),
NOT_EXIST_IDL, "xxx", null, null, null, false, null, artifactSize)),
"SoftwareModule");
() -> artifactManagement.create(new ArtifactUpload(
IOUtils.toInputStream(artifactData, "UTF-8"),
NOT_EXIST_IDL, "xxx", null, null, null, false, null, artifactSize)), "SoftwareModule");
verifyThrownExceptionBy(() -> artifactManagement.delete(NOT_EXIST_IDL), "Artifact");
verifyThrownExceptionBy(() -> artifactManagement.findBySoftwareModule(PAGE, NOT_EXIST_IDL), "SoftwareModule");
assertThat(artifactManagement.getByFilename(NOT_EXIST_ID).isPresent()).isFalse();
assertThat(artifactManagement.getByFilename(NOT_EXIST_ID)).isEmpty();
verifyThrownExceptionBy(() -> artifactManagement.getByFilenameAndSoftwareModule("xxx", NOT_EXIST_IDL),
"SoftwareModule");
verifyThrownExceptionBy(() -> artifactManagement.getByFilenameAndSoftwareModule("xxx", NOT_EXIST_IDL), "SoftwareModule");
}
@Test
@Description("Test if a local artifact can be created by API including metadata.")
void createArtifact() throws IOException {
// check baseline
assertThat(softwareModuleRepository.findAll()).hasSize(0);
assertThat(artifactRepository.findAll()).hasSize(0);
assertThat(softwareModuleRepository.findAll()).isEmpty();
assertThat(artifactRepository.findAll()).isEmpty();
final JpaSoftwareModule sm = softwareModuleRepository
.save(new JpaSoftwareModule(osType, "name 1", "version 1"));
final JpaSoftwareModule sm2 = softwareModuleRepository
.save(new JpaSoftwareModule(osType, "name 2", "version 2"));
final JpaSoftwareModule sm = softwareModuleRepository.save(new JpaSoftwareModule(osType, "name 1", "version 1"));
final JpaSoftwareModule sm2 = softwareModuleRepository.save(new JpaSoftwareModule(osType, "name 2", "version 2"));
softwareModuleRepository.save(new JpaSoftwareModule(osType, "name 3", "version 3"));
final int artifactSize = 5 * 1024;
@@ -129,49 +119,43 @@ class ArtifactManagementTest extends AbstractJpaIntegrationTest {
final InputStream inputStream2 = new ByteArrayInputStream(randomBytes);
final InputStream inputStream3 = new ByteArrayInputStream(randomBytes);
final InputStream inputStream4 = new ByteArrayInputStream(randomBytes)) {
final Artifact artifact1 = createArtifactForSoftwareModule("file1", sm.getId(), artifactSize, inputStream1);
createArtifactForSoftwareModule("file11", sm.getId(), artifactSize, inputStream2);
createArtifactForSoftwareModule("file12", sm.getId(), artifactSize, inputStream3);
final Artifact artifact2 = createArtifactForSoftwareModule("file2", sm2.getId(), artifactSize,
inputStream4);
final Artifact artifact2 = createArtifactForSoftwareModule("file2", sm2.getId(), artifactSize, inputStream4);
assertThat(artifact1).isInstanceOf(Artifact.class);
assertThat(artifact1.getSoftwareModule().getId()).isEqualTo(sm.getId());
assertThat(artifact2.getSoftwareModule().getId()).isEqualTo(sm2.getId());
assertThat(((JpaArtifact) artifact1).getFilename()).isEqualTo("file1");
assertThat(((JpaArtifact) artifact1).getSha1Hash()).isNotNull();
assertThat(artifact1.getFilename()).isEqualTo("file1");
assertThat(artifact1.getSha1Hash()).isNotNull();
assertThat(artifact1).isNotEqualTo(artifact2);
assertThat(((JpaArtifact) artifact1).getSha1Hash()).isEqualTo(((JpaArtifact) artifact2).getSha1Hash());
assertThat(artifact1.getSha1Hash()).isEqualTo(artifact2.getSha1Hash());
assertThat(artifactManagement.getByFilename("file1").get().getSha1Hash())
.isEqualTo(HashGeneratorUtils.generateSHA1(randomBytes));
assertThat(artifactManagement.getByFilename("file1").get().getMd5Hash())
.isEqualTo(HashGeneratorUtils.generateMD5(randomBytes));
assertThat(artifactManagement.getByFilename("file1").get().getSha256Hash())
.isEqualTo(HashGeneratorUtils.generateSHA256(randomBytes));
assertThat(artifactManagement.getByFilename("file1").get().getSha1Hash()).isEqualTo(HashGeneratorUtils.generateSHA1(randomBytes));
assertThat(artifactManagement.getByFilename("file1").get().getMd5Hash()).isEqualTo(HashGeneratorUtils.generateMD5(randomBytes));
assertThat(artifactManagement.getByFilename("file1").get().getSha256Hash()).isEqualTo(
HashGeneratorUtils.generateSHA256(randomBytes));
assertThat(artifactRepository.findAll()).hasSize(4);
assertThat(softwareModuleRepository.findAll()).hasSize(3);
assertThat(softwareModuleManagement.get(sm.getId()).get().getArtifacts()).hasSize(3);
}
}
@Test
@Description("Verifies that artifact management does not create artifacts with illegal filename.")
void entityQueryWithIllegalFilenameThrowsException() throws URISyntaxException {
void entityQueryWithIllegalFilenameThrowsException() {
final String illegalFilename = "<img src=ernw onerror=alert(1)>.xml";
final String artifactData = "test";
final int artifactSize = artifactData.length();
final long smID = softwareModuleRepository.save(new JpaSoftwareModule(osType, "smIllegalFilenameTest", "1.0"))
.getId();
assertThatExceptionOfType(ConstraintViolationException.class).isThrownBy(
() -> artifactManagement.create(new ArtifactUpload(IOUtils.toInputStream(artifactData, "UTF-8"), smID,
illegalFilename, false, artifactSize)));
assertThat(softwareModuleManagement.get(smID).get().getArtifacts()).hasSize(0);
final long smID = softwareModuleRepository.save(new JpaSoftwareModule(osType, "smIllegalFilenameTest", "1.0")).getId();
final ArtifactUpload artifactUpload = new ArtifactUpload(
IOUtils.toInputStream(artifactData, "UTF-8"), smID, illegalFilename, false, artifactSize);
assertThatExceptionOfType(ConstraintViolationException.class).isThrownBy(() -> artifactManagement.create(artifactUpload));
assertThat(softwareModuleManagement.get(smID).get().getArtifacts()).isEmpty();
}
@Test
@@ -206,9 +190,7 @@ class ArtifactManagementTest extends AbstractJpaIntegrationTest {
@Test
@Description("Verifies that the quota specifying the maximum artifact storage is enforced (across software modules).")
void createArtifactsUntilStorageQuotaIsExceeded() throws IOException {
// create as many small artifacts as possible w/o violating the storage
// quota
// create as many small artifacts as possible w/o violating the storage quota
final long maxBytes = quotaManagement.getMaxArtifactStorage();
final List<Long> artifactIds = new ArrayList<>();
@@ -221,99 +203,78 @@ class ArtifactManagementTest extends AbstractJpaIntegrationTest {
}
// upload one more artifact to trigger the quota exceeded error
final JpaSoftwareModule sm = softwareModuleRepository
.save(new JpaSoftwareModule(osType, "smd" + numArtifacts, "1.0"));
final long smId = softwareModuleRepository.save(new JpaSoftwareModule(osType, "smd" + numArtifacts, "1.0")).getId();
assertThatExceptionOfType(StorageQuotaExceededException.class)
.isThrownBy(() -> createArtifactForSoftwareModule("file" + numArtifacts, sm.getId(), artifactSize));
.isThrownBy(() -> createArtifactForSoftwareModule("file" + numArtifacts, smId, artifactSize));
// delete one of the artifacts
artifactManagement.delete(artifactIds.get(0));
// now we should be able to create an artifact again
createArtifactForSoftwareModule("fileXYZ", sm.getId(), artifactSize);
createArtifactForSoftwareModule("fileXYZ", smId, artifactSize);
}
@Test
@Description("Verifies that you cannot create artifacts which exceed the configured maximum size.")
void createArtifactFailsIfTooLarge() {
// create a software module
final JpaSoftwareModule sm1 = softwareModuleRepository.save(new JpaSoftwareModule(osType, "sm1", "1.0"));
final long smId = softwareModuleRepository.save(new JpaSoftwareModule(osType, "sm1", "1.0")).getId();
// create an artifact that exceeds the configured quota
final long maxSize = quotaManagement.getMaxArtifactSize();
final int artifactSize = Math.toIntExact(quotaManagement.getMaxArtifactSize()) + 8;
assertThatExceptionOfType(FileSizeQuotaExceededException.class)
.isThrownBy(() -> createArtifactForSoftwareModule("file", sm1.getId(), Math.toIntExact(maxSize) + 8));
.isThrownBy(() -> createArtifactForSoftwareModule("file", smId, artifactSize));
}
@Test
@Description("Tests hard delete directly on repository.")
void hardDeleteSoftwareModule() throws IOException {
final JpaSoftwareModule sm = softwareModuleRepository
.save(new JpaSoftwareModule(osType, "name 1", "version 1"));
final JpaSoftwareModule sm = softwareModuleRepository.save(new JpaSoftwareModule(osType, "name 1", "version 1"));
createArtifactForSoftwareModule("file1", sm.getId(), 5 * 1024);
assertThat(artifactRepository.findAll()).hasSize(1);
softwareModuleRepository.deleteAll();
assertThat(artifactRepository.findAll()).hasSize(0);
assertThat(artifactRepository.findAll()).isEmpty();
}
/**
* Test method for
* {@link org.eclipse.hawkbit.repository.ArtifactManagement#delete(long)} .
*
* @throws IOException
* Test method for {@link org.eclipse.hawkbit.repository.ArtifactManagement#delete(long)}.
*/
@Test
@Description("Tests the deletion of a local artifact including metadata.")
void deleteArtifact() throws IOException {
final JpaSoftwareModule sm = softwareModuleRepository
.save(new JpaSoftwareModule(osType, "name 1", "version 1"));
final JpaSoftwareModule sm2 = softwareModuleRepository
.save(new JpaSoftwareModule(osType, "name 2", "version 2"));
final JpaSoftwareModule sm = softwareModuleRepository.save(new JpaSoftwareModule(osType, "name 1", "version 1"));
final JpaSoftwareModule sm2 = softwareModuleRepository.save(new JpaSoftwareModule(osType, "name 2", "version 2"));
assertThat(artifactRepository.findAll()).isEmpty();
final int artifactSize = 5 * 1024;
try (final InputStream inputStream1 = new RandomGeneratedInputStream(artifactSize);
final InputStream inputStream2 = new RandomGeneratedInputStream(artifactSize)) {
final Artifact artifact1 = createArtifactForSoftwareModule("file1", sm.getId(), artifactSize, inputStream1);
final Artifact artifact2 = createArtifactForSoftwareModule("file2", sm2.getId(), artifactSize,
inputStream2);
final Artifact artifact2 = createArtifactForSoftwareModule("file2", sm2.getId(), artifactSize, inputStream2);
assertThat(artifactRepository.findAll()).hasSize(2);
assertThat(artifact1.getId()).isNotNull();
assertThat(artifact2.getId()).isNotNull();
assertThat(((JpaArtifact) artifact1).getSha1Hash()).isNotEqualTo(((JpaArtifact) artifact2).getSha1Hash());
assertThat(artifact1.getSha1Hash()).isNotEqualTo(artifact2.getSha1Hash());
assertThat(
binaryArtifactRepository.getArtifactBySha1(tenantAware.getCurrentTenant(), artifact1.getSha1Hash()))
.isNotNull();
assertThat(
binaryArtifactRepository.getArtifactBySha1(tenantAware.getCurrentTenant(), artifact2.getSha1Hash()))
.isNotNull();
assertThat(binaryArtifactRepository.getArtifactBySha1(tenantAware.getCurrentTenant(), artifact1.getSha1Hash())).isNotNull();
assertThat(binaryArtifactRepository.getArtifactBySha1(tenantAware.getCurrentTenant(), artifact2.getSha1Hash())).isNotNull();
artifactManagement.delete(artifact1.getId());
assertThat(artifactRepository.findAll()).hasSize(1);
assertThat(
binaryArtifactRepository.getArtifactBySha1(tenantAware.getCurrentTenant(), artifact1.getSha1Hash()))
.isNull();
assertThat(
binaryArtifactRepository.getArtifactBySha1(tenantAware.getCurrentTenant(), artifact2.getSha1Hash()))
.isNotNull();
assertThat(binaryArtifactRepository.getArtifactBySha1(tenantAware.getCurrentTenant(), artifact1.getSha1Hash())).isNull();
assertThat(binaryArtifactRepository.getArtifactBySha1(tenantAware.getCurrentTenant(), artifact2.getSha1Hash())).isNotNull();
artifactManagement.delete(artifact2.getId());
assertThat(
binaryArtifactRepository.getArtifactBySha1(tenantAware.getCurrentTenant(), artifact2.getSha1Hash()))
.isNull();
assertThat(binaryArtifactRepository.getArtifactBySha1(tenantAware.getCurrentTenant(), artifact2.getSha1Hash())).isNull();
assertThat(artifactRepository.findAll()).hasSize(0);
assertThat(artifactRepository.findAll()).isEmpty();
}
}
@@ -321,10 +282,8 @@ class ArtifactManagementTest extends AbstractJpaIntegrationTest {
@Description("Test the deletion of an artifact metadata where the binary is still linked to another metadata element. " +
"The expected result is that the metadata is deleted but the binary kept.")
void deleteDuplicateArtifacts() throws IOException {
final JpaSoftwareModule sm = softwareModuleRepository
.save(new JpaSoftwareModule(osType, "name 1", "version 1"));
final JpaSoftwareModule sm2 = softwareModuleRepository
.save(new JpaSoftwareModule(osType, "name 2", "version 2"));
final JpaSoftwareModule sm = softwareModuleRepository.save(new JpaSoftwareModule(osType, "name 1", "version 1"));
final JpaSoftwareModule sm2 = softwareModuleRepository.save(new JpaSoftwareModule(osType, "name 2", "version 2"));
final int artifactSize = 5 * 1024;
final byte[] randomBytes = randomBytes(artifactSize);
@@ -347,49 +306,44 @@ class ArtifactManagementTest extends AbstractJpaIntegrationTest {
artifactManagement.delete(artifact2.getId());
assertThat(binaryArtifactRepository.getArtifactBySha1(tenantAware.getCurrentTenant(), artifact1.getSha1Hash())).isNull();
assertThat(artifactRepository.findAll()).hasSize(0);
assertThat(artifactRepository.findAll()).isEmpty();
}
}
@Test
@Description("Verifies that you cannot delete an artifact which exists with the same hash, in the same tenant and the SoftwareModule is not deleted .")
void deleteArtifactWithSameHashAndSoftwareModuleIsNotDeletedInSameTenants() throws IOException {
final JpaSoftwareModule sm = softwareModuleRepository
.save(new JpaSoftwareModule(osType, "name 1", "version 1"));
final JpaSoftwareModule sm2 = softwareModuleRepository
.save(new JpaSoftwareModule(osType, "name 2", "version 2"));
final JpaSoftwareModule sm = softwareModuleRepository.save(new JpaSoftwareModule(osType, "name 1", "version 1"));
final JpaSoftwareModule sm2 = softwareModuleRepository.save(new JpaSoftwareModule(osType, "name 2", "version 2"));
final int artifactSize = 5 * 1024;
final byte[] randomBytes = randomBytes(artifactSize);
try (final InputStream inputStream1 = new ByteArrayInputStream(randomBytes);
final InputStream inputStream2 = new ByteArrayInputStream(randomBytes)) {
final Artifact artifact1 = createArtifactForSoftwareModule("file1", sm.getId(), artifactSize, inputStream1);
final Artifact artifact2 = createArtifactForSoftwareModule("file2", sm2.getId(), artifactSize,
inputStream2);
final Artifact artifact2 = createArtifactForSoftwareModule("file2", sm2.getId(), artifactSize, inputStream2);
assertEqualFileContents(
artifactManagement.loadArtifactBinary(artifact2.getSha1Hash(), sm2.getId(), sm2.isEncrypted()),
randomBytes);
artifactManagement.loadArtifactBinary(artifact2.getSha1Hash(), sm2.getId(), sm2.isEncrypted()), randomBytes);
assertThat(artifactRepository.findAll()).hasSize(2);
assertThat(artifact1.getSha1Hash()).isEqualTo(artifact2.getSha1Hash());
assertThat(artifactRepository.countBySha1HashAndTenantAndSoftwareModuleDeletedIsFalse(
artifact1.getSha1Hash(), artifact1.getTenant())).isGreaterThan(1);
artifact1.getSha1Hash(), artifact1.getTenant()))
.isGreaterThan(1);
artifactRepository.deleteById(artifact1.getId());
assertThat(artifactRepository.findAll()).hasSize(1);
assertThat(artifactRepository.countBySha1HashAndTenantAndSoftwareModuleDeletedIsFalse(
artifact2.getSha1Hash(), artifact2.getTenant())).isLessThanOrEqualTo(1);
artifact2.getSha1Hash(), artifact2.getTenant()))
.isLessThanOrEqualTo(1);
artifactRepository.deleteById(artifact2.getId());
assertThat(artifactRepository.findAll()).hasSize(0);
assertThat(artifactRepository.findAll()).isEmpty();
}
}
@@ -427,7 +381,7 @@ class ArtifactManagementTest extends AbstractJpaIntegrationTest {
try (final InputStream inputStream = new RandomGeneratedInputStream(artifactSize)) {
final Artifact artifact = createArtifactForSoftwareModule(
"file1", testdataFactory.createSoftwareModuleOs().getId(), artifactSize, inputStream);
assertThat(artifactManagement.get(artifact.getId()).get()).isEqualTo(artifact);
assertThat(artifactManagement.get(artifact.getId())).contains(artifact);
}
}
@@ -440,8 +394,7 @@ class ArtifactManagementTest extends AbstractJpaIntegrationTest {
final SoftwareModule smOs = testdataFactory.createSoftwareModuleOs();
final Artifact artifact = createArtifactForSoftwareModule("file1", smOs.getId(), artifactSize, input);
assertEqualFileContents(
artifactManagement.loadArtifactBinary(artifact.getSha1Hash(), smOs.getId(), smOs.isEncrypted()),
randomBytes);
artifactManagement.loadArtifactBinary(artifact.getSha1Hash(), smOs.getId(), smOs.isEncrypted()), randomBytes);
}
}
@@ -492,24 +445,25 @@ class ArtifactManagementTest extends AbstractJpaIntegrationTest {
final DbArtifactHash artifactHashes = calcHashes(testData);
try (final InputStream inputStream = new ByteArrayInputStream(testData)) {
final ArtifactUpload artifactUploadWithInvalidSha1 = new ArtifactUpload(inputStream, sm.getId(),
"test-file", artifactHashes.getMd5(), "sha1", artifactHashes.getSha256(), false, null,
testData.length);
final ArtifactUpload artifactUploadWithInvalidSha1 = new ArtifactUpload(
inputStream, sm.getId(), "test-file",
artifactHashes.getMd5(), "sha1", artifactHashes.getSha256(), false, null, testData.length);
assertThatExceptionOfType(InvalidSHA1HashException.class)
.isThrownBy(() -> artifactManagement.create(artifactUploadWithInvalidSha1));
}
try (final InputStream inputStream = new ByteArrayInputStream(testData)) {
final ArtifactUpload artifactUploadWithInvalidMd5 = new ArtifactUpload(inputStream, sm.getId(), "test-file",
final ArtifactUpload artifactUploadWithInvalidMd5 = new ArtifactUpload(
inputStream, sm.getId(), "test-file",
"md5", artifactHashes.getSha1(), artifactHashes.getSha256(), false, null, testData.length);
assertThatExceptionOfType(InvalidMD5HashException.class)
.isThrownBy(() -> artifactManagement.create(artifactUploadWithInvalidMd5));
}
try (final InputStream inputStream = new ByteArrayInputStream(testData)) {
final ArtifactUpload artifactUploadWithInvalidSha256 = new ArtifactUpload(inputStream, sm.getId(),
"test-file", artifactHashes.getMd5(), artifactHashes.getSha1(), "sha256", false, null,
testData.length);
final ArtifactUpload artifactUploadWithInvalidSha256 = new ArtifactUpload(
inputStream, sm.getId(), "test-file",
artifactHashes.getMd5(), artifactHashes.getSha1(), "sha256", false, null, testData.length);
assertThatExceptionOfType(InvalidSHA256HashException.class)
.isThrownBy(() -> artifactManagement.create(artifactUploadWithInvalidSha256));
}
@@ -524,17 +478,16 @@ class ArtifactManagementTest extends AbstractJpaIntegrationTest {
final DbArtifactHash artifactHashes = calcHashes(testData);
try (final InputStream inputStream = new ByteArrayInputStream(testData)) {
final ArtifactUpload artifactUpload = new ArtifactUpload(inputStream, sm.getId(), "test-file",
artifactHashes.getMd5(), artifactHashes.getSha1(), artifactHashes.getSha256(), false, null,
testData.length);
final ArtifactUpload artifactUpload = new ArtifactUpload(
inputStream, sm.getId(), "test-file",
artifactHashes.getMd5(), artifactHashes.getSha1(), artifactHashes.getSha256(), false, null, testData.length);
final Artifact createdArtifact = artifactManagement.create(artifactUpload);
assertThat(createdArtifact.getSha1Hash()).isEqualTo(artifactHashes.getSha1());
assertThat(createdArtifact.getMd5Hash()).isEqualTo(artifactHashes.getMd5());
assertThat(createdArtifact.getSha256Hash()).isEqualTo(artifactHashes.getSha256());
}
final Optional<DbArtifact> dbArtifact = artifactManagement.loadArtifactBinary(artifactHashes.getSha1(),
sm.getId(), sm.isEncrypted());
final Optional<DbArtifact> dbArtifact = artifactManagement.loadArtifactBinary(artifactHashes.getSha1(), sm.getId(), sm.isEncrypted());
assertThat(dbArtifact).isPresent();
}
@@ -548,19 +501,19 @@ class ArtifactManagementTest extends AbstractJpaIntegrationTest {
final DbArtifactHash artifactHashes = calcHashes(testData);
try (final InputStream inputStream = new ByteArrayInputStream(testData)) {
final ArtifactUpload artifactUpload = new ArtifactUpload(inputStream, smOs.getId(), "test-file",
artifactHashes.getMd5(), artifactHashes.getSha1(), artifactHashes.getSha256(), false, null,
testData.length);
final ArtifactUpload artifactUpload = new ArtifactUpload(
inputStream, smOs.getId(), "test-file",
artifactHashes.getMd5(), artifactHashes.getSha1(), artifactHashes.getSha256(), false, null, testData.length);
final Artifact artifact = artifactManagement.create(artifactUpload);
assertThat(artifact).isNotNull();
}
final Optional<DbArtifact> dbArtifact = artifactManagement.loadArtifactBinary(artifactHashes.getSha1(),
smOs.getId(), smOs.isEncrypted());
final Optional<DbArtifact> dbArtifact = artifactManagement.loadArtifactBinary(
artifactHashes.getSha1(), smOs.getId(), smOs.isEncrypted());
assertThat(dbArtifact).isPresent();
try (final InputStream inputStream = new ByteArrayInputStream(testData)) {
final ArtifactUpload existingArtifactUpload = new ArtifactUpload(inputStream, smApp.getId(), "test-file",
false, testData.length);
final ArtifactUpload existingArtifactUpload = new ArtifactUpload(
inputStream, smApp.getId(), "test-file", false, testData.length);
final Artifact createdArtifact = artifactManagement.create(existingArtifactUpload);
assertThat(createdArtifact.getSha1Hash()).isEqualTo(artifactHashes.getSha1());
assertThat(createdArtifact.getMd5Hash()).isEqualTo(artifactHashes.getMd5());
@@ -594,8 +547,8 @@ class ArtifactManagementTest extends AbstractJpaIntegrationTest {
}
}
private Artifact createArtifactForSoftwareModule(final String filename, final long moduleId, final int artifactSize,
final InputStream inputStream) {
private Artifact createArtifactForSoftwareModule(
final String filename, final long moduleId, final int artifactSize, final InputStream inputStream) {
return artifactManagement.create(new ArtifactUpload(inputStream, moduleId, filename, false, artifactSize));
}
@@ -607,8 +560,8 @@ class ArtifactManagementTest extends AbstractJpaIntegrationTest {
return runAsTenant(tenant, () -> testdataFactory.createSoftwareModuleApp());
}
private Artifact createArtifactForTenant(final String tenant, final String artifactData, final long moduleId,
final String filename) throws Exception {
private Artifact createArtifactForTenant(final String tenant, final String artifactData, final long moduleId, final String filename)
throws Exception {
return runAsTenant(tenant, () -> testdataFactory.createArtifact(artifactData, moduleId, filename));
}
@@ -616,12 +569,11 @@ class ArtifactManagementTest extends AbstractJpaIntegrationTest {
assertThat(runAsTenant(tenant, () -> artifactRepository.findAll())).hasSize(count);
}
private void assertEqualFileContents(final Optional<DbArtifact> artifact, final byte[] randomBytes)
throws IOException {
private void assertEqualFileContents(final Optional<DbArtifact> artifact, final byte[] randomBytes) throws IOException {
try (final InputStream inputStream = artifact.get().getFileInputStream()) {
assertTrue(IOUtils.contentEquals(new ByteArrayInputStream(randomBytes), inputStream),
assertTrue(
IOUtils.contentEquals(new ByteArrayInputStream(randomBytes), inputStream),
"The stored binary matches the given binary");
}
}
}
}

View File

@@ -607,17 +607,15 @@ class DistributionSetManagementTest extends AbstractJpaIntegrationTest {
void lockDistributionSet() {
final DistributionSet distributionSet = testdataFactory.createDistributionSet("ds-1");
assertThat(
distributionSetManagement.get(distributionSet.getId()).map(DistributionSet::isLocked)
.orElse(true))
distributionSetManagement.get(distributionSet.getId()).map(DistributionSet::isLocked).orElse(true))
.isFalse();
distributionSetManagement.lock(distributionSet.getId());
assertThat(
distributionSetManagement.get(distributionSet.getId()).map(DistributionSet::isLocked)
.orElse(false))
distributionSetManagement.get(distributionSet.getId()).map(DistributionSet::isLocked).orElse(false))
.isTrue();
// assert software modules are locked
assertThat(distributionSet.getModules().size()).isNotEqualTo(0);
distributionSetManagement.get(distributionSet.getId()).map(DistributionSet::getModules)
distributionSetManagement.getWithDetails(distributionSet.getId()).map(DistributionSet::getModules)
.orElseThrow().forEach(module -> assertThat(module.isLocked()).isTrue());
}
@@ -668,7 +666,7 @@ class DistributionSetManagementTest extends AbstractJpaIntegrationTest {
.isFalse();
// assert software modules are not unlocked
assertThat(distributionSet.getModules().size()).isNotEqualTo(0);
distributionSetManagement.get(distributionSet.getId()).map(DistributionSet::getModules)
distributionSetManagement.getWithDetails(distributionSet.getId()).map(DistributionSet::getModules)
.orElseThrow().forEach(module -> assertThat(module.isLocked()).isTrue());
}
@@ -688,7 +686,7 @@ class DistributionSetManagementTest extends AbstractJpaIntegrationTest {
.as("Attempt to modify a locked DS software modules should throw an exception")
.isThrownBy(() -> distributionSetManagement.assignSoftwareModules(
distributionSet.getId(), List.of(testdataFactory.createSoftwareModule("sm-1").getId())));
assertThat(distributionSetManagement.get(distributionSet.getId()).get().getModules().size())
assertThat(distributionSetManagement.getWithDetails(distributionSet.getId()).get().getModules().size())
.as("Software module shall not be added to a locked DS.")
.isEqualTo(softwareModuleCount);
@@ -697,7 +695,7 @@ class DistributionSetManagementTest extends AbstractJpaIntegrationTest {
.as("Attempt to modify a locked DS software modules should throw an exception")
.isThrownBy(() -> distributionSetManagement.unassignSoftwareModule(
distributionSet.getId(), distributionSet.getModules().stream().findFirst().get().getId()));
assertThat(distributionSetManagement.get(distributionSet.getId()).get().getModules().size())
assertThat(distributionSetManagement.getWithDetails(distributionSet.getId()).get().getModules().size())
.as("Software module shall not be removed from a locked DS.")
.isEqualTo(softwareModuleCount);
}
@@ -722,8 +720,7 @@ class DistributionSetManagementTest extends AbstractJpaIntegrationTest {
.toList());
// assert that implicit lock locks for every skip tag
skipTags.forEach(skipTag -> {
DistributionSet distributionSetWithSkipTag =
testdataFactory.createDistributionSet("ds-skip-" + skipTag.getName());
DistributionSet distributionSetWithSkipTag = testdataFactory.createDistributionSet("ds-skip-" + skipTag.getName());
distributionSetManagement.assignTag(List.of(distributionSetWithSkipTag.getId()), skipTag.getId());
distributionSetWithSkipTag = distributionSetManagement.get(distributionSetWithSkipTag.getId()).orElseThrow();
// assert that implicit lock isn't applicable for skip tags

View File

@@ -33,7 +33,7 @@ import org.springframework.core.env.Environment;
@Feature("Component Tests - Repository")
@Story("Tenant Configuration Management")
public class TenantConfigurationManagementTest extends AbstractJpaIntegrationTest implements EnvironmentAware {
class TenantConfigurationManagementTest extends AbstractJpaIntegrationTest implements EnvironmentAware {
private Environment environment;
@@ -44,7 +44,7 @@ public class TenantConfigurationManagementTest extends AbstractJpaIntegrationTes
@Test
@Description("Tests that tenant specific configuration can be persisted and in case the tenant does not have specific configuration the default from environment is used instead.")
public void storeTenantSpecificConfigurationAsString() {
void storeTenantSpecificConfigurationAsString() {
final String envPropertyDefault = environment
.getProperty("hawkbit.server.ddi.security.authentication.gatewaytoken.key");
assertThat(envPropertyDefault).isNotNull();
@@ -73,7 +73,7 @@ public class TenantConfigurationManagementTest extends AbstractJpaIntegrationTes
@Test
@Description("Tests that the tenant specific configuration can be updated")
public void updateTenantSpecificConfiguration() {
void updateTenantSpecificConfiguration() {
final String configKey = TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY;
final String value1 = "firstValue";
final String value2 = "secondValue";
@@ -89,7 +89,7 @@ public class TenantConfigurationManagementTest extends AbstractJpaIntegrationTes
@Test
@Description("Tests that the tenant specific configuration can be batch updated")
public void batchUpdateTenantSpecificConfiguration() {
void batchUpdateTenantSpecificConfiguration() {
Map<String, Serializable> configuration = new HashMap<>() {{
put(TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY, "token_123");
put(TenantConfigurationKey.ROLLOUT_APPROVAL_ENABLED, true);
@@ -107,7 +107,7 @@ public class TenantConfigurationManagementTest extends AbstractJpaIntegrationTes
@Test
@Description("Tests that the configuration value can be converted from String to Integer automatically")
public void storeAndUpdateTenantSpecificConfigurationAsBoolean() {
void storeAndUpdateTenantSpecificConfigurationAsBoolean() {
final String configKey = TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_ENABLED;
final Boolean value1 = true;
tenantConfigurationManagement.addOrUpdateConfiguration(configKey, value1);
@@ -119,7 +119,7 @@ public class TenantConfigurationManagementTest extends AbstractJpaIntegrationTes
@Test
@Description("Tests that the get configuration throws exception in case the value cannot be automatically converted from String to Boolean")
public void wrongTenantConfigurationValueTypeThrowsException() {
void wrongTenantConfigurationValueTypeThrowsException() {
final String configKey = TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_ENABLED;
final String value1 = "thisIsNotABoolean";
@@ -131,7 +131,7 @@ public class TenantConfigurationManagementTest extends AbstractJpaIntegrationTes
@Test
@Description("Tests that the get configuration throws exception in case the value is the wrong type")
public void batchWrongTenantConfigurationValueTypeThrowsException() {
void batchWrongTenantConfigurationValueTypeThrowsException() {
final Map<String, Serializable> configuration = new HashMap<>() {{
put(TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY, "token_123");
put(TenantConfigurationKey.ROLLOUT_APPROVAL_ENABLED, true);
@@ -155,7 +155,7 @@ public class TenantConfigurationManagementTest extends AbstractJpaIntegrationTes
@Test
@Description("Tests that a deletion of a tenant specific configuration deletes it from the database.")
public void deleteConfigurationReturnNullConfiguration() {
void deleteConfigurationReturnNullConfiguration() {
final String configKey = TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY;
// gateway token does not have default value so no configuration value should be available
@@ -180,7 +180,7 @@ public class TenantConfigurationManagementTest extends AbstractJpaIntegrationTes
@Test
@Description("Test that an Exception is thrown, when an integer is stored but a string expected.")
public void storesIntegerWhenStringIsExpected() {
void storesIntegerWhenStringIsExpected() {
final String configKey = TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY;
final Integer wrongDatType = 123;
assertThatThrownBy(() -> tenantConfigurationManagement.addOrUpdateConfiguration(configKey, wrongDatType))
@@ -190,7 +190,7 @@ public class TenantConfigurationManagementTest extends AbstractJpaIntegrationTes
@Test
@Description("Test that an Exception is thrown, when an integer is stored but a boolean expected.")
public void storesIntegerWhenBooleanIsExpected() {
void storesIntegerWhenBooleanIsExpected() {
final String configKey = TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_ENABLED;
final Integer wrongDataType = 123;
assertThatThrownBy(() -> tenantConfigurationManagement.addOrUpdateConfiguration(configKey, wrongDataType))
@@ -200,7 +200,7 @@ public class TenantConfigurationManagementTest extends AbstractJpaIntegrationTes
@Test
@Description("Test that an Exception is thrown, when an integer is stored as PollingTime.")
public void storesIntegerWhenPollingIntervalIsExpected() {
void storesIntegerWhenPollingIntervalIsExpected() {
final String configKey = TenantConfigurationKey.POLLING_TIME_INTERVAL;
final Integer wrongDataType = 123;
assertThatThrownBy(() -> tenantConfigurationManagement.addOrUpdateConfiguration(configKey, wrongDataType))
@@ -210,7 +210,7 @@ public class TenantConfigurationManagementTest extends AbstractJpaIntegrationTes
@Test
@Description("Test that an Exception is thrown, when an invalid formatted string is stored as PollingTime.")
public void storesWrongFormattedStringAsPollingInterval() {
void storesWrongFormattedStringAsPollingInterval() {
final String configKey = TenantConfigurationKey.POLLING_TIME_INTERVAL;
final String wrongFormatted = "wrongFormatted";
assertThatThrownBy(() -> tenantConfigurationManagement.addOrUpdateConfiguration(configKey, wrongFormatted))
@@ -220,7 +220,7 @@ public class TenantConfigurationManagementTest extends AbstractJpaIntegrationTes
@Test
@Description("Test that an Exception is thrown, when an invalid formatted string is stored as PollingTime.")
public void storesTooSmallDurationAsPollingInterval() {
void storesTooSmallDurationAsPollingInterval() {
final String configKey = TenantConfigurationKey.POLLING_TIME_INTERVAL;
final String tooSmallDuration = DurationHelper
@@ -232,7 +232,7 @@ public class TenantConfigurationManagementTest extends AbstractJpaIntegrationTes
@Test
@Description("Stores a correct formatted PollignTime and reads it again.")
public void storesCorrectDurationAsPollingInterval() {
void storesCorrectDurationAsPollingInterval() {
final String configKey = TenantConfigurationKey.POLLING_TIME_INTERVAL;
final Duration duration = DurationHelper.getDurationByTimeValues(1, 2, 0);
@@ -246,7 +246,7 @@ public class TenantConfigurationManagementTest extends AbstractJpaIntegrationTes
@Test
@Description("Request a config value in a wrong Value")
public void requestConfigValueWithWrongType() {
void requestConfigValueWithWrongType() {
assertThatThrownBy(() -> tenantConfigurationManagement.getConfigurationValue(
TenantConfigurationKey.POLLING_TIME_INTERVAL, Serializable.class))
.isInstanceOf(TenantConfigurationValidatorException.class);
@@ -254,7 +254,7 @@ public class TenantConfigurationManagementTest extends AbstractJpaIntegrationTes
@Test
@Description("Verifies that every TenenatConfiguraationKeyName exists only once")
public void verifyThatAllKeysAreDifferent() {
void verifyThatAllKeysAreDifferent() {
final Map<String, Void> keyNames = new HashMap<>();
tenantConfigurationProperties.getConfigurationKeys().forEach(key -> {
if (keyNames.containsKey(key.getKeyName())) {
@@ -266,14 +266,14 @@ public class TenantConfigurationManagementTest extends AbstractJpaIntegrationTes
@Test
@Description("Get TenantConfigurationKeyByName")
public void getTenantConfigurationKeyByName() {
void getTenantConfigurationKeyByName() {
final String configKey = TenantConfigurationKey.POLLING_TIME_INTERVAL;
assertThat(tenantConfigurationProperties.fromKeyName(configKey).getKeyName()).isEqualTo(configKey);
}
@Test
@Description("Tenant configuration which is not declared throws exception")
public void storeTenantConfigurationWhichIsNotDeclaredThrowsException() {
void storeTenantConfigurationWhichIsNotDeclaredThrowsException() {
final String configKeyWhichDoesNotExists = "configKeyWhichDoesNotExists";
assertThatThrownBy(() -> tenantConfigurationManagement.addOrUpdateConfiguration(configKeyWhichDoesNotExists, "value"))
.as("Expected InvalidTenantConfigurationKeyException for tenant configuration key which is not declared")

View File

@@ -9,6 +9,8 @@
*/
package org.eclipse.hawkbit.repository.jpa.rsql;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import jakarta.persistence.EntityManager;
@@ -23,11 +25,9 @@ import cz.jirutka.rsql.parser.ast.Node;
import cz.jirutka.rsql.parser.ast.RSQLOperators;
import cz.jirutka.rsql.parser.ast.RSQLVisitor;
import org.eclipse.hawkbit.repository.RsqlQueryField;
import org.eclipse.hawkbit.repository.jpa.Jpa;
import org.eclipse.hawkbit.repository.rsql.RsqlConfigHolder;
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer;
import org.eclipse.persistence.config.PersistenceUnitProperties;
import org.eclipse.persistence.jpa.JpaQuery;
import org.eclipse.persistence.queries.DatabaseQuery;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.util.CollectionUtils;
@@ -40,23 +40,33 @@ public class RSQLToSQL {
this.entityManager = entityManager;
}
public <T, A extends Enum<A> & RsqlQueryField> String toSQL(final Class<T> domainClass, final Class<A> fieldsClass, final String rsql,
final boolean legacyRsqlVisitor) {
return createDbQuery(domainClass, fieldsClass, rsql, legacyRsqlVisitor).getSQLString();
}
public <T, A extends Enum<A> & RsqlQueryField> DatabaseQuery createDbQuery(final Class<T> domainClass, final Class<A> fieldsClass,
final String rsql, final boolean legacyRsqlVisitor) {
public <T, A extends Enum<A> & RsqlQueryField> String toSQL(
final Class<T> domainClass, final Class<A> fieldsClass, final String rsql, final boolean legacyRsqlVisitor) {
final CriteriaQuery<T> query = createQuery(domainClass, fieldsClass, rsql, legacyRsqlVisitor);
final TypedQuery<?> typedQuery = entityManager.createQuery(query);
// executes the query - otherwise the SQL string is not generated
typedQuery.setParameter(PersistenceUnitProperties.MULTITENANT_PROPERTY_DEFAULT, "DEFAULT");
typedQuery.getResultList();
return typedQuery.unwrap(JpaQuery.class).getDatabaseQuery();
if (Jpa.JPA_VENDOR.equals(Jpa.JpaVendor.ECLIPSELINK)) {
typedQuery.setParameter("eclipselink.tenant-id", "DEFAULT");
typedQuery.getResultList();
try {
final Class<?> jpaQueryClass = Class.forName("org.eclipse.persistence.jpa.JpaQuery");
final Method getDatabaseQueryMethod = jpaQueryClass.getMethod("getDatabaseQuery");
final Method getSQLString = getDatabaseQueryMethod.getReturnType().getMethod("getSQLString");
return (String)getSQLString.invoke(getDatabaseQueryMethod.invoke(typedQuery.unwrap(jpaQueryClass)));
} catch (final RuntimeException e) {
throw e;
} catch (final ClassNotFoundException | NoSuchMethodException | IllegalAccessException e) {
throw new UnsupportedOperationException("EclipseLink is not supported", e);
} catch (final InvocationTargetException e) {
throw e.getCause() instanceof RuntimeException ? (RuntimeException)e.getCause() : new RuntimeException(e.getCause());
}
} else { // hibernate
throw new UnsupportedOperationException("Hibernate is not supported");
}
}
private <T, A extends Enum<A> & RsqlQueryField> CriteriaQuery<T> createQuery(final Class<T> domainClass, final Class<A> fieldsClass,
final String rsql, final boolean legacyRsqlVisitor) {
private <T, A extends Enum<A> & RsqlQueryField> CriteriaQuery<T> createQuery(
final Class<T> domainClass, final Class<A> fieldsClass, final String rsql, final boolean legacyRsqlVisitor) {
final CriteriaQuery<T> query = entityManager.getCriteriaBuilder().createQuery(domainClass);
final CriteriaBuilder cb = entityManager.getCriteriaBuilder();
return query.where(
@@ -78,16 +88,15 @@ public class RSQLToSQL {
query.distinct(true);
final RSQLVisitor<List<Predicate>, String> jpqQueryRSQLVisitor =
legacyRsqlVisitor ?
new JpaQueryRsqlVisitor<>(
legacyRsqlVisitor
? new JpaQueryRsqlVisitor<>(
root, cb, fieldsClass,
virtualPropertyReplacer, DATABASE, query,
!RsqlConfigHolder.getInstance().isCaseInsensitiveDB() && RsqlConfigHolder.getInstance().isIgnoreCase())
:
new JpaQueryRsqlVisitorG2<>(
fieldsClass, root, query, cb,
DATABASE, virtualPropertyReplacer,
!RsqlConfigHolder.getInstance().isCaseInsensitiveDB() && RsqlConfigHolder.getInstance().isIgnoreCase());
: new JpaQueryRsqlVisitorG2<>(
fieldsClass, root, query, cb,
DATABASE, virtualPropertyReplacer,
!RsqlConfigHolder.getInstance().isCaseInsensitiveDB() && RsqlConfigHolder.getInstance().isIgnoreCase());
final List<Predicate> accept = rootNode.accept(jpqQueryRSQLVisitor);
if (CollectionUtils.isEmpty(accept)) {

View File

@@ -15,7 +15,18 @@ logging.level.org.eclipse.persistence=ERROR
spring.jpa.properties.eclipselink.logging.level=FINE
spring.jpa.properties.eclipselink.logging.level.sql=FINE
spring.jpa.properties.eclipselink.logging.parameters=true
#hibernate.generate_statistics=true
#logging.level.org.hibernate.SQL=TRACE
#logging.level.org.hibernate.stat=TRACE
# Debug utility functions - END
# Switch to mysql
#spring.jpa.database=MYSQL
#spring.datasource.url=jdbc:mariadb://localhost:3306/hawkbit_test
#spring.datasource.driverClassName=org.mariadb.jdbc.Driver
#spring.datasource.username=root
#spring.datasource.password=
# enable / disable case sensitiveness of the DB when playing around
#hawkbit.rsql.caseInsensitiveDB=true