Split into separate maven modules.

Signed-off-by: Kai Zimmermann <kai.zimmermann@bosch-si.com>
This commit is contained in:
Kai Zimmermann
2016-05-25 17:43:57 +02:00
parent cf450dabfa
commit f2e13b8d22
310 changed files with 647 additions and 482 deletions

View File

@@ -0,0 +1,47 @@
# hawkBit metadata repository
The repository is in charge for managing the meta data of the update server, e.g. provisioning targets, distribution sets, software modules etc.
# Build
[indent=0]
----
$ mvn clean install
----
Note, in order to build correctly in your IDE, you have to add ./target/generated-sources/apt to your build path.
#Concepts
## Rollout
A rollout consists of the distribution set and a target-query-filter which defines the targets to be updated in this rollout. The targets within this filter are split up in configured amount of _Deployment Groups_ at creation time.
When starting a rollout, for all targets within this rollout deployment actions will be created. The deployment actions of the first group will be started immediately all other deployment actions will be scheduled.
> Due rollouts might include a large number of targets and deployment group, creation as well as starting a rollout might take some time and therefore the creation and starting of an rollout is executed asynchronously. The creation and starting progress is reflected by the rollout's status attribute
### Rollout Creation
The targets reflected by the target-query-filter is interpreted at creation time. Only targets which have been created at that time are included in the rollout. Targets which are created after the rollout creation will not be included. At rollout creation time all necessary deployment groups containing the targets will be created. Dynamically adding targets to a created or running rollout is currently not supported.
### Rollout Starting
The rollout is using the same concept based on _deployment acionts_ per target. All necessary _deployment acionts_ will be created at starting point but not all _deployment acionts_ will be set to running. Only the _deployment acionts_ of the first _deployment group_ is set to running, all other actions are created in _scheduled_ state.
If targets having not finished _deployment actions_ at rollout starting time, these action are tried to cancel (state: canceling). Scheduled actions from another rollout are canceled directly on server side because they were never running before and can be safely canceled.
>Multiple rollouts can be running at the same time, but if the rollouts effect the same targets they will interfer the targets _deployment actions_ e.g. canceling/cancel already running/scheduled _deployment actions_.
### Deployment Group Condition/Action
#### Success Condition/Action
A _success condition_ defines when a deployment group is interpreted as success and triggers the _success action_. E.g. a threshold of successfully updated targets within the group.
The _success action_ is executed if the _success_condition_ is fullfilled. Currently only the _success action_ starting the next following deployment group is available.
#### Error Condition/Action
A _error condition_ defines when a deployment group is interpreted as failure and triggers the _error action_. E.g. a threshold of update failures of targets within the group.
The _error action_ is executed if the _error condition_ is fullfilled. Currently only the _error action_ pausing the whole rollout is available, a paused rollout can be resumed by a user.
### Rollout Scheduler
The rollout is managed by a scheduler task which is checking for rollouts in _running_ state. This is done atomic by updating the row in the database, so only the rollouts are checked and processed for the current instance to avoid that multiple instances are processing a rollout.
>Due the scheduler, it might take some time until a rollouts is processed and switching is state or starting the next deployment group.

View File

@@ -0,0 +1,238 @@
<!--
Copyright (c) 2015 Bosch Software Innovations GmbH and others.
All rights reserved. This program and the accompanying materials
are made available under the terms of the Eclipse Public License v1.0
which accompanies this distribution, and is available at
http://www.eclipse.org/legal/epl-v10.html
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.eclipse.hawkbit</groupId>
<version>0.2.0-SNAPSHOT</version>
<artifactId>hawkbit-repository</artifactId>
</parent>
<artifactId>hawkbit-repository-jpa</artifactId>
<name>hawkBit :: Repository JPA Implementation</name>
<repositories>
<repository>
<id>com.ethlo.eclipselink.tools</id>
<url>http://ethlo.com/maven</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>com.ethlo.eclipselink.tools</id>
<url>http://ethlo.com/maven</url>
</pluginRepository>
</pluginRepositories>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!-- Hawkbit -->
<dependency>
<groupId>org.eclipse.hawkbit</groupId>
<artifactId>hawkbit-repository-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.hawkbit</groupId>
<artifactId>hawkbit-artifact-repository-mongo</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>net._01001111</groupId>
<artifactId>jlorem</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>org.eclipse.persistence.jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!-- Test -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.el</groupId>
<artifactId>javax.el-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ru.yandex.qatools.allure</groupId>
<artifactId>allure-junit-adaptor</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-rest-webmvc</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-aspects</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.easytesting</groupId>
<artifactId>fest-assert-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.easytesting</groupId>
<artifactId>fest-assert</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>cz.jirutka.rsql</groupId>
<artifactId>rsql-parser</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.bsc.maven</groupId>
<artifactId>maven-processor-plugin</artifactId>
<executions>
<execution>
<id>process</id>
<goals>
<goal>process</goal>
</goals>
<phase>generate-sources</phase>
<configuration>
<processors>
<processor>org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor</processor>
</processors>
</configuration>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-jpamodelgen</artifactId>
<version>${hibernate.version}</version>
</dependency>
</dependencies>
</plugin>
<!-- Static weaver for EclipseLink -->
<plugin>
<groupId>com.ethlo.persistence.tools</groupId>
<artifactId>eclipselink-maven-plugin</artifactId>
<version>2.6.2</version>
<executions>
<execution>
<phase>process-classes</phase>
<goals>
<goal>weave</goal>
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>org.eclipse.persistence.jpa</artifactId>
<version>${eclipselink.version}</version>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.20.0-GA</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>test-jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,15 @@
<!--
Copyright (c) 2015 Bosch Software Innovations GmbH and others.
All rights reserved. This program and the accompanying materials
are made available under the terms of the Eclipse Public License v1.0
which accompanies this distribution, and is available at
http://www.eclipse.org/legal/epl-v10.html
-->
<FindBugsFilter>
<Match>
<Method name="~_persistence_[sg]et.*" />
</Match>
</FindBugsFilter>

View File

@@ -0,0 +1,33 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit;
/**
* A constant class which holds only static constants used within the SP server.
*
*/
public final class Constants {
/**
* Defines the maximum entries for SQL in-statement. Many database have
* limit of elements within an SQL in-statement, e.g. Oracle with maximum
* 1000 elements. So everywhere we are using in-statements in our SQL
* queries we need to split these elements for the in-statements by this
* number.
*/
public static final int MAX_ENTRIES_IN_STATEMENT = 999;
/**
* Constant class only private constructor.
*/
private Constants() {
}
}

View File

@@ -0,0 +1,46 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit;
import javax.persistence.EntityManager;
import javax.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 {
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 EntityManagerHolder emHolder = (EntityManagerHolder) TransactionSynchronizationManager
.getResource(getEntityManagerFactory());
final EntityManager em = emHolder.getEntityManager();
em.setProperty(PersistenceUnitProperties.MULTITENANT_PROPERTY_DEFAULT, currentTenant.toUpperCase());
}
}
}

View File

@@ -0,0 +1,175 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.hawkbit.aspects.ExceptionMappingAspectHandler;
import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.repository.model.helper.AfterTransactionCommitExecutorHolder;
import org.eclipse.hawkbit.repository.model.helper.CacheManagerHolder;
import org.eclipse.hawkbit.repository.model.helper.SecurityTokenGeneratorHolder;
import org.eclipse.hawkbit.repository.model.helper.SystemManagementHolder;
import org.eclipse.hawkbit.repository.model.helper.SystemSecurityContextHolder;
import org.eclipse.hawkbit.repository.model.helper.TenantAwareHolder;
import org.eclipse.hawkbit.repository.model.helper.TenantConfigurationManagementHolder;
import org.eclipse.hawkbit.security.SecurityTokenGenerator;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.JpaBaseConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.vendor.AbstractJpaVendorAdapter;
import org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
/**
* General configuration for the SP Repository.
*
*/
@EnableJpaRepositories(basePackages = { "org.eclipse.hawkbit.repository.jpa" })
@EnableTransactionManagement
@EnableJpaAuditing
@EnableAspectJAutoProxy
@Configuration
@ComponentScan
@EnableAutoConfiguration
public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
/**
* @return the {@link SystemSecurityContext} singleton bean which make it
* accessible in beans which cannot access the service directly,
* e.g. JPA entities.
*/
@Bean
public SystemSecurityContextHolder systemSecurityContextHolder() {
return SystemSecurityContextHolder.getInstance();
}
/**
* @return the {@link TenantConfigurationManagement} singleton bean which
* make it accessible in beans which cannot access the service
* directly, e.g. JPA entities.
*/
@Bean
public TenantConfigurationManagementHolder tenantConfigurationManagementHolder() {
return TenantConfigurationManagementHolder.getInstance();
}
/**
* @return the {@link SystemManagementHolder} singleton bean which holds the
* current {@link SystemManagement} service and make it accessible
* in beans which cannot access the service directly, e.g. JPA
* entities.
*/
@Bean
public SystemManagementHolder systemManagementHolder() {
return SystemManagementHolder.getInstance();
}
/**
* @return the {@link TenantAwareHolder} singleton bean which holds the
* current {@link TenantAware} service and make it accessible in
* beans which cannot access the service directly, e.g. JPA
* entities.
*/
@Bean
public TenantAwareHolder tenantAwareHolder() {
return TenantAwareHolder.getInstance();
}
/**
* @return the {@link SecurityTokenGeneratorHolder} singleton bean which
* holds the current {@link SecurityTokenGenerator} service and make
* it accessible in beans which cannot access the service via
* injection
*/
@Bean
public SecurityTokenGeneratorHolder securityTokenGeneratorHolder() {
return SecurityTokenGeneratorHolder.getInstance();
}
/**
* @return the singleton instance of the {@link CacheManagerHolder}
*/
@Bean
public CacheManagerHolder cacheManagerHolder() {
return CacheManagerHolder.getInstance();
}
/**
*
* @return the singleton instance of the
* {@link AfterTransactionCommitExecutorHolder}
*/
@Bean
public AfterTransactionCommitExecutorHolder afterTransactionCommitExecutorHolder() {
return AfterTransactionCommitExecutorHolder.getInstance();
}
/**
* Defines the validation processor bean.
*
* @return the {@link MethodValidationPostProcessor}
*/
@Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
/**
* @return {@link ExceptionMappingAspectHandler} aspect bean
*/
@Bean
public ExceptionMappingAspectHandler createRepositoryExceptionHandlerAdvice() {
return new ExceptionMappingAspectHandler();
}
@Override
protected AbstractJpaVendorAdapter createJpaVendorAdapter() {
return new EclipseLinkJpaVendorAdapter();
}
@Override
protected Map<String, Object> getVendorProperties() {
final Map<String, Object> properties = new HashMap<>();
// Turn off dynamic weaving to disable LTW lookup in static weaving mode
properties.put("eclipselink.weaving", "false");
// needed for reports
properties.put("eclipselink.jdbc.allow-native-sql-queries", "true");
// flyway
properties.put("eclipselink.ddl-generation", "none");
properties.put("eclipselink.persistence-context.flush-mode", "auto");
return properties;
}
/**
* {@link MultiTenantJpaTransactionManager} bean.
*
* @see org.springframework.boot.autoconfigure.orm.jpa.JpaBaseConfiguration#transactionManager()
* @return a new {@link PlatformTransactionManager}
*/
@Override
@Bean
public PlatformTransactionManager transactionManager() {
return new MultiTenantJpaTransactionManager();
}
}

View File

@@ -0,0 +1,176 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.aspects;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.transaction.TransactionManager;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.eclipse.hawkbit.exception.GenericSpServerException;
import org.eclipse.hawkbit.repository.exception.ConcurrentModificationException;
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered;
import org.springframework.dao.ConcurrencyFailureException;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.jdbc.support.SQLStateSQLExceptionTranslator;
import org.springframework.orm.jpa.JpaSystemException;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.transaction.TransactionSystemException;
/**
* {@link Aspect} catches persistence exceptions and wraps them to custom
* specific exceptions Additionally it checks and prevents access to certain
* packages. Logging aspect which logs the call stack
*
*
*
*/
@Aspect
public class ExceptionMappingAspectHandler implements Ordered {
private static final Logger LOG = LoggerFactory.getLogger(ExceptionMappingAspectHandler.class);
private static final Map<String, String> EXCEPTION_MAPPING = new HashMap<>();
/**
* this is required to enable a certain order of exception and to select the
* most specific mappable exception according to the type hierarchy of the
* exception.
*/
private static final List<Class<?>> MAPPED_EXCEPTION_ORDER = new ArrayList<>();
@Autowired
private JpaVendorAdapter jpaVendorAdapter;
private final SQLStateSQLExceptionTranslator sqlStateExceptionTranslator = new SQLStateSQLExceptionTranslator();
static {
MAPPED_EXCEPTION_ORDER.add(DuplicateKeyException.class);
MAPPED_EXCEPTION_ORDER.add(DataIntegrityViolationException.class);
MAPPED_EXCEPTION_ORDER.add(ConcurrencyFailureException.class);
MAPPED_EXCEPTION_ORDER.add(AccessDeniedException.class);
EXCEPTION_MAPPING.put(DuplicateKeyException.class.getName(), EntityAlreadyExistsException.class.getName());
EXCEPTION_MAPPING.put(DataIntegrityViolationException.class.getName(),
EntityAlreadyExistsException.class.getName());
EXCEPTION_MAPPING.put(ConcurrencyFailureException.class.getName(),
ConcurrentModificationException.class.getName());
EXCEPTION_MAPPING.put(AccessDeniedException.class.getName(), InsufficientPermissionException.class.getName());
}
/**
* catch exceptions of the {@link TransactionManager} and wrap them to
* custom exceptions.
*
* @param ex
* the thrown and catched exception
* @throws Throwable
*/
@AfterThrowing(pointcut = "( execution( * org.springframework.transaction..*.*(..)) "
+ " || execution( * org.eclipse.hawkbit.repository.*.*(..)) "
+ " || execution( * org.eclipse.hawkbit.controller.*.*(..)) "
+ " || execution( * org.eclipse.hawkbit.rest.resource.*.*(..)) "
+ " || execution( * org.eclipse.hawkbit.service.*.*(..)) )", throwing = "ex")
// Exception squid:S00112 - Is aspectJ proxy
@SuppressWarnings({ "squid:S00112" })
public void catchAndWrapJpaExceptionsService(final Exception ex) throws Throwable {
LOG.trace("exception occured", ex);
Exception translatedAccessException = translateEclipseLinkExceptionIfPossible(ex);
if (translatedAccessException == null && ex instanceof TransactionSystemException) {
final TransactionSystemException systemException = (TransactionSystemException) ex;
translatedAccessException = translateEclipseLinkExceptionIfPossible(
(Exception) systemException.getOriginalException());
}
if (translatedAccessException == null) {
translatedAccessException = ex;
}
Exception mappingException = translatedAccessException;
LOG.trace("translated excpetion is", translatedAccessException);
for (final Class<?> mappedEx : MAPPED_EXCEPTION_ORDER) {
if (mappedEx.isAssignableFrom(translatedAccessException.getClass())) {
if (!EXCEPTION_MAPPING.containsKey(mappedEx.getName())) {
LOG.error("there is no mapping configured for exception class {}", mappedEx.getName());
mappingException = new GenericSpServerException(ex);
} else {
mappingException = (Exception) Class.forName(EXCEPTION_MAPPING.get(mappedEx.getName()))
.getConstructor(Throwable.class).newInstance(ex);
}
break;
}
}
LOG.trace("mapped exception {} to {}", translatedAccessException.getClass(), mappingException.getClass());
throw mappingException;
}
private DataAccessException translateEclipseLinkExceptionIfPossible(final Exception exception) {
final DataAccessException translatedAccessException = jpaVendorAdapter.getJpaDialect()
.translateExceptionIfPossible((RuntimeException) exception);
return translateSQLStateExceptionIfPossible(translatedAccessException);
}
/**
* There is no EclipseLinkExceptionTranslator. So we have to check and
* translate the exception by the sql error code. Luckily, there we can use
* {@link SQLStateSQLExceptionTranslator} if we can get a
* {@link SQLException}.
*
* @param accessException
* the base access exception from jpa
* @return the translated accessException
*/
private DataAccessException translateSQLStateExceptionIfPossible(final DataAccessException accessException) {
if (!(accessException instanceof JpaSystemException)) {
return accessException;
}
final SQLException ex = findSqlException((JpaSystemException) accessException);
if (ex == null) {
return accessException;
}
return sqlStateExceptionTranslator.translate(null, null, ex);
}
private static SQLException findSqlException(final JpaSystemException jpaSystemException) {
Throwable exception = jpaSystemException.getCause();
while (exception != null) {
final Throwable cause = exception.getCause();
if (cause instanceof SQLException) {
return (SQLException) cause;
}
exception = cause;
}
return null;
}
@Override
public int getOrder() {
return 1;
}
}

View File

@@ -0,0 +1,41 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.cache;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import org.eclipse.hawkbit.eventbus.CacheFieldEntityListener;
import org.springframework.cache.CacheManager;
import org.springframework.data.annotation.Transient;
/**
* Marks an field within a JPA entity as transient and this field should be
* loaded from a configured {@link CacheManager} by using the JPA entity
* listeners.
*
*
*
* @see CacheFieldEntityListener
*/
@Target({ FIELD })
@Retention(RUNTIME)
@Transient
public @interface CacheField {
/**
* @return the cache key name for this cacheable field which is used to
* store the value.
*/
String key();
}

View File

@@ -0,0 +1,56 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.cache;
import org.eclipse.hawkbit.eventbus.CacheFieldEntityListener;
/**
* Constants for cache keys used in multiple classes.
*
*
*
*
* @see CacheFieldEntityListener
* @see CacheWriteNotify
* @see CacheField
*
*/
public final class CacheKeys {
/**
* The cache key name for the {@link UpdateActionStatus} download progress
* event.
*/
public static final String DOWNLOAD_PROGRESS_PERCENT = "download.progress.percent";
public static final String ROLLOUT_GROUP_CREATED = "rollout.group.created";
public static final String ROLLOUT_GROUP_TOTAL = "rollout.group.total";
/**
* utility class only private constructor.
*/
private CacheKeys() {
}
/**
* calculates the cache key for a specific entity. The cache key must be
* different for each different identifiable entity by its ID.
*
* @param entityId
* the ID of the entity to build the specific cache key for this
* entity
* @param cacheKey
* the cache key for the field to be cached
* @return the combined cache key based on the given {@code entityId} and
* {@code cacheKey}
*/
public static String entitySpecificCacheKey(final String entityId, final String cacheKey) {
return entityId + "." + cacheKey;
}
}

View File

@@ -0,0 +1,135 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.cache;
import org.eclipse.hawkbit.eventbus.event.DownloadProgressEvent;
import org.eclipse.hawkbit.eventbus.event.RolloutGroupCreatedEvent;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.ActionStatus;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.stereotype.Service;
import com.google.common.eventbus.EventBus;
/**
* An service which combines the functionality for functional use cases to write
* into the cache an notify the writing to the cache to the {@link EventBus}.
*
*
*
*/
@Service
public class CacheWriteNotify {
/**
*
*/
private static final int DOWNLOAD_PROGRESS_MAX = 100;
@Autowired
private CacheManager cacheManager;
@Autowired
private EventBus eventBus;
@Autowired
private TenantAware tenantAware;
/**
* writes the download progress in percentage into the cache
* {@link CacheKeys#DOWNLOAD_PROGRESS_PERCENT} and notifies the
* {@link EventBus} with a {@link DownloadProgressEvent}.
*
* @param statusId
* the ID of the {@link ActionStatus}
* @param progressPercent
* the progress in percentage which must be between 0-100
*/
public void downloadProgressPercent(final long statusId, final int progressPercent) {
final Cache cache = cacheManager.getCache(Action.class.getName());
final String cacheKey = CacheKeys.entitySpecificCacheKey(String.valueOf(statusId),
CacheKeys.DOWNLOAD_PROGRESS_PERCENT);
if (progressPercent < DOWNLOAD_PROGRESS_MAX) {
cache.put(cacheKey, progressPercent);
} else {
// in case we reached progress 100 delete the cache value again
// because otherwise he will
// keep there forever
cache.evict(cacheKey);
}
eventBus.post(new DownloadProgressEvent(tenantAware.getCurrentTenant(), statusId, progressPercent));
}
/**
* Writes the {@link CacheKeys#ROLLOUT_GROUP_CREATED} and
* {@link CacheKeys#ROLLOUT_GROUP_TOTAL} into the cache and notfies the
* {@link EventBus} with a {@link RolloutGroupCreatedEvent}.
*
* @param revision
* the revision of the event
* @param rolloutId
* the ID of the rollout the group has been created
* @param rolloutGroupId
* the ID of the rollout group which has been created
* @param totalRolloutGroup
* the total number of rollout groups for this rollout
* @param createdRolloutGroup
* the number of already created groups of the rollout
*/
public void rolloutGroupCreated(final long revision, final Long rolloutId, final Long rolloutGroupId,
final int totalRolloutGroup, final int createdRolloutGroup) {
final Cache cache = cacheManager.getCache(Rollout.class.getName());
final String cacheKeyGroupTotal = CacheKeys.entitySpecificCacheKey(String.valueOf(rolloutId),
CacheKeys.ROLLOUT_GROUP_TOTAL);
final String cacheKeyGroupCreated = CacheKeys.entitySpecificCacheKey(String.valueOf(rolloutId),
CacheKeys.ROLLOUT_GROUP_CREATED);
if (createdRolloutGroup < totalRolloutGroup) {
cache.put(cacheKeyGroupTotal, totalRolloutGroup);
cache.put(cacheKeyGroupCreated, createdRolloutGroup);
} else {
// in case we reached progress 100 delete the cache value again
// because otherwise he will keep there forever
cache.evict(cacheKeyGroupTotal);
cache.evict(cacheKeyGroupCreated);
}
eventBus.post(new RolloutGroupCreatedEvent(tenantAware.getCurrentTenant(), revision, rolloutId, rolloutGroupId,
totalRolloutGroup, createdRolloutGroup));
}
/**
* @param cacheManager
* the cacheManager to set
*/
void setCacheManager(final CacheManager cacheManager) {
this.cacheManager = cacheManager;
}
/**
* @param eventBus
* the eventBus to set
*/
void setEventBus(final EventBus eventBus) {
this.eventBus = eventBus;
}
/**
* @param tenantAware
* the tenantAware to set
*/
void setTenantAware(final TenantAware tenantAware) {
this.tenantAware = tenantAware;
}
}

View File

@@ -0,0 +1,131 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.eventbus;
import java.io.Serializable;
import java.lang.reflect.Field;
import javax.persistence.PostLoad;
import javax.persistence.PostRemove;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.eclipse.hawkbit.cache.CacheField;
import org.eclipse.hawkbit.cache.CacheKeys;
import org.eclipse.hawkbit.repository.model.helper.CacheManagerHolder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.Cache;
import org.springframework.cache.Cache.ValueWrapper;
import org.springframework.cache.CacheManager;
import org.springframework.hateoas.Identifiable;
/**
* An JPA entity listener which enriches the JPA entity fields which are
* annotated with {@link CacheField} with values from the {@link CacheManager}.
* Only JPA entities which are implementing {@link Identifiable} can be handled
* by this entity listener cause the cache keys are calculated with the ID of
* the entity.
*
*
*
*
*/
public class CacheFieldEntityListener {
private static final Logger LOGGER = LoggerFactory.getLogger(CacheFieldEntityListener.class);
/**
* enriches the JPA entities after loading the entity from the SQL database.
*
* @param target
* the target which has been loaded from the database
*/
@PostLoad
public void postLoad(final Object target) {
if (target instanceof Identifiable) {
final CacheManager cacheManager = CacheManagerHolder.getInstance().getCacheManager();
@SuppressWarnings("rawtypes")
final String id = ((Identifiable) target).getId().toString();
final Class<? extends Object> type = target.getClass();
findCacheFields(type, id, new CacheFieldCallback() {
@Override
public void fromCache(final Field field, final String cacheKey, final Serializable id)
throws IllegalAccessException {
final Cache cache = cacheManager.getCache(type.getName());
final ValueWrapper valueWrapper = cache
.get(CacheKeys.entitySpecificCacheKey(id.toString(), cacheKey));
if (valueWrapper != null && valueWrapper.get() != null) {
FieldUtils.writeField(field, target, valueWrapper.get(), true);
}
}
});
}
}
/**
* deletes the associated cache fields from the cache when deleted the
* entity from the database.
*
* @param target
* the entity which has been deleted
*/
@PostRemove
public void postDelete(final Object target) {
if (target instanceof Identifiable) {
final CacheManager cacheManager = CacheManagerHolder.getInstance().getCacheManager();
@SuppressWarnings("rawtypes")
final String id = ((Identifiable) target).getId().toString();
final Class<? extends Object> type = target.getClass();
findCacheFields(type, id, new CacheFieldCallback() {
@Override
public void fromCache(final Field field, final String cacheKey, final Serializable id)
throws IllegalAccessException {
final Cache cache = cacheManager.getCache(type.getName());
cache.evict(CacheKeys.entitySpecificCacheKey(id.toString(), cacheKey));
}
});
}
}
private void findCacheFields(final Class<? extends Object> type, final Serializable id,
final CacheFieldCallback callback) {
final Field[] declaredFields = type.getDeclaredFields();
for (final Field field : declaredFields) {
if (field.getAnnotation(CacheField.class) != null) {
try {
final CacheField annotation = field.getAnnotation(CacheField.class);
callback.fromCache(field, annotation.key(), id);
} catch (final IllegalAccessException e) {
LOGGER.error("cannot access the field {} for the entity {}, ignoring the cacheable field", field,
type, e);
}
}
}
}
private interface CacheFieldCallback {
/**
* callback methods which is called by the
* {@link CacheFieldEntityListener#findCacheFields(Class, Serializable, CacheFieldCallback)}
* in case a field is annotated with {@link CacheField}.
*
* @param field
* the field which is annotaed with {@link CacheField}
* @param cacheKey
* the configured cache key in the annotation
* {@link CacheField#key()}
* @param id
* the ID of the entity
* @throws IllegalAccessException
* in case the field cannot be accessed
*/
void fromCache(final Field field, final String cacheKey, Serializable id) throws IllegalAccessException;
}
}

View File

@@ -0,0 +1,154 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.eventbus;
import java.util.Collection;
import javax.persistence.EntityManager;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.eclipse.hawkbit.eventbus.event.TargetCreatedEvent;
import org.eclipse.hawkbit.eventbus.event.TargetDeletedEvent;
import org.eclipse.hawkbit.eventbus.event.TargetInfoUpdateEvent;
import org.eclipse.hawkbit.executor.AfterTransactionCommitExecutor;
import org.eclipse.hawkbit.repository.jpa.TargetRepository;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetInfo;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetInfo;
import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.google.common.eventbus.EventBus;
/**
* An aspect implementation which wraps the necessary repository services for
* saving {@link TenantAwareBaseEntity}s to publish create or update events.
*
*
*
*
*/
@Service
@Aspect
public class EntityChangeEventListener {
@Autowired
private EventBus eventBus;
@Autowired
private TenantAware tenantAware;
@Autowired
private EntityManager entityManager;
@Autowired
private AfterTransactionCommitExecutor afterCommit;
/**
* In case the a {@link Target} is created a corresponding
* {@link TargetInfo} is created as well. We need the {@link TargetInfo}
* information in the target created event. So we are listening to the
* {@link TargetInfo} creation to indicate if an Target has been created.
*
* @param joinpoint
* the aspect join point
* @return the object of the {@link ProceedingJoinPoint#proceed()}
* @throws Throwable
* in case exception happens in the
* {@link ProceedingJoinPoint#proceed()}
*/
@Around("execution(* org.eclipse.hawkbit.repository.jpa.TargetInfoRepository.save(..))")
// Exception squid:S00112 - Is aspectJ proxy
@SuppressWarnings({ "squid:S00112" })
public Object targetCreated(final ProceedingJoinPoint joinpoint) throws Throwable {
final boolean isNew = isTargetInfoNew(joinpoint.getArgs()[0]);
final Object result = joinpoint.proceed();
if (result instanceof TargetInfo) {
if (isNew) {
notifyTargetCreated(entityManager.merge(entityManager.merge(((TargetInfo) result).getTarget())));
} else {
notifyTargetInfoChanged((TargetInfo) result);
}
}
return result;
}
/**
* Proxy method around the delete method of the {@link TargetRepository} to
* notify the {@link TargetDeletedEvent} in case targets has been deleted.
*
* @param joinpoint
* the aspect join point
* @return the object of the {@link ProceedingJoinPoint#proceed()}
* @throws Throwable
* in case exception happens in the
* {@link ProceedingJoinPoint#proceed()}
*/
@Around("execution(* org.eclipse.hawkbit.repository.jpa.TargetRepository.deleteByIdIn(..))")
// Exception squid:S00112 - Is aspectJ proxy
@SuppressWarnings({ "squid:S00112" })
public Object targetDeletedById(final ProceedingJoinPoint joinpoint) throws Throwable {
final String currentTenant = tenantAware.getCurrentTenant();
final Object result = joinpoint.proceed();
final Collection<Long> targetIds = (Collection<Long>) joinpoint.getArgs()[0];
targetIds.forEach(targetId -> notifyTargetDeleted(currentTenant, targetId));
return result;
}
/**
* Proxy method around the delete method of the {@link TargetRepository} to
* notify the {@link TargetDeletedEvent} in case targets has been deleted.
*
* @param joinpoint
* the aspect join point
* @return the object of the {@link ProceedingJoinPoint#proceed()}
* @throws Throwable
* in case exception happens in the
* {@link ProceedingJoinPoint#proceed()}
*/
@Around("execution(* org.eclipse.hawkbit.repository.jpa.TargetRepository.delete(..))")
// Exception squid:S00112 - Is aspectJ proxy
@SuppressWarnings({ "squid:S00112", "unchecked" })
public Object targetDeleted(final ProceedingJoinPoint joinpoint) throws Throwable {
final String currentTenant = tenantAware.getCurrentTenant();
final Object result = joinpoint.proceed();
final Object param = joinpoint.getArgs()[0];
// delete by id
if (param instanceof Long) {
notifyTargetDeleted(currentTenant, (Long) param);
} else if (param instanceof Target) {
notifyTargetDeleted(currentTenant, ((Target) param).getId());
} else if (param instanceof Iterable) {
((Iterable<Target>) param).forEach(target -> notifyTargetDeleted(currentTenant, target.getId()));
}
return result;
}
private void notifyTargetCreated(final Target t) {
afterCommit.afterCommit(() -> eventBus.post(new TargetCreatedEvent(t)));
}
private void notifyTargetInfoChanged(final TargetInfo targetInfo) {
afterCommit.afterCommit(() -> eventBus.post(new TargetInfoUpdateEvent(targetInfo)));
}
private void notifyTargetDeleted(final String tenant, final Long targetId) {
afterCommit.afterCommit(() -> eventBus.post(new TargetDeletedEvent(tenant, targetId)));
}
private static boolean isTargetInfoNew(final Object targetInfo) {
return ((JpaTargetInfo) targetInfo).isNew();
}
}

View File

@@ -0,0 +1,86 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.eventbus;
import java.util.Map;
import java.util.stream.Collectors;
import org.eclipse.hawkbit.eventbus.event.AbstractPropertyChangeEvent;
import org.eclipse.hawkbit.eventbus.event.ActionCreatedEvent;
import org.eclipse.hawkbit.eventbus.event.ActionPropertyChangeEvent;
import org.eclipse.hawkbit.eventbus.event.RolloutGroupPropertyChangeEvent;
import org.eclipse.hawkbit.eventbus.event.RolloutPropertyChangeEvent;
import org.eclipse.hawkbit.executor.AfterTransactionCommitExecutor;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity;
import org.eclipse.hawkbit.repository.model.helper.AfterTransactionCommitExecutorHolder;
import org.eclipse.hawkbit.repository.model.helper.EventBusHolder;
import org.eclipse.persistence.descriptors.DescriptorEvent;
import org.eclipse.persistence.descriptors.DescriptorEventAdapter;
import org.eclipse.persistence.internal.sessions.ObjectChangeSet;
import org.eclipse.persistence.queries.UpdateObjectQuery;
import org.eclipse.persistence.sessions.changesets.DirectToFieldChangeRecord;
import com.google.common.eventbus.EventBus;
/**
* Listens to change in property values of an entity.
*
*/
public class EntityPropertyChangeListener extends DescriptorEventAdapter {
@Override
public void postInsert(final DescriptorEvent event) {
if (event.getObject().getClass().equals(Action.class)) {
final Action action = (Action) event.getObject();
if (action.getRollout() != null) {
final EventBus eventBus = getEventBus();
final AfterTransactionCommitExecutor afterCommit = getAfterTransactionCommmitExecutor();
afterCommit.afterCommit(() -> eventBus.post(new ActionCreatedEvent(action)));
}
}
}
@Override
public void postUpdate(final DescriptorEvent event) {
if (event.getObject().getClass().equals(Action.class)) {
getAfterTransactionCommmitExecutor().afterCommit(() -> getEventBus().post(
new ActionPropertyChangeEvent((Action) event.getObject(), getChangeSet(Action.class, event))));
} else if (event.getObject().getClass().equals(Rollout.class)) {
getAfterTransactionCommmitExecutor().afterCommit(() -> getEventBus().post(
new RolloutPropertyChangeEvent((Rollout) event.getObject(), getChangeSet(Rollout.class, event))));
} else if (event.getObject().getClass().equals(RolloutGroup.class)) {
getAfterTransactionCommmitExecutor().afterCommit(
() -> getEventBus().post(new RolloutGroupPropertyChangeEvent((RolloutGroup) event.getObject(),
getChangeSet(RolloutGroup.class, event))));
}
}
private <T extends TenantAwareBaseEntity> Map<String, AbstractPropertyChangeEvent<T>.Values> getChangeSet(
final Class<T> clazz, final DescriptorEvent event) {
final T rolloutGroup = clazz.cast(event.getObject());
final ObjectChangeSet changeSet = ((UpdateObjectQuery) event.getQuery()).getObjectChangeSet();
return changeSet.getChanges().stream().filter(record -> record instanceof DirectToFieldChangeRecord)
.map(record -> (DirectToFieldChangeRecord) record)
.collect(Collectors.toMap(record -> record.getAttribute(),
record -> new AbstractPropertyChangeEvent<T>(rolloutGroup, null).new Values(
record.getOldValue(), record.getNewValue())));
}
private AfterTransactionCommitExecutor getAfterTransactionCommmitExecutor() {
return AfterTransactionCommitExecutorHolder.getInstance().getAfterCommit();
}
private EventBus getEventBus() {
return EventBusHolder.getInstance().getEventBus();
}
}

View File

@@ -0,0 +1,193 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.eventbus;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.eclipse.hawkbit.eventbus.EventSubscriber;
import org.eclipse.hawkbit.eventbus.event.ActionCreatedEvent;
import org.eclipse.hawkbit.eventbus.event.ActionPropertyChangeEvent;
import org.eclipse.hawkbit.eventbus.event.Event;
import org.eclipse.hawkbit.eventbus.event.RolloutChangeEvent;
import org.eclipse.hawkbit.eventbus.event.RolloutGroupChangeEvent;
import org.eclipse.hawkbit.eventbus.event.RolloutGroupCreatedEvent;
import org.eclipse.hawkbit.eventbus.event.RolloutGroupPropertyChangeEvent;
import org.eclipse.hawkbit.eventbus.event.RolloutPropertyChangeEvent;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import com.google.common.eventbus.AllowConcurrentEvents;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
/**
* Collects and merges fine grained events to more generic events and push them
* in a fixed delay on the events bus. This helps for code which are not
* interested in all fine grained events, e.g. UI code. The UI code is not
* interested in handling the flood of events, so collecting the events and
* merge them to one event together and post them in a fixed interval is easier
* to consume e.g. for push notifcations on UI.
*
* @author Michael Hirsch
*
*/
@EventSubscriber
public class EventMerger {
private static final Set<RolloutEventKey> rolloutEvents = ConcurrentHashMap.newKeySet();
private static final Set<RolloutEventKey> rolloutGroupEvents = ConcurrentHashMap.newKeySet();
@Autowired
private EventBus eventBus;
/**
* Checks if there are events to publish in the fixed interval.
*/
@Scheduled(initialDelay = 10000, fixedDelay = 2000)
public void rolloutEventScheduler() {
final Iterator<RolloutEventKey> rolloutIterator = rolloutEvents.iterator();
while (rolloutIterator.hasNext()) {
final RolloutEventKey eventKey = rolloutIterator.next();
eventBus.post(new RolloutChangeEvent(1, eventKey.tenant, eventKey.rolloutId));
rolloutIterator.remove();
}
final Iterator<RolloutEventKey> rolloutGroupIterator = rolloutGroupEvents.iterator();
while (rolloutGroupIterator.hasNext()) {
final RolloutEventKey eventKey = rolloutGroupIterator.next();
eventBus.post(new RolloutGroupChangeEvent(1, eventKey.tenant, eventKey.rolloutId, eventKey.rolloutGroupId));
rolloutGroupIterator.remove();
}
}
/**
* Called by the event bus to retrieve all necessary events to collect and
* merge.
*
* @param event
* the event on the event bus
*/
@Subscribe
@AllowConcurrentEvents
public void onEvent(final Event event) {
Long rolloutId = null;
Long rolloutGroupId = null;
if (event instanceof ActionCreatedEvent) {
rolloutId = getRolloutId(((ActionCreatedEvent) event).getEntity().getRollout());
rolloutGroupId = getRolloutGroupId(((ActionCreatedEvent) event).getEntity().getRolloutGroup());
} else if (event instanceof ActionPropertyChangeEvent) {
rolloutId = getRolloutId(((ActionPropertyChangeEvent) event).getEntity().getRollout());
rolloutGroupId = getRolloutGroupId(((ActionPropertyChangeEvent) event).getEntity().getRolloutGroup());
} else if (event instanceof RolloutPropertyChangeEvent) {
rolloutId = ((RolloutPropertyChangeEvent) event).getEntity().getId();
} else if (event instanceof RolloutGroupCreatedEvent) {
rolloutId = ((RolloutGroupCreatedEvent) event).getRolloutId();
rolloutGroupId = ((RolloutGroupCreatedEvent) event).getRolloutGroupId();
} else if (event instanceof RolloutGroupPropertyChangeEvent) {
final RolloutGroup rolloutGroup = ((RolloutGroupPropertyChangeEvent) event).getEntity();
rolloutId = rolloutGroup.getRollout().getId();
rolloutGroupId = rolloutGroup.getId();
}
if (rolloutId != null) {
rolloutEvents.add(new RolloutEventKey(rolloutId, event.getTenant()));
if (rolloutGroupId != null) {
rolloutGroupEvents.add(new RolloutEventKey(rolloutId, rolloutGroupId, event.getTenant()));
}
}
}
private Long getRolloutGroupId(final RolloutGroup rolloutGroup) {
if (rolloutGroup != null) {
return rolloutGroup.getId();
}
return null;
}
private Long getRolloutId(final Rollout rollout) {
if (rollout != null) {
return rollout.getId();
}
return null;
}
/**
* The rollout key in the concurrent set to be hold.
*
* @author Michael Hirsch
*
*/
private static final class RolloutEventKey {
private final Long rolloutId;
private final String tenant;
private final Long rolloutGroupId;
private RolloutEventKey(final Long rolloutId, final Long rolloutGroupId, final String tenant) {
this.rolloutGroupId = rolloutGroupId;
this.rolloutId = rolloutId;
this.tenant = tenant;
}
private RolloutEventKey(final Long rolloutId, final String tenant) {
this(rolloutId, null, tenant);
}
@Override
public int hashCode() {// NOSONAR - as this is generated
final int prime = 31;
int result = 1;
result = prime * result + ((rolloutGroupId == null) ? 0 : rolloutGroupId.hashCode());
result = prime * result + ((rolloutId == null) ? 0 : rolloutId.hashCode());
result = prime * result + ((tenant == null) ? 0 : tenant.hashCode());
return result;
}
@Override
public boolean equals(final Object obj) {// NOSONAR - as this is
// generated
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final RolloutEventKey other = (RolloutEventKey) obj;
if (rolloutGroupId == null) {
if (other.rolloutGroupId != null) {
return false;
}
} else if (!rolloutGroupId.equals(other.rolloutGroupId)) {
return false;
}
if (rolloutId == null) {
if (other.rolloutId != null) {
return false;
}
} else if (!rolloutId.equals(other.rolloutId)) {
return false;
}
if (tenant == null) {
if (other.tenant != null) {
return false;
}
} else if (!tenant.equals(other.tenant)) {
return false;
}
return true;
}
}
}

View File

@@ -0,0 +1,72 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.executor;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
import org.springframework.transaction.support.TransactionSynchronizationManager;
/**
*
* A Service which calls register runnable. This runnables will executed after a
* successful spring transaction commit.The class is thread safe.
*/
@Service
public class AfterTransactionCommitDefaultServiceExecutor extends TransactionSynchronizationAdapter
implements AfterTransactionCommitExecutor {
private static final Logger LOGGER = LoggerFactory.getLogger(AfterTransactionCommitDefaultServiceExecutor.class);
private static final ThreadLocal<List<Runnable>> THREAD_LOCAL_RUNNABLES = new ThreadLocal<>();
@Override
// Exception squid:S1217 - Is aspectJ proxy
@SuppressWarnings({ "squid:S1217" })
public void afterCommit() {
final List<Runnable> afterCommitRunnables = THREAD_LOCAL_RUNNABLES.get();
LOGGER.debug("Transaction successfully committed, executing {} runnables", afterCommitRunnables.size());
for (final Runnable afterCommitRunnable : afterCommitRunnables) {
LOGGER.debug("Executing runnable {}", afterCommitRunnable);
try {
afterCommitRunnable.run();
} catch (final RuntimeException e) {
LOGGER.error("Failed to execute runnable " + afterCommitRunnable, e);
}
}
}
@Override
public void afterCommit(final Runnable runnable) {
LOGGER.debug("Submitting new runnable {} to run after transaction commit", runnable);
if (TransactionSynchronizationManager.isSynchronizationActive()) {
List<Runnable> localRunnables = THREAD_LOCAL_RUNNABLES.get();
if (localRunnables == null) {
localRunnables = new ArrayList<>();
THREAD_LOCAL_RUNNABLES.set(localRunnables);
TransactionSynchronizationManager.registerSynchronization(this);
}
localRunnables.add(runnable);
return;
}
LOGGER.info("Transaction synchronization is NOT ACTIVE/ INACTIVE. Executing right now runnable {}", runnable);
runnable.run();
}
@Override
@SuppressWarnings({ "squid:S1217" })
public void afterCompletion(final int status) {
final String transactionStatus = status == STATUS_COMMITTED ? "COMMITTED" : "ROLLEDBACK";
LOGGER.debug("Transaction completed after commit with status {}", transactionStatus);
THREAD_LOCAL_RUNNABLES.remove();
}
}

View File

@@ -0,0 +1,28 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.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

@@ -0,0 +1,396 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa;
import java.util.Collection;
import java.util.List;
import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
import org.eclipse.hawkbit.repository.jpa.model.JpaRollout;
import org.eclipse.hawkbit.repository.jpa.model.JpaRolloutGroup;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TotalTargetCountActionStatus;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.EntityGraph.EntityGraphType;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
/**
* {@link Action} repository.
*
*/
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>, JpaSpecificationExecutor<JpaAction> {
/**
* Retrieves an Action with all lazy attributes.
*
* @param actionId
* the ID of the action
* @return the found {@link Action}
*/
@EntityGraph(value = "Action.all", type = EntityGraphType.LOAD)
JpaAction findById(Long actionId);
/**
* Retrieves all {@link Action}s which are referring the given
* {@link DistributionSet}.
*
* @param pageable
* page parameters
* @param ds
* the {@link DistributionSet} on which will be filtered
* @return the found {@link Action}s
*/
Page<Action> findByDistributionSet(final Pageable pageable, final JpaDistributionSet ds);
/**
* Retrieves all {@link Action}s which are referring the given
* {@link Target}.
*
* @param pageable
* page parameters
* @param target
* the target to find assigned actions
* @return the found {@link Action}s
*/
Slice<Action> findByTarget(Pageable pageable, JpaTarget target);
/**
* Retrieves all {@link Action}s which are active and referring the given
* {@link Target} in a specified order. Loads also the lazy
* {@link Action#getDistributionSet()} field.
*
* @param pageable
* page parameters
* @param target
* the target to find assigned actions
* @param active
* the action active flag
* @return the found {@link Action}s
*/
@EntityGraph(value = "Action.ds", type = EntityGraphType.LOAD)
List<Action> findByTargetAndActiveOrderByIdAsc(final JpaTarget target, boolean active);
/**
* Retrieves latest {@link UpdateAction} for given target and
* {@link SoftwareModule}.
*
* @param targetId
* to search for
* @param module
* to search for
* @return action if there is one with assigned target and module is part of
* assigned {@link DistributionSet}.
*/
@Query("Select a from JpaAction a join a.distributionSet ds join ds.modules modul where a.target.controllerId = :target and modul = :module order by a.id desc")
List<Action> findActionByTargetAndSoftwareModule(@Param("target") final String targetId,
@Param("module") JpaSoftwareModule module);
/**
* Retrieves all {@link UpdateAction}s which are referring the given
* {@link DistributionSet} and {@link Target}.
*
* @param pageable
* page parameters
* @param target
* is the assigned target
* @param ds
* the {@link DistributionSet} on which will be filtered
* @return the found {@link UpdateAction}s
*/
@Query("Select a from JpaAction a where a.target = :target and a.distributionSet = :ds order by a.id")
Page<Action> findByTargetAndDistributionSet(final Pageable pageable, @Param("target") final JpaTarget target,
@Param("ds") JpaDistributionSet ds);
/**
* Retrieves all {@link Action}s of a specific target, without pagination
* ordered by action ID.
*
* @param target
* to search for
* @return a list of actions according to the searched target
*/
@Query("Select a from JpaAction a where a.target = :target order by a.id")
List<Action> findByTarget(@Param("target") final JpaTarget target);
/**
* Retrieves all {@link Action}s of a specific target and given active flag
* ordered by action ID.
*
* @param pageable
* the pagination parameter
* @param target
* to search for
* @param active
* {@code true} for all actions which are currently active,
* {@code false} for inactive
* @return a paged list of actions ordered by action ID
*/
@Query("Select a from JpaAction a where a.target = :target and a.active= :active order by a.id")
Page<Action> findByActiveAndTarget(Pageable pageable, @Param("target") JpaTarget target,
@Param("active") boolean active);
/**
* Retrieves all {@link Action}s of a specific target and given active flag
* ordered by action ID. Loads also the lazy
* {@link Action#getDistributionSet()} field.
*
* @param target
* to search for
* @param active
* {@code true} for all actions which are currently active,
* {@code false} for inactive
* @return a list of actions ordered by action ID
*/
@EntityGraph(value = "Action.ds", type = EntityGraphType.LOAD)
@Query("Select a from JpaAction a where a.target = :target and a.active= :active order by a.id")
List<Action> findByActiveAndTarget(@Param("target") JpaTarget target, @Param("active") boolean active);
/**
* Updates all {@link Action} to inactive for all targets with given ID.
*
* @param keySet
* the list of actions to set inactive
* @param targetsIds
* the IDs of the targets according to the action to set in
* active
*/
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Query("UPDATE JpaAction a SET a.active = false WHERE a IN :keySet AND a.target IN :targetsIds")
void setToInactive(@Param("keySet") List<JpaAction> keySet, @Param("targetsIds") List<Long> targetsIds);
/**
* Switches the status of actions from one specific status into another,
* only if the actions are in a specific status. This should be a atomar
* operation.
*
* @param statusToSet
* the new status the actions should get
* @param targetIds
* the IDs of the targets of the actions which are affected
* @param active
* the active flag of the actions which should be affected
* @param currentStatus
* the current status of the actions which are affected
*/
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Query("UPDATE JpaAction a SET a.status = :statusToSet WHERE a.target IN :targetsIds AND a.active = :active AND a.status = :currentStatus AND a.distributionSet.requiredMigrationStep = false")
void switchStatus(@Param("statusToSet") Action.Status statusToSet, @Param("targetsIds") List<Long> targetIds,
@Param("active") boolean active, @Param("currentStatus") Action.Status currentStatus);
/**
* Switches the status of actions from one specific status into another,
* only if the actions are in a specific status. This should be a atomar
* operation.
*
* @param statusToSet
* the new status the actions should get
* @param rollout
* the rollout of the actions which are affected
* @param active
* the active flag of the actions which should be affected
* @param currentStatus
* the current status of the actions which are affected
*/
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Query("UPDATE JpaAction a SET a.status = :statusToSet WHERE a.rollout = :rollout AND a.active = :active AND a.status = :currentStatus")
void switchStatus(@Param("statusToSet") Action.Status statusToSet, @Param("rollout") JpaRollout rollout,
@Param("active") boolean active, @Param("currentStatus") Action.Status currentStatus);
/**
*
* Retrieves all {@link Action}s which are active and referring to the given
* target Ids and distribution set required migration step.
*
* @param targetIds
* the IDs of targets for the actions
* @param notStatus
* the status which the actions should not have
* @return the found list of {@link Action}s
*/
@Query("SELECT a FROM JpaAction a WHERE a.active = true AND a.distributionSet.requiredMigrationStep = false AND a.target IN ?1 AND a.status != ?2")
List<JpaAction> findByActiveAndTargetIdInAndActionStatusNotEqualToAndDistributionSetRequiredMigrationStep(
Collection<Long> targetIds, Action.Status notStatus);
/**
* Counts all {@link Action}s referring to the given target.
*
* @param target
* the target to count the {@link Action}s
* @return the count of actions referring to the given target
*/
Long countByTarget(JpaTarget target);
@Override
@CacheEvict(value = "feedbackReceivedOverTime", allEntries = true)
<S extends JpaAction> List<S> save(Iterable<S> entities);
@Override
@CacheEvict(value = "feedbackReceivedOverTime", allEntries = true)
<S extends JpaAction> S save(S entity);
/**
* Counts all {@link Action}s referring to the given DistributionSet.
*
* @param distributionSet
* DistributionSet to count the {@link Action}s from
* @return the count of actions referring to the given distributionSet
*/
Long countByDistributionSet(JpaDistributionSet distributionSet);
/**
* Counts all {@link Action}s referring to the given rollout.
*
* @param rollout
* the rollout to count the {@link Action}s from
* @return the count of actions referring to the given rollout
*/
Long countByRollout(JpaRollout rollout);
/**
* Counts all actions referring to a given rollout and rolloutgroup which
* are currently not in the given status. An in-clause statement does not
* work with the spring-data, so this is specific usecase regarding to the
* rollout-management to find out actions which are not in specific states.
*
* @param rollout
* the rollout the actions are belong to
* @param rolloutGroup
* the rolloutgroup the actions are belong to
* @param notStatus1
* the status the action should not have
* @param notStatus2
* the status the action should not have
* @param notStatus3
* the status the action should not have
* @return the count of actions referring the rollout and rolloutgroup and
* are not in given states
*/
Long countByRolloutAndRolloutGroupAndStatusNotAndStatusNotAndStatusNot(JpaRollout rollout,
JpaRolloutGroup rolloutGroup, Status notStatus1, Status notStatus2, Status notStatus3);
/**
* Counts all actions referring to a given rollout and rolloutgroup.
*
* @param rollout
* the rollout the actions belong to
* @param rolloutGroup
* the rolloutgroup the actions belong to
* @return the count of actions referring to a rollout and rolloutgroup
*/
Long countByRolloutAndRolloutGroup(JpaRollout rollout, JpaRolloutGroup rolloutGroup);
/**
* Counts all actions referring to a given rollout, rolloutgroup and status.
*
* @param rolloutId
* the ID of rollout the actions belong to
* @param rolloutGroupId
* the ID rolloutgroup the actions belong to
* @param status
* the status the actions should have
* @return the count of actions referring to a rollout, rolloutgroup and are
* in a given status
*/
Long countByRolloutIdAndRolloutGroupIdAndStatus(Long rolloutId, Long rolloutGroupId, Action.Status status);
/**
* Retrieving all actions referring to a given rollout with a specific
* action as parent reference and a specific status.
*
* Finding all actions of a specific rolloutgroup parent relation.
*
* @param rollout
* the rollout the actions belong to
* @param rolloutGroupParent
* the parent rolloutgroup the actions should reference
* @param actionStatus
* the status the actions have
* @return the actions referring a specific rollout and a specific parent
* rolloutgroup in a specific status
*/
List<Action> findByRolloutAndRolloutGroupParentAndStatus(JpaRollout rollout, JpaRolloutGroup rolloutGroupParent,
Status actionStatus);
/**
* Retrieves all actions for a specific rollout and in a specific status.
*
* @param rollout
* the rollout the actions beglong to
* @param actionStatus
* the status of the actions
* @return the actions referring a specific rollout an in a specific status
*/
List<Action> findByRolloutAndStatus(JpaRollout rollout, Status actionStatus);
/**
* Get list of objects which has details of status and count of targets in
* each status in specified rollout.
*
* @param rolloutId
* id of {@link Rollout}
* @return list of objects with status and target count
*/
@Query("SELECT NEW org.eclipse.hawkbit.repository.model.TotalTargetCountActionStatus( a.rollout.id, a.status , COUNT(a.target)) FROM JpaAction a WHERE a.rollout.id IN ?1 GROUP BY a.rollout.id,a.status")
List<TotalTargetCountActionStatus> getStatusCountByRolloutId(List<Long> rolloutId);
/**
* Get list of objects which has details of status and count of targets in
* each status in specified rollout.
*
* @param rolloutId
* id of {@link Rollout}
* @return list of objects with status and target count
*/
@Query("SELECT NEW org.eclipse.hawkbit.repository.model.TotalTargetCountActionStatus( a.rollout.id, a.status , COUNT(a.target)) FROM JpaAction a WHERE a.rollout.id = ?1 GROUP BY a.rollout.id,a.status")
List<TotalTargetCountActionStatus> getStatusCountByRolloutId(Long rolloutId);
/**
* Get list of objects which has details of status and count of targets in
* each status in specified rollout group.
*
* @param rolloutGroupId
* id of {@link RolloutGroup}
* @return list of objects with status and target count
*/
@Query("SELECT NEW org.eclipse.hawkbit.repository.model.TotalTargetCountActionStatus(a.rolloutGroup.id, a.status , COUNT(a.target)) FROM JpaAction a WHERE a.rolloutGroup.id = ?1 GROUP BY a.rolloutGroup.id, a.status")
List<TotalTargetCountActionStatus> getStatusCountByRolloutGroupId(Long rolloutGroupId);
/**
* Get list of objects which has details of status and count of targets in
* each status in specified rollout group.
*
* @param rolloutGroupId
* list of id of {@link RolloutGroup}
* @return list of objects with status and target count
*/
@Query("SELECT NEW org.eclipse.hawkbit.repository.model.TotalTargetCountActionStatus(a.rolloutGroup.id, a.status , COUNT(a.target)) FROM JpaAction a WHERE a.rolloutGroup.id IN ?1 GROUP BY a.rolloutGroup.id, a.status")
List<TotalTargetCountActionStatus> getStatusCountByRolloutGroupId(List<Long> rolloutGroupId);
}

View File

@@ -0,0 +1,81 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa;
import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
import org.eclipse.hawkbit.repository.jpa.model.JpaActionStatus;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.ActionStatus;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.EntityGraph.EntityGraphType;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
/**
* {@link ActionStatus} repository.
*
*/
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
public interface ActionStatusRepository
extends BaseEntityRepository<JpaActionStatus, Long>, JpaSpecificationExecutor<JpaActionStatus> {
/**
* Counts {@link ActionStatus} entries of given {@link Action} in
* repository.
*
* @param action
* to count status entries
* @return number of actions in repository
*/
Long countByAction(JpaAction action);
/**
* Counts {@link ActionStatus} entries of given {@link Action} with given
* {@link Status} in repository.
*
* @param action
* to count status entries
* @param status
* to filter for
* @return number of actions in repository
*/
Long countByActionAndStatus(JpaAction action, Status status);
/**
* Retrieves all {@link ActionStatus} entries from repository of given
* {@link Action}.
*
* @param pageReq
* parameters
* @param action
* of the status entries
* @return pages list of {@link ActionStatus} entries
*/
Page<ActionStatus> findByAction(Pageable pageReq, JpaAction action);
/**
* Finds all status updates for the defined action and target including
* {@link ActionStatus#getMessages()}.
*
* @param pageReq
* for page configuration
* @param target
* to look for
* @param action
* to look for
* @return Page with found targets
*/
@EntityGraph(value = "ActionStatus.withMessages", type = EntityGraphType.LOAD)
Page<ActionStatus> getByAction(Pageable pageReq, JpaAction action);
}

View File

@@ -0,0 +1,44 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa;
import java.io.Serializable;
import org.eclipse.hawkbit.repository.jpa.model.AbstractJpaTenantAwareBaseEntity;
import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
/**
* Command repository operations for all {@link TenantAwareBaseEntity}s.
*
* @param <T>
* type if the entity type
* @param <I>
* of the entity type
*/
@NoRepositoryBean
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
public interface BaseEntityRepository<T extends AbstractJpaTenantAwareBaseEntity, I extends Serializable>
extends PagingAndSortingRepository<T, I> {
/**
* Deletes all {@link TenantAwareBaseEntity} of a given tenant.
*
* @param tenant
* to delete data from
*/
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
void deleteByTenantIgnoreCase(String tenant);
}

View File

@@ -0,0 +1,102 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa;
import java.util.List;
import java.util.stream.Collectors;
import javax.persistence.EntityManager;
import javax.validation.constraints.NotNull;
import org.eclipse.hawkbit.repository.TargetManagement;
import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetInfo;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
/**
* Utility class for deployment related topics.
*
*/
public final class DeploymentHelper {
private DeploymentHelper() {
// utility class
}
/**
* Internal helper method used only inside service level. As a result is no
* additional security necessary.
*
* @param target
* to update
* @param status
* of the target
* @param setInstalledDate
* to set
* @param entityManager
* for the operation
* @param targetInfoRepository
* for the operation
*
* @return updated target
*/
static JpaTarget updateTargetInfo(@NotNull final JpaTarget target, @NotNull final TargetUpdateStatus status,
final boolean setInstalledDate, final TargetInfoRepository targetInfoRepository,
final EntityManager entityManager) {
final JpaTargetInfo ts = (JpaTargetInfo) target.getTargetInfo();
ts.setUpdateStatus(status);
if (setInstalledDate) {
ts.setInstallationDate(System.currentTimeMillis());
}
targetInfoRepository.save(ts);
return entityManager.merge(target);
}
/**
* This method is called, when cancellation has been successful. It sets the
* action to canceled, resets the meta data of the target and in case there
* is a new action this action is triggered.
*
* @param action
* the action which is set to canceled
* @param actionRepository
* for the operation
* @param targetManagement
* for the operation
* @param entityManager
* for the operation
* @param targetInfoRepository
* for the operation
*/
static void successCancellation(final JpaAction action, final ActionRepository actionRepository,
final TargetManagement targetManagement, final TargetInfoRepository targetInfoRepository,
final EntityManager entityManager) {
// set action inactive
action.setActive(false);
action.setStatus(Status.CANCELED);
final JpaTarget target = (JpaTarget) action.getTarget();
final List<Action> nextActiveActions = actionRepository.findByTargetAndActiveOrderByIdAsc(target, true).stream()
.filter(a -> !a.getId().equals(action.getId())).collect(Collectors.toList());
if (nextActiveActions.isEmpty()) {
target.setAssignedDistributionSet(target.getTargetInfo().getInstalledDistributionSet());
updateTargetInfo(target, TargetUpdateStatus.IN_SYNC, false, targetInfoRepository, entityManager);
} else {
target.setAssignedDistributionSet(nextActiveActions.get(0).getDistributionSet());
}
targetManagement.updateTarget(target);
}
}

View File

@@ -0,0 +1,27 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa;
import org.eclipse.hawkbit.repository.jpa.model.DsMetadataCompositeKey;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetMetadata;
import org.eclipse.hawkbit.repository.model.DistributionSetMetadata;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
/**
* {@link DistributionSetMetadata} repository.
*/
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
public interface DistributionSetMetadataRepository
extends PagingAndSortingRepository<JpaDistributionSetMetadata, DsMetadataCompositeKey>,
JpaSpecificationExecutor<JpaDistributionSetMetadata> {
}

View File

@@ -0,0 +1,139 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa;
import java.util.Collection;
import java.util.List;
import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetTag;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetType;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.Tag;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
/**
* {@link DistributionSet} repository.
*
*/
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
public interface DistributionSetRepository
extends BaseEntityRepository<JpaDistributionSet, Long>, JpaSpecificationExecutor<JpaDistributionSet> {
/**
* Finds {@link DistributionSet}s by assigned {@link Tag}.
*
* @param tag
* to be found
* @return list of found {@link DistributionSet}s
*/
@Query(value = "Select Distinct ds from JpaDistributionSet ds join ds.tags dst where dst = :tag")
List<JpaDistributionSet> findByTag(@Param("tag") final JpaDistributionSetTag tag);
/**
* deletes the {@link DistributionSet}s with the given IDs.
*
* @param ids
* to be deleted
*/
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Query("update JpaDistributionSet d set d.deleted = 1 where d.id in :ids")
void deleteDistributionSet(@Param("ids") Long... ids);
@Override
List<JpaDistributionSet> findAll(Iterable<Long> ids);
/**
* deletes {@link DistributionSet}s by the given IDs.
*
* @param ids
* List of IDs of {@link DistributionSet}s to be deleted
* @return number of affected/deleted records
*/
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
// Workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=349477
@Query("DELETE FROM JpaDistributionSet d WHERE d.id IN ?1")
int deleteByIdIn(Collection<Long> ids);
/**
* Finds {@link DistributionSet}s where given {@link SoftwareModule} is
* assigned.
*
* @param module
* to search for
* @return {@link List} of found {@link DistributionSet}s
*/
List<DistributionSet> findByModules(JpaSoftwareModule module);
/**
* Finds {@link DistributionSet}s based on given ID if they are not assigned
* yet to an {@link UpdateAction}, i.e. unused.
*
* @param ids
* to search for
* @return
*/
@Query("select ac.distributionSet.id from JpaAction ac where ac.distributionSet.id in :ids")
List<Long> findAssignedDistributionSetsById(@Param("ids") Long... ids);
/**
* Saves all given {@link DistributionSet}s.
*
* @param entities
* @return the saved entities
* @throws IllegalArgumentException
* in case the given entity is (@literal null}.
*
* @see org.springframework.data.repository.CrudRepository#save(java.lang.Iterable)
*/
@Override
<S extends JpaDistributionSet> List<S> save(Iterable<S> entities);
/**
* Finds the distribution set for a specific action.
*
* @param action
* the action associated with the distribution set to find
* @return the distribution set associated with the given action
*/
@Query("select DISTINCT d from JpaDistributionSet d join fetch d.modules m join d.actions a where a = :action")
JpaDistributionSet findByAction(@Param("action") JpaAction action);
/**
* Counts {@link DistributionSet} instances of given type in the repository.
*
* @param type
* to search for
* @return number of found {@link DistributionSet}s
*/
long countByType(JpaDistributionSetType type);
/**
* Counts {@link DistributionSet} with given
* {@link DistributionSet#getName()} and
* {@link DistributionSet#getVersion()}.
*
* @param name
* to search for
* @param version
* to search for
* @return number of found {@link DistributionSet}s
*/
long countByNameAndVersion(String name, String version);
}

View File

@@ -0,0 +1,59 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa;
import java.util.List;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetTag;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetTag;
import org.eclipse.hawkbit.repository.model.TargetTag;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
/**
* {@link TargetTag} repository.
*
*/
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
public interface DistributionSetTagRepository
extends BaseEntityRepository<JpaDistributionSetTag, Long>, JpaSpecificationExecutor<JpaDistributionSetTag> {
/**
* deletes the {@link DistributionSet} with the given name.
*
* @param tagName
* to be deleted
* @return 1 if tag was deleted
*/
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
Long deleteByName(final String tagName);
/**
* find {@link DistributionSetTag} by its name.
*
* @param tagName
* to filter on
* @return the {@link DistributionSetTag} if found, otherwise null
*/
JpaDistributionSetTag findByNameEquals(final String tagName);
/**
* Returns all instances of the type.
*
* @return all entities
*/
@Override
List<JpaDistributionSetTag> findAll();
@Override
<S extends JpaDistributionSetTag> List<S> save(Iterable<S> entities);
}

View File

@@ -0,0 +1,59 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetType;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModuleType;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
/**
* {@link PagingAndSortingRepository} for {@link DistributionSetType}.
*
*/
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
public interface DistributionSetTypeRepository
extends BaseEntityRepository<JpaDistributionSetType, Long>, JpaSpecificationExecutor<JpaDistributionSetType> {
/**
*
* @param pageable
* page parameters
* @param isDeleted
* to <code>true</code> if only soft deleted entries of
* <code>false</code> if undeleted ones
* @return list of found {@link DistributionSetType}s
*/
Page<JpaDistributionSetType> findByDeleted(Pageable pageable, boolean isDeleted);
/**
* @param isDeleted
* to <code>true</code> if only marked as deleted have to be
* count or all undeleted.
* @return number of {@link DistributionSetType}s in the repository.
*/
Long countByDeleted(boolean isDeleted);
/**
* Counts all distribution set type where a specific software module type is
* assigned to.
*
* @param softwareModuleType
* the software module type to count the distribution set type
* which has this software module type assigned
* @return the number of {@link DistributionSetType}s in the repository
* assigned to the given software module type
*/
Long countByElementsSmType(JpaSoftwareModuleType softwareModuleType);
}

View File

@@ -0,0 +1,74 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa;
import java.util.Collection;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetInfo;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
/**
* Custom repository implementation as standard spring repository fails as of
* https://bugs.eclipse.org/bugs/show_bug.cgi?id=415027 .
*
*/
@Service
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
public class EclipseLinkTargetInfoRepository implements TargetInfoRepository {
@Autowired
private EntityManager entityManager;
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public void setTargetUpdateStatus(final TargetUpdateStatus status, final List<Long> targets) {
final Query query = entityManager.createQuery(
"update JpaTargetInfo ti set ti.updateStatus = :status where ti.targetId in :targets and ti.updateStatus != :status");
query.setParameter("targets", targets);
query.setParameter("status", status);
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@CacheEvict(value = { "targetStatus", "distributionUsageInstalled", "targetsLastPoll" }, allEntries = true)
public <S extends JpaTargetInfo> S save(final S entity) {
if (entity.isNew()) {
entityManager.persist(entity);
return entity;
} else {
return entityManager.merge(entity);
}
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@CacheEvict(value = { "targetStatus", "distributionUsageInstalled", "targetsLastPoll" }, allEntries = true)
public void deleteByTargetIdIn(final Collection<Long> targetIDs) {
final javax.persistence.Query query = entityManager
.createQuery("DELETE FROM JpaTargetInfo ti where ti.targetId IN :target");
query.setParameter("target", targetIDs);
}
}

View File

@@ -0,0 +1,23 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa;
import org.eclipse.hawkbit.repository.jpa.model.JpaExternalArtifactProvider;
import org.eclipse.hawkbit.repository.model.ExternalArtifactProvider;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
/**
* Repository for {@link ExternalArtifactProvider}.
*
*/
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
public interface ExternalArtifactProviderRepository extends BaseEntityRepository<JpaExternalArtifactProvider, Long> {
}

View File

@@ -0,0 +1,37 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa;
import org.eclipse.hawkbit.repository.jpa.model.JpaExternalArtifact;
import org.eclipse.hawkbit.repository.model.ExternalArtifact;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
/**
* {@link ExternalArtifact} repository.
*
*/
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
public interface ExternalArtifactRepository extends BaseEntityRepository<JpaExternalArtifact, Long> {
/**
* Searches for external artifact for a base software module.
*
* @param pageReq
* Pageable
* @param swId
* software module id
*
* @return Page<ExternalArtifact>
*/
Page<JpaExternalArtifact> findBySoftwareModuleId(Pageable pageReq, final Long swId);
}

View File

@@ -0,0 +1,291 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa;
import java.io.InputStream;
import java.util.List;
import org.eclipse.hawkbit.artifact.repository.ArtifactRepository;
import org.eclipse.hawkbit.artifact.repository.ArtifactStoreException;
import org.eclipse.hawkbit.artifact.repository.HashNotMatchException;
import org.eclipse.hawkbit.artifact.repository.model.DbArtifact;
import org.eclipse.hawkbit.artifact.repository.model.DbArtifactHash;
import org.eclipse.hawkbit.repository.ArtifactManagement;
import org.eclipse.hawkbit.repository.exception.ArtifactDeleteFailedException;
import org.eclipse.hawkbit.repository.exception.ArtifactUploadFailedException;
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.GridFSDBFileNotFoundException;
import org.eclipse.hawkbit.repository.exception.InvalidMD5HashException;
import org.eclipse.hawkbit.repository.exception.InvalidSHA1HashException;
import org.eclipse.hawkbit.repository.jpa.model.JpaExternalArtifact;
import org.eclipse.hawkbit.repository.jpa.model.JpaExternalArtifactProvider;
import org.eclipse.hawkbit.repository.jpa.model.JpaLocalArtifact;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule;
import org.eclipse.hawkbit.repository.jpa.specifications.SoftwareModuleSpecification;
import org.eclipse.hawkbit.repository.model.Artifact;
import org.eclipse.hawkbit.repository.model.ExternalArtifact;
import org.eclipse.hawkbit.repository.model.ExternalArtifactProvider;
import org.eclipse.hawkbit.repository.model.LocalArtifact;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
/**
* JPA based {@link ArtifactManagement} implementation.
*
*/
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
@Validated
@Service
public class JpaArtifactManagement implements ArtifactManagement {
private static final Logger LOG = LoggerFactory.getLogger(JpaArtifactManagement.class);
@Autowired
private LocalArtifactRepository localArtifactRepository;
@Autowired
private ExternalArtifactRepository externalArtifactRepository;
@Autowired
private SoftwareModuleRepository softwareModuleRepository;
@Autowired
private ExternalArtifactProviderRepository externalArtifactProviderRepository;
@Autowired
private ArtifactRepository artifactRepository;
private static LocalArtifact checkForExistingArtifact(final String filename, final boolean overrideExisting,
final SoftwareModule softwareModule) {
if (softwareModule.getLocalArtifactByFilename(filename).isPresent()) {
if (overrideExisting) {
LOG.debug("overriding existing artifact with new filename {}", filename);
return softwareModule.getLocalArtifactByFilename(filename).get();
} else {
throw new EntityAlreadyExistsException("File with that name already exists in the Software Module");
}
}
return null;
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public ExternalArtifact createExternalArtifact(final ExternalArtifactProvider externalRepository,
final String urlSuffix, final Long moduleId) {
final SoftwareModule module = getModuleAndThrowExceptionIfThatFails(moduleId);
return externalArtifactRepository.save(new JpaExternalArtifact(externalRepository, urlSuffix, module));
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public ExternalArtifactProvider createExternalArtifactProvider(final String name, final String description,
final String basePath, final String defaultUrlSuffix) {
return externalArtifactProviderRepository
.save(new JpaExternalArtifactProvider(name, description, basePath, defaultUrlSuffix));
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public LocalArtifact createLocalArtifact(final InputStream stream, final Long moduleId, final String filename,
final String providedMd5Sum, final String providedSha1Sum, final boolean overrideExisting,
final String contentType) {
DbArtifact result = null;
final SoftwareModule softwareModule = getModuleAndThrowExceptionIfThatFails(moduleId);
final LocalArtifact existing = checkForExistingArtifact(filename, overrideExisting, softwareModule);
try {
result = artifactRepository.store(stream, filename, contentType,
new DbArtifactHash(providedSha1Sum, providedMd5Sum));
} catch (final ArtifactStoreException e) {
throw new ArtifactUploadFailedException(e);
} catch (final HashNotMatchException e) {
if (e.getHashFunction().equals(HashNotMatchException.SHA1)) {
throw new InvalidSHA1HashException(e.getMessage(), e);
} else {
throw new InvalidMD5HashException(e.getMessage(), e);
}
}
if (result == null) {
return null;
}
return storeArtifactMetadata(softwareModule, filename, result, existing);
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public void deleteExternalArtifact(final Long id) {
final ExternalArtifact existing = externalArtifactRepository.findOne(id);
if (null == existing) {
return;
}
existing.getSoftwareModule().removeArtifact(existing);
softwareModuleRepository.save((JpaSoftwareModule) existing.getSoftwareModule());
externalArtifactRepository.delete(id);
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public void deleteLocalArtifact(final LocalArtifact existing) {
if (existing == null) {
return;
}
boolean artifactIsOnlyUsedByOneSoftwareModule = true;
for (final LocalArtifact lArtifact : localArtifactRepository
.findByGridFsFileName(((JpaLocalArtifact) existing).getGridFsFileName())) {
if (!lArtifact.getSoftwareModule().isDeleted()
&& Long.compare(lArtifact.getSoftwareModule().getId(), existing.getSoftwareModule().getId()) != 0) {
artifactIsOnlyUsedByOneSoftwareModule = false;
break;
}
}
if (artifactIsOnlyUsedByOneSoftwareModule) {
try {
LOG.debug("deleting artifact from repository {}", ((JpaLocalArtifact) existing).getGridFsFileName());
artifactRepository.deleteBySha1(((JpaLocalArtifact) existing).getGridFsFileName());
} catch (final ArtifactStoreException e) {
throw new ArtifactDeleteFailedException(e);
}
}
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public void deleteLocalArtifact(final Long id) {
final JpaLocalArtifact existing = localArtifactRepository.findOne(id);
if (null == existing) {
return;
}
deleteLocalArtifact(existing);
existing.getSoftwareModule().removeArtifact(existing);
softwareModuleRepository.save((JpaSoftwareModule) existing.getSoftwareModule());
localArtifactRepository.delete(id);
}
@Override
public Artifact findArtifact(final Long id) {
return localArtifactRepository.findOne(id);
}
@Override
public List<LocalArtifact> findByFilenameAndSoftwareModule(final String filename, final Long softwareModuleId) {
return localArtifactRepository.findByFilenameAndSoftwareModuleId(filename, softwareModuleId);
}
@Override
public LocalArtifact findFirstLocalArtifactsBySHA1(final String sha1) {
return localArtifactRepository.findFirstByGridFsFileName(sha1);
}
@Override
public List<LocalArtifact> findLocalArtifactByFilename(final String filename) {
return localArtifactRepository.findByFilename(filename);
}
@Override
public Page<LocalArtifact> findLocalArtifactBySoftwareModule(final Pageable pageReq, final Long swId) {
return localArtifactRepository.findBySoftwareModuleId(pageReq, swId);
}
@Override
public SoftwareModule findSoftwareModuleById(final Long id) {
final Specification<JpaSoftwareModule> spec = SoftwareModuleSpecification.byId(id);
return softwareModuleRepository.findOne(spec);
}
@Override
public SoftwareModule findSoftwareModuleWithDetails(final Long id) {
final SoftwareModule result = findSoftwareModuleById(id);
if (result != null) {
result.getArtifacts().size();
}
return result;
}
private SoftwareModule getModuleAndThrowExceptionIfThatFails(final Long moduleId) {
final SoftwareModule softwareModule = findSoftwareModuleWithDetails(moduleId);
if (softwareModule == null) {
LOG.debug("no software module with ID {} exists", moduleId);
throw new EntityNotFoundException("Software Module: " + moduleId);
}
return softwareModule;
}
@Override
public DbArtifact loadLocalArtifactBinary(final LocalArtifact artifact) {
final DbArtifact result = artifactRepository
.getArtifactBySha1(((JpaLocalArtifact) artifact).getGridFsFileName());
if (result == null) {
throw new GridFSDBFileNotFoundException(((JpaLocalArtifact) artifact).getGridFsFileName());
}
return result;
}
private LocalArtifact storeArtifactMetadata(final SoftwareModule softwareModule, final String providedFilename,
final DbArtifact result, final LocalArtifact existing) {
JpaLocalArtifact artifact = (JpaLocalArtifact) existing;
if (existing == null) {
artifact = new JpaLocalArtifact(result.getHashes().getSha1(), providedFilename, softwareModule);
}
artifact.setMd5Hash(result.getHashes().getMd5());
artifact.setSha1Hash(result.getHashes().getSha1());
artifact.setSize(result.getSize());
LOG.debug("storing new artifact into repository {}", artifact);
return localArtifactRepository.save(artifact);
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public LocalArtifact createLocalArtifact(final InputStream inputStream, final Long moduleId, final String filename,
final boolean overrideExisting) {
return createLocalArtifact(inputStream, moduleId, filename, null, null, overrideExisting, null);
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public LocalArtifact createLocalArtifact(final InputStream inputStream, final Long moduleId, final String filename,
final boolean overrideExisting, final String contentType) {
return createLocalArtifact(inputStream, moduleId, filename, null, null, overrideExisting, contentType);
}
}

View File

@@ -0,0 +1,465 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import javax.persistence.EntityManager;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
import javax.validation.constraints.NotNull;
import org.eclipse.hawkbit.repository.ControllerManagement;
import org.eclipse.hawkbit.repository.TargetManagement;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.ToManyAttributeEntriesException;
import org.eclipse.hawkbit.repository.exception.ToManyStatusEntriesException;
import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
import org.eclipse.hawkbit.repository.jpa.model.JpaActionStatus;
import org.eclipse.hawkbit.repository.jpa.model.JpaActionStatus_;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetInfo;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget_;
import org.eclipse.hawkbit.repository.jpa.specifications.ActionSpecifications;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.ActionStatus;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.LocalArtifact;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetInfo;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
import org.eclipse.hawkbit.repository.model.TenantConfiguration;
import org.eclipse.hawkbit.security.HawkbitSecurityProperties;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
/**
* JPA based {@link ControllerManagement} implementation.
*
*/
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
@Validated
@Service
public class JpaControllerManagement implements ControllerManagement {
private static final Logger LOG = LoggerFactory.getLogger(ControllerManagement.class);
private static final Logger LOG_DOS = LoggerFactory.getLogger("server-security.dos");
@Autowired
private EntityManager entityManager;
@Autowired
private ActionRepository actionRepository;
@Autowired
private TargetRepository targetRepository;
@Autowired
private TargetManagement targetManagement;
@Autowired
private TargetInfoRepository targetInfoRepository;
@Autowired
private SoftwareModuleRepository softwareModuleRepository;
@Autowired
private ActionStatusRepository actionStatusRepository;
@Autowired
private HawkbitSecurityProperties securityProperties;
@Autowired
private TenantConfigurationRepository tenantConfigurationRepository;
@Autowired
private TenantConfigurationManagement tenantConfigurationManagement;
@Override
public String getPollingTime() {
final TenantConfigurationKey configurationKey = TenantConfigurationKey.POLLING_TIME_INTERVAL;
final Class<String> propertyType = String.class;
JpaTenantConfigurationManagement.validateTenantConfigurationDataType(configurationKey, propertyType);
final TenantConfiguration tenantConfiguration = tenantConfigurationRepository
.findByKey(configurationKey.getKeyName());
return tenantConfigurationManagement
.buildTenantConfigurationValueByKey(configurationKey, propertyType, tenantConfiguration).getValue();
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public Target updateLastTargetQuery(final String controllerId, final URI address) {
final Target target = targetRepository.findByControllerId(controllerId);
if (target == null) {
throw new EntityNotFoundException(controllerId);
}
return updateLastTargetQuery(target.getTargetInfo(), address).getTarget();
}
@Override
public Action getActionForDownloadByTargetAndSoftwareModule(final String controllerId,
final SoftwareModule module) {
final List<Action> action = actionRepository.findActionByTargetAndSoftwareModule(controllerId,
(JpaSoftwareModule) module);
if (action.isEmpty() || action.get(0).isCancelingOrCanceled()) {
throw new EntityNotFoundException(
"No assigment found for module " + module.getId() + " to target " + controllerId);
}
return action.get(0);
}
@Override
public boolean hasTargetArtifactAssigned(final String controllerId, final LocalArtifact localArtifact) {
final Target target = targetRepository.findByControllerId(controllerId);
if (target == null) {
return false;
}
return actionRepository.count(ActionSpecifications.hasTargetAssignedArtifact(target, localArtifact)) > 0;
}
@Override
public List<Action> findActionByTargetAndActive(final Target target) {
return actionRepository.findByTargetAndActiveOrderByIdAsc((JpaTarget) target, true);
}
@Override
public List<SoftwareModule> findSoftwareModulesByDistributionSet(final DistributionSet distributionSet) {
return new ArrayList<>(softwareModuleRepository.findByAssignedTo((JpaDistributionSet) distributionSet));
}
@Override
public Action findActionWithDetails(final Long actionId) {
return actionRepository.findById(actionId);
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public Target findOrRegisterTargetIfItDoesNotexist(final String controllerId, final URI address) {
final Specification<JpaTarget> spec = (targetRoot, query, cb) -> cb
.equal(targetRoot.get(JpaTarget_.controllerId), controllerId);
JpaTarget target = targetRepository.findOne(spec);
if (target == null) {
target = new JpaTarget(controllerId);
target.setDescription("Plug and Play target: " + controllerId);
target.setName(controllerId);
return targetManagement.createTarget(target, TargetUpdateStatus.REGISTERED, System.currentTimeMillis(),
address);
}
return updateLastTargetQuery(target.getTargetInfo(), address).getTarget();
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public TargetInfo updateTargetStatus(final TargetInfo targetInfo, final TargetUpdateStatus status,
final Long lastTargetQuery, final URI address) {
final JpaTargetInfo mtargetInfo = (JpaTargetInfo) entityManager.merge(targetInfo);
if (status != null) {
mtargetInfo.setUpdateStatus(status);
}
if (lastTargetQuery != null) {
mtargetInfo.setLastTargetQuery(lastTargetQuery);
}
if (address != null) {
mtargetInfo.setAddress(address.toString());
}
return targetInfoRepository.save(mtargetInfo);
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public Action addCancelActionStatus(final ActionStatus actionStatus) {
final JpaAction action = (JpaAction) actionStatus.getAction();
checkForToManyStatusEntries(action);
action.setStatus(actionStatus.getStatus());
switch (actionStatus.getStatus()) {
case WARNING:
case ERROR:
case RUNNING:
break;
case CANCELED:
case FINISHED:
// in case of successful cancellation we also report the success at
// the canceled action itself.
actionStatus.addMessage(
ControllerManagement.SERVER_MESSAGE_PREFIX + "Cancellation completion is finished sucessfully.");
DeploymentHelper.successCancellation(action, actionRepository, targetManagement, targetInfoRepository,
entityManager);
break;
case RETRIEVED:
actionStatus.addMessage(ControllerManagement.SERVER_MESSAGE_PREFIX + "Cancellation request retrieved.");
break;
default:
}
actionRepository.save(action);
actionStatusRepository.save((JpaActionStatus) actionStatus);
return action;
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public Action addUpdateActionStatus(@NotNull final ActionStatus actionStatus) {
final JpaAction action = (JpaAction) actionStatus.getAction();
if (!action.isActive()) {
LOG.debug("Update of actionStatus {} for action {} not possible since action not active anymore.",
actionStatus.getId(), action.getId());
return action;
}
return handleAddUpdateActionStatus((JpaActionStatus) actionStatus, action);
}
/**
* Sets {@link TargetUpdateStatus} based on given {@link ActionStatus}.
*
* @param actionStatus
* @param action
* @return
*/
private Action handleAddUpdateActionStatus(final JpaActionStatus actionStatus, final JpaAction action) {
LOG.debug("addUpdateActionStatus for action {}", action.getId());
final JpaAction mergedAction = entityManager.merge(action);
JpaTarget mergedTarget = (JpaTarget) mergedAction.getTarget();
// check for a potential DOS attack
checkForToManyStatusEntries(action);
switch (actionStatus.getStatus()) {
case ERROR:
mergedTarget = DeploymentHelper.updateTargetInfo(mergedTarget, TargetUpdateStatus.ERROR, false,
targetInfoRepository, entityManager);
handleErrorOnAction(mergedAction, mergedTarget);
break;
case FINISHED:
handleFinishedAndStoreInTargetStatus(mergedTarget, mergedAction);
break;
case CANCELED:
case WARNING:
case RUNNING:
DeploymentHelper.updateTargetInfo(mergedTarget, TargetUpdateStatus.PENDING, false, targetInfoRepository,
entityManager);
break;
default:
break;
}
actionStatusRepository.save(actionStatus);
LOG.debug("addUpdateActionStatus {} for target {} is finished.", action.getId(), mergedTarget.getId());
return actionRepository.save(mergedAction);
}
private void handleErrorOnAction(final JpaAction mergedAction, final JpaTarget mergedTarget) {
mergedAction.setActive(false);
mergedAction.setStatus(Status.ERROR);
mergedTarget.setAssignedDistributionSet(null);
targetManagement.updateTarget(mergedTarget);
}
private void checkForToManyStatusEntries(final JpaAction action) {
if (securityProperties.getDos().getMaxStatusEntriesPerAction() > 0) {
final Long statusCount = actionStatusRepository.countByAction(action);
if (statusCount >= securityProperties.getDos().getMaxStatusEntriesPerAction()) {
LOG_DOS.error(
"Potential denial of service (DOS) attack identfied. More status entries in the system than permitted ({})!",
securityProperties.getDos().getMaxStatusEntriesPerAction());
throw new ToManyStatusEntriesException(
String.valueOf(securityProperties.getDos().getMaxStatusEntriesPerAction()));
}
}
}
private void handleFinishedAndStoreInTargetStatus(final JpaTarget target, final JpaAction action) {
action.setActive(false);
action.setStatus(Status.FINISHED);
final JpaTargetInfo targetInfo = (JpaTargetInfo) target.getTargetInfo();
final JpaDistributionSet ds = (JpaDistributionSet) entityManager.merge(action.getDistributionSet());
targetInfo.setInstalledDistributionSet(ds);
if (target.getAssignedDistributionSet() != null && targetInfo.getInstalledDistributionSet() != null && target
.getAssignedDistributionSet().getId().equals(targetInfo.getInstalledDistributionSet().getId())) {
targetInfo.setUpdateStatus(TargetUpdateStatus.IN_SYNC);
targetInfo.setInstallationDate(System.currentTimeMillis());
} else {
targetInfo.setUpdateStatus(TargetUpdateStatus.PENDING);
targetInfo.setInstallationDate(System.currentTimeMillis());
}
targetInfoRepository.save(targetInfo);
entityManager.detach(ds);
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public Target updateControllerAttributes(final String controllerId, final Map<String, String> data) {
final JpaTarget target = targetRepository.findByControllerId(controllerId);
if (target == null) {
throw new EntityNotFoundException(controllerId);
}
final JpaTargetInfo targetInfo = (JpaTargetInfo) target.getTargetInfo();
targetInfo.getControllerAttributes().putAll(data);
if (targetInfo.getControllerAttributes().size() > securityProperties.getDos()
.getMaxAttributeEntriesPerTarget()) {
LOG_DOS.info("Target tries to insert more than the allowed number of entries ({}). DOS attack anticipated!",
securityProperties.getDos().getMaxAttributeEntriesPerTarget());
throw new ToManyAttributeEntriesException(
String.valueOf(securityProperties.getDos().getMaxAttributeEntriesPerTarget()));
}
targetInfo.setLastTargetQuery(System.currentTimeMillis());
targetInfo.setRequestControllerAttributes(false);
return targetRepository.save(target);
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public Action registerRetrieved(final Action action, final String message) {
return handleRegisterRetrieved((JpaAction) action, message);
}
/**
* Registers retrieved status for given {@link Target} and {@link Action} if
* it does not exist yet.
*
* @param action
* to the handle status for
* @param message
* for the status
* @return the updated action in case the status has been changed to
* {@link Status#RETRIEVED}
*/
private Action handleRegisterRetrieved(final JpaAction action, final String message) {
// do a manual query with CriteriaBuilder to avoid unnecessary field
// queries and an extra
// count query made by spring-data when using pageable requests, we
// don't need an extra count
// query, we just want to check if the last action status is a retrieved
// or not.
final CriteriaBuilder cb = entityManager.getCriteriaBuilder();
final CriteriaQuery<Object[]> queryActionStatus = cb.createQuery(Object[].class);
final Root<JpaActionStatus> actionStatusRoot = queryActionStatus.from(JpaActionStatus.class);
final CriteriaQuery<Object[]> query = queryActionStatus
.multiselect(actionStatusRoot.get(JpaActionStatus_.id), actionStatusRoot.get(JpaActionStatus_.status))
.where(cb.equal(actionStatusRoot.get(JpaActionStatus_.action), action))
.orderBy(cb.desc(actionStatusRoot.get(JpaActionStatus_.id)));
final List<Object[]> resultList = entityManager.createQuery(query).setFirstResult(0).setMaxResults(1)
.getResultList();
// if the latest status is not in retrieve state then we add a retrieved
// state again, we want
// to document a deployment retrieved status and a cancel retrieved
// status, but multiple
// retrieves after the other we don't want to store to protect to
// overflood action status in
// case controller retrieves a action multiple times.
if (resultList.isEmpty() || !Status.RETRIEVED.equals(resultList.get(0)[1])) {
// document that the status has been retrieved
actionStatusRepository
.save(new JpaActionStatus(action, Status.RETRIEVED, System.currentTimeMillis(), message));
// don't change the action status itself in case the action is in
// canceling state otherwise
// we modify the action status and the controller won't get the
// cancel job anymore.
if (!action.isCancelingOrCanceled()) {
final JpaAction actionMerge = entityManager.merge(action);
actionMerge.setStatus(Status.RETRIEVED);
return actionRepository.save(actionMerge);
}
}
return action;
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public void addInformationalActionStatus(final ActionStatus statusMessage) {
actionStatusRepository.save((JpaActionStatus) statusMessage);
}
@Override
public String getSecurityTokenByControllerId(final String controllerId) {
final Target target = targetRepository.findByControllerId(controllerId);
return target != null ? target.getSecurityToken() : null;
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public TargetInfo updateLastTargetQuery(final TargetInfo target, final URI address) {
return updateTargetStatus(target, null, System.currentTimeMillis(), address);
}
@Override
@Transactional(propagation = Propagation.SUPPORTS)
public ActionStatus generateActionStatus() {
return new JpaActionStatus();
}
@Override
@Transactional(propagation = Propagation.SUPPORTS)
public ActionStatus generateActionStatus(final Action action, final Status status, final Long occurredAt,
final String message) {
return new JpaActionStatus((JpaAction) action, status, occurredAt, message);
}
@Override
@Transactional(propagation = Propagation.SUPPORTS)
public ActionStatus generateActionStatus(final Action action, final Status status, final Long occurredAt,
final Collection<String> messages) {
final ActionStatus result = new JpaActionStatus((JpaAction) action, status, occurredAt, null);
messages.forEach(result::addMessage);
return result;
}
@Override
@Transactional(propagation = Propagation.SUPPORTS)
public ActionStatus generateActionStatus(final Action action, final Status status, final Long occurredAt) {
return new JpaActionStatus(action, status, occurredAt);
}
}

View File

@@ -0,0 +1,700 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import javax.persistence.EntityManager;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.ListJoin;
import javax.persistence.criteria.Root;
import javax.validation.constraints.NotNull;
import org.eclipse.hawkbit.Constants;
import org.eclipse.hawkbit.eventbus.event.CancelTargetAssignmentEvent;
import org.eclipse.hawkbit.eventbus.event.TargetAssignDistributionSetEvent;
import org.eclipse.hawkbit.eventbus.event.TargetInfoUpdateEvent;
import org.eclipse.hawkbit.executor.AfterTransactionCommitExecutor;
import org.eclipse.hawkbit.repository.ActionFields;
import org.eclipse.hawkbit.repository.DeploymentManagement;
import org.eclipse.hawkbit.repository.DistributionSetAssignmentResult;
import org.eclipse.hawkbit.repository.TargetManagement;
import org.eclipse.hawkbit.repository.TargetWithActionType;
import org.eclipse.hawkbit.repository.exception.CancelActionNotAllowedException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.ForceQuitActionNotAllowedException;
import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetException;
import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
import org.eclipse.hawkbit.repository.jpa.model.JpaActionStatus;
import org.eclipse.hawkbit.repository.jpa.model.JpaActionWithStatusCount;
import org.eclipse.hawkbit.repository.jpa.model.JpaAction_;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet_;
import org.eclipse.hawkbit.repository.jpa.model.JpaRollout;
import org.eclipse.hawkbit.repository.jpa.model.JpaRolloutGroup;
import org.eclipse.hawkbit.repository.jpa.model.JpaRollout_;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetInfo;
import org.eclipse.hawkbit.repository.jpa.specifications.TargetSpecifications;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.ActionStatus;
import org.eclipse.hawkbit.repository.model.ActionWithStatusCount;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
import org.eclipse.hawkbit.repository.rsql.RSQLUtility;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.hibernate.validator.constraints.NotEmpty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.data.domain.AuditorAware;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import com.google.common.collect.Lists;
import com.google.common.eventbus.EventBus;
/**
* JPA implementation for {@link DeploymentManagement}.
*
*/
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
@Validated
@Service
public class JpaDeploymentManagement implements DeploymentManagement {
private static final Logger LOG = LoggerFactory.getLogger(JpaDeploymentManagement.class);
@Autowired
private EntityManager entityManager;
@Autowired
private ActionRepository actionRepository;
@Autowired
private DistributionSetRepository distributoinSetRepository;
@Autowired
private SoftwareModuleRepository softwareModuleRepository;
@Autowired
private TargetRepository targetRepository;
@Autowired
private ActionStatusRepository actionStatusRepository;
@Autowired
private TargetManagement targetManagement;
@Autowired
private TargetInfoRepository targetInfoRepository;
@Autowired
private AuditorAware<String> auditorProvider;
@Autowired
private EventBus eventBus;
@Autowired
private AfterTransactionCommitExecutor afterCommit;
@Autowired
private SystemSecurityContext systemSecurityContext;
@Override
@Transactional(isolation = Isolation.READ_COMMITTED)
@Modifying
@CacheEvict(value = { "distributionUsageAssigned" }, allEntries = true)
public DistributionSetAssignmentResult assignDistributionSet(final DistributionSet pset,
final List<Target> targets) {
return assignDistributionSetByTargetId((JpaDistributionSet) pset,
targets.stream().map(target -> target.getControllerId()).collect(Collectors.toList()),
ActionType.FORCED, Action.NO_FORCE_TIME);
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_COMMITTED)
@CacheEvict(value = { "distributionUsageAssigned" }, allEntries = true)
public DistributionSetAssignmentResult assignDistributionSet(final Long dsID, final String... targetIDs) {
return assignDistributionSet(dsID, ActionType.FORCED, Action.NO_FORCE_TIME, targetIDs);
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@CacheEvict(value = { "distributionUsageAssigned" }, allEntries = true)
// Exception squid:S2095: see
// https://jira.sonarsource.com/browse/SONARJAVA-1478
@SuppressWarnings({ "squid:S2095" })
public DistributionSetAssignmentResult assignDistributionSet(final Long dsID, final ActionType actionType,
final long forcedTimestamp, final String... targetIDs) {
return assignDistributionSet(dsID, Arrays.stream(targetIDs)
.map(t -> new TargetWithActionType(t, actionType, forcedTimestamp)).collect(Collectors.toList()));
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_COMMITTED)
@CacheEvict(value = { "distributionUsageAssigned" }, allEntries = true)
public DistributionSetAssignmentResult assignDistributionSet(final Long dsID,
final Collection<TargetWithActionType> targets) {
final JpaDistributionSet set = distributoinSetRepository.findOne(dsID);
if (set == null) {
throw new EntityNotFoundException(
String.format("no %s with id %d found", DistributionSet.class.getSimpleName(), dsID));
}
return assignDistributionSetToTargets(set, targets, null, null);
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_COMMITTED)
@CacheEvict(value = { "distributionUsageAssigned" }, allEntries = true)
public DistributionSetAssignmentResult assignDistributionSet(final Long dsID,
final Collection<TargetWithActionType> targets, final Rollout rollout, final RolloutGroup rolloutGroup) {
final JpaDistributionSet set = distributoinSetRepository.findOne(dsID);
if (set == null) {
throw new EntityNotFoundException(
String.format("no %s with id %d found", DistributionSet.class.getSimpleName(), dsID));
}
return assignDistributionSetToTargets(set, targets, (JpaRollout) rollout, (JpaRolloutGroup) rolloutGroup);
}
/**
* method assigns the {@link DistributionSet} to all {@link Target}s by
* their IDs with a specific {@link ActionType} and {@code forcetime}.
*
* @param dsID
* the ID of the distribution set to assign
* @param targets
* a list of all targets and their action type
* @param rollout
* the rollout for this assignment
* @param rolloutGroup
* the rollout group for this assignment
* @return the assignment result
*
* @throw IncompleteDistributionSetException if mandatory
* {@link SoftwareModuleType} are not assigned as define by the
* {@link DistributionSetType}.
*/
private DistributionSetAssignmentResult assignDistributionSetToTargets(@NotNull final JpaDistributionSet set,
final Collection<TargetWithActionType> targetsWithActionType, final JpaRollout rollout,
final JpaRolloutGroup rolloutGroup) {
if (!set.isComplete()) {
throw new IncompleteDistributionSetException(
"Distribution set of type " + set.getType().getKey() + " is incomplete: " + set.getId());
}
final List<String> controllerIDs = targetsWithActionType.stream().map(TargetWithActionType::getTargetId)
.collect(Collectors.toList());
LOG.debug("assignDistribution({}) to {} targets", set, controllerIDs.size());
final Map<String, TargetWithActionType> targetsWithActionMap = targetsWithActionType.stream()
.collect(Collectors.toMap(TargetWithActionType::getTargetId, Function.identity()));
// split tIDs length into max entries in-statement because many database
// have constraint of max entries in in-statements e.g. Oracle with
// maximum 1000 elements, so we need to split the entries here and
// execute multiple statements we take the target only into account if
// the requested operation is no duplicate of a previous one
final List<JpaTarget> targets = Lists.partition(controllerIDs, Constants.MAX_ENTRIES_IN_STATEMENT).stream()
.map(ids -> targetRepository
.findAll(TargetSpecifications.hasControllerIdAndAssignedDistributionSetIdNot(ids, set.getId())))
.flatMap(t -> t.stream()).collect(Collectors.toList());
if (targets.isEmpty()) {
// detaching as it is not necessary to persist the set itself
entityManager.detach(set);
// return with nothing as all targets had the DS already assigned
return new DistributionSetAssignmentResult(Collections.emptyList(), 0, targetsWithActionType.size(),
Collections.emptyList(), targetManagement);
}
final List<List<Long>> targetIds = Lists.partition(
targets.stream().map(Target::getId).collect(Collectors.toList()), Constants.MAX_ENTRIES_IN_STATEMENT);
// override all active actions and set them into canceling state, we
// need to remember which one we have been switched to canceling state
// because for targets which we have changed to canceling we don't want
// to publish the new action update event.
final Set<Long> targetIdsCancellList = new HashSet<>();
targetIds.forEach(ids -> targetIdsCancellList.addAll(overrideObsoleteUpdateActions(ids)));
// cancel all scheduled actions which are in-active, these actions were
// not active before and the manual assignment which has been done
// cancels the
targetIds.forEach(tIds -> actionRepository.switchStatus(Status.CANCELED, tIds, false, Status.SCHEDULED));
// set assigned distribution set and TargetUpdateStatus
final String currentUser;
if (auditorProvider != null) {
currentUser = auditorProvider.getCurrentAuditor();
} else {
currentUser = null;
}
targetIds.forEach(tIds -> targetRepository.setAssignedDistributionSet(set, System.currentTimeMillis(),
currentUser, tIds));
targetIds.forEach(tIds -> targetInfoRepository.setTargetUpdateStatus(TargetUpdateStatus.PENDING, tIds));
final Map<String, JpaAction> targetIdsToActions = actionRepository
.save(targets.stream().map(t -> createTargetAction(targetsWithActionMap, t, set, rollout, rolloutGroup))
.collect(Collectors.toList()))
.stream().collect(Collectors.toMap(a -> a.getTarget().getControllerId(), Function.identity()));
// create initial action status when action is created so we remember
// the initial running status because we will change the status
// of the action itself and with this action status we have a nicer
// action history.
targetIdsToActions.values().forEach(action -> {
final JpaActionStatus actionStatus = new JpaActionStatus();
actionStatus.setAction(action);
actionStatus.setOccurredAt(action.getCreatedAt());
actionStatus.setStatus(Status.RUNNING);
actionStatusRepository.save(actionStatus);
});
// flush to get action IDs
entityManager.flush();
// collect updated target and actions IDs in order to return them
final DistributionSetAssignmentResult result = new DistributionSetAssignmentResult(
targets.stream().map(target -> target.getControllerId()).collect(Collectors.toList()), targets.size(),
controllerIDs.size() - targets.size(),
targetIdsToActions.values().stream().map(Action::getId).collect(Collectors.toList()), targetManagement);
LOG.debug("assignDistribution({}) finished {}", set, result);
final List<JpaSoftwareModule> softwareModules = softwareModuleRepository.findByAssignedTo(set);
// detaching as it is not necessary to persist the set itself
entityManager.detach(set);
sendDistributionSetAssignmentEvent(targets, targetIdsCancellList, targetIdsToActions, softwareModules);
return result;
}
private void sendDistributionSetAssignmentEvent(final List<JpaTarget> targets, final Set<Long> targetIdsCancellList,
final Map<String, JpaAction> targetIdsToActions, final List<JpaSoftwareModule> softwareModules) {
targets.stream().filter(t -> !!!targetIdsCancellList.contains(t.getId()))
.forEach(t -> assignDistributionSetEvent(t, targetIdsToActions.get(t.getControllerId()).getId(),
softwareModules));
}
private static JpaAction createTargetAction(final Map<String, TargetWithActionType> targetsWithActionMap,
final JpaTarget target, final JpaDistributionSet set, final JpaRollout rollout,
final JpaRolloutGroup rolloutGroup) {
final JpaAction actionForTarget = new JpaAction();
final TargetWithActionType targetWithActionType = targetsWithActionMap.get(target.getControllerId());
actionForTarget.setActionType(targetWithActionType.getActionType());
actionForTarget.setForcedTime(targetWithActionType.getForceTime());
actionForTarget.setActive(true);
actionForTarget.setStatus(Status.RUNNING);
actionForTarget.setTarget(target);
actionForTarget.setDistributionSet(set);
actionForTarget.setRollout(rollout);
actionForTarget.setRolloutGroup(rolloutGroup);
return actionForTarget;
}
/**
* Sends the {@link TargetAssignDistributionSetEvent} for a specific target
* to the {@link EventBus}.
*
* @param target
* the Target which has been assigned to a distribution set
* @param actionId
* the action id of the assignment
* @param softwareModules
* the software modules which have been assigned
*/
private void assignDistributionSetEvent(final JpaTarget target, final Long actionId,
final List<JpaSoftwareModule> modules) {
((JpaTargetInfo) target.getTargetInfo()).setUpdateStatus(TargetUpdateStatus.PENDING);
final String targetSecurityToken = systemSecurityContext.runAsSystem(() -> target.getSecurityToken());
@SuppressWarnings({ "unchecked", "rawtypes" })
final Collection<SoftwareModule> softwareModules = (Collection) modules;
afterCommit.afterCommit(() -> {
eventBus.post(new TargetInfoUpdateEvent(target.getTargetInfo()));
eventBus.post(new TargetAssignDistributionSetEvent(target.getOptLockRevision(), target.getTenant(),
target.getControllerId(), actionId, softwareModules, target.getTargetInfo().getAddress(),
targetSecurityToken));
});
}
/**
* Removes {@link UpdateAction}s that are no longer necessary and sends
* cancellations to the controller.
*
* @param myTarget
* to override {@link UpdateAction}s
*/
private Set<Long> overrideObsoleteUpdateActions(final List<Long> targetsIds) {
final Set<Long> cancelledTargetIds = new HashSet<>();
// Figure out if there are potential target/action combinations that
// need to be considered
// for cancelation
final List<JpaAction> activeActions = actionRepository
.findByActiveAndTargetIdInAndActionStatusNotEqualToAndDistributionSetRequiredMigrationStep(targetsIds,
Action.Status.CANCELING);
activeActions.forEach(action -> {
action.setStatus(Status.CANCELING);
// document that the status has been retrieved
actionStatusRepository.save(new JpaActionStatus(action, Status.CANCELING, System.currentTimeMillis(),
"manual cancelation requested"));
cancelAssignDistributionSetEvent(action.getTarget(), action.getId());
cancelledTargetIds.add(action.getTarget().getId());
});
actionRepository.save(activeActions);
return cancelledTargetIds;
}
private DistributionSetAssignmentResult assignDistributionSetByTargetId(@NotNull final JpaDistributionSet set,
@NotEmpty final List<String> tIDs, final ActionType actionType, final long forcedTime) {
return assignDistributionSetToTargets(set, tIDs.stream()
.map(t -> new TargetWithActionType(t, actionType, forcedTime)).collect(Collectors.toList()), null,
null);
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_COMMITTED)
public Action cancelAction(final Action action, final Target target) {
LOG.debug("cancelAction({}, {})", action, target);
if (action.isCancelingOrCanceled()) {
throw new CancelActionNotAllowedException("Actions in canceling or canceled state cannot be canceled");
}
final JpaAction myAction = (JpaAction) entityManager.merge(action);
if (myAction.isActive()) {
LOG.debug("action ({}) was still active. Change to {}.", action, Status.CANCELING);
myAction.setStatus(Status.CANCELING);
// document that the status has been retrieved
actionStatusRepository.save(new JpaActionStatus(myAction, Status.CANCELING, System.currentTimeMillis(),
"manual cancelation requested"));
final Action saveAction = actionRepository.save(myAction);
cancelAssignDistributionSetEvent(target, myAction.getId());
return saveAction;
} else {
throw new CancelActionNotAllowedException(
"Action [id: " + action.getId() + "] is not active and cannot be canceled");
}
}
/**
* Sends the {@link CancelTargetAssignmentEvent} for a specific target to
* the {@link EventBus}.
*
* @param target
* the Target which has been assigned to a distribution set
* @param actionId
* the action id of the assignment
*/
private void cancelAssignDistributionSetEvent(final Target target, final Long actionId) {
afterCommit.afterCommit(() -> eventBus.post(new CancelTargetAssignmentEvent(target.getOptLockRevision(),
target.getTenant(), target.getControllerId(), actionId, target.getTargetInfo().getAddress())));
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_COMMITTED)
public Action forceQuitAction(final Action action) {
final JpaAction mergedAction = (JpaAction) entityManager.merge(action);
if (!mergedAction.isCancelingOrCanceled()) {
throw new ForceQuitActionNotAllowedException(
"Action [id: " + action.getId() + "] is not canceled yet and cannot be force quit");
}
if (!mergedAction.isActive()) {
throw new ForceQuitActionNotAllowedException(
"Action [id: " + action.getId() + "] is not active and cannot be force quit");
}
LOG.warn("action ({}) was still activ and has been force quite.", action);
// document that the status has been retrieved
actionStatusRepository.save(new JpaActionStatus(mergedAction, Status.CANCELED, System.currentTimeMillis(),
"A force quit has been performed."));
DeploymentHelper.successCancellation(mergedAction, actionRepository, targetManagement, targetInfoRepository,
entityManager);
return actionRepository.save(mergedAction);
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_COMMITTED)
public void createScheduledAction(final Collection<Target> targets, final DistributionSet distributionSet,
final ActionType actionType, final Long forcedTime, final Rollout rollout,
final RolloutGroup rolloutGroup) {
// cancel all current scheduled actions for this target. E.g. an action
// is already scheduled and a next action is created then cancel the
// current scheduled action to cancel. E.g. a new scheduled action is
// created.
final List<Long> targetIds = targets.stream().map(t -> t.getId()).collect(Collectors.toList());
actionRepository.switchStatus(Action.Status.CANCELED, targetIds, false, Action.Status.SCHEDULED);
targets.forEach(target -> {
final JpaAction action = new JpaAction();
action.setTarget(target);
action.setActive(false);
action.setDistributionSet(distributionSet);
action.setActionType(actionType);
action.setForcedTime(forcedTime);
action.setStatus(Status.SCHEDULED);
action.setRollout(rollout);
action.setRolloutGroup(rolloutGroup);
actionRepository.save(action);
});
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_COMMITTED)
public Action startScheduledAction(final Action action) {
final JpaAction mergedAction = (JpaAction) entityManager.merge(action);
final JpaTarget mergedTarget = (JpaTarget) entityManager.merge(action.getTarget());
// check if we need to override running update actions
final Set<Long> overrideObsoleteUpdateActions = overrideObsoleteUpdateActions(
Collections.singletonList(action.getTarget().getId()));
final boolean hasDistributionSetAlreadyAssigned = targetRepository
.count(TargetSpecifications.hasControllerIdAndAssignedDistributionSetIdNot(
Collections.singletonList(mergedTarget.getControllerId()),
action.getDistributionSet().getId())) == 0;
if (hasDistributionSetAlreadyAssigned) {
// the target has already the distribution set assigned, we don't
// need to start the scheduled action, just finished it.
mergedAction.setStatus(Status.FINISHED);
mergedAction.setActive(false);
return actionRepository.save(mergedAction);
}
mergedAction.setActive(true);
mergedAction.setStatus(Status.RUNNING);
final Action savedAction = actionRepository.save(mergedAction);
final JpaActionStatus actionStatus = new JpaActionStatus();
actionStatus.setAction(action);
actionStatus.setOccurredAt(action.getCreatedAt());
actionStatus.setStatus(Status.RUNNING);
actionStatusRepository.save(actionStatus);
mergedTarget.setAssignedDistributionSet(action.getDistributionSet());
final JpaTargetInfo targetInfo = (JpaTargetInfo) mergedTarget.getTargetInfo();
targetInfo.setUpdateStatus(TargetUpdateStatus.PENDING);
targetRepository.save(mergedTarget);
targetInfoRepository.save(targetInfo);
// in case we canceled an action before for this target, then don't fire
// assignment event
if (!overrideObsoleteUpdateActions.contains(savedAction.getId())) {
final List<JpaSoftwareModule> softwareModules = softwareModuleRepository
.findByAssignedTo((JpaDistributionSet) action.getDistributionSet());
// send distribution set assignment event
assignDistributionSetEvent((JpaTarget) mergedAction.getTarget(), mergedAction.getId(), softwareModules);
}
return savedAction;
}
@Override
public Action findAction(final Long actionId) {
return actionRepository.findOne(actionId);
}
@Override
public Action findActionWithDetails(final Long actionId) {
return actionRepository.findById(actionId);
}
@Override
public Slice<Action> findActionsByTarget(final Pageable pageable, final Target target) {
return actionRepository.findByTarget(pageable, (JpaTarget) target);
}
@Override
public List<Action> findActionsByTarget(final Target target) {
return actionRepository.findByTarget((JpaTarget) target);
}
@Override
public List<ActionWithStatusCount> findActionsWithStatusCountByTargetOrderByIdDesc(final Target target) {
final CriteriaBuilder cb = entityManager.getCriteriaBuilder();
final CriteriaQuery<JpaActionWithStatusCount> query = cb.createQuery(JpaActionWithStatusCount.class);
final Root<JpaAction> actionRoot = query.from(JpaAction.class);
final ListJoin<JpaAction, JpaActionStatus> actionStatusJoin = actionRoot.join(JpaAction_.actionStatus,
JoinType.LEFT);
final Join<JpaAction, JpaDistributionSet> actionDsJoin = actionRoot.join(JpaAction_.distributionSet);
final Join<JpaAction, JpaRollout> actionRolloutJoin = actionRoot.join(JpaAction_.rollout, JoinType.LEFT);
final CriteriaQuery<JpaActionWithStatusCount> multiselect = query.distinct(true).multiselect(
actionRoot.get(JpaAction_.id), actionRoot.get(JpaAction_.actionType), actionRoot.get(JpaAction_.active),
actionRoot.get(JpaAction_.forcedTime), actionRoot.get(JpaAction_.status),
actionRoot.get(JpaAction_.createdAt), actionRoot.get(JpaAction_.lastModifiedAt),
actionDsJoin.get(JpaDistributionSet_.id), actionDsJoin.get(JpaDistributionSet_.name),
actionDsJoin.get(JpaDistributionSet_.version), cb.count(actionStatusJoin),
actionRolloutJoin.get(JpaRollout_.name));
multiselect.where(cb.equal(actionRoot.get(JpaAction_.target), target));
multiselect.orderBy(cb.desc(actionRoot.get(JpaAction_.id)));
multiselect.groupBy(actionRoot.get(JpaAction_.id));
return new ArrayList<>(entityManager.createQuery(multiselect).getResultList());
}
@Override
public Page<Action> findActionsByTarget(final String rsqlParam, final Target target, final Pageable pageable) {
final Specification<JpaAction> specification = RSQLUtility.parse(rsqlParam, ActionFields.class);
return convertAcPage(actionRepository.findAll((Specification<JpaAction>) (root, query, cb) -> cb
.and(specification.toPredicate(root, query, cb), cb.equal(root.get(JpaAction_.target), target)),
pageable), pageable);
}
private static Page<Action> convertAcPage(final Page<JpaAction> findAll, final Pageable pageable) {
return new PageImpl<>(new ArrayList<>(findAll.getContent()), pageable, findAll.getTotalElements());
}
@Override
public Slice<Action> findActionsByTarget(final Target foundTarget, final Pageable pageable) {
return actionRepository.findByTarget(pageable, (JpaTarget) foundTarget);
}
@Override
public Page<Action> findActiveActionsByTarget(final Pageable pageable, final Target target) {
return actionRepository.findByActiveAndTarget(pageable, (JpaTarget) target, true);
}
@Override
public List<Action> findActiveActionsByTarget(final Target target) {
return actionRepository.findByActiveAndTarget((JpaTarget) target, true);
}
@Override
public List<Action> findInActiveActionsByTarget(final Target target) {
return actionRepository.findByActiveAndTarget((JpaTarget) target, false);
}
@Override
public Page<Action> findInActiveActionsByTarget(final Pageable pageable, final Target target) {
return actionRepository.findByActiveAndTarget(pageable, (JpaTarget) target, false);
}
@Override
public Long countActionsByTarget(final Target target) {
return actionRepository.countByTarget((JpaTarget) target);
}
@Override
public Long countActionsByTarget(final String rsqlParam, final Target target) {
final Specification<JpaAction> spec = RSQLUtility.parse(rsqlParam, ActionFields.class);
return actionRepository.count((root, query, cb) -> cb.and(spec.toPredicate(root, query, cb),
cb.equal(root.get(JpaAction_.target), target)));
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public Action forceTargetAction(final Long actionId) {
final JpaAction action = actionRepository.findOne(actionId);
if (action != null && !action.isForced()) {
action.setActionType(ActionType.FORCED);
return actionRepository.save(action);
}
return action;
}
@Override
public Page<ActionStatus> findActionStatusByAction(final Pageable pageReq, final Action action) {
return actionStatusRepository.findByAction(pageReq, (JpaAction) action);
}
@Override
public Page<ActionStatus> findActionStatusByActionWithMessages(final Pageable pageReq, final Action action) {
return actionStatusRepository.getByAction(pageReq, (JpaAction) action);
}
@Override
public List<Action> findActionsByRolloutGroupParentAndStatus(final Rollout rollout,
final RolloutGroup rolloutGroupParent, final Action.Status actionStatus) {
return actionRepository.findByRolloutAndRolloutGroupParentAndStatus((JpaRollout) rollout,
(JpaRolloutGroup) rolloutGroupParent, actionStatus);
}
@Override
public List<Action> findActionsByRolloutAndStatus(final Rollout rollout, final Action.Status actionStatus) {
return actionRepository.findByRolloutAndStatus((JpaRollout) rollout, actionStatus);
}
@Override
@Transactional(propagation = Propagation.SUPPORTS)
public Action generateAction() {
return new JpaAction();
}
@Override
public Page<ActionStatus> findActionStatusAll(final Pageable pageable) {
return convertAcSPage(actionStatusRepository.findAll(pageable), pageable);
}
private static Page<ActionStatus> convertAcSPage(final Page<JpaActionStatus> findAll, final Pageable pageable) {
return new PageImpl<>(new ArrayList<>(findAll.getContent()), pageable, findAll.getTotalElements());
}
}

View File

@@ -0,0 +1,785 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa;
import static com.google.common.base.Preconditions.checkNotNull;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import javax.persistence.EntityManager;
import org.eclipse.hawkbit.eventbus.event.DistributionSetTagAssigmentResultEvent;
import org.eclipse.hawkbit.executor.AfterTransactionCommitExecutor;
import org.eclipse.hawkbit.repository.DistributionSetFields;
import org.eclipse.hawkbit.repository.DistributionSetFilter;
import org.eclipse.hawkbit.repository.DistributionSetFilter.DistributionSetFilterBuilder;
import org.eclipse.hawkbit.repository.DistributionSetManagement;
import org.eclipse.hawkbit.repository.DistributionSetMetadataFields;
import org.eclipse.hawkbit.repository.DistributionSetTypeFields;
import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.repository.TagManagement;
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
import org.eclipse.hawkbit.repository.exception.EntityLockedException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.EntityReadOnlyException;
import org.eclipse.hawkbit.repository.jpa.model.DsMetadataCompositeKey;
import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetMetadata;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetMetadata_;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetType;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet_;
import org.eclipse.hawkbit.repository.jpa.specifications.DistributionSetSpecification;
import org.eclipse.hawkbit.repository.jpa.specifications.DistributionSetTypeSpecification;
import org.eclipse.hawkbit.repository.jpa.specifications.SpecificationsBuilder;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetMetadata;
import org.eclipse.hawkbit.repository.model.DistributionSetTag;
import org.eclipse.hawkbit.repository.model.DistributionSetTagAssignmentResult;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.rsql.RSQLUtility;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import com.google.common.base.Strings;
import com.google.common.eventbus.EventBus;
/**
* JPA implementation of {@link DistributionSetManagement}.
*
*/
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
@Validated
@Service
public class JpaDistributionSetManagement implements DistributionSetManagement {
@Autowired
private EntityManager entityManager;
@Autowired
private DistributionSetRepository distributionSetRepository;
@Autowired
private TagManagement tagManagement;
@Autowired
private SystemManagement systemManagement;
@Autowired
private DistributionSetTypeRepository distributionSetTypeRepository;
@Autowired
private DistributionSetMetadataRepository distributionSetMetadataRepository;
@Autowired
private ActionRepository actionRepository;
@Autowired
private EventBus eventBus;
@Autowired
private AfterTransactionCommitExecutor afterCommit;
@Override
public DistributionSet findDistributionSetByIdWithDetails(final Long distid) {
return distributionSetRepository.findOne(DistributionSetSpecification.byId(distid));
}
@Override
public DistributionSet findDistributionSetById(final Long distid) {
return distributionSetRepository.findOne(distid);
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public DistributionSetTagAssignmentResult toggleTagAssignment(final Collection<Long> dsIds, final String tagName) {
final List<JpaDistributionSet> sets = findDistributionSetListWithDetails(dsIds);
final DistributionSetTag myTag = tagManagement.findDistributionSetTag(tagName);
DistributionSetTagAssignmentResult result;
final List<JpaDistributionSet> toBeChangedDSs = new ArrayList<>();
for (final JpaDistributionSet set : sets) {
if (set.getTags().add(myTag)) {
toBeChangedDSs.add(set);
}
}
// un-assignment case
if (toBeChangedDSs.isEmpty()) {
for (final JpaDistributionSet set : sets) {
if (set.getTags().remove(myTag)) {
toBeChangedDSs.add(set);
}
}
result = new DistributionSetTagAssignmentResult(dsIds.size() - toBeChangedDSs.size(), 0,
toBeChangedDSs.size(), Collections.emptyList(),
new ArrayList<>(distributionSetRepository.save(toBeChangedDSs)), myTag);
} else {
result = new DistributionSetTagAssignmentResult(dsIds.size() - toBeChangedDSs.size(), toBeChangedDSs.size(),
0, new ArrayList<>(distributionSetRepository.save(toBeChangedDSs)), Collections.emptyList(), myTag);
}
final DistributionSetTagAssignmentResult resultAssignment = result;
afterCommit.afterCommit(() -> eventBus.post(new DistributionSetTagAssigmentResultEvent(resultAssignment)));
// no reason to persist the tag
entityManager.detach(myTag);
return result;
}
private List<JpaDistributionSet> findDistributionSetListWithDetails(final Collection<Long> distributionIdSet) {
return distributionSetRepository.findAll(DistributionSetSpecification.byIds(distributionIdSet));
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public DistributionSet updateDistributionSet(final DistributionSet ds) {
checkNotNull(ds.getId());
final DistributionSet persisted = findDistributionSetByIdWithDetails(ds.getId());
checkDistributionSetSoftwareModulesIsAllowedToModify((JpaDistributionSet) ds, persisted.getModules());
return distributionSetRepository.save((JpaDistributionSet) ds);
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public void deleteDistributionSet(final Long... distributionSetIDs) {
final List<Long> toHardDelete = new ArrayList<>();
final List<Long> assigned = distributionSetRepository.findAssignedDistributionSetsById(distributionSetIDs);
// soft delete assigned
if (!assigned.isEmpty()) {
distributionSetRepository.deleteDistributionSet(assigned.toArray(new Long[assigned.size()]));
}
// mark the rest as hard delete
for (final Long setId : distributionSetIDs) {
if (!assigned.contains(setId)) {
toHardDelete.add(setId);
}
}
// hard delete the rest if exixts
if (!toHardDelete.isEmpty()) {
// don't give the delete statement an empty list, JPA/Oracle cannot
// handle the empty list
distributionSetRepository.deleteByIdIn(toHardDelete);
}
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public DistributionSet createDistributionSet(final DistributionSet dSet) {
prepareDsSave(dSet);
if (dSet.getType() == null) {
dSet.setType(systemManagement.getTenantMetadata().getDefaultDsType());
}
return distributionSetRepository.save((JpaDistributionSet) dSet);
}
private void prepareDsSave(final DistributionSet dSet) {
if (dSet.getId() != null) {
throw new EntityAlreadyExistsException("Parameter seems to be an existing, already persisted entity");
}
if (distributionSetRepository.countByNameAndVersion(dSet.getName(), dSet.getVersion()) > 0) {
throw new EntityAlreadyExistsException("DistributionSet with that name and version already exists.");
}
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public List<DistributionSet> createDistributionSets(final Collection<DistributionSet> distributionSets) {
for (final DistributionSet ds : distributionSets) {
prepareDsSave(ds);
if (ds.getType() == null) {
ds.setType(systemManagement.getTenantMetadata().getDefaultDsType());
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
final Collection<JpaDistributionSet> toSave = (Collection) distributionSets;
return new ArrayList<>(distributionSetRepository.save(toSave));
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public DistributionSet assignSoftwareModules(final DistributionSet ds, final Set<SoftwareModule> softwareModules) {
checkDistributionSetSoftwareModulesIsAllowedToModify((JpaDistributionSet) ds, softwareModules);
for (final SoftwareModule softwareModule : softwareModules) {
ds.addModule(softwareModule);
}
return distributionSetRepository.save((JpaDistributionSet) ds);
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public DistributionSet unassignSoftwareModule(final DistributionSet ds, final SoftwareModule softwareModule) {
final Set<SoftwareModule> softwareModules = new HashSet<>();
softwareModules.add(softwareModule);
ds.removeModule(softwareModule);
checkDistributionSetSoftwareModulesIsAllowedToModify((JpaDistributionSet) ds, softwareModules);
return distributionSetRepository.save((JpaDistributionSet) ds);
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public DistributionSetType updateDistributionSetType(final DistributionSetType dsType) {
checkNotNull(dsType.getId());
final JpaDistributionSetType persisted = distributionSetTypeRepository.findOne(dsType.getId());
// throw exception if user tries to update a DS type that is already in
// use
if (!persisted.areModuleEntriesIdentical(dsType) && distributionSetRepository.countByType(persisted) > 0) {
throw new EntityReadOnlyException(
String.format("distribution set type %s set is already assigned to targets and cannot be changed",
dsType.getName()));
}
return distributionSetTypeRepository.save((JpaDistributionSetType) dsType);
}
@Override
public Page<DistributionSetType> findDistributionSetTypesAll(final String rsqlParam, final Pageable pageable) {
final Specification<JpaDistributionSetType> spec = RSQLUtility.parse(rsqlParam,
DistributionSetTypeFields.class);
return convertDsTPage(distributionSetTypeRepository.findAll(spec, pageable));
}
private static Page<DistributionSetType> convertDsTPage(final Page<JpaDistributionSetType> findAll) {
return new PageImpl<>(new ArrayList<>(findAll.getContent()));
}
@Override
public Page<DistributionSetType> findDistributionSetTypesAll(final Pageable pageable) {
return convertDsTPage(distributionSetTypeRepository.findByDeleted(pageable, false));
}
@Override
public Page<DistributionSet> findDistributionSetsByFilters(final Pageable pageable,
final DistributionSetFilter distributionSetFilter) {
final List<Specification<JpaDistributionSet>> specList = buildDistributionSetSpecifications(
distributionSetFilter);
return convertDsPage(findByCriteriaAPI(pageable, specList), pageable);
}
private static Page<DistributionSet> convertDsPage(final Page<JpaDistributionSet> findAll,
final Pageable pageable) {
return new PageImpl<>(new ArrayList<>(findAll.getContent()), pageable, findAll.getTotalElements());
}
/**
*
* @param distributionSetFilter
* had details of filters to be applied
* @return a single DistributionSet which is either installed or assigned to
* a specific target or {@code null}.
*/
private DistributionSet findDistributionSetsByFiltersAndInstalledOrAssignedTarget(
final DistributionSetFilter distributionSetFilter) {
final List<Specification<JpaDistributionSet>> specList = buildDistributionSetSpecifications(
distributionSetFilter);
if (specList == null || specList.isEmpty()) {
return null;
}
return distributionSetRepository.findOne(SpecificationsBuilder.combineWithAnd(specList));
}
@Override
public Page<DistributionSet> findDistributionSetsByDeletedAndOrCompleted(final Pageable pageReq,
final Boolean deleted, final Boolean complete) {
final List<Specification<JpaDistributionSet>> specList = new ArrayList<>();
if (deleted != null) {
final Specification<JpaDistributionSet> spec = DistributionSetSpecification.isDeleted(deleted);
specList.add(spec);
}
if (complete != null) {
final Specification<JpaDistributionSet> spec = DistributionSetSpecification.isCompleted(complete);
specList.add(spec);
}
return convertDsPage(findByCriteriaAPI(pageReq, specList), pageReq);
}
@Override
public Page<DistributionSet> findDistributionSetsAll(final String rsqlParam, final Pageable pageReq,
final Boolean deleted) {
final Specification<JpaDistributionSet> spec = RSQLUtility.parse(rsqlParam, DistributionSetFields.class);
final List<Specification<JpaDistributionSet>> specList = new ArrayList<>();
if (deleted != null) {
specList.add(DistributionSetSpecification.isDeleted(deleted));
}
specList.add(spec);
return convertDsPage(findByCriteriaAPI(pageReq, specList), pageReq);
}
@Override
public Page<DistributionSet> findDistributionSetsAllOrderedByLinkTarget(final Pageable pageable,
final DistributionSetFilterBuilder distributionSetFilterBuilder, final String assignedOrInstalled) {
final DistributionSetFilter filterWithInstalledTargets = distributionSetFilterBuilder
.setInstalledTargetId(assignedOrInstalled).setAssignedTargetId(null).build();
final DistributionSet installedDS = findDistributionSetsByFiltersAndInstalledOrAssignedTarget(
filterWithInstalledTargets);
final DistributionSetFilter filterWithAssignedTargets = distributionSetFilterBuilder.setInstalledTargetId(null)
.setAssignedTargetId(assignedOrInstalled).build();
final DistributionSet assignedDS = findDistributionSetsByFiltersAndInstalledOrAssignedTarget(
filterWithAssignedTargets);
final DistributionSetFilter dsFilterWithNoTargetLinked = distributionSetFilterBuilder.setInstalledTargetId(null)
.setAssignedTargetId(null).build();
// first fine the distribution sets filtered by the given filter
// parameters
final Page<DistributionSet> findDistributionSetsByFilters = findDistributionSetsByFilters(pageable,
dsFilterWithNoTargetLinked);
final List<DistributionSet> resultSet = new LinkedList<>(findDistributionSetsByFilters.getContent());
int orderIndex = 0;
if (installedDS != null) {
final boolean remove = resultSet.remove(installedDS);
if (!remove) {
resultSet.remove(resultSet.size() - 1);
}
resultSet.add(orderIndex, installedDS);
orderIndex++;
}
if (assignedDS != null && !assignedDS.equals(installedDS)) {
final boolean remove = resultSet.remove(assignedDS);
if (!remove) {
resultSet.remove(resultSet.size() - 1);
}
resultSet.add(orderIndex, assignedDS);
}
return new PageImpl<>(resultSet, pageable, findDistributionSetsByFilters.getTotalElements());
}
@Override
public DistributionSet findDistributionSetByNameAndVersion(final String distributionName, final String version) {
final Specification<JpaDistributionSet> spec = DistributionSetSpecification
.equalsNameAndVersionIgnoreCase(distributionName, version);
return distributionSetRepository.findOne(spec);
}
@Override
public List<DistributionSet> findDistributionSetsAll(final Collection<Long> dist) {
return new ArrayList<>(distributionSetRepository.findAll(DistributionSetSpecification.byIds(dist)));
}
@Override
public Long countDistributionSetsAll() {
final List<Specification<JpaDistributionSet>> specList = new LinkedList<>();
final Specification<JpaDistributionSet> spec = DistributionSetSpecification.isDeleted(Boolean.FALSE);
specList.add(spec);
return distributionSetRepository.count(SpecificationsBuilder.combineWithAnd(specList));
}
@Override
public Long countDistributionSetTypesAll() {
return distributionSetTypeRepository.countByDeleted(false);
}
@Override
public DistributionSetType findDistributionSetTypeByName(final String name) {
return distributionSetTypeRepository.findOne(DistributionSetTypeSpecification.byName(name));
}
@Override
public DistributionSetType findDistributionSetTypeById(final Long id) {
return distributionSetTypeRepository.findOne(DistributionSetTypeSpecification.byId(id));
}
@Override
public DistributionSetType findDistributionSetTypeByKey(final String key) {
return distributionSetTypeRepository.findOne(DistributionSetTypeSpecification.byKey(key));
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public DistributionSetType createDistributionSetType(final DistributionSetType type) {
if (type.getId() != null) {
throw new EntityAlreadyExistsException("Given type contains an Id!");
}
return distributionSetTypeRepository.save((JpaDistributionSetType) type);
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public void deleteDistributionSetType(final DistributionSetType ty) {
final JpaDistributionSetType type = (JpaDistributionSetType) ty;
if (distributionSetRepository.countByType(type) > 0) {
final JpaDistributionSetType toDelete = entityManager.merge(type);
toDelete.setDeleted(true);
distributionSetTypeRepository.save(toDelete);
} else {
distributionSetTypeRepository.delete(type.getId());
}
}
@Override
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Modifying
public DistributionSetMetadata createDistributionSetMetadata(final DistributionSetMetadata md) {
final JpaDistributionSetMetadata metadata = (JpaDistributionSetMetadata) md;
if (distributionSetMetadataRepository.exists(metadata.getId())) {
throwMetadataKeyAlreadyExists(metadata.getId().getKey());
}
// merge base distribution set so optLockRevision gets updated and audit
// log written because
// modifying metadata is modifying the base distribution set itself for
// auditing purposes.
entityManager.merge((JpaDistributionSet) metadata.getDistributionSet()).setLastModifiedAt(0L);
return distributionSetMetadataRepository.save(metadata);
}
@Override
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Modifying
public List<DistributionSetMetadata> createDistributionSetMetadata(final Collection<DistributionSetMetadata> md) {
@SuppressWarnings({ "rawtypes", "unchecked" })
final Collection<JpaDistributionSetMetadata> metadata = (Collection) md;
for (final JpaDistributionSetMetadata distributionSetMetadata : metadata) {
checkAndThrowAlreadyIfDistributionSetMetadataExists(distributionSetMetadata.getId());
}
metadata.forEach(m -> entityManager.merge((JpaDistributionSet) m.getDistributionSet()).setLastModifiedAt(0L));
return new ArrayList<DistributionSetMetadata>(
(Collection<? extends DistributionSetMetadata>) distributionSetMetadataRepository.save(metadata));
}
@Override
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Modifying
public DistributionSetMetadata updateDistributionSetMetadata(final DistributionSetMetadata md) {
final JpaDistributionSetMetadata metadata = (JpaDistributionSetMetadata) md;
// check if exists otherwise throw entity not found exception
findOne(metadata.getDistributionSet(), metadata.getKey());
// touch it to update the lock revision because we are modifying the
// DS indirectly
entityManager.merge((JpaDistributionSet) metadata.getDistributionSet()).setLastModifiedAt(0L);
return distributionSetMetadataRepository.save(metadata);
}
@Override
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Modifying
public void deleteDistributionSetMetadata(final DistributionSet distributionSet, final String key) {
distributionSetMetadataRepository.delete(new DsMetadataCompositeKey(distributionSet, key));
}
@Override
public Page<DistributionSetMetadata> findDistributionSetMetadataByDistributionSetId(final Long distributionSetId,
final Pageable pageable) {
return convertMdPage(distributionSetMetadataRepository
.findAll((Specification<JpaDistributionSetMetadata>) (root, query, cb) -> cb.equal(
root.get(JpaDistributionSetMetadata_.distributionSet).get(JpaDistributionSet_.id),
distributionSetId), pageable),
pageable);
}
@Override
public Page<DistributionSetMetadata> findDistributionSetMetadataByDistributionSetId(final Long distributionSetId,
final String rsqlParam, final Pageable pageable) {
final Specification<JpaDistributionSetMetadata> spec = RSQLUtility.parse(rsqlParam,
DistributionSetMetadataFields.class);
return convertMdPage(
distributionSetMetadataRepository
.findAll((Specification<JpaDistributionSetMetadata>) (root, query, cb) -> cb.and(
cb.equal(root.get(JpaDistributionSetMetadata_.distributionSet)
.get(JpaDistributionSet_.id), distributionSetId),
spec.toPredicate(root, query, cb)), pageable),
pageable);
}
private static Page<DistributionSetMetadata> convertMdPage(final Page<JpaDistributionSetMetadata> findAll,
final Pageable pageable) {
return new PageImpl<>(new ArrayList<>(findAll.getContent()), pageable, findAll.getTotalElements());
}
@Override
public DistributionSetMetadata findOne(final DistributionSet distributionSet, final String key) {
final DistributionSetMetadata findOne = distributionSetMetadataRepository
.findOne(new DsMetadataCompositeKey(distributionSet, key));
if (findOne == null) {
throw new EntityNotFoundException("Metadata with key '" + key + "' does not exist");
}
return findOne;
}
@Override
public DistributionSet findDistributionSetByAction(final Action action) {
return distributionSetRepository.findByAction((JpaAction) action);
}
@Override
public boolean isDistributionSetInUse(final DistributionSet distributionSet) {
return actionRepository.countByDistributionSet((JpaDistributionSet) distributionSet) > 0;
}
private static List<Specification<JpaDistributionSet>> buildDistributionSetSpecifications(
final DistributionSetFilter distributionSetFilter) {
final List<Specification<JpaDistributionSet>> specList = new ArrayList<>();
Specification<JpaDistributionSet> spec;
if (null != distributionSetFilter.getIsComplete()) {
spec = DistributionSetSpecification.isCompleted(distributionSetFilter.getIsComplete());
specList.add(spec);
}
if (null != distributionSetFilter.getIsDeleted()) {
spec = DistributionSetSpecification.isDeleted(distributionSetFilter.getIsDeleted());
specList.add(spec);
}
if (distributionSetFilter.getType() != null) {
spec = DistributionSetSpecification.byType(distributionSetFilter.getType());
specList.add(spec);
}
if (!Strings.isNullOrEmpty(distributionSetFilter.getSearchText())) {
spec = DistributionSetSpecification.likeNameOrDescriptionOrVersion(distributionSetFilter.getSearchText());
specList.add(spec);
}
if (isDSWithNoTagSelected(distributionSetFilter) || isTagsSelected(distributionSetFilter)) {
spec = DistributionSetSpecification.hasTags(distributionSetFilter.getTagNames(),
distributionSetFilter.getSelectDSWithNoTag());
specList.add(spec);
}
if (distributionSetFilter.getInstalledTargetId() != null) {
spec = DistributionSetSpecification.installedTarget(distributionSetFilter.getInstalledTargetId());
specList.add(spec);
}
if (distributionSetFilter.getAssignedTargetId() != null) {
spec = DistributionSetSpecification.assignedTarget(distributionSetFilter.getAssignedTargetId());
specList.add(spec);
}
return specList;
}
private void checkDistributionSetSoftwareModulesIsAllowedToModify(final JpaDistributionSet distributionSet,
final Set<SoftwareModule> softwareModules) {
if (!new HashSet<SoftwareModule>(distributionSet.getModules()).equals(softwareModules)
&& actionRepository.countByDistributionSet(distributionSet) > 0) {
throw new EntityLockedException(
String.format("distribution set %s:%s is already assigned to targets and cannot be changed",
distributionSet.getName(), distributionSet.getVersion()));
}
}
private static Boolean isDSWithNoTagSelected(final DistributionSetFilter distributionSetFilter) {
if (distributionSetFilter.getSelectDSWithNoTag() != null && distributionSetFilter.getSelectDSWithNoTag()) {
return true;
}
return false;
}
private static Boolean isTagsSelected(final DistributionSetFilter distributionSetFilter) {
if (distributionSetFilter.getTagNames() != null && !distributionSetFilter.getTagNames().isEmpty()) {
return true;
}
return false;
}
/**
* executes findAll with the given {@link DistributionSet}
* {@link Specification}s.
*
* @param pageable
* paging parameter
* @param specList
* list of @link {@link Specification}
* @return the page with the found {@link DistributionSet}
*/
private Page<JpaDistributionSet> findByCriteriaAPI(final Pageable pageable,
final List<Specification<JpaDistributionSet>> specList) {
if (specList == null || specList.isEmpty()) {
return distributionSetRepository.findAll(pageable);
}
return distributionSetRepository.findAll(SpecificationsBuilder.combineWithAnd(specList), pageable);
}
private void checkAndThrowAlreadyIfDistributionSetMetadataExists(final DsMetadataCompositeKey metadataId) {
if (distributionSetMetadataRepository.exists(metadataId)) {
throw new EntityAlreadyExistsException(
"Metadata entry with key '" + metadataId.getKey() + "' already exists");
}
}
private static void throwMetadataKeyAlreadyExists(final String metadataKey) {
throw new EntityAlreadyExistsException("Metadata entry with key '" + metadataKey + "' already exists");
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public List<DistributionSet> assignTag(final Collection<Long> dsIds, final DistributionSetTag tag) {
final List<JpaDistributionSet> allDs = findDistributionSetListWithDetails(dsIds);
allDs.forEach(ds -> ds.getTags().add(tag));
final List<DistributionSet> save = new ArrayList<>(distributionSetRepository.save(allDs));
afterCommit.afterCommit(() -> {
final DistributionSetTagAssignmentResult result = new DistributionSetTagAssignmentResult(0, save.size(), 0,
save, Collections.emptyList(), tag);
eventBus.post(new DistributionSetTagAssigmentResultEvent(result));
});
return save;
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public List<DistributionSet> unAssignAllDistributionSetsByTag(final DistributionSetTag tag) {
@SuppressWarnings({ "unchecked", "rawtypes" })
final Collection<JpaDistributionSet> distributionSets = (Collection) tag.getAssignedToDistributionSet();
return new ArrayList<>(unAssignTag(distributionSets, tag));
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public DistributionSet unAssignTag(final Long dsId, final DistributionSetTag distributionSetTag) {
final List<JpaDistributionSet> allDs = findDistributionSetListWithDetails(Arrays.asList(dsId));
final List<JpaDistributionSet> unAssignTag = unAssignTag(allDs, distributionSetTag);
return unAssignTag.isEmpty() ? null : unAssignTag.get(0);
}
private List<JpaDistributionSet> unAssignTag(final Collection<JpaDistributionSet> distributionSets,
final DistributionSetTag tag) {
distributionSets.forEach(ds -> ds.getTags().remove(tag));
return distributionSetRepository.save(distributionSets);
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public List<DistributionSetType> createDistributionSetTypes(final Collection<DistributionSetType> types) {
return types.stream().map(this::createDistributionSetType).collect(Collectors.toList());
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public void deleteDistributionSet(final DistributionSet set) {
deleteDistributionSet(set.getId());
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public DistributionSetTagAssignmentResult toggleTagAssignment(final Collection<DistributionSet> sets,
final DistributionSetTag tag) {
return toggleTagAssignment(sets.stream().map(ds -> ds.getId()).collect(Collectors.toList()), tag.getName());
}
@Override
@Transactional(propagation = Propagation.SUPPORTS)
public DistributionSetType generateDistributionSetType() {
return new JpaDistributionSetType();
}
@Override
@Transactional(propagation = Propagation.SUPPORTS)
public DistributionSet generateDistributionSet() {
return new JpaDistributionSet();
}
@Override
@Transactional(propagation = Propagation.SUPPORTS)
public DistributionSetMetadata generateDistributionSetMetadata() {
return new JpaDistributionSetMetadata();
}
@Override
@Transactional(propagation = Propagation.SUPPORTS)
public DistributionSetMetadata generateDistributionSetMetadata(final DistributionSet distributionSet,
final String key, final String value) {
return new JpaDistributionSetMetadata(key, distributionSet, value);
}
@Override
@Transactional(propagation = Propagation.SUPPORTS)
public DistributionSetType generateDistributionSetType(final String key, final String name,
final String description) {
return new JpaDistributionSetType(key, name, description);
}
@Override
@Transactional(propagation = Propagation.SUPPORTS)
public DistributionSet generateDistributionSet(final String name, final String version, final String description,
final DistributionSetType type, final Collection<SoftwareModule> moduleList) {
return new JpaDistributionSet(name, version, description, type, moduleList);
}
@Override
public Long countDistributionSetsByType(final DistributionSetType type) {
return distributionSetRepository.countByType((JpaDistributionSetType) type);
}
}

View File

@@ -0,0 +1,436 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.ListJoin;
import javax.persistence.criteria.Root;
import org.eclipse.hawkbit.report.model.DataReportSeries;
import org.eclipse.hawkbit.report.model.DataReportSeriesItem;
import org.eclipse.hawkbit.report.model.InnerOuterDataReportSeries;
import org.eclipse.hawkbit.report.model.SeriesTime;
import org.eclipse.hawkbit.repository.ReportManagement;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet_;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetInfo;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetInfo_;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget_;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
/**
* JPA implementation of {@link ReportManagement}.
*
*/
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
@Validated
@Service
public class JpaReportManagement implements ReportManagement {
@Value("${spring.jpa.database}")
private String databaseType;
private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM");
private static final String H2_TARGET_CREATED_SQL_TEMPLATE = "SELECT TO_CHAR( DATEADD('second', target0_.created_at / 1000, DATE '1970-01-01'), '%s') AS col_0_0_, count(target0_.controller_id) AS col_1_0_ from sp_target target0_ WHERE TO_CHAR(DATEADD('second', target0_.created_at / 1000, DATE '1970-01-01'),'%s') BETWEEN TO_CHAR('%s', '%s') and TO_CHAR('%s', '%s') AND UPPER(target0_.tenant)=UPPER('%s') GROUP BY TO_CHAR(DATEADD('second', target0_.created_at / 1000, DATE '1970-01-01'), '%s')";
private static final String H2_CONTROLLER_FRRDBACK_SQL_TEMPLATE = "SELECT TO_CHAR(DATEADD('second', action_.created_at / 1000, DATE '1970-01-01'), '%s') AS col_0_0_, count(action_.id) AS col_1_0_ FROM sp_action action_ WHERE TO_CHAR(DATEADD('second', action_.created_at / 1000, DATE '1970-01-01'), '%s') BETWEEN TO_CHAR('%s', '%s') AND TO_CHAR('%s', '%s') AND UPPER(action_.tenant)=UPPER('%s') GROUP BY TO_CHAR(DATEADD('second', action_.created_at / 1000, DATE '1970-01-01'), '%s')";
private static final String MYSQL_TARGET_CREATED_SQL_TEMPLATE = "SELECT DATE_FORMAT(FROM_UNIXTIME(target0_.created_at / 1000), '%s') AS col_0_0_, COUNT(target0_.controller_id) AS col_1_0_ FROM sp_target target0_ WHERE DATE_FORMAT(FROM_UNIXTIME(target0_.created_at / 1000),'%s') BETWEEN DATE_FORMAT('%s', '%s') AND DATE_FORMAT('%s', '%s') AND UPPER(target0_.tenant)=UPPER('%s') GROUP BY DATE_FORMAT(FROM_UNIXTIME(target0_.created_at / 1000), '%s')";
private static final String MYSQL_CONTROLLER_FRRDBACK_SQL_TEMPLATE = "SELECT DATE_FORMAT(FROM_UNIXTIME(action_.created_at / 1000), '%s') AS col_0_0_, COUNT(action_.id) as col_1_0_ FROM sp_action action_ WHERE DATE_FORMAT(FROM_UNIXTIME(action_.created_at / 1000),'%s') BETWEEN DATE_FORMAT('%s', '%s') AND DATE_FORMAT('%s', '%s') AND UPPER(action_.tenant)=UPPER('%s') GROUP BY DATE_FORMAT(FROM_UNIXTIME(action_.created_at / 1000), '%s')";
private static final String MYSQL_DB_TYPE = "MYSQL";
private static final String H2_DB_TYPE = "H2";
@Autowired
private EntityManager entityManager;
@Autowired
private TenantAware tenantAware;
@Override
@Cacheable("targetStatus")
public DataReportSeries<TargetUpdateStatus> targetStatus() {
final CriteriaBuilder cb = entityManager.getCriteriaBuilder();
final CriteriaQuery<Object[]> query = cb.createQuery(Object[].class);
final Root<JpaTarget> targetRoot = query.from(JpaTarget.class);
final Join<JpaTarget, JpaTargetInfo> targetInfo = targetRoot.join(JpaTarget_.targetInfo);
final Expression<Long> countColumn = cb.count(targetInfo.get(JpaTargetInfo_.targetId));
final CriteriaQuery<Object[]> multiselect = query
.multiselect(targetInfo.get(JpaTargetInfo_.updateStatus), countColumn)
.groupBy(targetInfo.get(JpaTargetInfo_.updateStatus))
.orderBy(cb.desc(targetInfo.get(JpaTargetInfo_.updateStatus)));
// | col1 | col2 |
// | U_STATUS | COUNT |
final List<Object[]> resultList = entityManager.createQuery(multiselect).getResultList();
final List<DataReportSeriesItem<TargetUpdateStatus>> reportSeriesItems = resultList.stream()
.map(r -> new DataReportSeriesItem<TargetUpdateStatus>((TargetUpdateStatus) r[0], (Long) r[1]))
.collect(Collectors.toList());
return new DataReportSeries<>("Target Status Overview", reportSeriesItems);
}
@Override
@Cacheable("distributionUsageAssigned")
public List<InnerOuterDataReportSeries<String>> distributionUsageAssigned(final int topXEntries) {
// top X entries distribution usage
final CriteriaBuilder cbTopX = entityManager.getCriteriaBuilder();
final CriteriaQuery<Object[]> queryTopX = cbTopX.createQuery(Object[].class);
final Root<JpaDistributionSet> rootTopX = queryTopX.from(JpaDistributionSet.class);
final ListJoin<JpaDistributionSet, JpaTarget> joinTopX = rootTopX.join(JpaDistributionSet_.assignedToTargets,
JoinType.LEFT);
final Expression<Long> countColumn = cbTopX.count(joinTopX);
// top x usage query
final CriteriaQuery<Object[]> groupBy = queryTopX
.multiselect(rootTopX.get(JpaDistributionSet_.name), rootTopX.get(JpaDistributionSet_.version),
countColumn)
.where(cbTopX.equal(rootTopX.get(JpaDistributionSet_.deleted), false))
.groupBy(rootTopX.get(JpaDistributionSet_.name), rootTopX.get(JpaDistributionSet_.version))
.orderBy(cbTopX.desc(countColumn), cbTopX.asc(rootTopX.get(JpaDistributionSet_.name)));
// | col1 | col2 | col3 |
// | NAME | VER | COUNT |
final List<Object[]> resultListTop = entityManager.createQuery(groupBy).getResultList();
// end of top X entries distribution usage
return mapDistirbutionUsageResultToDataReport(topXEntries, resultListTop);
}
@Override
@Cacheable("distributionUsageInstalled")
public List<InnerOuterDataReportSeries<String>> distributionUsageInstalled(final int topXEntries) {
// top X entries distribution usage
final CriteriaBuilder cbTopX = entityManager.getCriteriaBuilder();
final CriteriaQuery<Object[]> queryTopX = cbTopX.createQuery(Object[].class);
final Root<JpaDistributionSet> rootTopX = queryTopX.from(JpaDistributionSet.class);
final ListJoin<JpaDistributionSet, JpaTargetInfo> joinTopX = rootTopX
.join(JpaDistributionSet_.installedAtTargets, JoinType.LEFT);
final Expression<Long> countColumn = cbTopX.count(joinTopX);
// top x usage query
final CriteriaQuery<Object[]> groupBy = queryTopX
.multiselect(rootTopX.get(JpaDistributionSet_.name), rootTopX.get(JpaDistributionSet_.version),
countColumn)
.where(cbTopX.equal(rootTopX.get(JpaDistributionSet_.deleted), false))
.groupBy(rootTopX.get(JpaDistributionSet_.name), rootTopX.get(JpaDistributionSet_.version))
.orderBy(cbTopX.desc(countColumn), cbTopX.asc(rootTopX.get(JpaDistributionSet_.name)));
// | col1 | col2 | col3 |
// | NAME | VER | COUNT |
final List<Object[]> resultListTop = entityManager.createQuery(groupBy).getResultList();
// end of top X entries distribution usage
return mapDistirbutionUsageResultToDataReport(topXEntries, resultListTop);
}
@Override
@Cacheable("targetsCreatedOverPeriod")
public <T extends Serializable> DataReportSeries<T> targetsCreatedOverPeriod(final DateType<T> dateType,
final LocalDateTime from, final LocalDateTime to) {
final Query createNativeQuery = entityManager
.createNativeQuery(getTargetsCreatedQueryTemplate(dateType, from, to));
final List<Object[]> resultList = createNativeQuery.getResultList();
final List<DataReportSeriesItem<T>> reportItems = resultList.stream()
.map(r -> new DataReportSeriesItem<>(dateType.format((String) r[0]), ((Number) r[1]).longValue()))
.collect(Collectors.toList());
return new DataReportSeries<>("CreatedTargets", reportItems);
}
private String getTargetsCreatedQueryTemplate(final DateType<?> dateType, final LocalDateTime from,
final LocalDateTime to) {
switch (databaseType) {
case H2_DB_TYPE:
return String.format(H2_TARGET_CREATED_SQL_TEMPLATE, dateTimeFormatToSqlFormat(dateType),
dateTimeFormatToSqlFormat(dateType), from.format(DATE_FORMAT), dateTimeFormatToSqlFormat(dateType),
to.format(DATE_FORMAT), dateTimeFormatToSqlFormat(dateType), tenantAware.getCurrentTenant(),
dateTimeFormatToSqlFormat(dateType));
case MYSQL_DB_TYPE:
return String.format(MYSQL_TARGET_CREATED_SQL_TEMPLATE, dateTimeFormatToSqlFormat(dateType),
dateTimeFormatToSqlFormat(dateType), from.toString(), dateTimeFormatToSqlFormat(dateType),
to.toString(), dateTimeFormatToSqlFormat(dateType), tenantAware.getCurrentTenant(),
dateTimeFormatToSqlFormat(dateType));
default:
return null;
}
}
private String getFeedbackReceivedQueryTemplate(final DateType<?> dateType, final LocalDateTime from,
final LocalDateTime to) {
switch (databaseType) {
case H2_DB_TYPE:
return String.format(H2_CONTROLLER_FRRDBACK_SQL_TEMPLATE, dateTimeFormatToSqlFormat(dateType),
dateTimeFormatToSqlFormat(dateType), from.format(DATE_FORMAT), dateTimeFormatToSqlFormat(dateType),
to.format(DATE_FORMAT), dateTimeFormatToSqlFormat(dateType), tenantAware.getCurrentTenant(),
dateTimeFormatToSqlFormat(dateType));
case MYSQL_DB_TYPE:
return String.format(MYSQL_CONTROLLER_FRRDBACK_SQL_TEMPLATE, dateTimeFormatToSqlFormat(dateType),
dateTimeFormatToSqlFormat(dateType), from.toString(), dateTimeFormatToSqlFormat(dateType),
to.toString(), dateTimeFormatToSqlFormat(dateType), tenantAware.getCurrentTenant(),
dateTimeFormatToSqlFormat(dateType));
default:
return null;
}
}
@Override
@Cacheable("feedbackReceivedOverTime")
public <T extends Serializable> DataReportSeries<T> feedbackReceivedOverTime(final DateType<T> dateType,
final LocalDateTime from, final LocalDateTime to) {
final Query createNativeQuery = entityManager
.createNativeQuery(getFeedbackReceivedQueryTemplate(dateType, from, to));
@SuppressWarnings("unchecked")
final List<Object[]> resultList = createNativeQuery.getResultList();
final List<DataReportSeriesItem<T>> reportItems = resultList.stream()
.map(r -> new DataReportSeriesItem<>(dateType.format((String) r[0]), ((Number) r[1]).longValue()))
.collect(Collectors.toList());
return new DataReportSeries<>("FeedbackRecieved", reportItems);
}
@Override
@Cacheable("targetsLastPoll")
public DataReportSeries<SeriesTime> targetsLastPoll() {
final LocalDateTime now = LocalDateTime.now();
final LocalDateTime beforeHour = now.minusHours(1);
final LocalDateTime beforeDay = now.minusDays(1);
final LocalDateTime beforeWeek = now.minusWeeks(1);
final LocalDateTime beforeMonth = now.minusMonths(1);
final LocalDateTime beforeYear = now.minusYears(1);
final CriteriaBuilder cb = entityManager.getCriteriaBuilder();
final List<DataReportSeriesItem<SeriesTime>> resultList = new ArrayList<>();
// hours
resultList.add(new DataReportSeriesItem<SeriesTime>(SeriesTime.HOUR,
entityManager.createQuery(createCountSelectTargetsLastPoll(cb, beforeHour, now)).getSingleResult()));
// days
resultList.add(new DataReportSeriesItem<SeriesTime>(SeriesTime.DAY, entityManager
.createQuery(createCountSelectTargetsLastPoll(cb, beforeDay, beforeHour)).getSingleResult()));
// weeks
resultList.add(new DataReportSeriesItem<SeriesTime>(SeriesTime.WEEK, entityManager
.createQuery(createCountSelectTargetsLastPoll(cb, beforeWeek, beforeDay)).getSingleResult()));
// months
resultList.add(new DataReportSeriesItem<SeriesTime>(SeriesTime.MONTH, entityManager
.createQuery(createCountSelectTargetsLastPoll(cb, beforeMonth, beforeWeek)).getSingleResult()));
// years
resultList.add(new DataReportSeriesItem<SeriesTime>(SeriesTime.YEAR, entityManager
.createQuery(createCountSelectTargetsLastPoll(cb, beforeYear, beforeMonth)).getSingleResult()));
// years
resultList.add(new DataReportSeriesItem<SeriesTime>(SeriesTime.MORE_THAN_YEAR,
entityManager.createQuery(createCountSelectTargetsLastPoll(cb, null, beforeYear)).getSingleResult()));
// never
resultList.add(new DataReportSeriesItem<SeriesTime>(SeriesTime.NEVER,
entityManager.createQuery(createCountSelectTargetsLastPoll(cb, null, null)).getSingleResult()));
return new DataReportSeries<>("TargetLastPoll", resultList);
}
private CriteriaQuery<Long> createCountSelectTargetsLastPoll(final CriteriaBuilder cb, final LocalDateTime from,
final LocalDateTime to) {
Long start = null;
Long end = null;
if (from != null) {
start = from.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
}
if (to != null) {
end = to.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
}
// count select statement
final CriteriaQuery<Long> countSelect = cb.createQuery(Long.class);
final Root<JpaTarget> countSelectRoot = countSelect.from(JpaTarget.class);
final Join<JpaTarget, JpaTargetInfo> targetInfoJoin = countSelectRoot.join(JpaTarget_.targetInfo);
countSelect.select(cb.count(countSelectRoot));
if (start != null && end != null) {
countSelect.where(cb.between(targetInfoJoin.get(JpaTargetInfo_.lastTargetQuery), start, end));
} else if (from == null && to != null) {
countSelect.where(cb.lessThanOrEqualTo(targetInfoJoin.get(JpaTargetInfo_.lastTargetQuery), end));
} else {
countSelect.where(cb.isNull(targetInfoJoin.get(JpaTargetInfo_.lastTargetQuery)));
}
return countSelect;
}
private List<InnerOuterDataReportSeries<String>> mapDistirbutionUsageResultToDataReport(final int topXEntries,
final List<Object[]> resultListTop) {
final List<InnerOuterDataReportSeries<String>> innerOuterReport = new ArrayList<>();
final Map<DSName, InnerOuter> map = new LinkedHashMap<>();
int topXCounter = 0;
for (final Object[] objects : resultListTop) {
final boolean containsInnerOuter = map.containsKey(new DSName((String) objects[0]));
final String name = containsInnerOuter || topXCounter < topXEntries ? (String) objects[0] : null;
final DSName dsName = new DSName(name);
final String version = containsInnerOuter || topXCounter < topXEntries ? (String) objects[1] : null;
final Long count = (Long) objects[2];
InnerOuter innerouter = map.get(dsName);
if (innerouter == null) {
topXCounter++;
innerouter = new InnerOuter(dsName);
map.put(new DSName(name), innerouter);
}
innerouter.addOuter(new DSName(version), count);
}
for (final InnerOuter inner : map.values()) {
final List<DataReportSeriesItem<String>> outerReportItems = new ArrayList<>();
if (inner.name.getName() != null) {
for (final InnerOuter outer : inner.outer) {
outerReportItems.add(outer.toItem());
}
} else {
outerReportItems.add(new DataReportSeriesItem<String>("misc", inner.count));
}
innerOuterReport.add(new InnerOuterDataReportSeries<String>(
new DataReportSeries<>("DS-Name", Collections.singletonList(inner.toItem())),
new DataReportSeries<>("DS-Version", outerReportItems)));
}
return innerOuterReport;
}
private static final class InnerOuter {
final DSName name;
long count;
final List<InnerOuter> outer;
private InnerOuter(final DSName idName) {
name = idName;
outer = new ArrayList<>();
}
private InnerOuter(final DSName idName, final long count) {
name = idName;
this.count = count;
outer = new ArrayList<>();
}
private void addOuter(final DSName idName, final long count) {
outer.add(new InnerOuter(idName, count));
this.count += count;
}
private DataReportSeriesItem<String> toItem() {
return new DataReportSeriesItem<>(name.getName() != null ? name.getName() : "misc", count);
}
}
/**
* Object contains the name and the id of an entity.
*
*/
private static final class DSName {
private final String name;
/**
* @param id
* the ID of an entity
* @param name
* the name of an entity
*/
private DSName(final String name) {
this.name = name;
}
/**
* @return the name
*/
private String getName() {
return name;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (name == null ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(final Object obj) { // NOSONAR - as this is
// generated
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final DSName other = (DSName) obj;
if (name == null) {
if (other.name != null) {
return false;
}
} else if (!name.equals(other.name)) {
return false;
}
return true;
}
@Override
public String toString() {
return "DSName [name=" + name + "]";
}
}
private String dateTimeFormatToSqlFormat(final DateType<?> datatype) {
switch (databaseType) {
case H2_DB_TYPE:
return datatype.h2Format();
case MYSQL_DB_TYPE:
return datatype.mySqlFormat();
default:
return null;
}
}
}

View File

@@ -0,0 +1,209 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.persistence.EntityManager;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.ListJoin;
import javax.persistence.criteria.Root;
import org.eclipse.hawkbit.repository.RolloutGroupFields;
import org.eclipse.hawkbit.repository.RolloutGroupManagement;
import org.eclipse.hawkbit.repository.TargetFields;
import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
import org.eclipse.hawkbit.repository.jpa.model.JpaAction_;
import org.eclipse.hawkbit.repository.jpa.model.JpaRolloutGroup;
import org.eclipse.hawkbit.repository.jpa.model.JpaRolloutGroup_;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget_;
import org.eclipse.hawkbit.repository.jpa.model.RolloutTargetGroup;
import org.eclipse.hawkbit.repository.jpa.model.RolloutTargetGroup_;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.Rollout.RolloutStatus;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetWithActionStatus;
import org.eclipse.hawkbit.repository.model.TotalTargetCountActionStatus;
import org.eclipse.hawkbit.repository.model.TotalTargetCountStatus;
import org.eclipse.hawkbit.repository.rsql.RSQLUtility;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
/**
* JPA implementation of {@link RolloutGroupManagement}.
*/
@Validated
@Service
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
public class JpaRolloutGroupManagement implements RolloutGroupManagement {
@Autowired
private RolloutGroupRepository rolloutGroupRepository;
@Autowired
private ActionRepository actionRepository;
@Autowired
private TargetRepository targetRepository;
@Autowired
private EntityManager entityManager;
@Override
public RolloutGroup findRolloutGroupById(final Long rolloutGroupId) {
return rolloutGroupRepository.findOne(rolloutGroupId);
}
@Override
public Page<RolloutGroup> findRolloutGroupsByRolloutId(final Long rolloutId, final Pageable pageable) {
return convertPage(rolloutGroupRepository.findByRolloutId(rolloutId, pageable), pageable);
}
private static Page<RolloutGroup> convertPage(final Page<JpaRolloutGroup> findAll, final Pageable pageable) {
return new PageImpl<>(new ArrayList<>(findAll.getContent()), pageable, findAll.getTotalElements());
}
private static Page<Target> convertTPage(final Page<JpaTarget> findAll, final Pageable pageable) {
return new PageImpl<>(new ArrayList<>(findAll.getContent()), pageable, findAll.getTotalElements());
}
@Override
public Page<RolloutGroup> findRolloutGroupsAll(final Rollout rollout, final String rsqlParam,
final Pageable pageable) {
final Specification<JpaRolloutGroup> specification = RSQLUtility.parse(rsqlParam, RolloutGroupFields.class);
return convertPage(
rolloutGroupRepository
.findAll(
(root, query, criteriaBuilder) -> criteriaBuilder.and(
criteriaBuilder.equal(root.get(JpaRolloutGroup_.rollout), rollout),
specification.toPredicate(root, query, criteriaBuilder)),
pageable),
pageable);
}
@Override
public Page<RolloutGroup> findAllRolloutGroupsWithDetailedStatus(final Long rolloutId, final Pageable pageable) {
final Page<JpaRolloutGroup> rolloutGroups = rolloutGroupRepository.findByRolloutId(rolloutId, pageable);
final List<Long> rolloutGroupIds = rolloutGroups.getContent().stream().map(rollout -> rollout.getId())
.collect(Collectors.toList());
final Map<Long, List<TotalTargetCountActionStatus>> allStatesForRollout = getStatusCountItemForRolloutGroup(
rolloutGroupIds);
for (final RolloutGroup rolloutGroup : rolloutGroups) {
final TotalTargetCountStatus totalTargetCountStatus = new TotalTargetCountStatus(
allStatesForRollout.get(rolloutGroup.getId()), rolloutGroup.getTotalTargets());
rolloutGroup.setTotalTargetCountStatus(totalTargetCountStatus);
}
return convertPage(rolloutGroups, pageable);
}
@Override
public RolloutGroup findRolloutGroupWithDetailedStatus(final Long rolloutGroupId) {
final RolloutGroup rolloutGroup = findRolloutGroupById(rolloutGroupId);
final List<TotalTargetCountActionStatus> rolloutStatusCountItems = actionRepository
.getStatusCountByRolloutGroupId(rolloutGroupId);
final TotalTargetCountStatus totalTargetCountStatus = new TotalTargetCountStatus(rolloutStatusCountItems,
rolloutGroup.getTotalTargets());
rolloutGroup.setTotalTargetCountStatus(totalTargetCountStatus);
return rolloutGroup;
}
private Map<Long, List<TotalTargetCountActionStatus>> getStatusCountItemForRolloutGroup(
final List<Long> rolloutGroupIds) {
final List<TotalTargetCountActionStatus> resultList = actionRepository
.getStatusCountByRolloutGroupId(rolloutGroupIds);
return resultList.stream().collect(Collectors.groupingBy(TotalTargetCountActionStatus::getId));
}
@Override
public Page<Target> findRolloutGroupTargets(final RolloutGroup rolloutGroup, final String rsqlParam,
final Pageable pageable) {
final Specification<JpaTarget> rsqlSpecification = RSQLUtility.parse(rsqlParam, TargetFields.class);
return convertTPage(targetRepository.findAll((root, query, criteriaBuilder) -> {
final ListJoin<JpaTarget, RolloutTargetGroup> rolloutTargetJoin = root.join(JpaTarget_.rolloutTargetGroup);
return criteriaBuilder.and(rsqlSpecification.toPredicate(root, query, criteriaBuilder),
criteriaBuilder.equal(rolloutTargetJoin.get(RolloutTargetGroup_.rolloutGroup), rolloutGroup));
}, pageable), pageable);
}
@Override
public Page<Target> findRolloutGroupTargets(final RolloutGroup rolloutGroup, final Pageable page) {
if (isRolloutStatusReady(rolloutGroup)) {
// in case of status ready the action has not been created yet and
// the relation information between target and rollout-group is
// stored in the #TargetRolloutGroup.
return targetRepository.findByRolloutTargetGroupRolloutGroupId(rolloutGroup.getId(), page);
}
return targetRepository.findByActionsRolloutGroup((JpaRolloutGroup) rolloutGroup, page);
}
private static boolean isRolloutStatusReady(final RolloutGroup rolloutGroup) {
return rolloutGroup != null && RolloutStatus.READY.equals(rolloutGroup.getRollout().getStatus());
}
@Override
public Page<TargetWithActionStatus> findAllTargetsWithActionStatus(final PageRequest pageRequest,
final RolloutGroup rolloutGroup) {
final CriteriaBuilder cb = entityManager.getCriteriaBuilder();
final CriteriaQuery<Object[]> query = cb.createQuery(Object[].class);
final CriteriaQuery<Long> countQuery = cb.createQuery(Long.class);
final Root<RolloutTargetGroup> targetRoot = query.distinct(true).from(RolloutTargetGroup.class);
final Join<RolloutTargetGroup, JpaTarget> targetJoin = targetRoot.join(RolloutTargetGroup_.target);
final ListJoin<RolloutTargetGroup, JpaAction> actionJoin = targetRoot.join(RolloutTargetGroup_.actions,
JoinType.LEFT);
final Root<RolloutTargetGroup> countQueryFrom = countQuery.distinct(true).from(RolloutTargetGroup.class);
countQueryFrom.join(RolloutTargetGroup_.target);
countQueryFrom.join(RolloutTargetGroup_.actions, JoinType.LEFT);
countQuery.select(cb.count(countQueryFrom))
.where(cb.equal(countQueryFrom.get(RolloutTargetGroup_.rolloutGroup), rolloutGroup));
final Long totalCount = entityManager.createQuery(countQuery).getSingleResult();
final CriteriaQuery<Object[]> multiselect = query.multiselect(targetJoin, actionJoin.get(JpaAction_.status))
.where(cb.equal(targetRoot.get(RolloutTargetGroup_.rolloutGroup), rolloutGroup));
final List<TargetWithActionStatus> targetWithActionStatus = entityManager.createQuery(multiselect)
.setFirstResult(pageRequest.getOffset()).setMaxResults(pageRequest.getPageSize()).getResultList()
.stream().map(o -> new TargetWithActionStatus((Target) o[0], (Action.Status) o[1]))
.collect(Collectors.toList());
return new PageImpl<>(targetWithActionStatus, pageRequest, totalCount);
}
@Override
@Transactional(propagation = Propagation.SUPPORTS)
public RolloutGroup generateRolloutGroup() {
return new JpaRolloutGroup();
}
}

View File

@@ -0,0 +1,675 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.stream.Collectors;
import javax.persistence.EntityManager;
import org.eclipse.hawkbit.cache.CacheWriteNotify;
import org.eclipse.hawkbit.repository.DeploymentManagement;
import org.eclipse.hawkbit.repository.OffsetBasedPageRequest;
import org.eclipse.hawkbit.repository.RolloutFields;
import org.eclipse.hawkbit.repository.RolloutManagement;
import org.eclipse.hawkbit.repository.TargetManagement;
import org.eclipse.hawkbit.repository.TargetWithActionType;
import org.eclipse.hawkbit.repository.exception.RolloutIllegalStateException;
import org.eclipse.hawkbit.repository.jpa.model.JpaRollout;
import org.eclipse.hawkbit.repository.jpa.model.JpaRolloutGroup;
import org.eclipse.hawkbit.repository.jpa.model.JpaRollout_;
import org.eclipse.hawkbit.repository.jpa.model.RolloutTargetGroup;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.Rollout.RolloutStatus;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupErrorCondition;
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupStatus;
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessCondition;
import org.eclipse.hawkbit.repository.model.RolloutGroupConditions;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TotalTargetCountActionStatus;
import org.eclipse.hawkbit.repository.model.TotalTargetCountStatus;
import org.eclipse.hawkbit.repository.rsql.RSQLUtility;
import org.eclipse.hawkbit.rollout.condition.RolloutGroupActionEvaluator;
import org.eclipse.hawkbit.rollout.condition.RolloutGroupConditionEvaluator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.Assert;
import org.springframework.validation.annotation.Validated;
/**
* JPA implementation of {@link RolloutManagement}.
*/
@Validated
@Service
@EnableScheduling
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
public class JpaRolloutManagement implements RolloutManagement {
private static final Logger LOGGER = LoggerFactory.getLogger(RolloutManagement.class);
@Autowired
private EntityManager entityManager;
@Autowired
private RolloutRepository rolloutRepository;
@Autowired
private TargetManagement targetManagement;
@Autowired
private TargetRepository targetRepository;
@Autowired
private RolloutGroupRepository rolloutGroupRepository;
@Autowired
private DeploymentManagement deploymentManagement;
@Autowired
private RolloutTargetGroupRepository rolloutTargetGroupRepository;
@Autowired
private ActionRepository actionRepository;
@Autowired
private ApplicationContext context;
@Autowired
private NoCountPagingRepository criteriaNoCountDao;
@Autowired
private PlatformTransactionManager txManager;
@Autowired
private CacheWriteNotify cacheWriteNotify;
@Autowired
@Qualifier("asyncExecutor")
private Executor executor;
/*
* set which stores the rollouts which are asynchronously creating. This is
* necessary to verify rollouts which maybe stuck during creationg e.g.
* because of database interruption, failures or even application crash.
* !This is not cluster aware!
*/
private static final Set<String> creatingRollouts = ConcurrentHashMap.newKeySet();
/*
* set which stores the rollouts which are asynchronously starting. This is
* necessary to verify rollouts which maybe stuck during starting e.g.
* because of database interruption, failures or even application crash.
* !This is not cluster aware!
*/
private static final Set<String> startingRollouts = ConcurrentHashMap.newKeySet();
@Override
public Page<Rollout> findAll(final Pageable pageable) {
return convertPage(rolloutRepository.findAll(pageable), pageable);
}
private static Page<Rollout> convertPage(final Page<JpaRollout> findAll, final Pageable pageable) {
return new PageImpl<>(new ArrayList<>(findAll.getContent()), pageable, findAll.getTotalElements());
}
private static Slice<Rollout> convertPage(final Slice<JpaRollout> findAll, final Pageable pageable) {
return new PageImpl<>(new ArrayList<>(findAll.getContent()), pageable, 0);
}
@Override
public Page<Rollout> findAllWithDetailedStatusByPredicate(final String rsqlParam, final Pageable pageable) {
final Specification<JpaRollout> specification = RSQLUtility.parse(rsqlParam, RolloutFields.class);
final Page<JpaRollout> findAll = rolloutRepository.findAll(specification, pageable);
setRolloutStatusDetails(findAll);
return convertPage(findAll, pageable);
}
@Override
public Rollout findRolloutById(final Long rolloutId) {
return rolloutRepository.findOne(rolloutId);
}
@Override
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Modifying
public Rollout createRollout(final Rollout rollout, final int amountGroup,
final RolloutGroupConditions conditions) {
final JpaRollout savedRollout = createRollout((JpaRollout) rollout, amountGroup);
return createRolloutGroups(amountGroup, conditions, savedRollout);
}
@Override
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Modifying
public Rollout createRolloutAsync(final Rollout rollout, final int amountGroup,
final RolloutGroupConditions conditions) {
final JpaRollout savedRollout = createRollout((JpaRollout) rollout, amountGroup);
creatingRollouts.add(savedRollout.getName());
// need to flush the entity manager here to get the ID of the rollout,
// because entity manager is set to FlushMode#Auto, entitymanager will
// flush the Target entity, due the indirect relationship to the Rollout
// entity without set an ID JPA is throwing a Invalid
// 'org.springframework.dao.InvalidDataAccessApiUsageException: During
// synchronization aect was found through a relationship that was not
// marked cascade PERSIST'
entityManager.flush();
executor.execute(() -> {
try {
createRolloutGroupsInNewTransaction(amountGroup, conditions, savedRollout);
} finally {
creatingRollouts.remove(savedRollout.getName());
}
});
return savedRollout;
}
private JpaRollout createRollout(final JpaRollout rollout, final int amountGroup) {
verifyRolloutGroupParameter(amountGroup);
final Long totalTargets = targetManagement.countTargetByTargetFilterQuery(rollout.getTargetFilterQuery());
rollout.setTotalTargets(totalTargets.longValue());
return rolloutRepository.save(rollout);
}
private static void verifyRolloutGroupParameter(final int amountGroup) {
if (amountGroup <= 0) {
throw new IllegalArgumentException("the amountGroup must be greater than zero");
} else if (amountGroup > 500) {
throw new IllegalArgumentException("the amountGroup must not be greater than 500");
}
}
private Rollout createRolloutGroupsInNewTransaction(final int amountOfGroups,
final RolloutGroupConditions conditions, final JpaRollout savedRollout) {
final DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setName("creatingRollout");
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
return new TransactionTemplate(txManager, def)
.execute(status -> createRolloutGroups(amountOfGroups, conditions, savedRollout));
}
/**
* Method for creating rollout groups and calculating group sizes. Group
* sizes are calculated by dividing the total count of targets through the
* amount of given groups. In same cases this will lead to less rollout
* groups than given by client.
*
* @param amountOfGroups
* the amount of groups
* @param conditions
* the rollout group conditions
* @param savedRollout
* the rollout
* @return the rollout with created groups
*/
private Rollout createRolloutGroups(final int amountOfGroups, final RolloutGroupConditions conditions,
final JpaRollout savedRollout) {
int pageIndex = 0;
int groupIndex = 0;
final Long totalCount = savedRollout.getTotalTargets();
final int groupSize = (int) Math.ceil((double) totalCount / (double) amountOfGroups);
// validate if the amount of groups that will be created are the amount
// of groups that the client what's to have created.
int amountGroupValidated = amountOfGroups;
final int amountGroupCreation = (int) (Math.ceil((double) totalCount / (double) groupSize));
if (amountGroupCreation == (amountOfGroups - 1)) {
amountGroupValidated--;
}
RolloutGroup lastSavedGroup = null;
while (pageIndex < totalCount) {
groupIndex++;
final String nameAndDesc = "group-" + groupIndex;
final JpaRolloutGroup group = new JpaRolloutGroup();
group.setName(nameAndDesc);
group.setDescription(nameAndDesc);
group.setRollout(savedRollout);
group.setParent(lastSavedGroup);
group.setSuccessCondition(conditions.getSuccessCondition());
group.setSuccessConditionExp(conditions.getSuccessConditionExp());
group.setErrorCondition(conditions.getErrorCondition());
group.setErrorConditionExp(conditions.getErrorConditionExp());
group.setErrorAction(conditions.getErrorAction());
group.setErrorActionExp(conditions.getErrorActionExp());
final JpaRolloutGroup savedGroup = rolloutGroupRepository.save(group);
final Slice<Target> targetGroup = targetManagement.findTargetsAll(savedRollout.getTargetFilterQuery(),
new OffsetBasedPageRequest(pageIndex, groupSize, new Sort(Direction.ASC, "id")));
savedGroup.setTotalTargets(targetGroup.getContent().size());
lastSavedGroup = savedGroup;
targetGroup
.forEach(target -> rolloutTargetGroupRepository.save(new RolloutTargetGroup(savedGroup, target)));
cacheWriteNotify.rolloutGroupCreated(groupIndex, savedRollout.getId(), savedGroup.getId(),
amountGroupValidated, groupIndex);
pageIndex += groupSize;
}
savedRollout.setStatus(RolloutStatus.READY);
return rolloutRepository.save(savedRollout);
}
@Override
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Modifying
public Rollout startRollout(final Rollout rollout) {
final JpaRollout mergedRollout = entityManager.merge((JpaRollout) rollout);
checkIfRolloutCanStarted(rollout, mergedRollout);
return doStartRollout(mergedRollout);
}
@Override
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Modifying
public Rollout startRolloutAsync(final Rollout rollout) {
final JpaRollout mergedRollout = entityManager.merge((JpaRollout) rollout);
checkIfRolloutCanStarted(rollout, mergedRollout);
mergedRollout.setStatus(RolloutStatus.STARTING);
final JpaRollout updatedRollout = rolloutRepository.save(mergedRollout);
startingRollouts.add(updatedRollout.getName());
executor.execute(() -> {
try {
final DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setName("startingRollout");
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
new TransactionTemplate(txManager, def).execute(status -> {
doStartRollout(updatedRollout);
return null;
});
} finally {
startingRollouts.remove(updatedRollout.getName());
}
});
return updatedRollout;
}
private Rollout doStartRollout(final JpaRollout rollout) {
final DistributionSet distributionSet = rollout.getDistributionSet();
final ActionType actionType = rollout.getActionType();
final long forceTime = rollout.getForcedTime();
final List<JpaRolloutGroup> rolloutGroups = rolloutGroupRepository.findByRolloutOrderByIdAsc(rollout);
for (int iGroup = 0; iGroup < rolloutGroups.size(); iGroup++) {
final JpaRolloutGroup rolloutGroup = rolloutGroups.get(iGroup);
final List<Target> targetGroup = targetRepository.findByRolloutTargetGroupRolloutGroup(rolloutGroup);
// firstgroup can already be started
if (iGroup == 0) {
final List<TargetWithActionType> targetsWithActionType = targetGroup.stream()
.map(t -> new TargetWithActionType(t.getControllerId(), actionType, forceTime))
.collect(Collectors.toList());
deploymentManagement.assignDistributionSet(distributionSet.getId(), targetsWithActionType, rollout,
rolloutGroup);
rolloutGroup.setStatus(RolloutGroupStatus.RUNNING);
} else {
// create only not active actions with status scheduled so they
// can be activated later
deploymentManagement.createScheduledAction(targetGroup, distributionSet, actionType, forceTime, rollout,
rolloutGroup);
rolloutGroup.setStatus(RolloutGroupStatus.SCHEDULED);
}
rolloutGroupRepository.save(rolloutGroup);
}
rollout.setStatus(RolloutStatus.RUNNING);
return rolloutRepository.save(rollout);
}
@Override
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Modifying
public void pauseRollout(final Rollout rollout) {
final JpaRollout mergedRollout = entityManager.merge((JpaRollout) rollout);
if (mergedRollout.getStatus() != RolloutStatus.RUNNING) {
throw new RolloutIllegalStateException("Rollout can only be paused in state running but current state is "
+ rollout.getStatus().name().toLowerCase());
}
// setting the complete rollout only in paused state. This is sufficient
// due the currently running groups will be completed and new groups are
// not started until rollout goes back to running state again. The
// periodically check for running rollouts will skip rollouts in pause
// state.
mergedRollout.setStatus(RolloutStatus.PAUSED);
rolloutRepository.save(mergedRollout);
}
@Override
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Modifying
public void resumeRollout(final Rollout rollout) {
final JpaRollout mergedRollout = entityManager.merge((JpaRollout) rollout);
if (!(RolloutStatus.PAUSED.equals(mergedRollout.getStatus()))) {
throw new RolloutIllegalStateException("Rollout can only be resumed in state paused but current state is "
+ rollout.getStatus().name().toLowerCase());
}
mergedRollout.setStatus(RolloutStatus.RUNNING);
rolloutRepository.save(mergedRollout);
}
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_UNCOMMITTED)
@Modifying
public void checkRunningRollouts(final long delayBetweenChecks) {
verifyStuckedRollouts();
final long lastCheck = System.currentTimeMillis();
final int updated = rolloutRepository.updateLastCheck(lastCheck, delayBetweenChecks, RolloutStatus.RUNNING);
if (updated == 0) {
// nothing to check, maybe another instance already checked in
// between
LOGGER.debug("No rolloutcheck necessary for current scheduled check {}, next check at {}", lastCheck,
lastCheck + delayBetweenChecks);
return;
}
final List<JpaRollout> rolloutsToCheck = rolloutRepository.findByLastCheckAndStatus(lastCheck,
RolloutStatus.RUNNING);
LOGGER.info("Found {} running rollouts to check", rolloutsToCheck.size());
for (final JpaRollout rollout : rolloutsToCheck) {
LOGGER.debug("Checking rollout {}", rollout);
final List<JpaRolloutGroup> rolloutGroups = rolloutGroupRepository.findByRolloutAndStatus(rollout,
RolloutGroupStatus.RUNNING);
if (rolloutGroups.isEmpty()) {
// no running rollouts, probably there was an error
// somewhere at the latest group. And the latest group has
// been switched from running into error state. So we need
// to find the latest group which
executeLatestRolloutGroup(rollout);
} else {
LOGGER.debug("Rollout {} has {} running groups", rollout.getId(), rolloutGroups.size());
executeRolloutGroups(rollout, rolloutGroups);
}
if (isRolloutComplete(rollout)) {
LOGGER.info("Rollout {} is finished, setting finished status", rollout);
rollout.setStatus(RolloutStatus.FINISHED);
rolloutRepository.save(rollout);
}
}
}
/**
* Verifies and handles stucked rollouts in asynchronous creation or
* starting state. If rollouts are created or started asynchronously it
* might be that they keep in state {@link RolloutStatus#CREATING} or
* {@link RolloutStatus#STARTING} due database or application interruption.
* In case this happens, set the rollout to error state.
*/
private void verifyStuckedRollouts() {
final List<JpaRollout> rolloutsInCreatingState = rolloutRepository.findByStatus(RolloutStatus.CREATING);
rolloutsInCreatingState.stream().filter(rollout -> !creatingRollouts.contains(rollout.getName()))
.forEach(rollout -> {
LOGGER.warn(
"Determined error during rollout creation of rollout {}, stucking in creating state, setting to status",
rollout, RolloutStatus.ERROR_CREATING);
rollout.setStatus(RolloutStatus.ERROR_CREATING);
rolloutRepository.save(rollout);
});
final List<JpaRollout> rolloutsInStartingState = rolloutRepository.findByStatus(RolloutStatus.STARTING);
rolloutsInStartingState.stream().filter(rollout -> !startingRollouts.contains(rollout.getName()))
.forEach(rollout -> {
LOGGER.warn(
"Determined error during rollout starting of rollout {}, stucking in starting state, setting to status",
rollout, RolloutStatus.ERROR_STARTING);
rollout.setStatus(RolloutStatus.ERROR_STARTING);
rolloutRepository.save(rollout);
});
}
private void executeRolloutGroups(final JpaRollout rollout, final List<JpaRolloutGroup> rolloutGroups) {
for (final JpaRolloutGroup rolloutGroup : rolloutGroups) {
// error state check, do we need to stop the whole
// rollout because of error?
final RolloutGroupErrorCondition errorCondition = rolloutGroup.getErrorCondition();
final boolean isError = checkErrorState(rollout, rolloutGroup, errorCondition);
if (isError) {
LOGGER.info("Rollout {} {} has error, calling error action", rollout.getName(), rollout.getId());
callErrorAction(rollout, rolloutGroup);
} else {
// not in error so check finished state, do we need to
// start the next group?
final RolloutGroupSuccessCondition finishedCondition = rolloutGroup.getSuccessCondition();
checkFinishCondition(rollout, rolloutGroup, finishedCondition);
if (isRolloutGroupComplete(rollout, rolloutGroup)) {
rolloutGroup.setStatus(RolloutGroupStatus.FINISHED);
rolloutGroupRepository.save(rolloutGroup);
}
}
}
}
private void executeLatestRolloutGroup(final JpaRollout rollout) {
final List<JpaRolloutGroup> latestRolloutGroup = rolloutGroupRepository
.findByRolloutAndStatusNotOrderByIdDesc(rollout, RolloutGroupStatus.SCHEDULED);
if (latestRolloutGroup.isEmpty()) {
return;
}
executeRolloutGroupSuccessAction(rollout, latestRolloutGroup.get(0));
}
private void callErrorAction(final Rollout rollout, final RolloutGroup rolloutGroup) {
try {
context.getBean(rolloutGroup.getErrorAction().getBeanName(), RolloutGroupActionEvaluator.class)
.eval(rollout, rolloutGroup, rolloutGroup.getErrorActionExp());
} catch (final BeansException e) {
LOGGER.error("Something bad happend when accessing the error action bean {}",
rolloutGroup.getErrorAction().getBeanName(), e);
}
}
private boolean isRolloutComplete(final JpaRollout rollout) {
final Long groupsActiveLeft = rolloutGroupRepository.countByRolloutAndStatusOrStatus(rollout,
RolloutGroupStatus.RUNNING, RolloutGroupStatus.SCHEDULED);
return groupsActiveLeft == 0;
}
private boolean isRolloutGroupComplete(final JpaRollout rollout, final JpaRolloutGroup rolloutGroup) {
final Long actionsLeftForRollout = actionRepository
.countByRolloutAndRolloutGroupAndStatusNotAndStatusNotAndStatusNot(rollout, rolloutGroup,
Action.Status.ERROR, Action.Status.FINISHED, Action.Status.CANCELED);
return actionsLeftForRollout == 0;
}
private boolean checkErrorState(final Rollout rollout, final RolloutGroup rolloutGroup,
final RolloutGroupErrorCondition errorCondition) {
if (errorCondition == null) {
// there is no error condition, so return false, don't have error.
return false;
}
try {
return context.getBean(errorCondition.getBeanName(), RolloutGroupConditionEvaluator.class).eval(rollout,
rolloutGroup, rolloutGroup.getErrorConditionExp());
} catch (final BeansException e) {
LOGGER.error("Something bad happend when accessing the error condition bean {}",
errorCondition.getBeanName(), e);
return false;
}
}
private boolean checkFinishCondition(final Rollout rollout, final RolloutGroup rolloutGroup,
final RolloutGroupSuccessCondition finishCondition) {
LOGGER.trace("Checking finish condition {} on rolloutgroup {}", finishCondition, rolloutGroup);
try {
final boolean isFinished = context
.getBean(finishCondition.getBeanName(), RolloutGroupConditionEvaluator.class)
.eval(rollout, rolloutGroup, rolloutGroup.getSuccessConditionExp());
if (isFinished) {
LOGGER.info("Rolloutgroup {} is finished, starting next group", rolloutGroup);
executeRolloutGroupSuccessAction(rollout, rolloutGroup);
} else {
LOGGER.debug("Rolloutgroup {} is still running", rolloutGroup);
}
return isFinished;
} catch (final BeansException e) {
LOGGER.error("Something bad happend when accessing the finish condition bean {}",
finishCondition.getBeanName(), e);
return false;
}
}
private void executeRolloutGroupSuccessAction(final Rollout rollout, final RolloutGroup rolloutGroup) {
context.getBean(rolloutGroup.getSuccessAction().getBeanName(), RolloutGroupActionEvaluator.class).eval(rollout,
rolloutGroup, rolloutGroup.getSuccessActionExp());
}
@Override
public Long countRolloutsAll() {
return rolloutRepository.count();
}
@Override
public Long countRolloutsAllByFilters(final String searchText) {
return rolloutRepository.count(likeNameOrDescription(searchText));
}
private static Specification<JpaRollout> likeNameOrDescription(final String searchText) {
return (rolloutRoot, query, criteriaBuilder) -> {
final String searchTextToLower = searchText.toLowerCase();
return criteriaBuilder.or(
criteriaBuilder.like(criteriaBuilder.lower(rolloutRoot.get(JpaRollout_.name)), searchTextToLower),
criteriaBuilder.like(criteriaBuilder.lower(rolloutRoot.get(JpaRollout_.description)),
searchTextToLower));
};
}
@Override
public Slice<Rollout> findRolloutByFilters(final Pageable pageable, final String searchText) {
final Specification<JpaRollout> specs = likeNameOrDescription(searchText);
final Slice<JpaRollout> findAll = criteriaNoCountDao.findAll(specs, pageable, JpaRollout.class);
setRolloutStatusDetails(findAll);
return convertPage(findAll, pageable);
}
@Override
public Rollout findRolloutByName(final String rolloutName) {
return rolloutRepository.findByName(rolloutName);
}
/**
* Update rollout details.
*
* @param rollout
* rollout to be updated
*
* @return Rollout updated rollout
*/
@Override
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Modifying
public Rollout updateRollout(final Rollout rollout) {
Assert.notNull(rollout.getId());
return rolloutRepository.save((JpaRollout) rollout);
}
/**
* Get count of targets in different status in rollout.
*
* @param page
* the page request to sort and limit the result
* @return a list of rollouts with details of targets count for different
* statuses
*
*/
@Override
public Page<Rollout> findAllRolloutsWithDetailedStatus(final Pageable pageable) {
final Page<JpaRollout> rollouts = rolloutRepository.findAll(pageable);
setRolloutStatusDetails(rollouts);
return convertPage(rollouts, pageable);
}
@Override
public Rollout findRolloutWithDetailedStatus(final Long rolloutId) {
final Rollout rollout = findRolloutById(rolloutId);
final List<TotalTargetCountActionStatus> rolloutStatusCountItems = actionRepository
.getStatusCountByRolloutId(rolloutId);
final TotalTargetCountStatus totalTargetCountStatus = new TotalTargetCountStatus(rolloutStatusCountItems,
rollout.getTotalTargets());
((JpaRollout) rollout).setTotalTargetCountStatus(totalTargetCountStatus);
return rollout;
}
private Map<Long, List<TotalTargetCountActionStatus>> getStatusCountItemForRollout(final List<Long> rolloutIds) {
final List<TotalTargetCountActionStatus> resultList = actionRepository.getStatusCountByRolloutId(rolloutIds);
return resultList.stream().collect(Collectors.groupingBy(TotalTargetCountActionStatus::getId));
}
private void setRolloutStatusDetails(final Slice<JpaRollout> rollouts) {
final List<Long> rolloutIds = rollouts.getContent().stream().map(rollout -> rollout.getId())
.collect(Collectors.toList());
final Map<Long, List<TotalTargetCountActionStatus>> allStatesForRollout = getStatusCountItemForRollout(
rolloutIds);
for (final Rollout rollout : rollouts) {
final TotalTargetCountStatus totalTargetCountStatus = new TotalTargetCountStatus(
allStatesForRollout.get(rollout.getId()), rollout.getTotalTargets());
((JpaRollout) rollout).setTotalTargetCountStatus(totalTargetCountStatus);
}
}
private static void checkIfRolloutCanStarted(final Rollout rollout, final Rollout mergedRollout) {
if (!(RolloutStatus.READY.equals(mergedRollout.getStatus()))) {
throw new RolloutIllegalStateException("Rollout can only be started in state ready but current state is "
+ rollout.getStatus().name().toLowerCase());
}
}
@Override
public float getFinishedPercentForRunningGroup(final Long rolloutId, final RolloutGroup rolloutGroup) {
final Long totalGroup = rolloutGroup.getTotalTargets();
final Long finished = actionRepository.countByRolloutIdAndRolloutGroupIdAndStatus(rolloutId,
rolloutGroup.getId(), Action.Status.FINISHED);
if (totalGroup == 0) {
// in case e.g. targets has been deleted we don't have any actions
// left for this group, so the group is finished
return 100;
}
// calculate threshold
return ((float) finished / (float) totalGroup) * 100;
}
@Override
@Transactional(propagation = Propagation.SUPPORTS)
public Rollout generateRollout() {
return new JpaRollout();
}
}

View File

@@ -0,0 +1,706 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa;
import static com.google.common.base.Preconditions.checkNotNull;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import javax.persistence.EntityManager;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.ListJoin;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions;
import org.eclipse.hawkbit.repository.ArtifactManagement;
import org.eclipse.hawkbit.repository.SoftwareManagement;
import org.eclipse.hawkbit.repository.SoftwareModuleFields;
import org.eclipse.hawkbit.repository.SoftwareModuleMetadataFields;
import org.eclipse.hawkbit.repository.SoftwareModuleTypeFields;
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet_;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModuleMetadata;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModuleMetadata_;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModuleType;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule_;
import org.eclipse.hawkbit.repository.jpa.model.SwMetadataCompositeKey;
import org.eclipse.hawkbit.repository.jpa.specifications.SoftwareModuleSpecification;
import org.eclipse.hawkbit.repository.jpa.specifications.SpecificationsBuilder;
import org.eclipse.hawkbit.repository.model.AssignedSoftwareModule;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.LocalArtifact;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.SoftwareModuleMetadata;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
import org.eclipse.hawkbit.repository.rsql.RSQLUtility;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.AuditorAware;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.SliceImpl;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import com.google.common.base.Strings;
import com.google.common.collect.Sets;
/**
* JPA implementation of {@link SoftwareManagement}.
*
*/
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
@Validated
@Service
public class JpaSoftwareManagement implements SoftwareManagement {
@Autowired
private EntityManager entityManager;
@Autowired
private DistributionSetRepository distributionSetRepository;
@Autowired
private DistributionSetTypeRepository distributionSetTypeRepository;
@Autowired
private SoftwareModuleRepository softwareModuleRepository;
@Autowired
private SoftwareModuleMetadataRepository softwareModuleMetadataRepository;
@Autowired
private SoftwareModuleTypeRepository softwareModuleTypeRepository;
@Autowired
private NoCountPagingRepository criteriaNoCountDao;
@Autowired
private AuditorAware<String> auditorProvider;
@Autowired
private ArtifactManagement artifactManagement;
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public SoftwareModule updateSoftwareModule(final SoftwareModule sm) {
checkNotNull(sm.getId());
final JpaSoftwareModule module = softwareModuleRepository.findOne(sm.getId());
boolean updated = false;
if (null == sm.getDescription() || !sm.getDescription().equals(module.getDescription())) {
module.setDescription(sm.getDescription());
updated = true;
}
if (null == sm.getVendor() || !sm.getVendor().equals(module.getVendor())) {
module.setVendor(sm.getVendor());
updated = true;
}
return updated ? softwareModuleRepository.save(module) : module;
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public SoftwareModuleType updateSoftwareModuleType(final SoftwareModuleType sm) {
checkNotNull(sm.getId());
final JpaSoftwareModuleType type = softwareModuleTypeRepository.findOne(sm.getId());
boolean updated = false;
if (sm.getDescription() != null && !sm.getDescription().equals(type.getDescription())) {
type.setDescription(sm.getDescription());
updated = true;
}
if (sm.getColour() != null && !sm.getColour().equals(type.getColour())) {
type.setColour(sm.getColour());
updated = true;
}
return updated ? softwareModuleTypeRepository.save(type) : type;
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public SoftwareModule createSoftwareModule(final SoftwareModule swModule) {
if (null != swModule.getId()) {
throw new EntityAlreadyExistsException();
}
return softwareModuleRepository.save((JpaSoftwareModule) swModule);
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public List<SoftwareModule> createSoftwareModule(final Collection<SoftwareModule> swModules) {
swModules.forEach(swModule -> {
if (null != swModule.getId()) {
throw new EntityAlreadyExistsException();
}
});
@SuppressWarnings({ "unchecked", "rawtypes" })
final Collection<JpaSoftwareModule> jpaCast = (Collection) swModules;
return new ArrayList<>(softwareModuleRepository.save(jpaCast));
}
@Override
public Slice<SoftwareModule> findSoftwareModulesByType(final Pageable pageable, final SoftwareModuleType type) {
final List<Specification<JpaSoftwareModule>> specList = new LinkedList<>();
Specification<JpaSoftwareModule> spec = SoftwareModuleSpecification.equalType((JpaSoftwareModuleType) type);
specList.add(spec);
spec = SoftwareModuleSpecification.isDeletedFalse();
specList.add(spec);
return convertSmPage(findSwModuleByCriteriaAPI(pageable, specList), pageable);
}
private static Slice<SoftwareModule> convertSmPage(final Slice<JpaSoftwareModule> findAll,
final Pageable pageable) {
return new PageImpl<>(new ArrayList<>(findAll.getContent()), pageable, 0);
}
private static Page<SoftwareModule> convertSmPage(final Page<JpaSoftwareModule> findAll, final Pageable pageable) {
return new PageImpl<>(new ArrayList<>(findAll.getContent()), pageable, findAll.getTotalElements());
}
private static Page<SoftwareModuleMetadata> convertSmMdPage(final Page<JpaSoftwareModuleMetadata> findAll,
final Pageable pageable) {
return new PageImpl<>(new ArrayList<>(findAll.getContent()), pageable, findAll.getTotalElements());
}
@Override
public Long countSoftwareModulesByType(final SoftwareModuleType type) {
final List<Specification<JpaSoftwareModule>> specList = new ArrayList<>();
Specification<JpaSoftwareModule> spec = SoftwareModuleSpecification.equalType((JpaSoftwareModuleType) type);
specList.add(spec);
spec = SoftwareModuleSpecification.isDeletedFalse();
specList.add(spec);
return countSwModuleByCriteriaAPI(specList);
}
@Override
public SoftwareModule findSoftwareModuleById(final Long id) {
return artifactManagement.findSoftwareModuleById(id);
}
@Override
public SoftwareModule findSoftwareModuleByNameAndVersion(final String name, final String version,
final SoftwareModuleType type) {
return softwareModuleRepository.findOneByNameAndVersionAndType(name, version, (JpaSoftwareModuleType) type);
}
private boolean isUnassigned(final JpaSoftwareModule bsmMerged) {
return distributionSetRepository.findByModules(bsmMerged).isEmpty();
}
private Slice<JpaSoftwareModule> findSwModuleByCriteriaAPI(final Pageable pageable,
final List<Specification<JpaSoftwareModule>> specList) {
return criteriaNoCountDao.findAll(SpecificationsBuilder.combineWithAnd(specList), pageable,
JpaSoftwareModule.class);
}
private Long countSwModuleByCriteriaAPI(final List<Specification<JpaSoftwareModule>> specList) {
return softwareModuleRepository.count(SpecificationsBuilder.combineWithAnd(specList));
}
private void deleteGridFsArtifacts(final JpaSoftwareModule swModule) {
for (final LocalArtifact localArtifact : swModule.getLocalArtifacts()) {
artifactManagement.deleteLocalArtifact(localArtifact);
}
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public void deleteSoftwareModules(final Collection<Long> ids) {
final List<JpaSoftwareModule> swModulesToDelete = softwareModuleRepository.findByIdIn(ids);
final Set<Long> assignedModuleIds = new HashSet<>();
swModulesToDelete.forEach(swModule -> {
// delete binary data of artifacts
deleteGridFsArtifacts(swModule);
if (isUnassigned(swModule)) {
softwareModuleRepository.delete(swModule);
} else {
assignedModuleIds.add(swModule.getId());
}
});
if (!assignedModuleIds.isEmpty()) {
String currentUser = null;
if (auditorProvider != null) {
currentUser = auditorProvider.getCurrentAuditor();
}
softwareModuleRepository.deleteSoftwareModule(System.currentTimeMillis(), currentUser,
assignedModuleIds.toArray(new Long[0]));
}
}
@Override
public Slice<SoftwareModule> findSoftwareModulesAll(final Pageable pageable) {
final List<Specification<JpaSoftwareModule>> specList = new ArrayList<>();
Specification<JpaSoftwareModule> spec = SoftwareModuleSpecification.isDeletedFalse();
specList.add(spec);
spec = (root, query, cb) -> {
if (!query.getResultType().isAssignableFrom(Long.class)) {
root.fetch(JpaSoftwareModule_.type);
}
return cb.conjunction();
};
specList.add(spec);
return convertSmPage(findSwModuleByCriteriaAPI(pageable, specList), pageable);
}
@Override
public Long countSoftwareModulesAll() {
final List<Specification<JpaSoftwareModule>> specList = new ArrayList<>();
final Specification<JpaSoftwareModule> spec = SoftwareModuleSpecification.isDeletedFalse();
specList.add(spec);
return countSwModuleByCriteriaAPI(specList);
}
@Override
public SoftwareModule findSoftwareModuleWithDetails(final Long id) {
return artifactManagement.findSoftwareModuleWithDetails(id);
}
@Override
public Page<SoftwareModule> findSoftwareModulesByPredicate(final String rsqlParam, final Pageable pageable) {
final Specification<JpaSoftwareModule> spec = RSQLUtility.parse(rsqlParam, SoftwareModuleFields.class);
return convertSmPage(softwareModuleRepository.findAll(spec, pageable), pageable);
}
@Override
public Page<SoftwareModuleType> findSoftwareModuleTypesAll(final String rsqlParam, final Pageable pageable) {
final Specification<JpaSoftwareModuleType> spec = RSQLUtility.parse(rsqlParam, SoftwareModuleTypeFields.class);
return convertSmTPage(softwareModuleTypeRepository.findAll(spec, pageable), pageable);
}
private static Page<SoftwareModuleType> convertSmTPage(final Page<JpaSoftwareModuleType> findAll,
final Pageable pageable) {
return new PageImpl<>(new ArrayList<>(findAll.getContent()), pageable, findAll.getTotalElements());
}
@Override
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY)
public List<SoftwareModule> findSoftwareModulesById(final Collection<Long> ids) {
return new ArrayList<>(softwareModuleRepository.findByIdIn(ids));
}
@Override
public Slice<SoftwareModule> findSoftwareModuleByFilters(final Pageable pageable, final String searchText,
final SoftwareModuleType type) {
final List<Specification<JpaSoftwareModule>> specList = new ArrayList<>();
Specification<JpaSoftwareModule> spec = SoftwareModuleSpecification.isDeletedFalse();
specList.add(spec);
if (!Strings.isNullOrEmpty(searchText)) {
spec = SoftwareModuleSpecification.likeNameOrVersion(searchText);
specList.add(spec);
}
if (null != type) {
spec = SoftwareModuleSpecification.equalType((JpaSoftwareModuleType) type);
specList.add(spec);
}
spec = (root, query, cb) -> {
if (!query.getResultType().isAssignableFrom(Long.class)) {
root.fetch(JpaSoftwareModule_.type);
}
return cb.conjunction();
};
specList.add(spec);
return convertSmPage(findSwModuleByCriteriaAPI(pageable, specList), pageable);
}
@Override
public Slice<AssignedSoftwareModule> findSoftwareModuleOrderBySetAssignmentAndModuleNameAscModuleVersionAsc(
final Pageable pageable, final Long orderByDistributionId, final String searchText,
final SoftwareModuleType ty) {
final JpaSoftwareModuleType type = (JpaSoftwareModuleType) ty;
final List<AssignedSoftwareModule> resultList = new ArrayList<>();
final int pageSize = pageable.getPageSize();
final CriteriaBuilder cb = entityManager.getCriteriaBuilder();
// get the assigned software modules
final CriteriaQuery<JpaSoftwareModule> assignedQuery = cb.createQuery(JpaSoftwareModule.class);
final Root<JpaSoftwareModule> assignedRoot = assignedQuery.from(JpaSoftwareModule.class);
assignedQuery.distinct(true);
final ListJoin<JpaSoftwareModule, JpaDistributionSet> assignedDsJoin = assignedRoot
.join(JpaSoftwareModule_.assignedTo);
// build the specifications and then to predicates necessary by the
// given filters
final Predicate[] specPredicate = specificationsToPredicate(buildSpecificationList(searchText, type),
assignedRoot, assignedQuery, cb,
cb.equal(assignedDsJoin.get(JpaDistributionSet_.id), orderByDistributionId));
// if we have some predicates then add it to the where clause of the
// multi select
assignedQuery.where(specPredicate);
assignedQuery.orderBy(cb.asc(assignedRoot.get(JpaSoftwareModule_.name)),
cb.asc(assignedRoot.get(JpaSoftwareModule_.version)));
// don't page the assigned query on database, we need all assigned
// software modules to filter
// them out in the unassigned query
final List<JpaSoftwareModule> assignedSoftwareModules = entityManager.createQuery(assignedQuery)
.getResultList();
// map result
if (pageable.getOffset() < assignedSoftwareModules.size()) {
assignedSoftwareModules
.subList(pageable.getOffset(), Math.min(assignedSoftwareModules.size(), pageable.getPageSize()))
.forEach(sw -> resultList.add(new AssignedSoftwareModule(sw, true)));
}
if (assignedSoftwareModules.size() >= pageSize) {
return new SliceImpl<>(resultList);
}
// get the unassigned software modules
final CriteriaQuery<JpaSoftwareModule> unassignedQuery = cb.createQuery(JpaSoftwareModule.class);
unassignedQuery.distinct(true);
final Root<JpaSoftwareModule> unassignedRoot = unassignedQuery.from(JpaSoftwareModule.class);
Predicate[] unassignedSpec;
if (!assignedSoftwareModules.isEmpty()) {
unassignedSpec = specificationsToPredicate(buildSpecificationList(searchText, type), unassignedRoot,
unassignedQuery, cb, cb.not(unassignedRoot.get(JpaSoftwareModule_.id)
.in(assignedSoftwareModules.stream().map(sw -> sw.getId()).collect(Collectors.toList()))));
} else {
unassignedSpec = specificationsToPredicate(buildSpecificationList(searchText, type), unassignedRoot,
unassignedQuery, cb);
}
unassignedQuery.where(unassignedSpec);
unassignedQuery.orderBy(cb.asc(unassignedRoot.get(JpaSoftwareModule_.name)),
cb.asc(unassignedRoot.get(JpaSoftwareModule_.version)));
final List<JpaSoftwareModule> unassignedSoftwareModules = entityManager.createQuery(unassignedQuery)
.setFirstResult(Math.max(0, pageable.getOffset() - assignedSoftwareModules.size()))
.setMaxResults(pageSize).getResultList();
// map result
unassignedSoftwareModules.forEach(sw -> resultList.add(new AssignedSoftwareModule(sw, false)));
return new SliceImpl<>(resultList);
}
private static List<Specification<JpaSoftwareModule>> buildSpecificationList(final String searchText,
final JpaSoftwareModuleType type) {
final List<Specification<JpaSoftwareModule>> specList = new ArrayList<>();
if (!Strings.isNullOrEmpty(searchText)) {
specList.add(SoftwareModuleSpecification.likeNameOrVersion(searchText));
}
if (type != null) {
specList.add(SoftwareModuleSpecification.equalType(type));
}
specList.add(SoftwareModuleSpecification.isDeletedFalse());
return specList;
}
private Predicate[] specificationsToPredicate(final List<Specification<JpaSoftwareModule>> specifications,
final Root<JpaSoftwareModule> root, final CriteriaQuery<?> query, final CriteriaBuilder cb,
final Predicate... additionalPredicates) {
final List<Predicate> predicates = new ArrayList<>();
specifications.forEach(spec -> predicates.add(spec.toPredicate(root, query, cb)));
for (final Predicate predicate : additionalPredicates) {
predicates.add(predicate);
}
return predicates.toArray(new Predicate[predicates.size()]);
}
@Override
public Long countSoftwareModuleByFilters(final String searchText, final SoftwareModuleType type) {
final List<Specification<JpaSoftwareModule>> specList = new ArrayList<>();
Specification<JpaSoftwareModule> spec = SoftwareModuleSpecification.isDeletedFalse();
specList.add(spec);
if (!Strings.isNullOrEmpty(searchText)) {
spec = SoftwareModuleSpecification.likeNameOrVersion(searchText);
specList.add(spec);
}
if (null != type) {
spec = SoftwareModuleSpecification.equalType((JpaSoftwareModuleType) type);
specList.add(spec);
}
return countSwModuleByCriteriaAPI(specList);
}
@Override
public Page<SoftwareModuleType> findSoftwareModuleTypesAll(final Pageable pageable) {
return softwareModuleTypeRepository.findByDeleted(pageable, false);
}
@Override
public Long countSoftwareModuleTypesAll() {
return softwareModuleTypeRepository.countByDeleted(false);
}
@Override
public SoftwareModuleType findSoftwareModuleTypeByKey(final String key) {
return softwareModuleTypeRepository.findByKey(key);
}
@Override
public SoftwareModuleType findSoftwareModuleTypeById(final Long id) {
return softwareModuleTypeRepository.findOne(id);
}
@Override
public SoftwareModuleType findSoftwareModuleTypeByName(final String name) {
return softwareModuleTypeRepository.findByName(name);
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public SoftwareModuleType createSoftwareModuleType(final SoftwareModuleType type) {
if (type.getId() != null) {
throw new EntityAlreadyExistsException("Given type contains an Id!");
}
return softwareModuleTypeRepository.save((JpaSoftwareModuleType) type);
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public void deleteSoftwareModuleType(final SoftwareModuleType ty) {
final JpaSoftwareModuleType type = (JpaSoftwareModuleType) ty;
if (softwareModuleRepository.countByType(type) > 0
|| distributionSetTypeRepository.countByElementsSmType(type) > 0) {
final JpaSoftwareModuleType toDelete = entityManager.merge(type);
toDelete.setDeleted(true);
softwareModuleTypeRepository.save(toDelete);
} else {
softwareModuleTypeRepository.delete(type.getId());
}
}
@Override
public Page<SoftwareModule> findSoftwareModuleByAssignedTo(final Pageable pageable, final DistributionSet set) {
return softwareModuleRepository.findByAssignedTo(pageable, (JpaDistributionSet) set);
}
@Override
public Page<SoftwareModule> findSoftwareModuleByAssignedToAndType(final Pageable pageable,
final DistributionSet set, final SoftwareModuleType type) {
return softwareModuleRepository.findByAssignedToAndType(pageable, (JpaDistributionSet) set,
(JpaSoftwareModuleType) type);
}
@Override
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Modifying
public SoftwareModuleMetadata createSoftwareModuleMetadata(final SoftwareModuleMetadata md) {
final JpaSoftwareModuleMetadata metadata = (JpaSoftwareModuleMetadata) md;
if (softwareModuleMetadataRepository.exists(metadata.getId())) {
throwMetadataKeyAlreadyExists(metadata.getId().getKey());
}
// merge base software module so optLockRevision gets updated and audit
// log written because
// modifying metadata is modifying the base software module itself for
// auditing purposes.
entityManager.merge((JpaSoftwareModule) metadata.getSoftwareModule()).setLastModifiedAt(-1L);
return softwareModuleMetadataRepository.save(metadata);
}
@Override
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Modifying
public List<SoftwareModuleMetadata> createSoftwareModuleMetadata(final Collection<SoftwareModuleMetadata> md) {
@SuppressWarnings({ "unchecked", "rawtypes" })
final Collection<JpaSoftwareModuleMetadata> metadata = (Collection) md;
for (final JpaSoftwareModuleMetadata softwareModuleMetadata : metadata) {
checkAndThrowAlreadyExistsIfSoftwareModuleMetadataExists(softwareModuleMetadata.getId());
}
metadata.forEach(m -> entityManager.merge((JpaSoftwareModule) m.getSoftwareModule()).setLastModifiedAt(-1L));
return new ArrayList<>(softwareModuleMetadataRepository.save(metadata));
}
@Override
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Modifying
public SoftwareModuleMetadata updateSoftwareModuleMetadata(final SoftwareModuleMetadata md) {
final JpaSoftwareModuleMetadata metadata = (JpaSoftwareModuleMetadata) md;
// check if exists otherwise throw entity not found exception
findSoftwareModuleMetadata(metadata.getId());
// touch it to update the lock revision because we are modifying the
// software module
// indirectly
entityManager.merge((JpaSoftwareModule) metadata.getSoftwareModule()).setLastModifiedAt(-1L);
return softwareModuleMetadataRepository.save(metadata);
}
@Override
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Modifying
public void deleteSoftwareModuleMetadata(final SoftwareModule softwareModule, final String key) {
softwareModuleMetadataRepository.delete(new SwMetadataCompositeKey(softwareModule, key));
}
@Override
public Page<SoftwareModuleMetadata> findSoftwareModuleMetadataBySoftwareModuleId(final Long swId,
final Pageable pageable) {
return softwareModuleMetadataRepository.findBySoftwareModuleId(swId, pageable);
}
@Override
public Page<SoftwareModuleMetadata> findSoftwareModuleMetadataBySoftwareModuleId(final Long softwareModuleId,
final String rsqlParam, final Pageable pageable) {
final Specification<JpaSoftwareModuleMetadata> spec = RSQLUtility.parse(rsqlParam,
SoftwareModuleMetadataFields.class);
return convertSmMdPage(
softwareModuleMetadataRepository
.findAll(
(Specification<JpaSoftwareModuleMetadata>) (root, query, cb) -> cb.and(
cb.equal(root.get(JpaSoftwareModuleMetadata_.softwareModule)
.get(JpaSoftwareModule_.id), softwareModuleId),
spec.toPredicate(root, query, cb)),
pageable),
pageable);
}
@Override
public SoftwareModuleMetadata findSoftwareModuleMetadata(final SoftwareModule softwareModule, final String key) {
return findSoftwareModuleMetadata(new SwMetadataCompositeKey(softwareModule, key));
}
private SoftwareModuleMetadata findSoftwareModuleMetadata(final SwMetadataCompositeKey id) {
final SoftwareModuleMetadata findOne = softwareModuleMetadataRepository.findOne(id);
if (findOne == null) {
throw new EntityNotFoundException("Metadata with key '" + id.getKey() + "' does not exist");
}
return findOne;
}
private void checkAndThrowAlreadyExistsIfSoftwareModuleMetadataExists(final SwMetadataCompositeKey metadataId) {
if (softwareModuleMetadataRepository.exists(metadataId)) {
throw new EntityAlreadyExistsException(
"Metadata entry with key '" + metadataId.getKey() + "' already exists");
}
}
private static void throwMetadataKeyAlreadyExists(final String metadataKey) {
throw new EntityAlreadyExistsException("Metadata entry with key '" + metadataKey + "' already exists");
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public void deleteSoftwareModule(final SoftwareModule bsm) {
deleteSoftwareModules(Sets.newHashSet(bsm.getId()));
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public List<SoftwareModuleType> createSoftwareModuleType(final Collection<SoftwareModuleType> types) {
return types.stream().map(this::createSoftwareModuleType).collect(Collectors.toList());
}
@Override
@Transactional(propagation = Propagation.SUPPORTS)
public SoftwareModuleType generateSoftwareModuleType() {
return new JpaSoftwareModuleType();
}
@Override
@Transactional(propagation = Propagation.SUPPORTS)
public SoftwareModule generateSoftwareModule() {
return new JpaSoftwareModule();
}
@Override
@Transactional(propagation = Propagation.SUPPORTS)
public SoftwareModule generateSoftwareModule(final SoftwareModuleType type, final String name, final String version,
final String description, final String vendor) {
return new JpaSoftwareModule(type, name, version, description, vendor);
}
@Override
@Transactional(propagation = Propagation.SUPPORTS)
public SoftwareModuleMetadata generateSoftwareModuleMetadata() {
return new JpaSoftwareModuleMetadata();
}
@Override
@Transactional(propagation = Propagation.SUPPORTS)
public SoftwareModuleMetadata generateSoftwareModuleMetadata(final SoftwareModule softwareModule, final String key,
final String value) {
return new JpaSoftwareModuleMetadata(key, softwareModule, value);
}
@Override
@Transactional(propagation = Propagation.SUPPORTS)
public SoftwareModuleType generateSoftwareModuleType(final String key, final String name, final String description,
final int maxAssignments) {
return new JpaSoftwareModuleType(key, name, description, maxAssignments);
}
}

View File

@@ -0,0 +1,306 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.util.List;
import java.util.stream.Collectors;
import javax.persistence.EntityManager;
import org.eclipse.hawkbit.cache.TenancyCacheManager;
import org.eclipse.hawkbit.report.model.SystemUsageReport;
import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.repository.TenantStatsManagement;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetType;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModuleType;
import org.eclipse.hawkbit.repository.jpa.model.JpaTenantMetaData;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
import org.eclipse.hawkbit.repository.model.TenantMetaData;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.eclipse.persistence.config.PersistenceUnitProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.cache.interceptor.SimpleKeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
/**
* JPA implementation of {@link SystemManagement}.
*
*/
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
@Validated
@Service
public class JpaSystemManagement implements SystemManagement {
@Autowired
private EntityManager entityManager;
@Autowired
private TargetRepository targetRepository;
@Autowired
private ActionRepository actionRepository;
@Autowired
private DistributionSetRepository distributionSetRepository;
@Autowired
private SoftwareModuleRepository softwareModuleRepository;
@Autowired
private TenantMetaDataRepository tenantMetaDataRepository;
@Autowired
private DistributionSetTypeRepository distributionSetTypeRepository;
@Autowired
private SoftwareModuleTypeRepository softwareModuleTypeRepository;
@Autowired
private TargetTagRepository targetTagRepository;
@Autowired
private DistributionSetTagRepository distributionSetTagRepository;
@Autowired
private ExternalArtifactRepository externalArtifactRepository;
@Autowired
private LocalArtifactRepository artifactRepository;
@Autowired
private ExternalArtifactProviderRepository externalArtifactProviderRepository;
@Autowired
private TenantConfigurationRepository tenantConfigurationRepository;
@Autowired
private RolloutRepository rolloutRepository;
@Autowired
private RolloutGroupRepository rolloutGroupRepository;
@Autowired
private TenantAware tenantAware;
@Autowired
private TenantStatsManagement systemStatsManagement;
@Autowired
private TenancyCacheManager cacheManager;
private final ThreadLocal<String> createInitialTenant = new ThreadLocal<>();
@Override
public SystemUsageReport getSystemUsageStatistics() {
BigDecimal sumOfArtifacts = (BigDecimal) entityManager
.createNativeQuery(
"select SUM(file_size) from sp_artifact a INNER JOIN sp_base_software_module sm ON a.software_module = sm.id WHERE sm.deleted = 0")
.getSingleResult();
if (sumOfArtifacts == null) {
sumOfArtifacts = new BigDecimal(0);
}
// we use native queries to punch through the tenant boundaries. This
// has to be used with care!
final Long targets = (Long) entityManager.createNativeQuery("SELECT COUNT(id) FROM sp_target")
.getSingleResult();
final Long artifacts = (Long) entityManager
.createNativeQuery(
"SELECT COUNT(a.id) FROM sp_artifact a INNER JOIN sp_base_software_module sm ON a.software_module = sm.id WHERE sm.deleted = 0")
.getSingleResult();
final Long actions = (Long) entityManager.createNativeQuery("SELECT COUNT(id) FROM sp_action")
.getSingleResult();
final SystemUsageReport result = new SystemUsageReport(targets, artifacts, actions,
sumOfArtifacts.setScale(0, BigDecimal.ROUND_HALF_UP).longValue());
usageStatsPerTenant(result);
return result;
}
private void usageStatsPerTenant(final SystemUsageReport report) {
final List<String> tenants = findTenants();
tenants.forEach(tenant -> tenantAware.runAsTenant(tenant, () -> {
report.addTenantData(systemStatsManagement.getStatsOfTenant(tenant));
return null;
}));
}
@Override
@Transactional(propagation = Propagation.SUPPORTS)
@Bean
public KeyGenerator currentTenantKeyGenerator() {
return new CurrentTenantKeyGenerator();
}
@Override
@Cacheable(value = "tenantMetadata", key = "#tenant.toUpperCase()")
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Modifying
public TenantMetaData getTenantMetadata(final String tenant) {
final TenantMetaData result = tenantMetaDataRepository.findByTenantIgnoreCase(tenant);
// Create if it does not exist
if (result == null) {
try {
createInitialTenant.set(tenant);
cacheManager.getCache("currentTenant").evict(currentTenantKeyGenerator().generate(null, null));
return tenantMetaDataRepository.save(new JpaTenantMetaData(createStandardSoftwareDataSetup(), tenant));
} finally {
createInitialTenant.remove();
}
}
return result;
}
@Override
public List<String> findTenants() {
return tenantMetaDataRepository.findAll().stream().map(md -> md.getTenant()).collect(Collectors.toList());
}
@Override
@CacheEvict(value = { "tenantMetadata" }, key = "#tenant.toUpperCase()")
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Modifying
public void deleteTenant(final String tenant) {
cacheManager.evictCaches(tenant);
cacheManager.getCache("currentTenant").evict(currentTenantKeyGenerator().generate(null, null));
tenantAware.runAsTenant(tenant, () -> {
entityManager.setProperty(PersistenceUnitProperties.MULTITENANT_PROPERTY_DEFAULT, tenant.toUpperCase());
tenantMetaDataRepository.deleteByTenantIgnoreCase(tenant);
tenantConfigurationRepository.deleteByTenantIgnoreCase(tenant);
targetRepository.deleteByTenantIgnoreCase(tenant);
actionRepository.deleteByTenantIgnoreCase(tenant);
rolloutGroupRepository.deleteByTenantIgnoreCase(tenant);
rolloutRepository.deleteByTenantIgnoreCase(tenant);
artifactRepository.deleteByTenantIgnoreCase(tenant);
externalArtifactRepository.deleteByTenantIgnoreCase(tenant);
externalArtifactProviderRepository.deleteByTenantIgnoreCase(tenant);
targetTagRepository.deleteByTenantIgnoreCase(tenant);
distributionSetTagRepository.deleteByTenantIgnoreCase(tenant);
distributionSetRepository.deleteByTenantIgnoreCase(tenant);
distributionSetTypeRepository.deleteByTenantIgnoreCase(tenant);
softwareModuleRepository.deleteByTenantIgnoreCase(tenant);
softwareModuleTypeRepository.deleteByTenantIgnoreCase(tenant);
return null;
});
}
@Override
@Cacheable(value = "tenantMetadata", keyGenerator = "tenantKeyGenerator")
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Modifying
public TenantMetaData getTenantMetadata() {
if (tenantAware.getCurrentTenant() == null) {
throw new IllegalStateException("Tenant not set");
}
return getTenantMetadata(tenantAware.getCurrentTenant());
}
@Override
@Cacheable(value = "currentTenant", keyGenerator = "currentTenantKeyGenerator")
// set transaction to not supported, due we call this in
// BaseEntity#prePersist methods
// and it seems that JPA committing the transaction when executing this
// transactional method,
// which then leads that the BaseEntity#prePersist is called again to
// persist the un-persisted
// entity and we landing again in the #currentTenant() method
// suspend the transaction here to do a read-request against the medata
// table, when the current
// tenant is not cached anyway already.
@Transactional(propagation = Propagation.NOT_SUPPORTED, isolation = Isolation.READ_UNCOMMITTED)
public String currentTenant() {
final String initialTenantCreation = createInitialTenant.get();
if (initialTenantCreation == null) {
final TenantMetaData findByTenant = tenantMetaDataRepository
.findByTenantIgnoreCase(tenantAware.getCurrentTenant());
return findByTenant != null ? findByTenant.getTenant() : null;
}
return initialTenantCreation;
}
@Override
@CachePut(value = "tenantMetadata", key = "#metaData.tenant.toUpperCase()")
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Modifying
public TenantMetaData updateTenantMetadata(final TenantMetaData metaData) {
if (!tenantMetaDataRepository.exists(metaData.getId())) {
throw new EntityNotFoundException("Metadata does not exist: " + metaData.getId());
}
return tenantMetaDataRepository.save((JpaTenantMetaData) metaData);
}
private DistributionSetType createStandardSoftwareDataSetup() {
final SoftwareModuleType eclApp = softwareModuleTypeRepository.save(new JpaSoftwareModuleType("application",
"ECL Application", "Edge Controller Linux base application type", 1));
final SoftwareModuleType eclOs = softwareModuleTypeRepository.save(
new JpaSoftwareModuleType("os", "ECL OS", "Edge Controller Linux operation system image type", 1));
final SoftwareModuleType eclJvm = softwareModuleTypeRepository.save(
new JpaSoftwareModuleType("runtime", "ECL JVM", "Edge Controller Linux java virtual machine type.", 1));
distributionSetTypeRepository.save((JpaDistributionSetType) new JpaDistributionSetType("ecl_os", "OS only",
"Standard Edge Controller Linux distribution set type.").addMandatoryModuleType(eclOs));
distributionSetTypeRepository.save((JpaDistributionSetType) new JpaDistributionSetType("ecl_os_app",
"OS with optional app", "Standard Edge Controller Linux distribution set type. OS only.")
.addMandatoryModuleType(eclOs).addOptionalModuleType(eclApp));
return distributionSetTypeRepository.save(
(JpaDistributionSetType) new JpaDistributionSetType("ecl_os_app_jvm", "OS with optional app and jvm",
"Standard Edge Controller Linux distribution set type. OS with optional application.")
.addMandatoryModuleType(eclOs).addOptionalModuleType(eclApp)
.addOptionalModuleType(eclJvm));
}
/**
* A implementation of the {@link KeyGenerator} to generate a key based on
* either the {@code createInitialTenant} thread local and the
* {@link TenantAware}, but in case we are in a tenant creation with its
* default types we need to use the tenant the current tenant which is
* currently created and not the one currently in the {@link TenantAware}.
*
*/
public class CurrentTenantKeyGenerator implements KeyGenerator {
@Override
// Exception squid:S923 - override
@SuppressWarnings({ "squid:S923" })
public Object generate(final Object target, final Method method, final Object... params) {
final String initialTenantCreation = createInitialTenant.get();
if (initialTenantCreation == null) {
return SimpleKeyGenerator.generateKey(tenantAware.getCurrentTenant().toUpperCase(),
tenantAware.getCurrentTenant().toUpperCase());
}
return SimpleKeyGenerator.generateKey(initialTenantCreation.toUpperCase(),
initialTenantCreation.toUpperCase());
}
}
}

View File

@@ -0,0 +1,327 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa;
import static com.google.common.base.Preconditions.checkNotNull;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import org.eclipse.hawkbit.eventbus.event.DistributionSetTagCreatedBulkEvent;
import org.eclipse.hawkbit.eventbus.event.DistributionSetTagDeletedEvent;
import org.eclipse.hawkbit.eventbus.event.DistributionSetTagUpdateEvent;
import org.eclipse.hawkbit.eventbus.event.TargetTagCreatedBulkEvent;
import org.eclipse.hawkbit.eventbus.event.TargetTagDeletedEvent;
import org.eclipse.hawkbit.eventbus.event.TargetTagUpdateEvent;
import org.eclipse.hawkbit.executor.AfterTransactionCommitExecutor;
import org.eclipse.hawkbit.repository.TagFields;
import org.eclipse.hawkbit.repository.TagManagement;
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetTag;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetTag;
import org.eclipse.hawkbit.repository.model.DistributionSetTag;
import org.eclipse.hawkbit.repository.model.TargetTag;
import org.eclipse.hawkbit.repository.rsql.RSQLUtility;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import com.google.common.eventbus.EventBus;
/**
* JP>A implementation of {@link TagManagement}.
*
*/
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
@Validated
@Service
public class JpaTagManagement implements TagManagement {
@Autowired
private TargetTagRepository targetTagRepository;
@Autowired
private TargetRepository targetRepository;
@Autowired
private DistributionSetTagRepository distributionSetTagRepository;
@Autowired
private DistributionSetRepository distributionSetRepository;
@Autowired
private EventBus eventBus;
@Autowired
private TenantAware tenantAware;
@Autowired
private AfterTransactionCommitExecutor afterCommit;
@Override
public TargetTag findTargetTag(final String name) {
return targetTagRepository.findByNameEquals(name);
}
@Override
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public TargetTag createTargetTag(final TargetTag targetTag) {
if (null != targetTag.getId()) {
throw new EntityAlreadyExistsException();
}
if (findTargetTag(targetTag.getName()) != null) {
throw new EntityAlreadyExistsException();
}
final TargetTag save = targetTagRepository.save((JpaTargetTag) targetTag);
afterCommit
.afterCommit(() -> eventBus.post(new TargetTagCreatedBulkEvent(tenantAware.getCurrentTenant(), save)));
return save;
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public List<TargetTag> createTargetTags(final Collection<TargetTag> tt) {
@SuppressWarnings({ "unchecked", "rawtypes" })
final Collection<JpaTargetTag> targetTags = (Collection) tt;
targetTags.forEach(tag -> {
if (tag.getId() != null) {
throw new EntityAlreadyExistsException();
}
});
final List<TargetTag> save = new ArrayList<>(targetTagRepository.save(targetTags));
afterCommit
.afterCommit(() -> eventBus.post(new TargetTagCreatedBulkEvent(tenantAware.getCurrentTenant(), save)));
return save;
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public void deleteTargetTag(final String targetTagName) {
final JpaTargetTag tag = targetTagRepository.findByNameEquals(targetTagName);
final List<JpaTarget> changed = new LinkedList<>();
for (final JpaTarget target : targetRepository.findByTag(tag)) {
target.getTags().remove(tag);
changed.add(target);
}
// save association delete
targetRepository.save(changed);
// finally delete the tag itself
targetTagRepository.deleteByName(targetTagName);
afterCommit.afterCommit(() -> eventBus.post(new TargetTagDeletedEvent(tag)));
}
@Override
public List<TargetTag> findAllTargetTags() {
return new ArrayList<>(targetTagRepository.findAll());
}
@Override
public Page<TargetTag> findAllTargetTags(final String rsqlParam, final Pageable pageable) {
final Specification<JpaTargetTag> spec = RSQLUtility.parse(rsqlParam, TagFields.class);
return convertTPage(targetTagRepository.findAll(spec, pageable), pageable);
}
private static Page<TargetTag> convertTPage(final Page<JpaTargetTag> findAll, final Pageable pageable) {
return new PageImpl<>(new ArrayList<>(findAll.getContent()), pageable, findAll.getTotalElements());
}
private static Page<DistributionSetTag> convertDsPage(final Page<JpaDistributionSetTag> findAll,
final Pageable pageable) {
return new PageImpl<>(new ArrayList<>(findAll.getContent()), pageable, findAll.getTotalElements());
}
@Override
public long countTargetTags() {
return targetTagRepository.count();
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public TargetTag updateTargetTag(final TargetTag targetTag) {
checkNotNull(targetTag.getName());
checkNotNull(targetTag.getId());
final TargetTag save = targetTagRepository.save((JpaTargetTag) targetTag);
afterCommit.afterCommit(() -> eventBus.post(new TargetTagUpdateEvent(save)));
return save;
}
@Override
public DistributionSetTag findDistributionSetTag(final String name) {
return distributionSetTagRepository.findByNameEquals(name);
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public DistributionSetTag createDistributionSetTag(final DistributionSetTag distributionSetTag) {
if (null != distributionSetTag.getId()) {
throw new EntityAlreadyExistsException();
}
if (distributionSetTagRepository.findByNameEquals(distributionSetTag.getName()) != null) {
throw new EntityAlreadyExistsException();
}
final DistributionSetTag save = distributionSetTagRepository.save((JpaDistributionSetTag) distributionSetTag);
afterCommit.afterCommit(
() -> eventBus.post(new DistributionSetTagCreatedBulkEvent(tenantAware.getCurrentTenant(), save)));
return save;
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public List<DistributionSetTag> createDistributionSetTags(final Collection<DistributionSetTag> dst) {
@SuppressWarnings({ "rawtypes", "unchecked" })
final Collection<JpaDistributionSetTag> distributionSetTags = (Collection) dst;
for (final DistributionSetTag dsTag : distributionSetTags) {
if (dsTag.getId() != null) {
throw new EntityAlreadyExistsException();
}
}
final List<DistributionSetTag> save = new ArrayList<>(distributionSetTagRepository.save(distributionSetTags));
afterCommit.afterCommit(
() -> eventBus.post(new DistributionSetTagCreatedBulkEvent(tenantAware.getCurrentTenant(), save)));
return save;
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public void deleteDistributionSetTag(final String tagName) {
final JpaDistributionSetTag tag = distributionSetTagRepository.findByNameEquals(tagName);
final List<JpaDistributionSet> changed = new LinkedList<>();
for (final JpaDistributionSet set : distributionSetRepository.findByTag(tag)) {
set.getTags().remove(tag);
changed.add(set);
}
// save association delete
distributionSetRepository.save(changed);
distributionSetTagRepository.deleteByName(tagName);
afterCommit.afterCommit(() -> eventBus.post(new DistributionSetTagDeletedEvent(tag)));
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public DistributionSetTag updateDistributionSetTag(final DistributionSetTag distributionSetTag) {
checkNotNull(distributionSetTag.getName());
checkNotNull(distributionSetTag.getId());
final DistributionSetTag save = distributionSetTagRepository.save((JpaDistributionSetTag) distributionSetTag);
afterCommit.afterCommit(() -> eventBus.post(new DistributionSetTagUpdateEvent(save)));
return save;
}
@Override
public List<DistributionSetTag> findAllDistributionSetTags() {
return new ArrayList<>(distributionSetTagRepository.findAll());
}
@Override
public TargetTag findTargetTagById(final Long id) {
return targetTagRepository.findOne(id);
}
@Override
public DistributionSetTag findDistributionSetTagById(final Long id) {
return distributionSetTagRepository.findOne(id);
}
@Override
public Page<TargetTag> findAllTargetTags(final Pageable pageable) {
return convertTPage(targetTagRepository.findAll(pageable), pageable);
}
@Override
public Page<DistributionSetTag> findAllDistributionSetTags(final Pageable pageable) {
return convertDsPage(distributionSetTagRepository.findAll(pageable), pageable);
}
@Override
public Page<DistributionSetTag> findAllDistributionSetTags(final String rsqlParam, final Pageable pageable) {
final Specification<JpaDistributionSetTag> spec = RSQLUtility.parse(rsqlParam, TagFields.class);
return convertDsPage(distributionSetTagRepository.findAll(spec, pageable), pageable);
}
@Override
@Transactional(propagation = Propagation.SUPPORTS)
public TargetTag generateTargetTag() {
return new JpaTargetTag();
}
@Override
@Transactional(propagation = Propagation.SUPPORTS)
public DistributionSetTag generateDistributionSetTag() {
return new JpaDistributionSetTag();
}
@Override
@Transactional(propagation = Propagation.SUPPORTS)
public TargetTag generateTargetTag(final String name, final String description, final String colour) {
return new JpaTargetTag(name, description, colour);
}
@Override
@Transactional(propagation = Propagation.SUPPORTS)
public DistributionSetTag generateDistributionSetTag(final String name, final String description,
final String colour) {
return new JpaDistributionSetTag(name, description, colour);
}
@Override
@Transactional(propagation = Propagation.SUPPORTS)
public TargetTag generateTargetTag(final String name) {
return new JpaTargetTag(name);
}
@Override
@Transactional(propagation = Propagation.SUPPORTS)
public DistributionSetTag generateDistributionSetTag(final String name) {
return new JpaDistributionSetTag(name);
}
}

View File

@@ -0,0 +1,117 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.hawkbit.repository.TargetFilterQueryManagement;
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetFilterQuery;
import org.eclipse.hawkbit.repository.jpa.specifications.SpecificationsBuilder;
import org.eclipse.hawkbit.repository.jpa.specifications.TargetFilterQuerySpecification;
import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.domain.Specifications;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.validation.annotation.Validated;
import com.google.common.base.Strings;
/**
* JPA implementation of {@link TargetFilterQueryManagement}.
*
*/
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
@Validated
@Service
public class JpaTargetFilterQueryManagement implements TargetFilterQueryManagement {
@Autowired
private TargetFilterQueryRepository targetFilterQueryRepository;
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public TargetFilterQuery createTargetFilterQuery(final TargetFilterQuery customTargetFilter) {
if (targetFilterQueryRepository.findByName(customTargetFilter.getName()) != null) {
throw new EntityAlreadyExistsException(customTargetFilter.getName());
}
return targetFilterQueryRepository.save((JpaTargetFilterQuery) customTargetFilter);
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public void deleteTargetFilterQuery(final Long targetFilterQueryId) {
targetFilterQueryRepository.delete(targetFilterQueryId);
}
@Override
public Page<TargetFilterQuery> findAllTargetFilterQuery(final Pageable pageable) {
return convertPage(targetFilterQueryRepository.findAll(pageable), pageable);
}
private static Page<TargetFilterQuery> convertPage(final Page<JpaTargetFilterQuery> findAll,
final Pageable pageable) {
return new PageImpl<>(new ArrayList<>(findAll.getContent()), pageable, findAll.getTotalElements());
}
@Override
public Page<TargetFilterQuery> findTargetFilterQueryByFilters(final Pageable pageable, final String name) {
final List<Specification<JpaTargetFilterQuery>> specList = new ArrayList<>();
if (!Strings.isNullOrEmpty(name)) {
specList.add(TargetFilterQuerySpecification.likeName(name));
}
return convertPage(findTargetFilterQueryByCriteriaAPI(pageable, specList), pageable);
}
private Page<JpaTargetFilterQuery> findTargetFilterQueryByCriteriaAPI(final Pageable pageable,
final List<Specification<JpaTargetFilterQuery>> specList) {
if (specList == null || specList.isEmpty()) {
return targetFilterQueryRepository.findAll(pageable);
}
final Specifications<JpaTargetFilterQuery> specs = SpecificationsBuilder.combineWithAnd(specList);
return targetFilterQueryRepository.findAll(specs, pageable);
}
@Override
public TargetFilterQuery findTargetFilterQueryByName(final String targetFilterQueryName) {
return targetFilterQueryRepository.findByName(targetFilterQueryName);
}
@Override
public TargetFilterQuery findTargetFilterQueryById(final Long targetFilterQueryId) {
return targetFilterQueryRepository.findOne(targetFilterQueryId);
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public TargetFilterQuery updateTargetFilterQuery(final TargetFilterQuery targetFilterQuery) {
Assert.notNull(targetFilterQuery.getId());
return targetFilterQueryRepository.save((JpaTargetFilterQuery) targetFilterQuery);
}
@Override
public TargetFilterQuery generateTargetFilterQuery() {
return new JpaTargetFilterQuery();
}
}

View File

@@ -0,0 +1,681 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import javax.annotation.PreDestroy;
import javax.persistence.EntityManager;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.Order;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import org.eclipse.hawkbit.Constants;
import org.eclipse.hawkbit.eventbus.event.TargetTagAssigmentResultEvent;
import org.eclipse.hawkbit.executor.AfterTransactionCommitExecutor;
import org.eclipse.hawkbit.repository.TargetFields;
import org.eclipse.hawkbit.repository.TargetManagement;
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet_;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetInfo;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetInfo_;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetTag;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget_;
import org.eclipse.hawkbit.repository.jpa.specifications.SpecificationsBuilder;
import org.eclipse.hawkbit.repository.jpa.specifications.TargetSpecifications;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
import org.eclipse.hawkbit.repository.model.TargetIdName;
import org.eclipse.hawkbit.repository.model.TargetTag;
import org.eclipse.hawkbit.repository.model.TargetTagAssignmentResult;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
import org.eclipse.hawkbit.repository.rsql.RSQLUtility;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.SliceImpl;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.validation.annotation.Validated;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.eventbus.EventBus;
/**
* JPA implementation of {@link TargetManagement}.
*
*/
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
@Validated
@Service
public class JpaTargetManagement implements TargetManagement {
@Autowired
private EntityManager entityManager;
@Autowired
private TargetRepository targetRepository;
@Autowired
private TargetTagRepository targetTagRepository;
@Autowired
private TargetInfoRepository targetInfoRepository;
@Autowired
private NoCountPagingRepository criteriaNoCountDao;
@Autowired
private EventBus eventBus;
@Autowired
private AfterTransactionCommitExecutor afterCommit;
@Override
public Target findTargetByControllerID(final String controllerId) {
return targetRepository.findByControllerId(controllerId);
}
@Override
public Target findTargetByControllerIDWithDetails(final String controllerId) {
final Target result = targetRepository.findByControllerId(controllerId);
// load lazy relations
if (result != null) {
result.getTargetInfo().getControllerAttributes().size();
if (result.getTargetInfo() != null && result.getTargetInfo().getInstalledDistributionSet() != null) {
result.getTargetInfo().getInstalledDistributionSet().getName();
result.getTargetInfo().getInstalledDistributionSet().getModules().size();
}
if (result.getAssignedDistributionSet() != null) {
result.getAssignedDistributionSet().getName();
result.getAssignedDistributionSet().getModules().size();
}
}
return result;
}
@Override
public List<Target> findTargetByControllerID(final Collection<String> controllerIDs) {
return new ArrayList<>(targetRepository
.findAll(TargetSpecifications.byControllerIdWithStatusAndAssignedInJoin(controllerIDs)));
}
@Override
public Long countTargetsAll() {
return targetRepository.count();
}
@Override
public Slice<Target> findTargetsAll(final Pageable pageable) {
// workarround - no join fetch allowed that is why we need specification
// instead of query for
// count() of Pageable
final Specification<JpaTarget> spec = (root, query, cb) -> {
if (!query.getResultType().isAssignableFrom(Long.class)) {
root.fetch(JpaTarget_.targetInfo);
}
return cb.conjunction();
};
return convertPage(criteriaNoCountDao.findAll(spec, pageable, JpaTarget.class), pageable);
}
@Override
public Slice<Target> findTargetsAll(final TargetFilterQuery targetFilterQuery, final Pageable pageable) {
return findTargetsBySpec(RSQLUtility.parse(targetFilterQuery.getQuery(), TargetFields.class), pageable);
}
@Override
public Page<Target> findTargetsAll(final String targetFilterQuery, final Pageable pageable) {
return findTargetsBySpec(RSQLUtility.parse(targetFilterQuery, TargetFields.class), pageable);
}
private Page<Target> findTargetsBySpec(final Specification<JpaTarget> spec, final Pageable pageable) {
return convertPage(targetRepository.findAll(spec, pageable), pageable);
}
@Override
public List<Target> findTargetsByControllerIDsWithTags(final List<String> controllerIDs) {
final List<List<String>> partition = Lists.partition(controllerIDs, Constants.MAX_ENTRIES_IN_STATEMENT);
return partition.stream()
.map(ids -> targetRepository.findAll(TargetSpecifications.byControllerIdWithStatusAndTagsInJoin(ids)))
.flatMap(t -> t.stream()).collect(Collectors.toList());
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public Target updateTarget(final Target target) {
Assert.notNull(target.getId());
final JpaTarget toUpdate = (JpaTarget) target;
toUpdate.setNew(false);
return targetRepository.save(toUpdate);
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public List<Target> updateTargets(final Collection<Target> targets) {
@SuppressWarnings({ "unchecked", "rawtypes" })
final Collection<JpaTarget> toUpdate = (Collection) targets;
toUpdate.forEach(target -> target.setNew(false));
return new ArrayList<>(targetRepository.save(toUpdate));
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public void deleteTargets(final Long... targetIDs) {
// we need to select the target IDs first to check the if the targetIDs
// belonging to the
// tenant! Delete statement are not automatically enhanced with the
// @FilterDef of the
// hibernate session.
final List<Long> targetsForCurrentTenant = targetRepository.findAll(Lists.newArrayList(targetIDs)).stream()
.map(Target::getId).collect(Collectors.toList());
if (!targetsForCurrentTenant.isEmpty()) {
targetInfoRepository.deleteByTargetIdIn(targetsForCurrentTenant);
targetRepository.deleteByIdIn(targetsForCurrentTenant);
}
}
@Override
public Page<Target> findTargetByAssignedDistributionSet(final Long distributionSetID, final Pageable pageReq) {
return targetRepository.findByAssignedDistributionSetId(pageReq, distributionSetID);
}
@Override
public Page<Target> findTargetByAssignedDistributionSet(final Long distributionSetID, final String rsqlParam,
final Pageable pageReq) {
final Specification<JpaTarget> spec = RSQLUtility.parse(rsqlParam, TargetFields.class);
return convertPage(
targetRepository
.findAll((Specification<JpaTarget>) (root, query,
cb) -> cb.and(TargetSpecifications.hasAssignedDistributionSet(distributionSetID)
.toPredicate(root, query, cb), spec.toPredicate(root, query, cb)),
pageReq),
pageReq);
}
private static Page<Target> convertPage(final Page<JpaTarget> findAll, final Pageable pageable) {
return new PageImpl<>(new ArrayList<>(findAll.getContent()), pageable, findAll.getTotalElements());
}
private static Slice<Target> convertPage(final Slice<JpaTarget> findAll, final Pageable pageable) {
return new PageImpl<>(new ArrayList<>(findAll.getContent()), pageable, 0);
}
@Override
public Page<Target> findTargetByInstalledDistributionSet(final Long distributionSetID, final Pageable pageReq) {
return targetRepository.findByTargetInfoInstalledDistributionSetId(pageReq, distributionSetID);
}
@Override
public Page<Target> findTargetByInstalledDistributionSet(final Long distributionSetId, final String rsqlParam,
final Pageable pageable) {
final Specification<JpaTarget> spec = RSQLUtility.parse(rsqlParam, TargetFields.class);
return convertPage(
targetRepository
.findAll((Specification<JpaTarget>) (root, query,
cb) -> cb.and(TargetSpecifications.hasInstalledDistributionSet(distributionSetId)
.toPredicate(root, query, cb), spec.toPredicate(root, query, cb)),
pageable),
pageable);
}
@Override
public Page<Target> findTargetByUpdateStatus(final Pageable pageable, final TargetUpdateStatus status) {
return targetRepository.findByTargetInfoUpdateStatus(pageable, status);
}
@Override
public Slice<Target> findTargetByFilters(final Pageable pageable, final Collection<TargetUpdateStatus> status,
final String searchText, final Long installedOrAssignedDistributionSetId,
final Boolean selectTargetWithNoTag, final String... tagNames) {
final List<Specification<JpaTarget>> specList = buildSpecificationList(status, searchText,
installedOrAssignedDistributionSetId, selectTargetWithNoTag, true, tagNames);
return findByCriteriaAPI(pageable, specList);
}
@Override
public Long countTargetByFilters(final Collection<TargetUpdateStatus> status, final String searchText,
final Long installedOrAssignedDistributionSetId, final Boolean selectTargetWithNoTag,
final String... tagNames) {
final List<Specification<JpaTarget>> specList = buildSpecificationList(status, searchText,
installedOrAssignedDistributionSetId, selectTargetWithNoTag, true, tagNames);
return countByCriteriaAPI(specList);
}
private static List<Specification<JpaTarget>> buildSpecificationList(final Collection<TargetUpdateStatus> status,
final String searchText, final Long installedOrAssignedDistributionSetId,
final Boolean selectTargetWithNoTag, final boolean fetch, final String... tagNames) {
final List<Specification<JpaTarget>> specList = new ArrayList<>();
if (status != null && !status.isEmpty()) {
specList.add(TargetSpecifications.hasTargetUpdateStatus(status, fetch));
}
if (installedOrAssignedDistributionSetId != null) {
specList.add(
TargetSpecifications.hasInstalledOrAssignedDistributionSet(installedOrAssignedDistributionSetId));
}
if (!Strings.isNullOrEmpty(searchText)) {
specList.add(TargetSpecifications.likeNameOrDescriptionOrIp(searchText));
}
if (selectTargetWithNoTag != null && (selectTargetWithNoTag || (tagNames != null && tagNames.length > 0))) {
specList.add(TargetSpecifications.hasTags(tagNames, selectTargetWithNoTag));
}
return specList;
}
private Slice<Target> findByCriteriaAPI(final Pageable pageable, final List<Specification<JpaTarget>> specList) {
if (specList == null || specList.isEmpty()) {
return convertPage(criteriaNoCountDao.findAll(pageable, JpaTarget.class), pageable);
}
return convertPage(
criteriaNoCountDao.findAll(SpecificationsBuilder.combineWithAnd(specList), pageable, JpaTarget.class),
pageable);
}
private Long countByCriteriaAPI(final List<Specification<JpaTarget>> specList) {
if (specList == null || specList.isEmpty()) {
return targetRepository.count();
}
return targetRepository.count(SpecificationsBuilder.combineWithAnd(specList));
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public TargetTagAssignmentResult toggleTagAssignment(final Collection<Target> targets, final TargetTag tag) {
return toggleTagAssignment(
targets.stream().map(target -> target.getControllerId()).collect(Collectors.toList()), tag.getName());
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public TargetTagAssignmentResult toggleTagAssignment(final Collection<String> targetIds, final String tagName) {
final TargetTag tag = targetTagRepository.findByNameEquals(tagName);
final List<Target> alreadyAssignedTargets = targetRepository.findByTagNameAndControllerIdIn(tagName, targetIds);
final List<JpaTarget> allTargets = targetRepository
.findAll(TargetSpecifications.byControllerIdWithStatusAndTagsInJoin(targetIds));
// all are already assigned -> unassign
if (alreadyAssignedTargets.size() == allTargets.size()) {
alreadyAssignedTargets.forEach(target -> target.getTags().remove(tag));
final TargetTagAssignmentResult result = new TargetTagAssignmentResult(0, 0, alreadyAssignedTargets.size(),
Collections.emptyList(), alreadyAssignedTargets, tag);
afterCommit.afterCommit(() -> eventBus.post(new TargetTagAssigmentResultEvent(result)));
return result;
}
allTargets.removeAll(alreadyAssignedTargets);
// some or none are assigned -> assign
allTargets.forEach(target -> target.getTags().add(tag));
final TargetTagAssignmentResult result = new TargetTagAssignmentResult(alreadyAssignedTargets.size(),
allTargets.size(), 0, new ArrayList<>(targetRepository.save(allTargets)), Collections.emptyList(), tag);
afterCommit.afterCommit(() -> eventBus.post(new TargetTagAssigmentResultEvent(result)));
// no reason to persist the tag
entityManager.detach(tag);
return result;
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public List<Target> assignTag(final Collection<String> targetIds, final TargetTag tag) {
final List<JpaTarget> allTargets = targetRepository
.findAll(TargetSpecifications.byControllerIdWithStatusAndTagsInJoin(targetIds));
allTargets.forEach(target -> target.getTags().add(tag));
final List<Target> save = new ArrayList<>(targetRepository.save(allTargets));
afterCommit.afterCommit(() -> {
final TargetTagAssignmentResult assigmentResult = new TargetTagAssignmentResult(0, save.size(), 0, save,
Collections.emptyList(), tag);
eventBus.post(new TargetTagAssigmentResultEvent(assigmentResult));
});
return save;
}
private List<Target> unAssignTag(final Collection<Target> targets, final TargetTag tag) {
final Collection<JpaTarget> toUnassign = (Collection) targets;
toUnassign.forEach(target -> target.getTags().remove(tag));
final List<Target> save = new ArrayList<>(targetRepository.save(toUnassign));
afterCommit.afterCommit(() -> {
final TargetTagAssignmentResult assigmentResult = new TargetTagAssignmentResult(0, 0, save.size(),
Collections.emptyList(), save, tag);
eventBus.post(new TargetTagAssigmentResultEvent(assigmentResult));
});
return save;
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public List<Target> unAssignAllTargetsByTag(final TargetTag tag) {
return unAssignTag(tag.getAssignedToTargets(), tag);
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public Target unAssignTag(final String controllerID, final TargetTag targetTag) {
// TODO : optimize this, findone?
final List<Target> allTargets = new ArrayList<>(targetRepository
.findAll(TargetSpecifications.byControllerIdWithStatusAndTagsInJoin(Arrays.asList(controllerID))));
final List<Target> unAssignTag = unAssignTag(allTargets, targetTag);
return unAssignTag.isEmpty() ? null : unAssignTag.get(0);
}
@Override
public Slice<Target> findTargetsAllOrderByLinkedDistributionSet(final Pageable pageable,
final Long orderByDistributionId, final Long filterByDistributionId,
final Collection<TargetUpdateStatus> filterByStatus, final String filterBySearchText,
final Boolean selectTargetWithNoTag, final String... filterByTagNames) {
final CriteriaBuilder cb = entityManager.getCriteriaBuilder();
final CriteriaQuery<JpaTarget> query = cb.createQuery(JpaTarget.class);
final Root<JpaTarget> targetRoot = query.from(JpaTarget.class);
// necessary joins for the select
final Join<JpaTarget, JpaTargetInfo> targetInfo = (Join<JpaTarget, JpaTargetInfo>) targetRoot
.fetch(JpaTarget_.targetInfo, JoinType.LEFT);
// select case expression to retrieve the case value as a column to be
// able to order based on
// this column, installed first,...
final Expression<Object> selectCase = cb.selectCase()
.when(cb.equal(targetInfo.get(JpaTargetInfo_.installedDistributionSet).get(JpaDistributionSet_.id),
orderByDistributionId), 1)
.when(cb.equal(targetRoot.get(JpaTarget_.assignedDistributionSet).get(JpaDistributionSet_.id),
orderByDistributionId), 2)
.otherwise(100);
// multiselect statement order by the select case and controllerId
query.distinct(true);
// build the specifications and then to predicates necessary by the
// given filters
final Predicate[] specificationsForMultiSelect = specificationsToPredicate(
buildSpecificationList(filterByStatus, filterBySearchText, filterByDistributionId,
selectTargetWithNoTag, true, filterByTagNames),
targetRoot, query, cb);
// if we have some predicates then add it to the where clause of the
// multiselect
if (specificationsForMultiSelect.length > 0) {
query.where(specificationsForMultiSelect);
}
// add the order to the multi select first based on the selectCase
query.orderBy(cb.asc(selectCase), cb.desc(targetRoot.get(JpaTarget_.id)));
// the result is a Object[] due the fact that the selectCase is an extra
// column, so it cannot
// be mapped directly to a Target entity because the selectCase is not a
// attribute of the
// Target entity, the the Object array contains the Target on the first
// index (case of the
// multiselect order) of the array and
// the 2nd contains the selectCase int value.
final int pageSize = pageable.getPageSize();
final List<JpaTarget> resultList = entityManager.createQuery(query).setFirstResult(pageable.getOffset())
.setMaxResults(pageSize + 1).getResultList();
final boolean hasNext = resultList.size() > pageSize;
return new SliceImpl<>(new ArrayList<>(resultList), pageable, hasNext);
}
private static Predicate[] specificationsToPredicate(final List<Specification<JpaTarget>> specifications,
final Root<JpaTarget> root, final CriteriaQuery<?> query, final CriteriaBuilder cb) {
final Predicate[] predicates = new Predicate[specifications.size()];
for (int index = 0; index < predicates.length; index++) {
predicates[index] = specifications.get(index).toPredicate(root, query, cb);
}
return predicates;
}
@Override
public Long countTargetByAssignedDistributionSet(final Long distId) {
return targetRepository.countByAssignedDistributionSetId(distId);
}
@Override
public Long countTargetByInstalledDistributionSet(final Long distId) {
return targetRepository.countByTargetInfoInstalledDistributionSetId(distId);
}
@Override
public List<TargetIdName> findAllTargetIds() {
final CriteriaBuilder cb = entityManager.getCriteriaBuilder();
final CriteriaQuery<TargetIdName> query = cb.createQuery(TargetIdName.class);
final Root<JpaTarget> targetRoot = query.from(JpaTarget.class);
return entityManager.createQuery(query.multiselect(targetRoot.get(JpaTarget_.id),
targetRoot.get(JpaTarget_.controllerId), targetRoot.get(JpaTarget_.name))).getResultList();
}
@Override
public List<TargetIdName> findAllTargetIdsByFilters(final Pageable pageRequest,
final Collection<TargetUpdateStatus> filterByStatus, final String filterBySearchText,
final Long installedOrAssignedDistributionSetId, final Boolean selectTargetWithNoTag,
final String... filterByTagNames) {
final CriteriaBuilder cb = entityManager.getCriteriaBuilder();
final CriteriaQuery<Object[]> query = cb.createQuery(Object[].class);
final Root<JpaTarget> targetRoot = query.from(JpaTarget.class);
List<Object[]> resultList;
String sortProperty = JpaTarget_.id.getName();
if (pageRequest.getSort() != null && pageRequest.getSort().iterator().hasNext()) {
sortProperty = pageRequest.getSort().iterator().next().getProperty();
}
final CriteriaQuery<Object[]> multiselect = query.multiselect(targetRoot.get(JpaTarget_.id),
targetRoot.get(JpaTarget_.controllerId), targetRoot.get(JpaTarget_.name), targetRoot.get(sortProperty));
final Predicate[] specificationsForMultiSelect = specificationsToPredicate(
buildSpecificationList(filterByStatus, filterBySearchText, installedOrAssignedDistributionSetId,
selectTargetWithNoTag, false, filterByTagNames),
targetRoot, multiselect, cb);
// if we have some predicates then add it to the where clause of the
// multiselect
if (specificationsForMultiSelect.length > 0) {
multiselect.where(specificationsForMultiSelect);
}
resultList = getTargetIdNameResultSet(pageRequest, cb, targetRoot, multiselect);
return resultList.parallelStream().map(o -> new TargetIdName((long) o[0], o[1].toString(), o[2].toString()))
.collect(Collectors.toList());
}
@Override
public List<TargetIdName> findAllTargetIdsByTargetFilterQuery(final Pageable pageRequest,
final TargetFilterQuery targetFilterQuery) {
final CriteriaBuilder cb = entityManager.getCriteriaBuilder();
final CriteriaQuery<Object[]> query = cb.createQuery(Object[].class);
final Root<JpaTarget> targetRoot = query.from(JpaTarget.class);
String sortProperty = JpaTarget_.id.getName();
if (pageRequest.getSort() != null && pageRequest.getSort().iterator().hasNext()) {
sortProperty = pageRequest.getSort().iterator().next().getProperty();
}
final CriteriaQuery<Object[]> multiselect = query.multiselect(targetRoot.get(JpaTarget_.id),
targetRoot.get(JpaTarget_.controllerId), targetRoot.get(JpaTarget_.name), targetRoot.get(sortProperty));
final Specification<JpaTarget> spec = RSQLUtility.parse(targetFilterQuery.getQuery(), TargetFields.class);
final List<Specification<JpaTarget>> specList = new ArrayList<>();
specList.add(spec);
final Predicate[] specificationsForMultiSelect = specificationsToPredicate(specList, targetRoot, multiselect,
cb);
// if we have some predicates then add it to the where clause of the
// multiselect
if (specificationsForMultiSelect.length > 0) {
multiselect.where(specificationsForMultiSelect);
}
final List<Object[]> resultList = getTargetIdNameResultSet(pageRequest, cb, targetRoot, multiselect);
return resultList.parallelStream().map(o -> new TargetIdName((long) o[0], o[1].toString(), o[2].toString()))
.collect(Collectors.toList());
}
@PreDestroy
void destroy() {
eventBus.unregister(this);
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@CacheEvict(value = { "targetsCreatedOverPeriod" }, allEntries = true)
public Target createTarget(final Target t, final TargetUpdateStatus status, final Long lastTargetQuery,
final URI address) {
final JpaTarget target = (JpaTarget) t;
if (targetRepository.findByControllerId(target.getControllerId()) != null) {
throw new EntityAlreadyExistsException(target.getControllerId());
}
target.setNew(true);
final JpaTarget savedTarget = targetRepository.save(target);
final JpaTargetInfo targetInfo = (JpaTargetInfo) savedTarget.getTargetInfo();
targetInfo.setUpdateStatus(status);
if (lastTargetQuery != null) {
targetInfo.setLastTargetQuery(lastTargetQuery);
}
if (address != null) {
targetInfo.setAddress(address.toString());
}
targetInfo.setNew(true);
final Target targetToReturn = targetInfoRepository.save(targetInfo).getTarget();
targetInfo.setNew(false);
return targetToReturn;
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@CacheEvict(value = { "targetsCreatedOverPeriod" }, allEntries = true)
public Target createTarget(final Target target) {
return createTarget(target, TargetUpdateStatus.UNKNOWN, null, null);
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public List<Target> createTargets(final Collection<Target> targets) {
if (!targets.isEmpty() && targetRepository.countByControllerIdIn(
targets.stream().map(target -> target.getControllerId()).collect(Collectors.toList())) > 0) {
throw new EntityAlreadyExistsException();
}
final List<Target> savedTargets = new ArrayList<>();
for (final Target t : targets) {
final Target myTarget = createTarget(t);
savedTargets.add(myTarget);
}
return savedTargets;
}
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public List<Target> createTargets(final Collection<Target> targets, final TargetUpdateStatus status,
final Long lastTargetQuery, final URI address) {
if (targetRepository.countByControllerIdIn(
targets.stream().map(target -> target.getControllerId()).collect(Collectors.toList())) > 0) {
throw new EntityAlreadyExistsException();
}
final List<Target> savedTargets = new ArrayList<>();
for (final Target t : targets) {
final Target myTarget = createTarget(t, status, lastTargetQuery, address);
savedTargets.add(myTarget);
}
return savedTargets;
}
@Override
public List<Target> findTargetsByTag(final String tagName) {
final JpaTargetTag tag = targetTagRepository.findByNameEquals(tagName);
return new ArrayList<>(targetRepository.findByTag(tag));
}
@Override
public Long countTargetByTargetFilterQuery(final TargetFilterQuery targetFilterQuery) {
final Specification<JpaTarget> specs = RSQLUtility.parse(targetFilterQuery.getQuery(), TargetFields.class);
return targetRepository.count(specs);
}
@Override
public Long countTargetByTargetFilterQuery(final String targetFilterQuery) {
final Specification<JpaTarget> specs = RSQLUtility.parse(targetFilterQuery, TargetFields.class);
return targetRepository.count(specs);
}
private List<Object[]> getTargetIdNameResultSet(final Pageable pageRequest, final CriteriaBuilder cb,
final Root<JpaTarget> targetRoot, final CriteriaQuery<Object[]> multiselect) {
List<Object[]> resultList;
if (pageRequest.getSort() != null) {
final List<Order> orders = new ArrayList<>();
final Sort sort = pageRequest.getSort();
for (final Sort.Order sortOrder : sort) {
if (sortOrder.getDirection() == Direction.ASC) {
orders.add(cb.asc(targetRoot.get(sortOrder.getProperty())));
} else {
orders.add(cb.desc(targetRoot.get(sortOrder.getProperty())));
}
}
multiselect.orderBy(orders);
resultList = entityManager.createQuery(multiselect).setFirstResult(pageRequest.getOffset())
.setMaxResults(pageRequest.getPageSize()).getResultList();
} else {
resultList = entityManager.createQuery(multiselect).getResultList();
}
return resultList;
}
@Override
@Transactional(propagation = Propagation.SUPPORTS)
public Target generateTarget(final String controllerId) {
return new JpaTarget(controllerId);
}
}

View File

@@ -0,0 +1,171 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.repository.jpa.model.JpaTenantConfiguration;
import org.eclipse.hawkbit.repository.model.TenantConfiguration;
import org.eclipse.hawkbit.repository.model.TenantConfigurationValue;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey;
import org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationValidatorException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.ApplicationContext;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.convert.support.ConfigurableConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.env.Environment;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
/**
* Central tenant configuration management operations of the SP server.
*/
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
@Validated
@Service
public class JpaTenantConfigurationManagement implements EnvironmentAware, TenantConfigurationManagement {
@Autowired
private TenantConfigurationRepository tenantConfigurationRepository;
@Autowired
private ApplicationContext applicationContext;
private static final ConfigurableConversionService conversionService = new DefaultConversionService();
private Environment environment;
@Override
@Cacheable(value = "tenantConfiguration", key = "#configurationKey.getKeyName()")
public <T> TenantConfigurationValue<T> getConfigurationValue(final TenantConfigurationKey configurationKey,
final Class<T> propertyType) {
validateTenantConfigurationDataType(configurationKey, propertyType);
final TenantConfiguration tenantConfiguration = tenantConfigurationRepository
.findByKey(configurationKey.getKeyName());
return buildTenantConfigurationValueByKey(configurationKey, propertyType, tenantConfiguration);
}
/**
* Validates the data type of the tenant configuration. If it is possible to
* cast to the given data type.
*
* @param configurationKey
* the key
* @param propertyType
* the class
*/
static <T> void validateTenantConfigurationDataType(final TenantConfigurationKey configurationKey,
final Class<T> propertyType) {
if (!configurationKey.getDataType().isAssignableFrom(propertyType)) {
throw new TenantConfigurationValidatorException(
String.format("Cannot parse the database value of type %s into the type %s.",
configurationKey.getDataType(), propertyType));
}
}
@Override
public <T> TenantConfigurationValue<T> buildTenantConfigurationValueByKey(
final TenantConfigurationKey configurationKey, final Class<T> propertyType,
final TenantConfiguration tenantConfiguration) {
if (tenantConfiguration != null) {
return TenantConfigurationValue.<T> builder().global(false).createdBy(tenantConfiguration.getCreatedBy())
.createdAt(tenantConfiguration.getCreatedAt())
.lastModifiedAt(tenantConfiguration.getLastModifiedAt())
.lastModifiedBy(tenantConfiguration.getLastModifiedBy())
.value(conversionService.convert(tenantConfiguration.getValue(), propertyType)).build();
} else if (configurationKey.getDefaultKeyName() != null) {
return TenantConfigurationValue.<T> builder().global(true).createdBy(null).createdAt(null)
.lastModifiedAt(null).lastModifiedBy(null)
.value(getGlobalConfigurationValue(configurationKey, propertyType)).build();
}
return null;
}
@Override
public TenantConfigurationValue<?> getConfigurationValue(final TenantConfigurationKey configurationKey) {
return getConfigurationValue(configurationKey, configurationKey.getDataType());
}
@Override
public <T> T getGlobalConfigurationValue(final TenantConfigurationKey configurationKey,
final Class<T> propertyType) {
if (!configurationKey.getDataType().isAssignableFrom(propertyType)) {
throw new TenantConfigurationValidatorException(
String.format("Cannot parse the database value of type %s into the type %s.",
configurationKey.getDataType(), propertyType));
}
final T valueInProperties = environment.getProperty(configurationKey.getDefaultKeyName(), propertyType);
if (valueInProperties == null) {
return conversionService.convert(configurationKey.getDefaultValue(), propertyType);
}
return valueInProperties;
}
@Override
@CacheEvict(value = "tenantConfiguration", key = "#configurationKey.getKeyName()")
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Modifying
public <T> TenantConfigurationValue<T> addOrUpdateConfiguration(final TenantConfigurationKey configurationKey,
final T value) {
if (!configurationKey.getDataType().isAssignableFrom(value.getClass())) {
throw new TenantConfigurationValidatorException(String.format(
"Cannot parse the value %s of type %s into the type %s defined by the configuration key.", value,
value.getClass(), configurationKey.getDataType()));
}
configurationKey.validate(applicationContext, value);
JpaTenantConfiguration tenantConfiguration = tenantConfigurationRepository
.findByKey(configurationKey.getKeyName());
if (tenantConfiguration == null) {
tenantConfiguration = new JpaTenantConfiguration(configurationKey.getKeyName(), value.toString());
} else {
tenantConfiguration.setValue(value.toString());
}
final JpaTenantConfiguration updatedTenantConfiguration = tenantConfigurationRepository
.save(tenantConfiguration);
final Class<T> clazzT = (Class<T>) value.getClass();
return TenantConfigurationValue.<T> builder().global(false).createdBy(updatedTenantConfiguration.getCreatedBy())
.createdAt(updatedTenantConfiguration.getCreatedAt())
.lastModifiedAt(updatedTenantConfiguration.getLastModifiedAt())
.lastModifiedBy(updatedTenantConfiguration.getLastModifiedBy())
.value(conversionService.convert(updatedTenantConfiguration.getValue(), clazzT)).build();
}
@Override
@CacheEvict(value = "tenantConfiguration", key = "#configurationKey.getKeyName()")
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Modifying
public void deleteConfiguration(final TenantConfigurationKey configurationKey) {
tenantConfigurationRepository.deleteByKey(configurationKey.getKeyName());
}
@Override
public void setEnvironment(final Environment environment) {
this.environment = environment;
}
}

View File

@@ -0,0 +1,60 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa;
import java.util.Optional;
import org.eclipse.hawkbit.report.model.TenantUsage;
import org.eclipse.hawkbit.repository.TenantStatsManagement;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
/**
* Management service for statistics of a single tenant.
*
*/
@Validated
@Service
public class JpaTenantStatsManagement implements TenantStatsManagement {
@Autowired
private TargetRepository targetRepository;
@Autowired
private LocalArtifactRepository artifactRepository;
@Autowired
private ActionRepository actionRepository;
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_UNCOMMITTED)
public TenantUsage getStatsOfTenant(final String tenant) {
final TenantUsage result = new TenantUsage(tenant);
result.setTargets(targetRepository.count());
final Long artifacts = artifactRepository.countBySoftwareModuleDeleted(false);
result.setArtifacts(artifacts);
final Optional<Long> artifactsSize = artifactRepository.getSumOfUndeletedArtifactSize();
if (artifactsSize.isPresent()) {
result.setOverallArtifactVolumeInBytes(artifactsSize.get());
}
result.setActions(actionRepository.count());
return result;
}
}

View File

@@ -0,0 +1,100 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa;
import java.util.List;
import java.util.Optional;
import org.eclipse.hawkbit.repository.jpa.model.JpaLocalArtifact;
import org.eclipse.hawkbit.repository.model.LocalArtifact;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.Query;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
/**
* {@link LocalArtifact} repository.
*
*/
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
public interface LocalArtifactRepository extends BaseEntityRepository<JpaLocalArtifact, Long> {
/**
* Counts artifacts size where the related software module is not
* deleted/archived.
*
* @return sum of artifacts size in bytes
*/
@Query("SELECT SUM(la.size) FROM JpaLocalArtifact la WHERE la.softwareModule.deleted = 0")
Optional<Long> getSumOfUndeletedArtifactSize();
/**
* Counts artifacts where the related software module is deleted/archived.
*
* @param deleted
* to true for counting the deleted artifacts
*
* @return number of artifacts
*/
Long countBySoftwareModuleDeleted(boolean deleted);
/**
* Searches for a {@link LocalArtifact} based on given gridFsFileName.
*
* @param gridFsFileName
* to search
* @return list of {@link LocalArtifact}s.
*/
List<LocalArtifact> findByGridFsFileName(String gridFsFileName);
/**
* Searches for a {@link LocalArtifact} based on given gridFsFileName.
*
* @param gridFsFileName
* to search
* @return {@link LocalArtifact} the first in the result list
*/
JpaLocalArtifact findFirstByGridFsFileName(String gridFsFileName);
/**
* Searches for a {@link LocalArtifact} based user provided filename at
* upload.
*
* @param filename
* to search
* @return list of {@link LocalArtifact}.
*/
List<LocalArtifact> findByFilename(String filename);
/**
* Searches for local artifact for a base software module.
*
* @param pageReq
* Pageable
* @param swId
* software module id
*
* @return Page<LocalArtifact>
*/
Page<LocalArtifact> findBySoftwareModuleId(Pageable pageReq, final Long swId);
/**
* Searches for a {@link LocalArtifact} based user provided filename at
* upload and selected software module id.
*
* @param filename
* to search
* @param softwareModuleId
* selected software module id
* @return list of {@link LocalArtifact}.
*/
List<LocalArtifact> findByFilenameAndSoftwareModuleId(final String filename, final Long softwareModuleId);
}

View File

@@ -0,0 +1,114 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa;
import java.io.Serializable;
import java.util.List;
import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.stereotype.Repository;
/**
* Workaround as spring data does not provide a {@link Slice} based
* {@link JpaRepository#findAll()}.
*
*/
@Repository
public class NoCountPagingRepository {
@Autowired
protected EntityManager em;
/**
* Searches without the need for an extra count query.
*
* @param spec
* to search for
* @param pageable
* information
* @param domainClass
* of the {@link Entity}
*
* @return {@link Slice} of data
*
* @see org.springframework.data.jpa.repository.JpaSpecificationExecutor#findAll(org.springframework
* .data.jpa.domain.Specification,
* org.springframework.data.domain.Pageable)
*/
public <T, I extends Serializable> Slice<T> findAll(final Specification<T> spec, final Pageable pageable,
final Class<T> domainClass) {
final SimpleJpaNoCountRepository<T, I> noCountDao = new SimpleJpaNoCountRepository<>(domainClass, em);
return noCountDao.findAll(spec, pageable);
}
/**
* Searches without the need for an extra count query.
*
* @param pageable
* information
* @param domainClass
* of the {@link Entity}
*
* @return {@link Slice} of data
*
* @see org.springframework.data.jpa.repository.JpaSpecificationExecutor#findAll(org.springframework
* .data.jpa.domain.Specification,
* org.springframework.data.domain.Pageable)
*/
public <T, I extends Serializable> Slice<T> findAll(final Pageable pageable, final Class<T> domainClass) {
final SimpleJpaNoCountRepository<T, I> noCountDao = new SimpleJpaNoCountRepository<>(domainClass, em);
return noCountDao.findAll(pageable);
}
/**
* Repository implementation with disabled count query.
*
*
*
* @param <T>
* entity type
* @param <I>
* key or ID type
*/
public static class SimpleJpaNoCountRepository<T, I extends Serializable> extends SimpleJpaRepository<T, I> {
/**
* Constructor.
*
* @param domainClass
* of the {@link Entity}
* @param em
* {@link EntityManager} instance for the queries
*/
public SimpleJpaNoCountRepository(final Class<T> domainClass, final EntityManager em) {
super(domainClass, em);
}
@Override
protected Page<T> readPage(final TypedQuery<T> query, final Pageable pageable, final Specification<T> spec) {
query.setFirstResult(pageable.getOffset());
query.setMaxResults(pageable.getPageSize());
final List<T> content = query.getResultList();
return new PageImpl<>(content, pageable, content.size());
}
}
}

View File

@@ -0,0 +1,134 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa;
import java.util.List;
import org.eclipse.hawkbit.repository.jpa.model.JpaRollout;
import org.eclipse.hawkbit.repository.jpa.model.JpaRolloutGroup;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupStatus;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
/**
* The repository interface for the {@link RolloutGroup} model.
*/
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
public interface RolloutGroupRepository
extends BaseEntityRepository<JpaRolloutGroup, Long>, JpaSpecificationExecutor<JpaRolloutGroup> {
/**
* Retrieves all {@link RolloutGroup} referring a specific rollout in the
* order of creating them. ID ASC.
*
* @param rollout
* the rollout the rolloutgroups belong to
* @return the rollout groups belonging to a rollout ordered by ID ASC.
*/
List<JpaRolloutGroup> findByRolloutOrderByIdAsc(final JpaRollout rollout);
/**
* Retrieves all {@link RolloutGroup} referring a specific rollout in a
* specific {@link RolloutGroupStatus}.
*
* @param rollout
* the rollout the rolloutgroup belong to
* @param status
* the status of the rollout groups
* @return the rollout groups belonging to a rollout in a specific status
*/
List<JpaRolloutGroup> findByRolloutAndStatus(final Rollout rollout, final RolloutGroupStatus status);
/**
* Counts all {@link RolloutGroup} referring a specific rollout.
*
* @param rollout
* the rollout the rolloutgroup belong to
* @return the count of the rollout groups for a specific rollout
*/
Long countByRollout(final JpaRollout rollout);
/**
* Counts all {@link RolloutGroup} referring a specific rollout in a
* specific {@link RolloutGroupStatus}.
*
* @param rollout
* the rollout the rolloutgroup belong to
* @param rolloutGroupStatus
* the status of the rollout groups
* @return the count of rollout groups belonging to a rollout in a specific
* status
*/
Long countByRolloutAndStatus(JpaRollout rollout, RolloutGroupStatus rolloutGroupStatus);
/**
* Counts all {@link RolloutGroup} referring a specific rollout in specific
* {@link RolloutGroupStatus}s. An in-clause statement does not work with
* the spring-data, so this is specific usecase regarding to the
* rollout-management to find out rolloutgroups which are in specific
* states.
*
* @param rollout
* the rollout the rolloutgroup belong to
* @param rolloutGroupStatus1
* the status of the rollout groups
* @param rolloutGroupStatus2
* the status of the rollout groups
* @return the count of rollout groups belonging to a rollout in specific
* status
*/
@Query("SELECT COUNT(r.id) FROM JpaRolloutGroup r WHERE r.rollout = :rollout and (r.status = :status1 or r.status = :status2)")
Long countByRolloutAndStatusOrStatus(@Param("rollout") JpaRollout rollout,
@Param("status1") RolloutGroupStatus rolloutGroupStatus1,
@Param("status2") RolloutGroupStatus rolloutGroupStatus2);
/**
* Retrieves all {@link RolloutGroup} for a specific parent in a specific
* status. Retrieves the child rolloutgroup for a specific status.
*
* @param rolloutGroup
* the parent rolloutgroup
* @param status
* the status of the rolloutgroups
* @return The child {@link RolloutGroup}s in a specific status
*/
List<JpaRolloutGroup> findByParentAndStatus(JpaRolloutGroup rolloutGroup, RolloutGroupStatus status);
/**
* Retrieves all {@link RolloutGroup} for a specific rollout and status not
* having ordered by ID DESC, latest top.
*
* @param rollout
* the rollout the rolloutgroup belong to
* @param notStatus
* the status which the rolloutgroup should not have
* @return rolloutgroup referring to a rollout and not having a specific
* status ordered by ID DESC.
*/
List<JpaRolloutGroup> findByRolloutAndStatusNotOrderByIdDesc(JpaRollout rollout, RolloutGroupStatus notStatus);
/**
* Retrieves all {@link RolloutGroup} for a specific rollout.
*
* @param rolloutId
* the ID of the rollout to find the rollout groups
* @param page
* the page request to sort, limit the result
* @return a page of found {@link RolloutGroup} or {@code empty}.
*/
Page<JpaRolloutGroup> findByRolloutId(final Long rolloutId, Pageable page);
}

View File

@@ -0,0 +1,90 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa;
import java.util.List;
import org.eclipse.hawkbit.repository.jpa.model.JpaRollout;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.Rollout.RolloutStatus;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
/**
* The repository interface for the {@link Rollout} model.
*/
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
public interface RolloutRepository
extends BaseEntityRepository<JpaRollout, Long>, JpaSpecificationExecutor<JpaRollout> {
/**
* Updates the {@code lastCheck} field of the {@link Rollout} for rollouts
* in a specific status and only if the {@code lastCheck} is overdue.
*
* @param lastCheck
* the time in milliseconds to set to the lastCheck column
* @param delay
* the delay between last checks
* @param status
* the status which the rollout should have to update the last
* check field
* @return the count of the updated rows. Zero if no row has been updated
*/
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Query("UPDATE JpaRollout r SET r.lastCheck = :lastCheck WHERE r.lastCheck < (:lastCheck - :delay) AND r.status=:status")
int updateLastCheck(@Param("lastCheck") final long lastCheck, @Param("delay") final long delay,
@Param("status") final RolloutStatus status);
/**
* Retrieves all {@link Rollout} for a specific {@code lastCheck} time and
* for a specific status.
*
* @param lastCheck
* the lastCheck time to find the specific rollout.
* @param status
* the status of the rollout to find
* @return the list of {@link Rollout} for specific lastCheck time and
* status
*/
List<JpaRollout> findByLastCheckAndStatus(long lastCheck, RolloutStatus status);
/**
* Retrieves all {@link Rollout} for a specific {@code name}
*
* @param name
* the rollout name
* @return {@link Rollout} for specific name
*/
Page<JpaRollout> findByName(final Pageable pageable, String name);
/**
* Retrieves all {@link Rollout} for a specific {@code name}
*
* @param name
* the rollout name
* @return {@link Rollout} for specific name
*/
JpaRollout findByName(String name);
/**
* Retrieves all {@link Rollout} for a specific status.
*
* @param status
* the status of the rollouts to retrieve
* @return a list of {@link Rollout} having the given status
*/
List<JpaRollout> findByStatus(final RolloutStatus status);
}

View File

@@ -0,0 +1,25 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa;
import org.eclipse.hawkbit.repository.jpa.model.RolloutTargetGroup;
import org.eclipse.hawkbit.repository.jpa.model.RolloutTargetGroupId;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.repository.CrudRepository;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
/**
* Spring data repository for {@link RolloutTargetGroup}.
*
*/
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
public interface RolloutTargetGroupRepository
extends CrudRepository<RolloutTargetGroup, RolloutTargetGroupId>, JpaSpecificationExecutor<RolloutTargetGroup> {
}

View File

@@ -0,0 +1,54 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa;
import java.util.List;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModuleMetadata;
import org.eclipse.hawkbit.repository.jpa.model.SwMetadataCompositeKey;
import org.eclipse.hawkbit.repository.model.SoftwareModuleMetadata;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
/**
* {@link SoftwareModuleMetadata} repository.
*
*/
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
public interface SoftwareModuleMetadataRepository
extends PagingAndSortingRepository<JpaSoftwareModuleMetadata, SwMetadataCompositeKey>,
JpaSpecificationExecutor<JpaSoftwareModuleMetadata> {
/**
* Saves all given entities.
*
* @param entities
* @return the saved entities
* @throws IllegalArgumentException
* in case the given entity is (@literal null}.
*/
@Override
<S extends JpaSoftwareModuleMetadata> List<S> save(Iterable<S> entities);
/**
* finds all software module meta data of the given software module id.
*
* @param swId
* the ID of the software module to retrieve the meta data
* @param pageable
* the page request to page the result set
* @return the paged result of all meta data of an given software module id
*/
Page<SoftwareModuleMetadata> findBySoftwareModuleId(final Long swId, Pageable pageable);
}

View File

@@ -0,0 +1,140 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa;
import java.util.List;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModuleType;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.EntityGraph.EntityGraphType;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
/**
* {@link SoftwareModule} repository.
*
*/
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
public interface SoftwareModuleRepository
extends BaseEntityRepository<JpaSoftwareModule, Long>, JpaSpecificationExecutor<JpaSoftwareModule> {
/**
* Counts all {@link SoftwareModule}s based on the given {@link Type}.
*
* @param type
* to count for
* @return number of {@link SoftwareModule}s
*/
Long countByType(JpaSoftwareModuleType type);
/**
* Retrieves {@link SoftwareModule} by filtering on name AND version AND
* type (which is unique per tenant.
*
* @param name
* to be filtered on
* @param version
* to be filtered on
* @param type
* to be filtered on
* @return the found {@link SoftwareModule} with the given name AND version
* AND type
*/
JpaSoftwareModule findOneByNameAndVersionAndType(String name, String version, JpaSoftwareModuleType type);
/**
* deletes the {@link SoftwareModule}s with the given IDs.
*
* @param modifiedAt
* current timestamp
* @param modifiedBy
* user name of current auditor
* @param ids
* to be deleted
*
*/
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Query("UPDATE JpaSoftwareModule b SET b.deleted = 1, b.lastModifiedAt = :lastModifiedAt, b.lastModifiedBy = :lastModifiedBy WHERE b.id IN :ids")
void deleteSoftwareModule(@Param("lastModifiedAt") Long modifiedAt, @Param("lastModifiedBy") String modifiedBy,
@Param("ids") final Long... ids);
/**
* @param pageable
* the page request to page the result set
* @param set
* to search for
* @return all {@link SoftwareModule}s that are assigned to given
* {@link DistributionSet}.
*/
Page<SoftwareModule> findByAssignedTo(Pageable pageable, JpaDistributionSet set);
/**
*
*
* @param set
* to search for
* @return all {@link SoftwareModule}s that are assigned to given
* {@link DistributionSet}
*/
@EntityGraph(value = "SoftwareModule.artifacts", type = EntityGraphType.LOAD)
List<JpaSoftwareModule> findByAssignedTo(JpaDistributionSet set);
/**
* @param pageable
* the page request to page the result set
* @param set
* to search for
* @param type
* to filter
* @return all {@link SoftwareModule}s that are assigned to given
* {@link DistributionSet} filtered by {@link SoftwareModuleType}.
*/
Page<SoftwareModule> findByAssignedToAndType(Pageable pageable, JpaDistributionSet set, JpaSoftwareModuleType type);
/**
* retrieves all software modules with a given {@link SoftwareModuleType}
* and {@link SoftwareModule#getId()}.
*
* @param ids
* to search for
* @param type
* to search for
* @return {@link List} of found {@link SoftwareModule}s
*/
// Workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=349477
@Query("SELECT sm FROM JpaSoftwareModule sm WHERE sm.id IN ?1 and sm.type = ?2")
List<SoftwareModule> findByIdInAndType(Iterable<Long> ids, JpaSoftwareModuleType type);
@Override
<S extends JpaSoftwareModule> List<S> save(Iterable<S> entities);
/**
* retrieves all software modules with a given
* {@link SoftwareModule#getId()}.
*
* @param ids
* to search for
* @return {@link List} of found {@link SoftwareModule}s
*/
// Workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=349477
@Query("SELECT sm FROM JpaSoftwareModule sm WHERE sm.id IN ?1")
List<JpaSoftwareModule> findByIdIn(Iterable<Long> ids);
}

View File

@@ -0,0 +1,61 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModuleType;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
/**
* Repository for {@link SoftwareModuleType}.
*
*/
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
public interface SoftwareModuleTypeRepository
extends BaseEntityRepository<JpaSoftwareModuleType, Long>, JpaSpecificationExecutor<JpaSoftwareModuleType> {
/**
* @param pageable
* @param isDeleted
* to <code>true</code> if only marked as deleted have to be
* count or all undeleted.
* @return found {@link SoftwareModuleType}s.
*/
Page<SoftwareModuleType> findByDeleted(Pageable pageable, boolean isDeleted);
/**
* @param isDeleted
* to <code>true</code> if only marked as deleted have to be
* count or all undeleted.
* @return number of {@link SoftwareModuleType}s in the repository.
*/
Long countByDeleted(boolean isDeleted);
/**
*
* @param key
* to search for
* @return all {@link SoftwareModuleType}s in the repository with given
* {@link SoftwareModuleType#getKey()}
*/
JpaSoftwareModuleType findByKey(String key);
/**
*
* @param name
* to search for
* @return all {@link SoftwareModuleType}s in the repository with given
* {@link SoftwareModuleType#getName()}
*/
JpaSoftwareModuleType findByName(String name);
}

View File

@@ -0,0 +1,46 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetFilterQuery;
import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
import org.springframework.data.domain.Page;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
/**
* Spring data repositories for {@link TargetFilterQuery}s.
*
*/
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
public interface TargetFilterQueryRepository
extends BaseEntityRepository<JpaTargetFilterQuery, Long>, JpaSpecificationExecutor<JpaTargetFilterQuery> {
/**
* Find customer target filter by name
*
* @param name
* @return custom target filter
*/
TargetFilterQuery findByName(final String name);
/**
* Find list of all custom target filters.
*/
@Override
Page<JpaTargetFilterQuery> findAll();
@Override
@Modifying
@Transactional
<S extends JpaTargetFilterQuery> S save(S entity);
}

View File

@@ -0,0 +1,70 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa;
import java.util.Collection;
import java.util.List;
import javax.persistence.Entity;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetInfo;
import org.eclipse.hawkbit.repository.model.TargetInfo;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
/**
* Usually a JPA spring data repository to handle {@link TargetInfo} entity.
* However, do to an eclipselink bug with spring boot now a regular interface
* that is implemented by {@link EclipseLinkTargetInfoRepository}.
*
*/
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
public interface TargetInfoRepository {
/**
* Sets new TargetUpdateStatus of given target if is not already on that
* value.
*
* @param status
* to set
* @param targets
* to set it for
*/
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Query("update JpaTargetInfo ti set ti.updateStatus = :status where ti.targetId in :targets and ti.updateStatus != :status")
void setTargetUpdateStatus(@Param("status") TargetUpdateStatus status, @Param("targets") List<Long> targets);
/**
* Save entity and evict cache with it.
*
* @param entity
* to persists
*
* @return persisted or updated {@link Entity}
*/
@CacheEvict(value = { "targetStatus", "distributionUsageInstalled", "targetsLastPoll" }, allEntries = true)
<S extends JpaTargetInfo> S save(S entity);
/**
* Deletes info entries by ID.
*
* @param targetIDs
* to delete
*/
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@CacheEvict(value = { "targetStatus", "distributionUsageInstalled", "targetsLastPoll" }, allEntries = true)
void deleteByTargetIdIn(final Collection<Long> targetIDs);
}

View File

@@ -0,0 +1,323 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa;
import java.util.Collection;
import java.util.List;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
import org.eclipse.hawkbit.repository.jpa.model.JpaRolloutGroup;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetTag;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.Tag;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
import org.eclipse.hawkbit.repository.model.TargetWithActionStatus;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.EntityGraph.EntityGraphType;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
/**
* {@link Target} repository.
*
*/
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
public interface TargetRepository extends BaseEntityRepository<JpaTarget, Long>, JpaSpecificationExecutor<JpaTarget> {
/**
* Loads {@link Target} including details {@link EntityGraph} by given ID.
*
* @param controllerID
* to search for
* @return found {@link Target} or <code>null</code> if not found.
*/
@EntityGraph(value = "Target.detail", type = EntityGraphType.LOAD)
JpaTarget findByControllerId(String controllerID);
/**
* Finds targets by given list of {@link Target#getControllerId()}s.
*
* @param controllerIDs
* to serach for
* @return list of found {@link Target}s
*/
List<Target> findByControllerIdIn(String... controllerIDs);
/**
* Deletes the {@link Target}s with the given target IDs.
*
* @param targetIDs
* to be deleted
*/
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
// Workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=349477
@Query("DELETE FROM JpaTarget t WHERE t.id IN ?1")
void deleteByIdIn(final Collection<Long> targetIDs);
/**
* Finds {@link Target}s by assigned {@link Tag}.
*
* @param tag
* to be found
* @return list of found targets
*/
@Query(value = "SELECT DISTINCT t FROM JpaTarget t JOIN t.tags tt WHERE tt = :tag")
List<JpaTarget> findByTag(@Param("tag") final JpaTargetTag tag);
/**
* Finds all {@link Target}s based on given {@link Target#getControllerId()}
* list and assigned {@link Tag#getName()}.
*
* @param tag
* to search for
* @param controllerIds
* to search for
* @return {@link List} of found {@link Target}s.
*/
@Query(value = "SELECT DISTINCT t from JpaTarget t JOIN t.tags tt WHERE tt.name = :tagname AND t.controllerId IN :targets")
List<Target> findByTagNameAndControllerIdIn(@Param("tagname") final String tag,
@Param("targets") final Collection<String> controllerIds);
/**
* Used by UI to filter based on selected status.
*
* @param pageable
* for page configuration
* @param status
* to filter for
*
* @return found targets
*/
Page<Target> findByTargetInfoUpdateStatus(final Pageable pageable, final TargetUpdateStatus status);
/**
* Finds all targets that have defined {@link DistributionSet} installed.
*
* @param pageable
* for page configuration
* @param set
* is the {@link DistributionSet} to filter for.
*
* @return found targets
*/
Page<Target> findByTargetInfoInstalledDistributionSet(final Pageable pageable, final JpaDistributionSet set);
/**
* retrieves the {@link Target}s which has the {@link DistributionSet}
* installed with the given ID.
*
* @param pageable
* parameter
* @param setID
* the ID of the {@link DistributionSet}
* @return the found {@link Target}s
*/
Page<Target> findByTargetInfoInstalledDistributionSetId(final Pageable pageable, final Long setID);
/**
* Finds all targets that have defined {@link DistributionSet} assigned.
*
* @param pageable
* for page configuration
* @param set
* is the {@link DistributionSet} to filter for.
*
* @return found targets
*/
Page<Target> findByAssignedDistributionSet(final Pageable pageable, final JpaDistributionSet set);
/**
* Saves all given {@link Target}s.
*
* @param entities
* @return the saved entities
* @throws IllegalArgumentException
* in case the given entity is (@literal null}.
*
* @see org.springframework.data.repository.CrudRepository#save(java.lang.Iterable)
*/
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@CacheEvict(value = { "targetStatus", "distributionUsageInstalled", "targetsLastPoll" }, allEntries = true)
<S extends JpaTarget> List<S> save(Iterable<S> entities);
/**
* Saves a given entity. Use the returned instance for further operations as
* the save operation might have changed the entity instance completely.
*
* @param entity
* the target to save
* @return the saved entity
*/
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@CacheEvict(value = { "targetStatus", "distributionUsageInstalled", "targetsLastPoll" }, allEntries = true)
<S extends JpaTarget> S save(S entity);
/**
* Finds all targets that have defined {@link DistributionSet} assigned.
*
* @param pageable
* for page configuration
* @param setID
* is the ID of the {@link DistributionSet} to filter for.
*
* @return page of found targets
*/
Page<Target> findByAssignedDistributionSetId(final Pageable pageable, final Long setID);
/**
* Counts number of targets with given
* {@link Target#getAssignedDistributionSet()}.
*
* @param distId
* to search for
*
* @return number of found {@link Target}s.
*/
Long countByAssignedDistributionSetId(final Long distId);
/**
* @param ids
* of count in DB
* @return number of found {@link Target}s with given
* {@link Target#getControllerId()}s
*/
@Query("SELECT COUNT(t) FROM JpaTarget t WHERE t.controllerId IN ?1")
Long countByControllerIdIn(final Collection<String> ids);
/**
* Counts number of targets with given
* {@link TargetStatus#getInstalledDistributionSet()}.
*
* @param distId
* to search for
* @return number of found {@link Target}s.
*/
Long countByTargetInfoInstalledDistributionSetId(final Long distId);
/**
* Finds all targets that have defined {@link DistributionSet} assigned or
* installed.
*
* @param pageable
* for page configuration
* @param assigned
* {@link DistributionSet} filter for; please note: must not be
* null
* @param installed
* {@link DistributionSet} filter for; please note: must not be
* null
*
* @return found targets
*/
Page<Target> findByAssignedDistributionSetOrTargetInfoInstalledDistributionSet(final Pageable pageable,
final JpaDistributionSet assigned, final JpaDistributionSet installed);
/**
* Finds all targets that have defined {@link DistributionSet} assigned or
* installed.
*
* @param pageable
* for page configuration
* @param assigned
* {@link DistributionSet} filter for; please note: must not be
* null
* @param installed
* {@link DistributionSet} filter for; please note: must not be
* null
* @return found targets
*/
Page<Target> findByAssignedDistributionSetIdOrTargetInfoInstalledDistributionSetId(final Pageable pageable,
final Long assigned, final Long installed);
/**
* Finds all {@link Target}s in the repository.
*
* @return {@link List} of {@link Target}s
*
* @see org.springframework.data.repository.CrudRepository#findAll()
*/
@Override
List<JpaTarget> findAll();
@Override
// Workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=349477
@Query("SELECT t FROM JpaTarget t WHERE t.id IN ?1")
List<JpaTarget> findAll(Iterable<Long> ids);
/**
* Sets {@link Target#getAssignedDistributionSet()}.
*
* @param set
* to use
* @param modifiedAt
* current time
* @param modifiedBy
* current auditor
* @param targets
* to update
*/
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Query("UPDATE JpaTarget t SET t.assignedDistributionSet = :set, t.lastModifiedAt = :lastModifiedAt, t.lastModifiedBy = :lastModifiedBy WHERE t.id IN :targets")
void setAssignedDistributionSet(@Param("set") JpaDistributionSet set, @Param("lastModifiedAt") Long modifiedAt,
@Param("lastModifiedBy") String modifiedBy, @Param("targets") Collection<Long> targets);
List<Target> findByRolloutTargetGroupRolloutGroup(final JpaRolloutGroup rolloutGroup);
/**
*
* Finds all targets of a rollout group.
*
* @param rolloutGroupId
* the ID of the rollout group
* @param page
* the page request parameter
* @return a page of all targets related to a rollout group
*/
Page<Target> findByRolloutTargetGroupRolloutGroupId(final Long rolloutGroupId, Pageable page);
/**
* Finds all targets related to a target rollout group stored for a specific
* rollout.
*
* @param rolloutGroup
* the rollout group the targets should belong to
* @param page
* the page request parameter
* @return a page of all targets related to a rollout group
*/
Page<Target> findByActionsRolloutGroup(JpaRolloutGroup rolloutGroup, Pageable page);
/**
* Find all targets with action status for a specific group.
*
* @param pageable
* the page request parameter
* @param rolloutGroupId
* the ID of the rollout group
* @return targets with action status
*/
@Query("select DISTINCT NEW org.eclipse.hawkbit.repository.model.TargetWithActionStatus(a.target,a.status) from JpaAction a inner join fetch a.target t where a.rolloutGroup.id = :rolloutGroupId")
Page<TargetWithActionStatus> findTargetsWithActionStatusByRolloutGroupId(final Pageable pageable,
@Param("rolloutGroupId") Long rolloutGroupId);
}

View File

@@ -0,0 +1,58 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa;
import java.util.List;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetTag;
import org.eclipse.hawkbit.repository.model.TargetTag;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
/**
* {@link TargetTag} repository.
*
*/
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
public interface TargetTagRepository
extends BaseEntityRepository<JpaTargetTag, Long>, JpaSpecificationExecutor<JpaTargetTag> {
/**
* deletes the {@link TargetTag}s with the given tag names.
*
* @param tagNames
* to be deleted
* @return 1 if tag was deleted
*/
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
Long deleteByName(final String tagName);
/**
* find {@link TargetTag} by its name.
*
* @param tagName
* to filter on
* @return the {@link TargetTag} if found, otherwise null
*/
JpaTargetTag findByNameEquals(final String tagName);
/**
* Returns all instances of the type.
*
* @return all entities
*/
@Override
List<JpaTargetTag> findAll();
@Override
<S extends JpaTargetTag> List<S> save(Iterable<S> entities);
}

View File

@@ -0,0 +1,47 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa;
import java.util.List;
import org.eclipse.hawkbit.repository.jpa.model.JpaTenantConfiguration;
import org.eclipse.hawkbit.repository.model.TenantConfiguration;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
/**
* The spring-data repository for the entity {@link TenantConfiguration}.
*
*/
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
public interface TenantConfigurationRepository extends BaseEntityRepository<JpaTenantConfiguration, Long> {
/**
* Finds a specific {@link TenantConfiguration} by the configuration key.
*
* @param configurationKey
* the configuration key to find the configuration for
* @return the found tenant configuration object otherwise {@code null}
*/
JpaTenantConfiguration findByKey(String configurationKey);
@Override
List<JpaTenantConfiguration> findAll();
/**
* Deletes a tenant configuration by tenant and key.
*
* @param tenant
* the tenant for this configuration
* @param keyName
* the name of the key to be deleted
*/
void deleteByKey(String keyName);
}

View File

@@ -0,0 +1,33 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa;
import java.lang.reflect.Method;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.stereotype.Service;
/**
* {@link KeyGenerator} for tenant related caches.
*
*/
@Service
public class TenantKeyGenerator implements KeyGenerator {
@Autowired
private TenantAware tenantAware;
@Override
public Object generate(final Object target, final Method method, final Object... params) {
return tenantAware.getCurrentTenant().toUpperCase();
}
}

View File

@@ -0,0 +1,53 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa;
import java.util.List;
import org.eclipse.hawkbit.repository.jpa.model.JpaTenantMetaData;
import org.eclipse.hawkbit.repository.model.TenantMetaData;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
/**
* repository for operations on {@link TenantMetaData} entity.
*
*/
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
public interface TenantMetaDataRepository extends PagingAndSortingRepository<JpaTenantMetaData, Long> {
/**
* Search {@link TenantMetaData} by tenant name.
*
* @param tenant
* to search for
* @return found {@link TenantMetaData} or <code>null</code>
*/
TenantMetaData findByTenantIgnoreCase(String tenant);
/**
* Counts the tenant by the tenant field which is either a count of one or a
* count of zero, this is mostly to check if the tenant exists.
*
* @param tenant
* the name of the tenant to check if it is exists
* @return the count of the tenant by name which is either one or zero
*/
Long countByTenantIgnoreCase(String tenant);
@Override
List<JpaTenantMetaData> findAll();
/**
* @param tenant
*/
void deleteByTenantIgnoreCase(String tenant);
}

View File

@@ -0,0 +1,63 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa.model;
import javax.persistence.Column;
import javax.persistence.MappedSuperclass;
import org.eclipse.hawkbit.repository.model.Artifact;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
/**
* Tenant specific locally stored artifact representation that is used by
* {@link SoftwareModule}.
*/
@MappedSuperclass
public abstract class AbstractJpaArtifact extends AbstractJpaTenantAwareBaseEntity implements Artifact {
private static final long serialVersionUID = 1L;
@Column(name = "sha1_hash", length = 40, nullable = true)
private String sha1Hash;
@Column(name = "md5_hash", length = 32, nullable = true)
private String md5Hash;
@Column(name = "file_size")
private Long size;
@Override
public abstract SoftwareModule getSoftwareModule();
@Override
public String getMd5Hash() {
return md5Hash;
}
@Override
public String getSha1Hash() {
return sha1Hash;
}
public void setMd5Hash(final String md5Hash) {
this.md5Hash = md5Hash;
}
public void setSha1Hash(final String sha1Hash) {
this.sha1Hash = sha1Hash;
}
@Override
public Long getSize() {
return size;
}
public void setSize(final Long size) {
this.size = size;
}
}

View File

@@ -0,0 +1,184 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa.model;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.Column;
import javax.persistence.EntityListeners;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import javax.persistence.Version;
import org.eclipse.hawkbit.eventbus.CacheFieldEntityListener;
import org.eclipse.hawkbit.eventbus.EntityPropertyChangeListener;
import org.eclipse.hawkbit.repository.model.BaseEntity;
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;
/**
* Holder of the base attributes common to all entities.
*
*/
@MappedSuperclass
@Access(AccessType.FIELD)
@EntityListeners({ AuditingEntityListener.class, CacheFieldEntityListener.class, EntityPropertyChangeListener.class })
public abstract class AbstractJpaBaseEntity implements BaseEntity {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;
private String createdBy;
private String lastModifiedBy;
private Long createdAt;
private Long lastModifiedAt;
@Version
@Column(name = "optlock_revision")
private long optLockRevision;
/**
* Default constructor needed for JPA entities.
*/
public AbstractJpaBaseEntity() {
// Default constructor needed for JPA entities.
}
@Override
@Access(AccessType.PROPERTY)
@Column(name = "created_at", insertable = true, updatable = false)
public Long getCreatedAt() {
return createdAt;
}
@Override
@Access(AccessType.PROPERTY)
@Column(name = "created_by", insertable = true, updatable = false, length = 40)
public String getCreatedBy() {
return createdBy;
}
@Override
@Access(AccessType.PROPERTY)
@Column(name = "last_modified_at", insertable = false, updatable = true)
public Long getLastModifiedAt() {
return lastModifiedAt;
}
@Override
@Access(AccessType.PROPERTY)
@Column(name = "last_modified_by", insertable = false, updatable = true, length = 40)
public String getLastModifiedBy() {
return lastModifiedBy;
}
@CreatedBy
public void setCreatedBy(final String createdBy) {
this.createdBy = createdBy;
}
@LastModifiedBy
public void setLastModifiedBy(final String lastModifiedBy) {
this.lastModifiedBy = lastModifiedBy;
}
@CreatedDate
public void setCreatedAt(final Long createdAt) {
this.createdAt = createdAt;
}
@LastModifiedDate
public void setLastModifiedAt(final Long lastModifiedAt) {
this.lastModifiedAt = lastModifiedAt;
}
@Override
public long getOptLockRevision() {
return optLockRevision;
}
public void setOptLockRevision(final long optLockRevision) {
this.optLockRevision = optLockRevision;
}
@Override
public Long getId() {
return id;
}
@Override
public String toString() {
return "BaseEntity [id=" + id + "]";
}
public void setId(final Long id) {
this.id = id;
}
/**
* Defined equals/hashcode strategy for the repository in general is that an
* entity is equal if it has the same {@link #getId()} and
* {@link #getOptLockRevision()} and class.
*
* @see java.lang.Object#hashCode()
*/
@Override
// 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 + (int) (optLockRevision ^ optLockRevision >>> 32);
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.
*
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(final Object obj) { // NOSONAR - as this is generated
// code
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(this.getClass().isInstance(obj))) {
return false;
}
final AbstractJpaBaseEntity other = (AbstractJpaBaseEntity) obj;
if (id == null) {
if (other.id != null) {
return false;
}
} else if (!id.equals(other.id)) {
return false;
}
if (optLockRevision != other.optLockRevision) {
return false;
}
return true;
}
}

View File

@@ -0,0 +1,102 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa.model;
import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import org.eclipse.hawkbit.repository.model.MetaData;
/**
* Meta data for entities.
*
*/
@MappedSuperclass
public abstract class AbstractJpaMetaData implements MetaData {
private static final long serialVersionUID = 1L;
@Id
@Column(name = "meta_key", length = 128)
private String key;
@Column(name = "meta_value", length = 4000)
@Basic
private String value;
public AbstractJpaMetaData(final String key, final String value) {
super();
this.key = key;
this.value = value;
}
public AbstractJpaMetaData() {
// Default constructor needed for JPA entities
}
@Override
public String getKey() {
return key;
}
@Override
public void setKey(final String key) {
this.key = key;
}
@Override
public String getValue() {
return value;
}
@Override
public void setValue(final String value) {
this.value = value;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((key == null) ? 0 : key.hashCode());
result = prime * result + ((value == null) ? 0 : value.hashCode());
return result;
}
@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 AbstractJpaMetaData other = (AbstractJpaMetaData) obj;
if (key == null) {
if (other.key != null) {
return false;
}
} else if (!key.equals(other.key)) {
return false;
}
if (value == null) {
if (other.value != null) {
return false;
}
} else if (!value.equals(other.value)) {
return false;
}
return true;
}
}

View File

@@ -0,0 +1,70 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa.model;
import javax.persistence.Column;
import javax.persistence.MappedSuperclass;
import org.eclipse.hawkbit.repository.model.NamedEntity;
import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity;
/**
* {@link TenantAwareBaseEntity} extension for all entities that are named in
* addition to their technical ID.
*/
@MappedSuperclass
public abstract class AbstractJpaNamedEntity extends AbstractJpaTenantAwareBaseEntity implements NamedEntity {
private static final long serialVersionUID = 1L;
@Column(name = "name", nullable = false, length = 64)
private String name;
@Column(name = "description", nullable = true, length = 512)
private String description;
/**
* Default constructor.
*/
public AbstractJpaNamedEntity() {
super();
}
/**
* Parameterized constructor.
*
* @param name
* of the {@link NamedEntity}
* @param description
* of the {@link NamedEntity}
*/
public AbstractJpaNamedEntity(final String name, final String description) {
this.name = name;
this.description = description;
}
@Override
public String getDescription() {
return description;
}
@Override
public String getName() {
return name;
}
@Override
public void setDescription(final String description) {
this.description = description;
}
@Override
public void setName(final String name) {
this.name = name;
}
}

View File

@@ -0,0 +1,55 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa.model;
import javax.persistence.Column;
import javax.persistence.MappedSuperclass;
import org.eclipse.hawkbit.repository.model.NamedEntity;
import org.eclipse.hawkbit.repository.model.NamedVersionedEntity;
/**
* Extension for {@link NamedEntity} that are versioned.
*
*/
@MappedSuperclass
public abstract class AbstractJpaNamedVersionedEntity extends AbstractJpaNamedEntity implements NamedVersionedEntity {
private static final long serialVersionUID = 1L;
@Column(name = "version", nullable = false, length = 64)
private String version;
/**
* parameterized constructor.
*
* @param name
* of the entity
* @param version
* of the entity
* @param description
*/
public AbstractJpaNamedVersionedEntity(final String name, final String version, final String description) {
super(name, description);
this.version = version;
}
AbstractJpaNamedVersionedEntity() {
super();
}
@Override
public String getVersion() {
return version;
}
@Override
public void setVersion(final String version) {
this.version = version;
}
}

View File

@@ -0,0 +1,61 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa.model;
import javax.persistence.Column;
import javax.persistence.MappedSuperclass;
import org.eclipse.hawkbit.repository.model.Tag;
/**
* A Tag can be used as describing and organizational meta information for any
* kind of entity.
*
*/
@MappedSuperclass
public abstract class AbstractJpaTag extends AbstractJpaNamedEntity implements Tag {
private static final long serialVersionUID = 1L;
@Column(name = "colour", nullable = true, length = 16)
private String colour;
protected AbstractJpaTag() {
super();
}
/**
* Public constructor.
*
* @param name
* of the {@link Tag}
* @param description
* of the {@link Tag}
* @param colour
* of tag in UI
*/
public AbstractJpaTag(final String name, final String description, final String colour) {
super(name, description);
this.colour = colour;
}
@Override
public String getColour() {
return colour;
}
@Override
public void setColour(final String colour) {
this.colour = colour;
}
@Override
public String toString() {
return "Tag [getOptLockRevision()=" + getOptLockRevision() + ", getId()=" + getId() + "]";
}
}

View File

@@ -0,0 +1,115 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa.model;
import javax.persistence.Column;
import javax.persistence.MappedSuperclass;
import javax.persistence.PrePersist;
import org.eclipse.hawkbit.repository.exception.TenantNotExistException;
import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity;
import org.eclipse.hawkbit.repository.model.helper.SystemManagementHolder;
import org.eclipse.hawkbit.repository.model.helper.TenantAwareHolder;
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.
*
*/
@MappedSuperclass
@TenantDiscriminatorColumn(name = "tenant", length = 40)
@Multitenant(MultitenantType.SINGLE_TABLE)
public abstract class AbstractJpaTenantAwareBaseEntity extends AbstractJpaBaseEntity implements TenantAwareBaseEntity {
private static final long serialVersionUID = 1L;
@Column(name = "tenant", nullable = false, insertable = false, updatable = false, length = 40)
private String tenant;
/**
* Default constructor needed for JPA entities.
*/
public AbstractJpaTenantAwareBaseEntity() {
// Default constructor needed for JPA entities.
}
/**
* PrePersist listener method for all {@link TenantAwareBaseEntity}
* entities.
*/
@PrePersist
public void prePersist() {
// before persisting the entity check the current ID of the tenant by
// using the TenantAware
// service
final String currentTenant = SystemManagementHolder.getInstance().currentTenant();
if (currentTenant == null) {
throw new TenantNotExistException("Tenant "
+ TenantAwareHolder.getInstance().getTenantAware().getCurrentTenant()
+ " does not exists, cannot create entity " + this.getClass() + " with id " + super.getId());
}
setTenant(currentTenant.toUpperCase());
}
@Override
public String getTenant() {
return tenant;
}
public void setTenant(final String tenant) {
this.tenant = tenant;
}
@Override
public String toString() {
return "BaseEntity [id=" + super.getId() + "]";
}
/**
* Tenant aware entities extend the equals/hashcode strategy with the tenant
* name. That would allow for instance in a multi-schema based data
* separation setup to have the same primary key for different entities of
* different tenants.
*
* @see org.eclipse.hawkbit.repository.model.BaseEntity#hashCode()
*/
@Override
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.
*
* @see org.eclipse.hawkbit.repository.model.BaseEntity#equals(java.lang.Object)
*/
@Override
public boolean equals(final Object obj) {
if (!super.equals(obj)) {
return false;
}
final AbstractJpaTenantAwareBaseEntity other = (AbstractJpaTenantAwareBaseEntity) obj;
if (tenant == null) {
if (other.tenant != null) {
return false;
}
} else if (!tenant.equals(other.tenant)) {
return false;
}
return true;
}
}

View File

@@ -0,0 +1,98 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa.model;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.ConstraintMode;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.ForeignKey;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.MapsId;
import javax.persistence.Table;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
/**
* Relation element between a {@link DistributionSetType} and its
* {@link SoftwareModuleType} elements.
*
*/
@Entity
@Table(name = "sp_ds_type_element")
public class DistributionSetTypeElement implements Serializable {
private static final long serialVersionUID = 1L;
@EmbeddedId
private DistributionSetTypeElementCompositeKey key;
@Column(name = "mandatory")
private boolean mandatory;
@MapsId("dsType")
@ManyToOne(optional = false, fetch = FetchType.LAZY)
@JoinColumn(name = "distribution_set_type", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_ds_type_element_dstype"))
private JpaDistributionSetType dsType;
@MapsId("smType")
@ManyToOne(optional = false, fetch = FetchType.LAZY)
@JoinColumn(name = "software_module_type", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_ds_type_element_smtype"))
private JpaSoftwareModuleType smType;
public DistributionSetTypeElement() {
// Default constructor for JPA
}
/**
* Standard constructor.
*
* @param dsType
* of the element
* @param smType
* of the element
* @param mandatory
* to <code>true</code> if the {@link SoftwareModuleType} if
* mandatory element in the {@link DistributionSet}.
*/
public DistributionSetTypeElement(final JpaDistributionSetType dsType, final JpaSoftwareModuleType smType,
final boolean mandatory) {
super();
key = new DistributionSetTypeElementCompositeKey(dsType, smType);
this.dsType = dsType;
this.smType = smType;
this.mandatory = mandatory;
}
public boolean isMandatory() {
return mandatory;
}
public DistributionSetType getDsType() {
return dsType;
}
public SoftwareModuleType getSmType() {
return smType;
}
public DistributionSetTypeElementCompositeKey getKey() {
return key;
}
@Override
public String toString() {
return "DistributionSetTypeElement [mandatory=" + mandatory + ", dsType=" + dsType + ", smType=" + smType + "]";
}
}

View File

@@ -0,0 +1,64 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa.model;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Embeddable;
/**
* Composite key for {@link DistributionSetTypeElement}.
*/
@Embeddable
public class DistributionSetTypeElementCompositeKey implements Serializable {
private static final long serialVersionUID = 1L;
@Column(name = "distribution_set_type", nullable = false)
private Long dsType;
@Column(name = "software_module_type", nullable = false)
private Long smType;
/**
* Default constructor.
*/
DistributionSetTypeElementCompositeKey() {
}
/**
* Constructor.
*
* @param dsType
* in the key
* @param smType
* in the key
*/
DistributionSetTypeElementCompositeKey(final JpaDistributionSetType dsType, final JpaSoftwareModuleType smType) {
super();
this.dsType = dsType.getId();
this.smType = smType.getId();
}
public Long getDsType() {
return dsType;
}
public void setDsType(final Long dsType) {
this.dsType = dsType;
}
public Long getSmType() {
return smType;
}
public void setSmType(final Long smType) {
this.smType = smType;
}
}

View File

@@ -0,0 +1,97 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa.model;
import java.io.Serializable;
import org.eclipse.hawkbit.repository.model.DistributionSet;
/**
* The DistributionSet Metadata composite key which contains the meta data key
* and the ID of the DistributionSet itself.
*
*/
public final class DsMetadataCompositeKey implements Serializable {
private static final long serialVersionUID = 1L;
private String key;
private Long distributionSet;
public DsMetadataCompositeKey() {
// Default constructor for JPA.
}
/**
* @param distributionSet
* the distribution set for this meta data
* @param key
* the key of the meta data
*/
public DsMetadataCompositeKey(final DistributionSet distributionSet, final String key) {
this.distributionSet = distributionSet.getId();
this.key = key;
}
public String getKey() {
return key;
}
public void setKey(final String key) {
this.key = key;
}
public Long getDistributionSet() {
return distributionSet;
}
public void setDistributionSet(final Long distributionSet) {
this.distributionSet = distributionSet;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (distributionSet == null ? 0 : distributionSet.hashCode());
result = prime * result + (key == null ? 0 : key.hashCode());
return result;
}
@Override
public boolean equals(final Object obj) { // NOSONAR - as this is generated
// code
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final DsMetadataCompositeKey other = (DsMetadataCompositeKey) obj;
if (distributionSet == null) {
if (other.distributionSet != null) {
return false;
}
} else if (!distributionSet.equals(other.distributionSet)) {
return false;
}
if (key == null) {
if (other.key != null) {
return false;
}
} else if (!key.equals(other.key)) {
return false;
}
return true;
}
}

View File

@@ -0,0 +1,190 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa.model;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.ConstraintMode;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.ForeignKey;
import javax.persistence.Index;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.NamedAttributeNode;
import javax.persistence.NamedEntityGraph;
import javax.persistence.NamedEntityGraphs;
import javax.persistence.NamedSubgraph;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Transient;
import org.eclipse.hawkbit.cache.CacheField;
import org.eclipse.hawkbit.cache.CacheKeys;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.ActionStatus;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.persistence.annotations.CascadeOnDelete;
/**
* JPA implementation of {@link Action}.
*/
@Table(name = "sp_action", indexes = { @Index(name = "sp_idx_action_01", columnList = "tenant,distribution_set"),
@Index(name = "sp_idx_action_02", columnList = "tenant,target,active"),
@Index(name = "sp_idx_action_prim", columnList = "tenant,id") })
@NamedEntityGraphs({ @NamedEntityGraph(name = "Action.ds", attributeNodes = { @NamedAttributeNode("distributionSet") }),
@NamedEntityGraph(name = "Action.all", attributeNodes = { @NamedAttributeNode("distributionSet"),
@NamedAttributeNode(value = "target", subgraph = "target.ds") }, subgraphs = @NamedSubgraph(name = "target.ds", attributeNodes = @NamedAttributeNode("assignedDistributionSet"))) })
@Entity
public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Action {
private static final long serialVersionUID = 1L;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "distribution_set", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_action_ds"))
private JpaDistributionSet distributionSet;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "target", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_action_target"))
private JpaTarget target;
@Column(name = "active")
private boolean active;
@Column(name = "action_type", nullable = false)
@Enumerated(EnumType.STRING)
private ActionType actionType;
@Column(name = "forced_time")
private long forcedTime;
@Column(name = "status")
private Status status;
@CascadeOnDelete
@OneToMany(mappedBy = "action", targetEntity = JpaActionStatus.class, fetch = FetchType.LAZY, cascade = {
CascadeType.REMOVE })
private List<ActionStatus> actionStatus;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "rolloutgroup", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_action_rolloutgroup"))
private JpaRolloutGroup rolloutGroup;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "rollout", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_action_rollout"))
private JpaRollout rollout;
/**
* Note: filled only in {@link Status#DOWNLOAD}.
*/
@Transient
@CacheField(key = CacheKeys.DOWNLOAD_PROGRESS_PERCENT)
private int downloadProgressPercent;
@Override
public DistributionSet getDistributionSet() {
return distributionSet;
}
@Override
public void setDistributionSet(final DistributionSet distributionSet) {
this.distributionSet = (JpaDistributionSet) distributionSet;
}
public void setActive(final boolean active) {
this.active = active;
}
@Override
public Status getStatus() {
return status;
}
@Override
public void setStatus(final Status status) {
this.status = status;
}
@Override
public int getDownloadProgressPercent() {
return downloadProgressPercent;
}
public void setDownloadProgressPercent(final int downloadProgressPercent) {
this.downloadProgressPercent = downloadProgressPercent;
}
@Override
public boolean isActive() {
return active;
}
public void setActionType(final ActionType actionType) {
this.actionType = actionType;
}
@Override
public ActionType getActionType() {
return actionType;
}
@Override
public List<ActionStatus> getActionStatus() {
return actionStatus;
}
@Override
public void setTarget(final Target target) {
this.target = (JpaTarget) target;
}
@Override
public Target getTarget() {
return target;
}
@Override
public long getForcedTime() {
return forcedTime;
}
public void setForcedTime(final long forcedTime) {
this.forcedTime = forcedTime;
}
@Override
public RolloutGroup getRolloutGroup() {
return rolloutGroup;
}
public void setRolloutGroup(final RolloutGroup rolloutGroup) {
this.rolloutGroup = (JpaRolloutGroup) rolloutGroup;
}
@Override
public Rollout getRollout() {
return rollout;
}
public void setRollout(final Rollout rollout) {
this.rollout = (JpaRollout) rollout;
}
@Override
public String toString() {
return "Action [distributionSet=" + distributionSet + ", getId()=" + getId() + "]";
}
}

View File

@@ -0,0 +1,147 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa.model;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.ConstraintMode;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.ForeignKey;
import javax.persistence.Index;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.NamedAttributeNode;
import javax.persistence.NamedEntityGraph;
import javax.persistence.Table;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.ActionStatus;
import org.eclipse.persistence.annotations.CascadeOnDelete;
import com.google.common.base.Splitter;
/**
* Entity to store the status for a specific action.
*/
@Table(name = "sp_action_status", indexes = { @Index(name = "sp_idx_action_status_01", columnList = "tenant,action"),
@Index(name = "sp_idx_action_status_02", columnList = "tenant,action,status"),
@Index(name = "sp_idx_action_status_prim", columnList = "tenant,id") })
@NamedEntityGraph(name = "ActionStatus.withMessages", attributeNodes = { @NamedAttributeNode("messages") })
@Entity
public class JpaActionStatus extends AbstractJpaTenantAwareBaseEntity implements ActionStatus {
private static final long serialVersionUID = 1L;
@Column(name = "target_occurred_at")
private Long occurredAt;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "action", nullable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_act_stat_action"))
private JpaAction action;
@Column(name = "status")
private Status status;
@CascadeOnDelete
@ElementCollection(fetch = FetchType.LAZY, targetClass = String.class)
@CollectionTable(name = "sp_action_status_messages", joinColumns = @JoinColumn(name = "action_status_id", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_stat_msg_act_stat")), indexes = {
@Index(name = "sp_idx_action_status_msgs_01", columnList = "action_status_id") })
@Column(name = "detail_message", length = 512)
private final List<String> messages = new ArrayList<>();
/**
* Creates a new {@link ActionStatus} object.
*
* @param action
* the action for this action status
* @param status
* the status for this action status
* @param occurredAt
* the occurred timestamp
*/
public JpaActionStatus(final Action action, final Status status, final Long occurredAt) {
this.action = (JpaAction) action;
this.status = status;
this.occurredAt = occurredAt;
}
/**
* Creates a new {@link ActionStatus} object.
*
* @param action
* the action for this action status
* @param status
* the status for this action status
* @param occurredAt
* the occurred timestamp
* @param messages
* the messages which should be added to this action status
*/
public JpaActionStatus(final JpaAction action, final Status status, final Long occurredAt, final String message) {
this.action = action;
this.status = status;
this.occurredAt = occurredAt;
addMessage(message);
}
/**
* JPA default constructor.
*/
public JpaActionStatus() {
// JPA default constructor.
}
@Override
public Long getOccurredAt() {
return occurredAt;
}
@Override
public void setOccurredAt(final Long occurredAt) {
this.occurredAt = occurredAt;
}
@Override
public final void addMessage(final String message) {
if (message != null) {
Splitter.fixedLength(512).split(message).forEach(messages::add);
}
}
@Override
public List<String> getMessages() {
return messages;
}
@Override
public Action getAction() {
return action;
}
@Override
public void setAction(final Action action) {
this.action = (JpaAction) action;
}
@Override
public Status getStatus() {
return status;
}
@Override
public void setStatus(final Status status) {
this.status = status;
}
}

View File

@@ -0,0 +1,112 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa.model;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.ActionStatus;
import org.eclipse.hawkbit.repository.model.ActionWithStatusCount;
/**
* Custom JPA Model for querying {@link Action} include the count of the
* action's {@link ActionStatus}.
*
*/
public class JpaActionWithStatusCount implements ActionWithStatusCount {
private final Long actionStatusCount;
private final Long dsId;
private final String dsName;
private final String dsVersion;
private final JpaAction action;
private final String rolloutName;
/**
* JPA constructor, the parameter are the result set columns of the custom
* query.
*
* @param actionId
* the ID of the action
* @param actionType
* the action type
* @param active
* the active attribute of the action
* @param forcedTime
* the forced time attribute of the action
* @param status
* the status attribute of the action
* @param actionCreatedAt
* the createdAt timestamp of the action
* @param actionLastModifiedAt
* the last modified timestamp of the action
* @param dsId
* the ID of the distributionset
* @param dsName
* the name of the distributionset
* @param dsVersion
* the version of the distributionset
* @param actionStatusCount
* the count of the action status for this action
* @param rolloutName
* the rollout name
*/
// Exception squid:S00107 - needed this way for JPA to fill the view
@SuppressWarnings("squid:S00107")
public JpaActionWithStatusCount(final Long actionId, final ActionType actionType, final boolean active,
final long forcedTime, final Status status, final Long actionCreatedAt, final Long actionLastModifiedAt,
final Long dsId, final String dsName, final String dsVersion, final Long actionStatusCount,
final String rolloutName) {
this.dsId = dsId;
this.dsName = dsName;
this.dsVersion = dsVersion;
this.actionStatusCount = actionStatusCount;
this.rolloutName = rolloutName;
action = new JpaAction();
action.setActionType(actionType);
action.setActive(active);
action.setForcedTime(forcedTime);
action.setStatus(status);
action.setId(actionId);
action.setActionType(actionType);
action.setCreatedAt(actionCreatedAt);
action.setLastModifiedAt(actionLastModifiedAt);
}
@Override
public Action getAction() {
return action;
}
@Override
public Long getDsId() {
return dsId;
}
@Override
public String getDsName() {
return dsName;
}
@Override
public String getDsVersion() {
return dsVersion;
}
@Override
public Long getActionStatusCount() {
return actionStatusCount;
}
@Override
public String getRolloutName() {
return rolloutName;
}
}

View File

@@ -0,0 +1,285 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa.model;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.ConstraintMode;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.ForeignKey;
import javax.persistence.Index;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.NamedAttributeNode;
import javax.persistence.NamedEntityGraph;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import org.eclipse.hawkbit.repository.exception.DistributionSetTypeUndefinedException;
import org.eclipse.hawkbit.repository.exception.UnsupportedSoftwareModuleForThisDistributionSetException;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetIdName;
import org.eclipse.hawkbit.repository.model.DistributionSetMetadata;
import org.eclipse.hawkbit.repository.model.DistributionSetTag;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetInfo;
import org.eclipse.persistence.annotations.CascadeOnDelete;
/**
* Jpa implementation of {@link DistributionSet}.
*
*/
@Entity
@Table(name = "sp_distribution_set", uniqueConstraints = {
@UniqueConstraint(columnNames = { "name", "version", "tenant" }, name = "uk_distrib_set") }, indexes = {
@Index(name = "sp_idx_distribution_set_01", columnList = "tenant,deleted,name,complete"),
@Index(name = "sp_idx_distribution_set_02", columnList = "tenant,required_migration_step"),
@Index(name = "sp_idx_distribution_set_prim", columnList = "tenant,id") })
@NamedEntityGraph(name = "DistributionSet.detail", attributeNodes = { @NamedAttributeNode("modules"),
@NamedAttributeNode("tags"), @NamedAttributeNode("type") })
public class JpaDistributionSet extends AbstractJpaNamedVersionedEntity implements DistributionSet {
private static final long serialVersionUID = 1L;
@Column(name = "required_migration_step")
private boolean requiredMigrationStep;
@ManyToMany(targetEntity = JpaSoftwareModule.class, fetch = FetchType.LAZY)
@JoinTable(name = "sp_ds_module", joinColumns = {
@JoinColumn(name = "ds_id", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_ds_module_ds")) }, inverseJoinColumns = {
@JoinColumn(name = "module_id", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_ds_module_module")) })
private final Set<SoftwareModule> modules = new HashSet<>();
@ManyToMany(targetEntity = JpaDistributionSetTag.class)
@JoinTable(name = "sp_ds_dstag", joinColumns = {
@JoinColumn(name = "ds", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_ds_dstag_ds")) }, inverseJoinColumns = {
@JoinColumn(name = "TAG", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_ds_dstag_tag")) })
private Set<DistributionSetTag> tags = new HashSet<>();
@Column(name = "deleted")
private boolean deleted;
@OneToMany(mappedBy = "assignedDistributionSet", targetEntity = JpaTarget.class, fetch = FetchType.LAZY)
private List<Target> assignedToTargets;
@OneToMany(mappedBy = "installedDistributionSet", targetEntity = JpaTargetInfo.class, fetch = FetchType.LAZY)
private List<TargetInfo> installedAtTargets;
@OneToMany(mappedBy = "distributionSet", targetEntity = JpaAction.class, fetch = FetchType.LAZY)
private List<Action> actions;
@CascadeOnDelete
@OneToMany(fetch = FetchType.LAZY, targetEntity = JpaDistributionSetMetadata.class, cascade = {
CascadeType.REMOVE })
@JoinColumn(name = "ds_id", insertable = false, updatable = false)
private final List<DistributionSetMetadata> metadata = new ArrayList<>();
@ManyToOne(fetch = FetchType.LAZY, targetEntity = JpaDistributionSetType.class)
@JoinColumn(name = "ds_id", nullable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_ds_dstype_ds"))
private DistributionSetType type;
@Column(name = "complete")
private boolean complete;
/**
* Default constructor.
*/
public JpaDistributionSet() {
super();
}
/**
* Parameterized constructor.
*
* @param name
* of the {@link DistributionSet}
* @param version
* of the {@link DistributionSet}
* @param description
* of the {@link DistributionSet}
* @param type
* of the {@link DistributionSet}
* @param moduleList
* {@link SoftwareModule}s of the {@link DistributionSet}
*/
public JpaDistributionSet(final String name, final String version, final String description,
final DistributionSetType type, final Collection<SoftwareModule> moduleList) {
super(name, version, description);
this.type = type;
if (moduleList != null) {
moduleList.forEach(this::addModule);
}
if (this.type != null) {
complete = this.type.checkComplete(this);
}
}
@Override
public Set<DistributionSetTag> getTags() {
return tags;
}
@Override
public boolean isDeleted() {
return deleted;
}
@Override
public List<DistributionSetMetadata> getMetadata() {
return Collections.unmodifiableList(metadata);
}
public List<Action> getActions() {
return actions;
}
@Override
public boolean isRequiredMigrationStep() {
return requiredMigrationStep;
}
@Override
public DistributionSet setDeleted(final boolean deleted) {
this.deleted = deleted;
return this;
}
@Override
public DistributionSet setRequiredMigrationStep(final boolean isRequiredMigrationStep) {
requiredMigrationStep = isRequiredMigrationStep;
return this;
}
public DistributionSet setTags(final Set<DistributionSetTag> tags) {
this.tags = tags;
return this;
}
@Override
public List<Target> getAssignedTargets() {
return assignedToTargets;
}
@Override
public List<TargetInfo> getInstalledTargets() {
return installedAtTargets;
}
@Override
public String toString() {
return "DistributionSet [getName()=" + getName() + ", getOptLockRevision()=" + getOptLockRevision()
+ ", getId()=" + getId() + "]";
}
@Override
public Set<SoftwareModule> getModules() {
return Collections.unmodifiableSet(modules);
}
@Override
public DistributionSetIdName getDistributionSetIdName() {
return new DistributionSetIdName(getId(), getName(), getVersion());
}
@Override
public boolean addModule(final SoftwareModule softwareModule) {
// we cannot allow that modules are added without a type defined
if (type == null) {
throw new DistributionSetTypeUndefinedException();
}
// check if it is allowed to such a module to this DS type
if (!type.containsModuleType(softwareModule.getType())) {
throw new UnsupportedSoftwareModuleForThisDistributionSetException();
}
final Optional<SoftwareModule> found = modules.stream()
.filter(module -> module.getId().equals(softwareModule.getId())).findFirst();
if (found.isPresent()) {
return false;
}
final long allready = modules.stream()
.filter(module -> module.getType().getKey().equals(softwareModule.getType().getKey())).count();
if (allready >= softwareModule.getType().getMaxAssignments()) {
final Optional<SoftwareModule> sameKey = modules.stream()
.filter(module -> module.getType().getKey().equals(softwareModule.getType().getKey())).findFirst();
modules.remove(sameKey.get());
}
if (modules.add(softwareModule)) {
complete = type.checkComplete(this);
return true;
}
return false;
}
@Override
public boolean removeModule(final SoftwareModule softwareModule) {
final Optional<SoftwareModule> found = modules.stream()
.filter(module -> module.getId().equals(softwareModule.getId())).findFirst();
if (found.isPresent()) {
modules.remove(found.get());
complete = type.checkComplete(this);
return true;
}
return false;
}
@Override
public SoftwareModule findFirstModuleByType(final SoftwareModuleType type) {
final Optional<SoftwareModule> result = modules.stream().filter(module -> module.getType().equals(type))
.findFirst();
if (result.isPresent()) {
return result.get();
}
return null;
}
@Override
public DistributionSetType getType() {
return type;
}
@Override
public void setType(final DistributionSetType type) {
this.type = type;
}
@Override
public boolean isComplete() {
return complete;
}
}

View File

@@ -0,0 +1,84 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa.model;
import javax.persistence.ConstraintMode;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.ForeignKey;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetMetadata;
/**
* Meta data for {@link DistributionSet}.
*
*/
@IdClass(DsMetadataCompositeKey.class)
@Entity
@Table(name = "sp_ds_metadata")
public class JpaDistributionSetMetadata extends AbstractJpaMetaData implements DistributionSetMetadata {
private static final long serialVersionUID = 1L;
@Id
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "ds_id", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_metadata_ds"))
private JpaDistributionSet distributionSet;
public JpaDistributionSetMetadata() {
// default public constructor for JPA
}
public JpaDistributionSetMetadata(final String key, final DistributionSet distributionSet, final String value) {
super(key, value);
this.distributionSet = (JpaDistributionSet) distributionSet;
}
public DsMetadataCompositeKey getId() {
return new DsMetadataCompositeKey(distributionSet, getKey());
}
public void setDistributionSet(final DistributionSet distributionSet) {
this.distributionSet = (JpaDistributionSet) distributionSet;
}
@Override
public DistributionSet getDistributionSet() {
return distributionSet;
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + ((distributionSet == null) ? 0 : distributionSet.hashCode());
return result;
}
@Override
public boolean equals(final Object obj) {
if (!super.equals(obj)) {
return false;
}
final JpaDistributionSetMetadata other = (JpaDistributionSetMetadata) obj;
if (distributionSet == null) {
if (other.distributionSet != null) {
return false;
}
} else if (!distributionSet.equals(other.distributionSet)) {
return false;
}
return true;
}
}

View File

@@ -0,0 +1,93 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa.model;
import java.util.List;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Index;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetTag;
/**
* A {@link DistributionSetTag} is used to describe DistributionSet attributes
* and use them also for filtering the DistributionSet list.
*
*/
@Entity
@Table(name = "sp_distributionset_tag", indexes = {
@Index(name = "sp_idx_distribution_set_tag_prim", columnList = "tenant,id") }, uniqueConstraints = @UniqueConstraint(columnNames = {
"name", "tenant" }, name = "uk_ds_tag"))
public class JpaDistributionSetTag extends AbstractJpaTag implements DistributionSetTag {
private static final long serialVersionUID = 1L;
@ManyToMany(mappedBy = "tags", targetEntity = JpaDistributionSet.class, fetch = FetchType.LAZY)
private List<DistributionSet> assignedToDistributionSet;
/**
* Public constructor.
*
* @param name
* of the {@link DistributionSetTag}
**/
public JpaDistributionSetTag(final String name) {
super(name, null, null);
}
/**
* Public constructor.
*
* @param name
* of the {@link DistributionSetTag}
* @param description
* of the {@link DistributionSetTag}
* @param colour
* of tag in UI
*/
public JpaDistributionSetTag(final String name, final String description, final String colour) {
super(name, description, colour);
}
/**
* Default constructor for JPA.
*/
public JpaDistributionSetTag() {
// Default constructor for JPA.
}
@Override
public List<DistributionSet> getAssignedToDistributionSet() {
return assignedToDistributionSet;
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + this.getClass().getName().hashCode();
return result;
}
@Override
public boolean equals(final Object obj) { // NOSONAR - as this is generated
if (!super.equals(obj)) {
return false;
}
if (!(obj instanceof DistributionSetTag)) {
return false;
}
return true;
}
}

View File

@@ -0,0 +1,221 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa.model;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Index;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
/**
* A distribution set type defines which software module types can or have to be
* {@link DistributionSet}.
*
*/
@Entity
@Table(name = "sp_distribution_set_type", indexes = {
@Index(name = "sp_idx_distribution_set_type_01", columnList = "tenant,deleted"),
@Index(name = "sp_idx_distribution_set_type_prim", columnList = "tenant,id") }, uniqueConstraints = {
@UniqueConstraint(columnNames = { "name", "tenant" }, name = "uk_dst_name"),
@UniqueConstraint(columnNames = { "type_key", "tenant" }, name = "uk_dst_key") })
public class JpaDistributionSetType extends AbstractJpaNamedEntity implements DistributionSetType {
private static final long serialVersionUID = 1L;
@OneToMany(targetEntity = DistributionSetTypeElement.class, cascade = {
CascadeType.ALL }, fetch = FetchType.EAGER, orphanRemoval = true)
@JoinColumn(name = "distribution_set_type", insertable = false, updatable = false)
private final Set<DistributionSetTypeElement> elements = new HashSet<>();
@Column(name = "type_key", nullable = false, length = 64)
private String key;
@Column(name = "colour", nullable = true, length = 16)
private String colour;
@Column(name = "deleted")
private boolean deleted;
public JpaDistributionSetType() {
// default public constructor for JPA
}
/**
* Standard constructor.
*
* @param key
* of the type (unique)
* @param name
* of the type (unique)
* @param description
* of the type
*/
public JpaDistributionSetType(final String key, final String name, final String description) {
this(key, name, description, null);
}
/**
* Constructor.
*
* @param key
* of the type
* @param name
* of the type
* @param description
* of the type
* @param color
* of the type. It will be null by default
*/
public JpaDistributionSetType(final String key, final String name, final String description, final String color) {
super(name, description);
this.key = key;
colour = color;
}
@Override
public boolean isDeleted() {
return deleted;
}
public void setDeleted(final boolean deleted) {
this.deleted = deleted;
}
@Override
public Set<SoftwareModuleType> getMandatoryModuleTypes() {
return elements.stream().filter(element -> element.isMandatory()).map(element -> element.getSmType())
.collect(Collectors.toSet());
}
@Override
public Set<SoftwareModuleType> getOptionalModuleTypes() {
return elements.stream().filter(element -> !element.isMandatory()).map(element -> element.getSmType())
.collect(Collectors.toSet());
}
@Override
public boolean containsModuleType(final SoftwareModuleType softwareModuleType) {
for (final DistributionSetTypeElement distributionSetTypeElement : elements) {
if (distributionSetTypeElement.getSmType().equals(softwareModuleType)) {
return true;
}
}
return false;
}
@Override
public boolean containsMandatoryModuleType(final SoftwareModuleType softwareModuleType) {
return elements.stream().filter(element -> element.isMandatory())
.filter(element -> element.getSmType().equals(softwareModuleType)).findFirst().isPresent();
}
@Override
public boolean containsMandatoryModuleType(final Long softwareModuleTypeId) {
return elements.stream().filter(element -> element.isMandatory())
.filter(element -> element.getSmType().getId().equals(softwareModuleTypeId)).findFirst().isPresent();
}
@Override
public boolean containsOptionalModuleType(final SoftwareModuleType softwareModuleType) {
return elements.stream().filter(element -> !element.isMandatory())
.filter(element -> element.getSmType().equals(softwareModuleType)).findFirst().isPresent();
}
@Override
public boolean containsOptionalModuleType(final Long softwareModuleTypeId) {
return elements.stream().filter(element -> !element.isMandatory())
.filter(element -> element.getSmType().getId().equals(softwareModuleTypeId)).findFirst().isPresent();
}
@Override
public boolean areModuleEntriesIdentical(final DistributionSetType dsType) {
return new HashSet<DistributionSetTypeElement>(((JpaDistributionSetType) dsType).elements).equals(elements);
}
@Override
public DistributionSetType addOptionalModuleType(final SoftwareModuleType smType) {
elements.add(new DistributionSetTypeElement(this, (JpaSoftwareModuleType) smType, false));
return this;
}
@Override
public DistributionSetType addMandatoryModuleType(final SoftwareModuleType smType) {
elements.add(new DistributionSetTypeElement(this, (JpaSoftwareModuleType) smType, true));
return this;
}
@Override
public DistributionSetType removeModuleType(final Long smTypeId) {
// we search by id (standard equals compares also revison)
final Optional<DistributionSetTypeElement> found = elements.stream()
.filter(element -> element.getSmType().getId().equals(smTypeId)).findFirst();
if (found.isPresent()) {
elements.remove(found.get());
}
return this;
}
@Override
public String getKey() {
return key;
}
@Override
public void setKey(final String key) {
this.key = key;
}
@Override
public boolean checkComplete(final DistributionSet distributionSet) {
return distributionSet.getModules().stream().map(module -> module.getType()).collect(Collectors.toList())
.containsAll(getMandatoryModuleTypes());
}
@Override
public String getColour() {
return colour;
}
@Override
public void setColour(final String colour) {
this.colour = colour;
}
public Set<DistributionSetTypeElement> getElements() {
return elements;
}
@Override
public String toString() {
return "DistributionSetType [key=" + key + ", isDeleted()=" + isDeleted() + ", getId()=" + getId() + "]";
}
}

View File

@@ -0,0 +1,140 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa.model;
import java.net.URL;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.ConstraintMode;
import javax.persistence.Entity;
import javax.persistence.ForeignKey;
import javax.persistence.Index;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.validation.constraints.NotNull;
import org.eclipse.hawkbit.repository.model.ExternalArtifact;
import org.eclipse.hawkbit.repository.model.ExternalArtifactProvider;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
/**
* External artifact representation with all the necessary information to
* generate an artifact {@link URL} at runtime.
*
*/
@Table(name = "sp_external_artifact", indexes = {
@Index(name = "sp_idx_external_artifact_prim", columnList = "id,tenant") })
@Entity
public class JpaExternalArtifact extends AbstractJpaArtifact implements ExternalArtifact {
private static final long serialVersionUID = 1L;
@ManyToOne
@JoinColumn(name = "provider", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_art_to_ext_provider"))
private JpaExternalArtifactProvider externalArtifactProvider;
@Column(name = "url_suffix", length = 512)
private String urlSuffix;
// CascadeType.PERSIST as we register ourself at the BSM
@ManyToOne(optional = false, cascade = { CascadeType.PERSIST })
@JoinColumn(name = "software_module", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_external_assigned_sm"))
private JpaSoftwareModule softwareModule;
/**
* Default constructor.
*/
public JpaExternalArtifact() {
super();
}
/**
* Constructs {@link ExternalArtifact}.
*
* @param externalArtifactProvider
* of the artifact
* @param urlSuffix
* of the artifact
* @param softwareModule
* of the artifact
*/
public JpaExternalArtifact(@NotNull final ExternalArtifactProvider externalArtifactProvider, final String urlSuffix,
final SoftwareModule softwareModule) {
setSoftwareModule(softwareModule);
this.externalArtifactProvider = (JpaExternalArtifactProvider) externalArtifactProvider;
if (urlSuffix != null) {
this.urlSuffix = urlSuffix;
} else {
this.urlSuffix = externalArtifactProvider.getDefaultSuffix();
}
}
/**
* @return the softwareModule
*/
@Override
public SoftwareModule getSoftwareModule() {
return softwareModule;
}
public final void setSoftwareModule(final SoftwareModule softwareModule) {
this.softwareModule = (JpaSoftwareModule) softwareModule;
this.softwareModule.addArtifact(this);
}
@Override
public ExternalArtifactProvider getExternalArtifactProvider() {
return externalArtifactProvider;
}
@Override
public String getUrl() {
return new StringBuilder().append(externalArtifactProvider.getBasePath()).append(urlSuffix).toString();
}
@Override
public String getUrlSuffix() {
return urlSuffix;
}
public void setExternalArtifactProvider(final JpaExternalArtifactProvider externalArtifactProvider) {
this.externalArtifactProvider = externalArtifactProvider;
}
/**
* @param urlSuffix
* the urlSuffix to set
*/
@Override
public void setUrlSuffix(final String urlSuffix) {
this.urlSuffix = urlSuffix;
}
@Override
public int hashCode() { // NOSONAR - as this is generated
final int prime = 31;
int result = super.hashCode();
result = prime * result + this.getClass().getName().hashCode();
return result;
}
@Override
public boolean equals(final Object obj) { // NOSONAR - as this is generated
if (!super.equals(obj)) {
return false;
}
if (!(obj instanceof JpaExternalArtifact)) {
return false;
}
return true;
}
}

View File

@@ -0,0 +1,81 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa.model;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Index;
import javax.persistence.Table;
import org.eclipse.hawkbit.repository.model.ExternalArtifact;
import org.eclipse.hawkbit.repository.model.ExternalArtifactProvider;
/**
* JPA implementation of {@link ExternalArtifactProvider}.
*
*/
@Table(name = "sp_external_provider", indexes = {
@Index(name = "sp_idx_external_provider_prim", columnList = "tenant,id") })
@Entity
public class JpaExternalArtifactProvider extends AbstractJpaNamedEntity implements ExternalArtifactProvider {
private static final long serialVersionUID = 1L;
@Column(name = "base_url", length = 512, nullable = false)
private String basePath;
@Column(name = "default_url_suffix", length = 512, nullable = true)
private String defaultSuffix;
/**
* Constructs {@link ExternalArtifactProvider} based on given properties.
*
* @param name
* of the provided
* @param description
* which is optional
* @param baseURL
* of all {@link ExternalArtifact}s of the provider
* @param defaultUrlSuffix
* that is used if {@link ExternalArtifact#getUrlSuffix()} is
* empty.
*/
public JpaExternalArtifactProvider(final String name, final String description, final String baseURL,
final String defaultUrlSuffix) {
super(name, description);
basePath = baseURL;
defaultSuffix = defaultUrlSuffix;
}
JpaExternalArtifactProvider() {
super();
defaultSuffix = "";
basePath = "";
}
@Override
public String getBasePath() {
return basePath;
}
@Override
public String getDefaultSuffix() {
return defaultSuffix;
}
@Override
public void setBasePath(final String basePath) {
this.basePath = basePath;
}
@Override
public void setDefaultSuffix(final String defaultSuffix) {
this.defaultSuffix = defaultSuffix;
}
}

View File

@@ -0,0 +1,112 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa.model;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.ConstraintMode;
import javax.persistence.Entity;
import javax.persistence.ForeignKey;
import javax.persistence.Index;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.validation.constraints.NotNull;
import org.eclipse.hawkbit.repository.model.LocalArtifact;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import com.mongodb.gridfs.GridFS;
import com.mongodb.gridfs.GridFSFile;
/**
* JPA implementation of {@link LocalArtifact}.
*
*/
@Table(name = "sp_artifact", indexes = { @Index(name = "sp_idx_artifact_01", columnList = "tenant,software_module"),
@Index(name = "sp_idx_artifact_prim", columnList = "tenant,id") })
@Entity
public class JpaLocalArtifact extends AbstractJpaArtifact implements LocalArtifact {
private static final long serialVersionUID = 1L;
@NotNull
@Column(name = "gridfs_file_name", length = 40)
private String gridFsFileName;
@NotNull
@Column(name = "provided_file_name", length = 256)
private String filename;
@ManyToOne(optional = false, cascade = { CascadeType.PERSIST })
@JoinColumn(name = "software_module", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_assigned_sm"))
private JpaSoftwareModule softwareModule;
/**
* Default constructor.
*/
public JpaLocalArtifact() {
super();
}
/**
* Constructs artifact.
*
* @param gridFsFileName
* that is the link to the {@link GridFS} entity.
* @param filename
* that is used by {@link GridFSFile} store.
* @param softwareModule
* of this artifact
*/
public JpaLocalArtifact(@NotNull final String gridFsFileName, @NotNull final String filename,
final SoftwareModule softwareModule) {
setSoftwareModule(softwareModule);
this.gridFsFileName = gridFsFileName;
this.filename = filename;
}
@Override
public int hashCode() { // NOSONAR - as this is generated
final int prime = 31;
int result = super.hashCode();
result = prime * result + this.getClass().getName().hashCode();
return result;
}
@Override
public boolean equals(final Object obj) { // NOSONAR - as this is generated
if (!super.equals(obj)) {
return false;
}
if (!(obj instanceof LocalArtifact)) {
return false;
}
return true;
}
@Override
public SoftwareModule getSoftwareModule() {
return softwareModule;
}
public final void setSoftwareModule(final SoftwareModule softwareModule) {
this.softwareModule = (JpaSoftwareModule) softwareModule;
this.softwareModule.addArtifact(this);
}
public String getGridFsFileName() {
return gridFsFileName;
}
@Override
public String getFilename() {
return filename;
}
}

View File

@@ -0,0 +1,197 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa.model;
import java.util.List;
import javax.persistence.Column;
import javax.persistence.ConstraintMode;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.ForeignKey;
import javax.persistence.Index;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.persistence.UniqueConstraint;
import org.eclipse.hawkbit.cache.CacheField;
import org.eclipse.hawkbit.cache.CacheKeys;
import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.TotalTargetCountStatus;
/**
* JPA implementation of a {@link Rollout}.
*
*/
@Entity
@Table(name = "sp_rollout", indexes = {
@Index(name = "sp_idx_rollout_01", columnList = "tenant,name") }, uniqueConstraints = @UniqueConstraint(columnNames = {
"name", "tenant" }, name = "uk_rollout"))
public class JpaRollout extends AbstractJpaNamedEntity implements Rollout {
private static final long serialVersionUID = 1L;
@OneToMany(targetEntity = JpaRolloutGroup.class)
@JoinColumn(name = "rollout", insertable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_rollout_rolloutgroup"))
private List<RolloutGroup> rolloutGroups;
@Column(name = "target_filter", length = 1024, nullable = false)
private String targetFilterQuery;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "distribution_set", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_rolltout_ds"))
private JpaDistributionSet distributionSet;
@Column(name = "status")
private RolloutStatus status = RolloutStatus.CREATING;
@Column(name = "last_check")
private long lastCheck;
@Column(name = "action_type", nullable = false)
@Enumerated(EnumType.STRING)
private ActionType actionType = ActionType.FORCED;
@Column(name = "forced_time")
private long forcedTime;
@Column(name = "total_targets")
private long totalTargets;
@Transient
@CacheField(key = CacheKeys.ROLLOUT_GROUP_TOTAL)
private int rolloutGroupsTotal;
@Transient
@CacheField(key = CacheKeys.ROLLOUT_GROUP_CREATED)
private int rolloutGroupsCreated;
@Transient
private transient TotalTargetCountStatus totalTargetCountStatus;
@Override
public DistributionSet getDistributionSet() {
return distributionSet;
}
@Override
public void setDistributionSet(final DistributionSet distributionSet) {
this.distributionSet = (JpaDistributionSet) distributionSet;
}
@Override
public List<RolloutGroup> getRolloutGroups() {
return rolloutGroups;
}
public void setRolloutGroups(final List<RolloutGroup> rolloutGroups) {
this.rolloutGroups = rolloutGroups;
}
@Override
public String getTargetFilterQuery() {
return targetFilterQuery;
}
@Override
public void setTargetFilterQuery(final String targetFilterQuery) {
this.targetFilterQuery = targetFilterQuery;
}
@Override
public RolloutStatus getStatus() {
return status;
}
public void setStatus(final RolloutStatus status) {
this.status = status;
}
public long getLastCheck() {
return lastCheck;
}
public void setLastCheck(final long lastCheck) {
this.lastCheck = lastCheck;
}
@Override
public ActionType getActionType() {
return actionType;
}
@Override
public void setActionType(final ActionType actionType) {
this.actionType = actionType;
}
@Override
public long getForcedTime() {
return forcedTime;
}
@Override
public void setForcedTime(final long forcedTime) {
this.forcedTime = forcedTime;
}
@Override
public long getTotalTargets() {
return totalTargets;
}
public void setTotalTargets(final long totalTargets) {
this.totalTargets = totalTargets;
}
public int getRolloutGroupsTotal() {
return rolloutGroupsTotal;
}
public void setRolloutGroupsTotal(final int rolloutGroupsTotal) {
this.rolloutGroupsTotal = rolloutGroupsTotal;
}
@Override
public int getRolloutGroupsCreated() {
return rolloutGroupsCreated;
}
public void setRolloutGroupsCreated(final int rolloutGroupsCreated) {
this.rolloutGroupsCreated = rolloutGroupsCreated;
}
@Override
public TotalTargetCountStatus getTotalTargetCountStatus() {
if (totalTargetCountStatus == null) {
totalTargetCountStatus = new TotalTargetCountStatus(totalTargets);
}
return totalTargetCountStatus;
}
public void setTotalTargetCountStatus(final TotalTargetCountStatus totalTargetCountStatus) {
this.totalTargetCountStatus = totalTargetCountStatus;
}
@Override
public String toString() {
return "Rollout [rolloutGroups=" + rolloutGroups + ", targetFilterQuery=" + targetFilterQuery
+ ", distributionSet=" + distributionSet + ", status=" + status + ", lastCheck=" + lastCheck
+ ", getName()=" + getName() + ", getId()=" + getId() + "]";
}
}

View File

@@ -0,0 +1,236 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa.model;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.ConstraintMode;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.ForeignKey;
import javax.persistence.Index;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.persistence.UniqueConstraint;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.TotalTargetCountStatus;
/**
* JPA entity definition of persisting a group of an rollout.
*
*/
@Entity
@Table(name = "sp_rolloutgroup", indexes = {
@Index(name = "sp_idx_rolloutgroup_01", columnList = "tenant,name") }, uniqueConstraints = @UniqueConstraint(columnNames = {
"name", "rollout", "tenant" }, name = "uk_rolloutgroup"))
public class JpaRolloutGroup extends AbstractJpaNamedEntity implements RolloutGroup {
private static final long serialVersionUID = 1L;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "rollout", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_rolloutgroup_rollout"))
private JpaRollout rollout;
@Column(name = "status")
private RolloutGroupStatus status = RolloutGroupStatus.READY;
@OneToMany(fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST }, targetEntity = RolloutTargetGroup.class)
@JoinColumn(name = "rolloutGroup_Id", insertable = false, updatable = false)
private final List<RolloutTargetGroup> rolloutTargetGroup = new ArrayList<>();
@ManyToOne(fetch = FetchType.LAZY)
private JpaRolloutGroup parent;
@Column(name = "success_condition", nullable = false)
private RolloutGroupSuccessCondition successCondition = RolloutGroupSuccessCondition.THRESHOLD;
@Column(name = "success_condition_exp", length = 512, nullable = false)
private String successConditionExp;
@Column(name = "success_action", nullable = false)
private RolloutGroupSuccessAction successAction = RolloutGroupSuccessAction.NEXTGROUP;
@Column(name = "success_action_exp", length = 512, nullable = false)
private String successActionExp;
@Column(name = "error_condition")
private RolloutGroupErrorCondition errorCondition;
@Column(name = "error_condition_exp", length = 512)
private String errorConditionExp;
@Column(name = "error_action")
private RolloutGroupErrorAction errorAction;
@Column(name = "error_action_exp", length = 512)
private String errorActionExp;
@Column(name = "total_targets")
private long totalTargets;
@Transient
private transient TotalTargetCountStatus totalTargetCountStatus;
@Override
public Rollout getRollout() {
return rollout;
}
@Override
public void setRollout(final Rollout rollout) {
this.rollout = (JpaRollout) rollout;
}
@Override
public RolloutGroupStatus getStatus() {
return status;
}
@Override
public void setStatus(final RolloutGroupStatus status) {
this.status = status;
}
public List<RolloutTargetGroup> getRolloutTargetGroup() {
return rolloutTargetGroup;
}
@Override
public RolloutGroup getParent() {
return parent;
}
public void setParent(final RolloutGroup parent) {
this.parent = (JpaRolloutGroup) parent;
}
@Override
public RolloutGroupSuccessCondition getSuccessCondition() {
return successCondition;
}
@Override
public void setSuccessCondition(final RolloutGroupSuccessCondition finishCondition) {
successCondition = finishCondition;
}
@Override
public String getSuccessConditionExp() {
return successConditionExp;
}
@Override
public void setSuccessConditionExp(final String finishExp) {
successConditionExp = finishExp;
}
@Override
public RolloutGroupErrorCondition getErrorCondition() {
return errorCondition;
}
@Override
public void setErrorCondition(final RolloutGroupErrorCondition errorCondition) {
this.errorCondition = errorCondition;
}
@Override
public String getErrorConditionExp() {
return errorConditionExp;
}
@Override
public void setErrorConditionExp(final String errorExp) {
errorConditionExp = errorExp;
}
@Override
public RolloutGroupErrorAction getErrorAction() {
return errorAction;
}
@Override
public void setErrorAction(final RolloutGroupErrorAction errorAction) {
this.errorAction = errorAction;
}
@Override
public String getErrorActionExp() {
return errorActionExp;
}
@Override
public void setErrorActionExp(final String errorActionExp) {
this.errorActionExp = errorActionExp;
}
@Override
public RolloutGroupSuccessAction getSuccessAction() {
return successAction;
}
@Override
public String getSuccessActionExp() {
return successActionExp;
}
@Override
public long getTotalTargets() {
return totalTargets;
}
public void setTotalTargets(final long totalTargets) {
this.totalTargets = totalTargets;
}
public void setSuccessAction(final RolloutGroupSuccessAction successAction) {
this.successAction = successAction;
}
public void setSuccessActionExp(final String successActionExp) {
this.successActionExp = successActionExp;
}
/**
* @return the totalTargetCountStatus
*/
@Override
public TotalTargetCountStatus getTotalTargetCountStatus() {
if (totalTargetCountStatus == null) {
totalTargetCountStatus = new TotalTargetCountStatus(totalTargets);
}
return totalTargetCountStatus;
}
/**
* @param totalTargetCountStatus
* the totalTargetCountStatus to set
*/
@Override
public void setTotalTargetCountStatus(final TotalTargetCountStatus totalTargetCountStatus) {
this.totalTargetCountStatus = totalTargetCountStatus;
}
@Override
public String toString() {
return "RolloutGroup [rollout=" + rollout + ", status=" + status + ", rolloutTargetGroup=" + rolloutTargetGroup
+ ", parent=" + parent + ", finishCondition=" + successCondition + ", finishExp=" + successConditionExp
+ ", errorCondition=" + errorCondition + ", errorExp=" + errorConditionExp + ", getName()=" + getName()
+ ", getId()=" + getId() + "]";
}
}

View File

@@ -0,0 +1,266 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa.model;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.ConstraintMode;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.ForeignKey;
import javax.persistence.Index;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.NamedAttributeNode;
import javax.persistence.NamedEntityGraph;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import org.eclipse.hawkbit.repository.model.Artifact;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.ExternalArtifact;
import org.eclipse.hawkbit.repository.model.LocalArtifact;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.SoftwareModuleMetadata;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
import org.eclipse.persistence.annotations.CascadeOnDelete;
/**
* Base Software Module that is supported by OS level provisioning mechanism on
* the edge controller, e.g. OS, JVM, AgentHub.
*
*/
@Entity
@Table(name = "sp_base_software_module", uniqueConstraints = @UniqueConstraint(columnNames = { "module_type", "name",
"version", "tenant" }, name = "uk_base_sw_mod"), indexes = {
@Index(name = "sp_idx_base_sw_module_01", columnList = "tenant,deleted,name,version"),
@Index(name = "sp_idx_base_sw_module_02", columnList = "tenant,deleted,module_type"),
@Index(name = "sp_idx_base_sw_module_prim", columnList = "tenant,id") })
@NamedEntityGraph(name = "SoftwareModule.artifacts", attributeNodes = { @NamedAttributeNode("artifacts") })
public class JpaSoftwareModule extends AbstractJpaNamedVersionedEntity implements SoftwareModule {
private static final long serialVersionUID = 1L;
@ManyToOne
@JoinColumn(name = "module_type", nullable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_module_type"))
private JpaSoftwareModuleType type;
@ManyToMany(mappedBy = "modules", targetEntity = JpaDistributionSet.class, fetch = FetchType.LAZY)
private final List<DistributionSet> assignedTo = new ArrayList<>();
@Column(name = "deleted")
private boolean deleted;
@Column(name = "vendor", nullable = true, length = 256)
private String vendor;
@OneToMany(mappedBy = "softwareModule", cascade = { CascadeType.ALL }, targetEntity = JpaLocalArtifact.class)
private List<LocalArtifact> artifacts;
@OneToMany(mappedBy = "softwareModule", cascade = { CascadeType.ALL }, targetEntity = JpaExternalArtifact.class)
private List<ExternalArtifact> externalArtifacts;
@CascadeOnDelete
@OneToMany(fetch = FetchType.LAZY, cascade = { CascadeType.REMOVE }, targetEntity = JpaSoftwareModuleMetadata.class)
@JoinColumn(name = "sw_id", insertable = false, updatable = false)
private final List<SoftwareModuleMetadata> metadata = new ArrayList<>();
/**
* Default constructor.
*/
public JpaSoftwareModule() {
super();
}
/**
* parameterized constructor.
*
* @param type
* of the {@link SoftwareModule}
* @param name
* abstract name of the {@link SoftwareModule}
* @param version
* of the {@link SoftwareModule}
* @param description
* of the {@link SoftwareModule}
* @param vendor
* of the {@link SoftwareModule}
*/
public JpaSoftwareModule(final SoftwareModuleType type, final String name, final String version,
final String description, final String vendor) {
super(name, version, description);
this.vendor = vendor;
this.type = (JpaSoftwareModuleType) type;
}
/**
* @param artifact
* is added to the assigned {@link Artifact}s.
*/
@Override
public void addArtifact(final LocalArtifact artifact) {
if (null == artifacts) {
artifacts = new ArrayList<>(4);
}
if (!artifacts.contains(artifact)) {
artifacts.add(artifact);
}
}
/**
* @param artifact
* is added to the assigned {@link Artifact}s.
*/
@Override
public void addArtifact(final ExternalArtifact artifact) {
if (null == externalArtifacts) {
externalArtifacts = new ArrayList<>(4);
}
if (!externalArtifacts.contains(artifact)) {
externalArtifacts.add(artifact);
}
}
/**
* @param artifactId
* to look for
* @return found {@link Artifact}
*/
@Override
public Optional<LocalArtifact> getLocalArtifact(final Long artifactId) {
if (null == artifacts) {
return Optional.empty();
}
return artifacts.stream().filter(artifact -> artifact.getId().equals(artifactId)).findFirst();
}
/**
* @param fileName
* to look for
* @return found {@link Artifact}
*/
@Override
public Optional<LocalArtifact> getLocalArtifactByFilename(final String fileName) {
if (null == artifacts) {
return Optional.empty();
}
return artifacts.stream().filter(artifact -> artifact.getFilename().equalsIgnoreCase(fileName.trim()))
.findFirst();
}
/**
* @return the artifacts
*/
@Override
public List<Artifact> getArtifacts() {
final List<Artifact> result = new ArrayList<>();
result.addAll(artifacts);
result.addAll(externalArtifacts);
return result;
}
/**
* @return local artifacts only
*/
@Override
public List<LocalArtifact> getLocalArtifacts() {
if (artifacts == null) {
return Collections.emptyList();
}
return artifacts;
}
@Override
public String getVendor() {
return vendor;
}
/**
* @param artifact
* is removed from the assigned {@link LocalArtifact}s.
*/
@Override
public void removeArtifact(final LocalArtifact artifact) {
if (null != artifacts) {
artifacts.remove(artifact);
}
}
/**
* @param artifact
* is removed from the assigned {@link ExternalArtifact}s.
*/
@Override
public void removeArtifact(final ExternalArtifact artifact) {
if (null != externalArtifacts) {
externalArtifacts.remove(artifact);
}
}
@Override
public void setVendor(final String vendor) {
this.vendor = vendor;
}
@Override
public SoftwareModuleType getType() {
return type;
}
@Override
public boolean isDeleted() {
return deleted;
}
@Override
public void setDeleted(final boolean deleted) {
this.deleted = deleted;
}
@Override
public void setType(final SoftwareModuleType type) {
this.type = (JpaSoftwareModuleType) type;
}
/**
* @return immutable list of meta data elements.
*/
@Override
public List<SoftwareModuleMetadata> getMetadata() {
return Collections.unmodifiableList(metadata);
}
@Override
public String toString() {
return "SoftwareModule [deleted=" + deleted + ", name=" + getName() + ", version=" + getVersion()
+ ", revision=" + getOptLockRevision() + ", Id=" + getId() + ", type=" + getType().getName() + "]";
}
/**
* @return the assignedTo
*/
@Override
public List<DistributionSet> getAssignedTo() {
return assignedTo;
}
}

View File

@@ -0,0 +1,85 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa.model;
import javax.persistence.ConstraintMode;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.ForeignKey;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.SoftwareModuleMetadata;
/**
* Metadata for {@link SoftwareModule}.
*
*/
@IdClass(SwMetadataCompositeKey.class)
@Entity
@Table(name = "sp_sw_metadata")
public class JpaSoftwareModuleMetadata extends AbstractJpaMetaData implements SoftwareModuleMetadata {
private static final long serialVersionUID = 1L;
@Id
@ManyToOne(targetEntity = JpaSoftwareModule.class, fetch = FetchType.LAZY)
@JoinColumn(name = "sw_id", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_metadata_sw"))
private SoftwareModule softwareModule;
public JpaSoftwareModuleMetadata() {
// default public constructor for JPA
}
public JpaSoftwareModuleMetadata(final String key, final SoftwareModule softwareModule, final String value) {
super(key, value);
this.softwareModule = softwareModule;
}
public SwMetadataCompositeKey getId() {
return new SwMetadataCompositeKey(softwareModule, getKey());
}
@Override
public SoftwareModule getSoftwareModule() {
return softwareModule;
}
@Override
public void setSoftwareModule(final SoftwareModule softwareModule) {
this.softwareModule = softwareModule;
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + ((softwareModule == null) ? 0 : softwareModule.hashCode());
return result;
}
@Override
public boolean equals(final Object obj) {
if (!super.equals(obj)) {
return false;
}
final JpaSoftwareModuleMetadata other = (JpaSoftwareModuleMetadata) obj;
if (softwareModule == null) {
if (other.softwareModule != null) {
return false;
}
} else if (!softwareModule.equals(other.softwareModule)) {
return false;
}
return true;
}
}

View File

@@ -0,0 +1,135 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa.model;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Index;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
/**
* Type of a software modules.
*
*/
@Entity
@Table(name = "sp_software_module_type", indexes = {
@Index(name = "sp_idx_software_module_type_01", columnList = "tenant,deleted"),
@Index(name = "sp_idx_software_module_type_prim", columnList = "tenant,id") }, uniqueConstraints = {
@UniqueConstraint(columnNames = { "type_key", "tenant" }, name = "uk_smt_type_key"),
@UniqueConstraint(columnNames = { "name", "tenant" }, name = "uk_smt_name") })
public class JpaSoftwareModuleType extends AbstractJpaNamedEntity implements SoftwareModuleType {
private static final long serialVersionUID = 1L;
@Column(name = "type_key", nullable = false, length = 64)
private String key;
@Column(name = "max_ds_assignments", nullable = false)
private int maxAssignments;
@Column(name = "colour", nullable = true, length = 16)
private String colour;
@Column(name = "deleted")
private boolean deleted;
/**
* Constructor.
*
* @param key
* of the type
* @param name
* of the type
* @param description
* of the type
* @param maxAssignments
* assignments to a DS
*/
public JpaSoftwareModuleType(final String key, final String name, final String description,
final int maxAssignments) {
this(key, name, description, maxAssignments, null);
}
/**
* Constructor.
*
* @param key
* of the type
* @param name
* of the type
* @param description
* of the type
* @param maxAssignments
* assignments to a DS
* @param colour
* of the type. It will be null by default
*/
public JpaSoftwareModuleType(final String key, final String name, final String description,
final int maxAssignments, final String colour) {
super();
this.key = key;
this.maxAssignments = maxAssignments;
setDescription(description);
setName(name);
this.colour = colour;
}
/**
* Default Constructor for JPA.
*/
public JpaSoftwareModuleType() {
// Default Constructor for JPA.
}
@Override
public void setMaxAssignments(final int maxAssignments) {
this.maxAssignments = maxAssignments;
}
@Override
public String getKey() {
return key;
}
@Override
public int getMaxAssignments() {
return maxAssignments;
}
@Override
public boolean isDeleted() {
return deleted;
}
public void setDeleted(final boolean deleted) {
this.deleted = deleted;
}
@Override
public String getColour() {
return colour;
}
@Override
public void setColour(final String colour) {
this.colour = colour;
}
@Override
public String toString() {
return "SoftwareModuleType [key=" + key + ", getName()=" + getName() + ", getId()=" + getId() + "]";
}
@Override
public void setKey(final String key) {
this.key = key;
}
}

View File

@@ -0,0 +1,219 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa.model;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.ConstraintMode;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.ForeignKey;
import javax.persistence.Index;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.NamedAttributeNode;
import javax.persistence.NamedEntityGraph;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.PrimaryKeyJoinColumn;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.persistence.UniqueConstraint;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import org.eclipse.hawkbit.im.authentication.SpPermission;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetInfo;
import org.eclipse.hawkbit.repository.model.TargetTag;
import org.eclipse.hawkbit.repository.model.helper.SecurityChecker;
import org.eclipse.hawkbit.repository.model.helper.SecurityTokenGeneratorHolder;
import org.eclipse.hawkbit.repository.model.helper.SystemSecurityContextHolder;
import org.eclipse.persistence.annotations.CascadeOnDelete;
import org.springframework.data.domain.Persistable;
/**
* JPA implementation of {@link Target}.
*
*/
@Entity
@Table(name = "sp_target", indexes = {
@Index(name = "sp_idx_target_01", columnList = "tenant,name,assigned_distribution_set"),
@Index(name = "sp_idx_target_02", columnList = "tenant,name"),
@Index(name = "sp_idx_target_03", columnList = "tenant,controller_id,assigned_distribution_set"),
@Index(name = "sp_idx_target_04", columnList = "tenant,created_at"),
@Index(name = "sp_idx_target_prim", columnList = "tenant,id") }, uniqueConstraints = @UniqueConstraint(columnNames = {
"controller_id", "tenant" }, name = "uk_tenant_controller_id"))
@NamedEntityGraph(name = "Target.detail", attributeNodes = { @NamedAttributeNode("tags"),
@NamedAttributeNode(value = "assignedDistributionSet"), @NamedAttributeNode(value = "targetInfo") })
public class JpaTarget extends AbstractJpaNamedEntity implements Persistable<Long>, Target {
private static final long serialVersionUID = 1L;
@Column(name = "controller_id", length = 64)
@Size(min = 1)
@NotNull
private String controllerId;
@Transient
private boolean entityNew;
@ManyToMany(targetEntity = JpaTargetTag.class)
@JoinTable(name = "sp_target_target_tag", joinColumns = {
@JoinColumn(name = "target", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_targ_targtag_target")) }, inverseJoinColumns = {
@JoinColumn(name = "tag", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_targ_targtag_tag")) })
private Set<TargetTag> tags = new HashSet<>();
@CascadeOnDelete
@OneToMany(fetch = FetchType.LAZY, orphanRemoval = true, cascade = {
CascadeType.REMOVE }, targetEntity = JpaAction.class)
@JoinColumn(name = "target", insertable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_targ_act_hist_targ"))
private final List<Action> actions = new ArrayList<>();
@ManyToOne(optional = true, fetch = FetchType.LAZY, targetEntity = JpaDistributionSet.class)
@JoinColumn(name = "assigned_distribution_set", nullable = true, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_target_assign_ds"))
private JpaDistributionSet assignedDistributionSet;
@CascadeOnDelete
@OneToOne(cascade = { CascadeType.ALL }, fetch = FetchType.LAZY, targetEntity = JpaTargetInfo.class)
@PrimaryKeyJoinColumn
private JpaTargetInfo targetInfo;
/**
* the security token of the target which allows if enabled to authenticate
* with this security token.
*/
@Column(name = "sec_token", insertable = true, updatable = true, nullable = false, length = 128)
private String securityToken;
@CascadeOnDelete
@OneToMany(fetch = FetchType.LAZY, cascade = { CascadeType.REMOVE, CascadeType.PERSIST })
@JoinColumn(name = "target_Id", insertable = false, updatable = false)
private final List<RolloutTargetGroup> rolloutTargetGroup = new ArrayList<>();
/**
* Constructor.
*
* @param controllerId
* controller ID of the {@link Target}
*/
public JpaTarget(final String controllerId) {
this.controllerId = controllerId;
setName(controllerId);
securityToken = SecurityTokenGeneratorHolder.getInstance().generateToken();
targetInfo = new JpaTargetInfo(this);
}
/**
* empty constructor for JPA.
*/
JpaTarget() {
controllerId = null;
securityToken = null;
}
@Override
public DistributionSet getAssignedDistributionSet() {
return assignedDistributionSet;
}
@Override
public String getControllerId() {
return controllerId;
}
@Override
public Set<TargetTag> getTags() {
return tags;
}
public void setAssignedDistributionSet(final DistributionSet assignedDistributionSet) {
this.assignedDistributionSet = (JpaDistributionSet) assignedDistributionSet;
}
public void setControllerId(final String controllerId) {
this.controllerId = controllerId;
}
public void setTags(final Set<TargetTag> tags) {
this.tags = tags;
}
@Override
public List<Action> getActions() {
return actions;
}
@Override
@Transient
public boolean isNew() {
return entityNew;
}
/**
* @param isNew
* the isNew to set
*/
public void setNew(final boolean entityNew) {
this.entityNew = entityNew;
}
/**
* @return the targetInfo
*/
@Override
public TargetInfo getTargetInfo() {
return targetInfo;
}
/**
* @param targetInfo
* the targetInfo to set
*/
public void setTargetInfo(final TargetInfo targetInfo) {
this.targetInfo = (JpaTargetInfo) targetInfo;
}
/**
* @return the securityToken if the current security context contains the
* necessary permission {@link SpPermission#READ_TARGET_SEC_TOKEN}
* or the current context is executed as system code, otherwise
* {@code null}.
*/
@Override
public String getSecurityToken() {
if (SystemSecurityContextHolder.getInstance().getSystemSecurityContext().isCurrentThreadSystemCode()
|| SecurityChecker.hasPermission(SpPermission.READ_TARGET_SEC_TOKEN)) {
return securityToken;
}
return null;
}
/**
* @param securityToken
* the securityToken to set
*/
public void setSecurityToken(final String securityToken) {
this.securityToken = securityToken;
}
@Override
public String toString() {
return "Target [controllerId=" + controllerId + ", getId()=" + getId() + "]";
}
}

View File

@@ -0,0 +1,72 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa.model;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Index;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
/**
* Stored target filter.
*
*/
@Entity
@Table(name = "sp_target_filter_query", indexes = {
@Index(name = "sp_idx_target_filter_query_01", columnList = "tenant,name") }, uniqueConstraints = @UniqueConstraint(columnNames = {
"name", "tenant" }, name = "uk_tenant_custom_filter_name"))
public class JpaTargetFilterQuery extends AbstractJpaTenantAwareBaseEntity implements TargetFilterQuery {
private static final long serialVersionUID = 7493966984413479089L;
@Column(name = "name", length = 64)
private String name;
@Column(name = "query", length = 1024)
private String query;
public JpaTargetFilterQuery() {
// Default constructor for JPA.
}
/**
* Public constructor.
*
* @param name
* of the {@link TargetFilterQuery}.
* @param query
* of the {@link TargetFilterQuery}.
*/
public JpaTargetFilterQuery(final String name, final String query) {
this.name = name;
this.query = query;
}
@Override
public String getName() {
return name;
}
@Override
public void setName(final String name) {
this.name = name;
}
@Override
public String getQuery() {
return query;
}
@Override
public void setQuery(final String query) {
this.query = query;
}
}

View File

@@ -0,0 +1,317 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa.model;
import java.net.URI;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import javax.persistence.CascadeType;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.ConstraintMode;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.ForeignKey;
import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.MapKeyColumn;
import javax.persistence.MapsId;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import javax.persistence.Transient;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.PollStatus;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetInfo;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
import org.eclipse.hawkbit.repository.model.helper.SystemSecurityContextHolder;
import org.eclipse.hawkbit.repository.model.helper.TenantConfigurationManagementHolder;
import org.eclipse.hawkbit.tenancy.configuration.DurationHelper;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey;
import org.eclipse.persistence.annotations.CascadeOnDelete;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Persistable;
/**
* A table which contains all the information inserted, updated by the
* controller itself. So this entity does not provide audit information because
* changes on this entity are mostly only done by controller requests. That's
* the reason that we store these information in a separated table so we don't
* modifying the {@link Target} itself when a controller reports it's
* {@link #lastTargetQuery} for example.
*
*/
@Table(name = "sp_target_info", indexes = {
@Index(name = "sp_idx_target_info_02", columnList = "target_id,update_status") })
@Entity
public class JpaTargetInfo implements Persistable<Long>, TargetInfo {
private static final long serialVersionUID = 1L;
private static final Logger LOG = LoggerFactory.getLogger(TargetInfo.class);
@Id
private Long targetId;
@Transient
private boolean entityNew;
@CascadeOnDelete
@OneToOne(cascade = { CascadeType.MERGE,
CascadeType.REMOVE }, fetch = FetchType.LAZY, targetEntity = JpaTarget.class)
@JoinColumn(name = "target_id", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_targ_stat_targ"))
@MapsId
private JpaTarget target;
@Column(name = "address", length = 512)
private String address;
@Column(name = "last_target_query")
private Long lastTargetQuery;
@Column(name = "install_date")
private Long installationDate;
@Column(name = "update_status", nullable = false, length = 255)
@Enumerated(EnumType.STRING)
private TargetUpdateStatus updateStatus = TargetUpdateStatus.UNKNOWN;
@ManyToOne(optional = true, fetch = FetchType.LAZY)
@JoinColumn(name = "installed_distribution_set", nullable = true, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_target_inst_ds"))
private JpaDistributionSet installedDistributionSet;
/**
* Read only on management API. Are commited by controller.
*/
@ElementCollection
@Column(name = "attribute_value", length = 128)
@MapKeyColumn(name = "attribute_key", nullable = false, length = 32)
@CollectionTable(name = "sp_target_attributes", joinColumns = {
@JoinColumn(name = "target_id") }, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_targ_attrib_target"))
private final Map<String, String> controllerAttributes = Collections.synchronizedMap(new HashMap<String, String>());
// set default request controller attributes to true, because we want to
// request them the first
// time
@Column(name = "request_controller_attributes", nullable = false)
private boolean requestControllerAttributes = true;
/**
* Constructor for {@link TargetStatus}.
*
* @param target
* related to this status.
*/
public JpaTargetInfo(final JpaTarget target) {
this.target = target;
targetId = target.getId();
}
JpaTargetInfo() {
target = null;
targetId = null;
}
@Override
public Long getId() {
return targetId;
}
@Override
@Transient
public boolean isNew() {
return entityNew;
}
/**
* @param isNew
* the isNew to set
*/
public void setNew(final boolean entityNew) {
this.entityNew = entityNew;
}
/**
* @return the ipAddress
*/
@Override
public URI getAddress() {
if (address == null) {
return null;
}
try {
return URI.create(address);
} catch (final IllegalArgumentException e) {
LOG.warn("Invalid address provided. Cloud not be configured to URI", e);
return null;
}
}
/**
* @param address
* the ipAddress to set
*
* @throws IllegalArgumentException
* If the given string violates RFC&nbsp;2396
*/
public void setAddress(final String address) {
// check if this is a real URI
if (address != null) {
URI.create(address);
}
this.address = address;
}
public Long getTargetId() {
return targetId;
}
public void setTargetId(final Long targetId) {
this.targetId = targetId;
}
@Override
public Target getTarget() {
return target;
}
public void setTarget(final JpaTarget target) {
this.target = target;
}
@Override
public Long getLastTargetQuery() {
return lastTargetQuery;
}
public void setLastTargetQuery(final Long lastTargetQuery) {
this.lastTargetQuery = lastTargetQuery;
}
public void setRequestControllerAttributes(final boolean requestControllerAttributes) {
this.requestControllerAttributes = requestControllerAttributes;
}
@Override
public Map<String, String> getControllerAttributes() {
return controllerAttributes;
}
@Override
public boolean isRequestControllerAttributes() {
return requestControllerAttributes;
}
@Override
public Long getInstallationDate() {
return installationDate;
}
public void setInstallationDate(final Long installationDate) {
this.installationDate = installationDate;
}
@Override
public TargetUpdateStatus getUpdateStatus() {
return updateStatus;
}
public void setUpdateStatus(final TargetUpdateStatus updateStatus) {
this.updateStatus = updateStatus;
}
@Override
public DistributionSet getInstalledDistributionSet() {
return installedDistributionSet;
}
public void setInstalledDistributionSet(final JpaDistributionSet installedDistributionSet) {
this.installedDistributionSet = installedDistributionSet;
}
/**
* @return the poll time which holds the last poll time of the target, the
* next poll time and the overdue time. In case the
* {@link #lastTargetQuery} is not set e.g. the target never polled
* before this method returns {@code null}
*/
@Override
public PollStatus getPollStatus() {
if (lastTargetQuery == null) {
return null;
}
return SystemSecurityContextHolder.getInstance().getSystemSecurityContext().runAsSystem(() -> {
final Duration pollTime = DurationHelper.formattedStringToDuration(TenantConfigurationManagementHolder
.getInstance().getTenantConfigurationManagement()
.getConfigurationValue(TenantConfigurationKey.POLLING_TIME_INTERVAL, String.class).getValue());
final Duration overdueTime = DurationHelper.formattedStringToDuration(
TenantConfigurationManagementHolder.getInstance().getTenantConfigurationManagement()
.getConfigurationValue(TenantConfigurationKey.POLLING_OVERDUE_TIME_INTERVAL, String.class)
.getValue());
final LocalDateTime currentDate = LocalDateTime.now();
final LocalDateTime lastPollDate = LocalDateTime.ofInstant(Instant.ofEpochMilli(lastTargetQuery),
ZoneId.systemDefault());
final LocalDateTime nextPollDate = lastPollDate.plus(pollTime);
final LocalDateTime overdueDate = nextPollDate.plus(overdueTime);
return new PollStatus(lastPollDate, nextPollDate, overdueDate, currentDate);
});
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((target == null) ? 0 : target.hashCode());
result = prime * result + ((targetId == null) ? 0 : targetId.hashCode());
return result;
}
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof TargetInfo)) {
return false;
}
final JpaTargetInfo other = (JpaTargetInfo) obj;
if (target == null) {
if (other.target != null) {
return false;
}
} else if (!target.equals(other.target)) {
return false;
}
if (targetId == null) {
if (other.targetId != null) {
return false;
}
} else if (!targetId.equals(other.targetId)) {
return false;
}
return true;
}
}

View File

@@ -0,0 +1,91 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa.model;
import java.util.List;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Index;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetTag;
/**
* A {@link TargetTag} is used to describe Target attributes and use them also
* for filtering the target list.
*
*/
@Entity
@Table(name = "sp_target_tag", indexes = {
@Index(name = "sp_idx_target_tag_prim", columnList = "tenant,id") }, uniqueConstraints = @UniqueConstraint(columnNames = {
"name", "tenant" }, name = "uk_targ_tag"))
public class JpaTargetTag extends AbstractJpaTag implements TargetTag {
private static final long serialVersionUID = 1L;
@ManyToMany(mappedBy = "tags", targetEntity = JpaTarget.class, fetch = FetchType.LAZY)
private List<Target> assignedToTargets;
/**
* Constructor.
*
* @param name
* of {@link TargetTag}
* @param description
* of {@link TargetTag}
* @param colour
* of {@link TargetTag}
*/
public JpaTargetTag(final String name, final String description, final String colour) {
super(name, description, colour);
}
/**
* Public constructor.
*
* @param name
* of the {@link TargetTag}
**/
public JpaTargetTag(final String name) {
super(name, null, null);
}
public JpaTargetTag() {
// Default constructor for JPA.
}
@Override
public List<Target> getAssignedToTargets() {
return assignedToTargets;
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + this.getClass().getName().hashCode();
return result;
}
@Override
public boolean equals(final Object obj) {
if (!super.equals(obj)) {
return false;
}
if (!(obj instanceof TargetTag)) {
return false;
}
return true;
}
}

View File

@@ -0,0 +1,75 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa.model;
import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import org.eclipse.hawkbit.repository.model.TenantConfiguration;
/**
* A JPA entity which stores the tenant specific configuration.
*
*/
@Entity
@Table(name = "sp_tenant_configuration", uniqueConstraints = @UniqueConstraint(columnNames = { "conf_key",
"tenant" }, name = "uk_tenant_key"))
public class JpaTenantConfiguration extends AbstractJpaTenantAwareBaseEntity implements TenantConfiguration {
private static final long serialVersionUID = 1L;
@Column(name = "conf_key", length = 128)
private String key;
@Column(name = "conf_value", length = 512)
@Basic
private String value;
/**
* JPA default constructor.
*/
public JpaTenantConfiguration() {
// JPA default constructor.
}
/**
* @param key
* the key of this configuration
* @param value
* the value of this configuration
*/
public JpaTenantConfiguration(final String key, final String value) {
this.key = key;
this.value = value;
}
@Override
public String getKey() {
return key;
}
@Override
public void setKey(final String key) {
this.key = key;
}
@Override
public String getValue() {
return value;
}
@Override
public void setValue(final String value) {
this.value = value;
}
}

View File

@@ -0,0 +1,107 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa.model;
import javax.persistence.Column;
import javax.persistence.ConstraintMode;
import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.FetchType;
import javax.persistence.ForeignKey;
import javax.persistence.Index;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity;
import org.eclipse.hawkbit.repository.model.TenantMetaData;
/**
* Tenant entity with meta data that is configured globally for the entire
* tenant. This entity is not tenant aware to allow the system to access it
* through the {@link EntityManager} even before the actual tenant exists.
*
* Entities owned by the tenant are based on {@link TenantAwareBaseEntity}.
*
*/
@Table(name = "sp_tenant", indexes = {
@Index(name = "sp_idx_tenant_prim", columnList = "tenant,id") }, uniqueConstraints = {
@UniqueConstraint(columnNames = { "tenant" }, name = "uk_tenantmd_tenant") })
@Entity
public class JpaTenantMetaData extends AbstractJpaBaseEntity implements TenantMetaData {
private static final long serialVersionUID = 1L;
@Column(name = "tenant", nullable = false, length = 40)
private String tenant;
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "default_ds_type", nullable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_tenant_md_default_ds_type"))
private JpaDistributionSetType defaultDsType;
/**
* Default constructor needed for JPA entities.
*/
public JpaTenantMetaData() {
// Default constructor needed for JPA entities.
}
/**
* Standard constructor.
*
* @param defaultDsType
* of this tenant
* @param tenant
*/
public JpaTenantMetaData(final DistributionSetType defaultDsType, final String tenant) {
super();
this.defaultDsType = (JpaDistributionSetType) defaultDsType;
this.tenant = tenant;
}
@Override
public DistributionSetType getDefaultDsType() {
return defaultDsType;
}
@Override
public void setDefaultDsType(final DistributionSetType defaultDsType) {
this.defaultDsType = (JpaDistributionSetType) defaultDsType;
}
@Override
public String getTenant() {
return tenant;
}
public void setTenant(final String tenant) {
this.tenant = tenant;
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + this.getClass().getName().hashCode();
return result;
}
@Override
public boolean equals(final Object obj) {
if (!super.equals(obj)) {
return false;
}
if (!(obj instanceof TenantMetaData)) {
return false;
}
return true;
}
}

View File

@@ -0,0 +1,76 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa.model;
import java.io.Serializable;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.ConstraintMode;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.ForeignKey;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.JoinColumn;
import javax.persistence.JoinColumns;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.persistence.annotations.ExistenceChecking;
import org.eclipse.persistence.annotations.ExistenceType;
/**
* Entity with JPA annotation to store the information which {@link Target} is
* in a specific {@link RolloutGroup}.
*
*/
@IdClass(RolloutTargetGroupId.class)
@Entity
@Table(name = "sp_rollouttargetgroup")
@ExistenceChecking(ExistenceType.ASSUME_NON_EXISTENCE)
public class RolloutTargetGroup implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@ManyToOne(targetEntity = JpaRolloutGroup.class, fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST })
@JoinColumn(name = "rolloutGroup_Id", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_rollouttargetgroup_group"))
private RolloutGroup rolloutGroup;
@Id
@ManyToOne(targetEntity = JpaTarget.class, fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST })
@JoinColumn(name = "target_id", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_rollouttargetgroup_target"))
private JpaTarget target;
@OneToMany(targetEntity = JpaAction.class, fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST })
@JoinColumns(value = { @JoinColumn(name = "rolloutgroup", referencedColumnName = "rolloutGroup_Id"),
@JoinColumn(name = "target", referencedColumnName = "target_id") })
private List<Action> actions;
/**
* default constructor for JPA.
*/
public RolloutTargetGroup() {
// JPA constructor
}
public RolloutTargetGroup(final RolloutGroup rolloutGroup, final Target target) {
this.rolloutGroup = rolloutGroup;
this.target = (JpaTarget) target;
}
public RolloutTargetGroupId getId() {
return new RolloutTargetGroupId(rolloutGroup, target);
}
}

View File

@@ -0,0 +1,53 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa.model;
import java.io.Serializable;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.Target;
/**
* Combined unique key of the table {@link RolloutTargetGroup}.
*
*/
public class RolloutTargetGroupId implements Serializable {
private static final long serialVersionUID = 1L;
private Long rolloutGroup;
private Long target;
/**
* default constructor necessary for JPA.
*/
public RolloutTargetGroupId() {
// default constructor necessary for JPA, empty.
}
/**
* Constructor.
*
* @param rolloutGroup
* the rollout group for this key
* @param target
* the target for this key
*/
public RolloutTargetGroupId(final RolloutGroup rolloutGroup, final Target target) {
this.rolloutGroup = rolloutGroup.getId();
this.target = target.getId();
}
public Long getRolloutGroup() {
return rolloutGroup;
}
public Long getTarget() {
return target;
}
}

View File

@@ -0,0 +1,113 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa.model;
import java.io.Serializable;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
/**
* The Software Module meta data composite key which contains the meta data key
* and the ID of the software module itself.
*/
public final class SwMetadataCompositeKey implements Serializable {
private static final long serialVersionUID = 1L;
private String key;
private Long softwareModule;
/**
* Default constructor for JPA.
*/
public SwMetadataCompositeKey() {
// Default constructor for JPA.
}
/**
* @param softwareModule
* the software module for this meta data
* @param key
* the key of the meta data
*/
public SwMetadataCompositeKey(final SoftwareModule softwareModule, final String key) {
this.softwareModule = softwareModule.getId();
this.key = key;
}
/**
* @return the key
*/
public String getKey() {
return key;
}
/**
* @param key
* the key to set
*/
public void setKey(final String key) {
this.key = key;
}
/**
* @return the softwareModule
*/
public Long getSoftwareModule() {
return softwareModule;
}
/**
* @param softwareModule
* the softwareModule to set
*/
public void setSoftwareModule(final Long softwareModule) {
this.softwareModule = softwareModule;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (key == null ? 0 : key.hashCode());
result = prime * result + (softwareModule == null ? 0 : softwareModule.hashCode());
return result;
}
@Override
public boolean equals(final Object obj) { // NOSONAR - as this is generated
// code
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final SwMetadataCompositeKey other = (SwMetadataCompositeKey) obj;
if (key == null) {
if (other.key != null) {
return false;
}
} else if (!key.equals(other.key)) {
return false;
}
if (softwareModule == null) {
if (other.softwareModule != null) {
return false;
}
} else if (!softwareModule.equals(other.softwareModule)) {
return false;
}
return true;
}
}

View File

@@ -0,0 +1,64 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa.specifications;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.ListJoin;
import javax.persistence.criteria.SetJoin;
import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
import org.eclipse.hawkbit.repository.jpa.model.JpaAction_;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet_;
import org.eclipse.hawkbit.repository.jpa.model.JpaLocalArtifact;
import org.eclipse.hawkbit.repository.jpa.model.JpaLocalArtifact_;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule_;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.LocalArtifact;
import org.eclipse.hawkbit.repository.model.Target;
import org.springframework.data.jpa.domain.Specification;
/**
* Utility class for {@link Action}s {@link Specification}s. The class provides
* Spring Data JPQL Specifications.
*
*/
public final class ActionSpecifications {
private ActionSpecifications() {
// utility class
}
/**
* Specification which joins all necessary tables to retrieve the dependency
* between a target and a local file assignment through the assigen action
* of the target. All actions are included, not only active actions.
*
* @param target
* the target to verfiy if the given artifact is currently
* assigned or had been assigned
* @param localArtifact
* the local artifact to check wherever the target had ever been
* assigned
* @return a specification to use with spring JPA
*/
public static Specification<JpaAction> hasTargetAssignedArtifact(final Target target,
final LocalArtifact localArtifact) {
return (actionRoot, query, criteriaBuilder) -> {
final Join<JpaAction, JpaDistributionSet> dsJoin = actionRoot.join(JpaAction_.distributionSet);
final SetJoin<JpaDistributionSet, JpaSoftwareModule> modulesJoin = dsJoin.join(JpaDistributionSet_.modules);
final ListJoin<JpaSoftwareModule, JpaLocalArtifact> artifactsJoin = modulesJoin
.join(JpaSoftwareModule_.artifacts);
return criteriaBuilder.and(
criteriaBuilder.equal(artifactsJoin.get(JpaLocalArtifact_.filename), localArtifact.getFilename()),
criteriaBuilder.equal(actionRoot.get(JpaAction_.target), target));
};
}
}

View File

@@ -0,0 +1,225 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa.specifications;
import java.util.Collection;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.ListJoin;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.SetJoin;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetTag;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetTag_;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetType;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet_;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetInfo;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetInfo_;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget_;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.springframework.data.jpa.domain.Specification;
/**
* Specifications class for {@link DistributionSet}s. The class provides Spring
* Data JPQL Specifications.
*
*/
public final class DistributionSetSpecification {
private DistributionSetSpecification() {
// utility class
}
/**
* {@link Specification} for retrieving {@link DistributionSet}s by its
* DELETED attribute.
*
* @param isDeleted
* TRUE/FALSE are compared to the attribute DELETED. If NULL the
* attribute is ignored
* @return the {@link DistributionSet} {@link Specification}
*/
public static Specification<JpaDistributionSet> isDeleted(final Boolean isDeleted) {
return (targetRoot, query, cb) -> cb.equal(targetRoot.<Boolean> get(JpaDistributionSet_.deleted), isDeleted);
}
/**
* {@link Specification} for retrieving {@link DistributionSet}s by its
* COMPLETED attribute.
*
* @param isCompleted
* TRUE/FALSE are compared to the attribute COMPLETED. If NULL
* the attribute is ignored
* @return the {@link DistributionSet} {@link Specification}
*/
public static Specification<JpaDistributionSet> isCompleted(final Boolean isCompleted) {
return (targetRoot, query, cb) -> cb.equal(targetRoot.<Boolean> get(JpaDistributionSet_.complete), isCompleted);
}
/**
* {@link Specification} for retrieving {@link DistributionSet} with given
* {@link DistributionSet#getId()}.
*
* @param distid
* to search
* @return the {@link DistributionSet} {@link Specification}
*/
public static Specification<JpaDistributionSet> byId(final Long distid) {
return (targetRoot, query, cb) -> {
final Predicate predicate = cb.equal(targetRoot.<Long> get(JpaDistributionSet_.id), distid);
targetRoot.fetch(JpaDistributionSet_.modules, JoinType.LEFT);
targetRoot.fetch(JpaDistributionSet_.tags, JoinType.LEFT);
targetRoot.fetch(JpaDistributionSet_.type, JoinType.LEFT);
query.distinct(true);
return predicate;
};
}
/**
* {@link Specification} for retrieving {@link DistributionSet} with given
* {@link DistributionSet#getId()}s.
*
* @param distids
* to search
* @return the {@link DistributionSet} {@link Specification}
*/
public static Specification<JpaDistributionSet> byIds(final Collection<Long> distids) {
return (targetRoot, query, cb) -> {
final Predicate predicate = targetRoot.<Long> get(JpaDistributionSet_.id).in(distids);
targetRoot.fetch(JpaDistributionSet_.modules, JoinType.LEFT);
targetRoot.fetch(JpaDistributionSet_.tags, JoinType.LEFT);
targetRoot.fetch(JpaDistributionSet_.type, JoinType.LEFT);
query.distinct(true);
return predicate;
};
}
/**
* {@link Specification} for retrieving {@link DistributionSet}s by
* "like name or like description or like version".
*
* @param subString
* to be filtered on
* @return the {@link DistributionSet} {@link Specification}
*/
public static Specification<JpaDistributionSet> likeNameOrDescriptionOrVersion(final String subString) {
return (targetRoot, query, cb) -> cb.or(
cb.like(cb.lower(targetRoot.<String> get(JpaDistributionSet_.name)), subString.toLowerCase()),
cb.like(cb.lower(targetRoot.<String> get(JpaDistributionSet_.version)), subString.toLowerCase()),
cb.like(cb.lower(targetRoot.<String> get(JpaDistributionSet_.description)), subString.toLowerCase()));
}
/**
* {@link Specification} for retrieving {@link DistributionSet}s by
* "has at least one of the given tag names".
*
* @param tagNames
* to be filtered on
* @param selectDSWithNoTag
* flag to select distribution sets with no tag
* @return the {@link DistributionSet} {@link Specification}
*/
public static Specification<JpaDistributionSet> hasTags(final Collection<String> tagNames,
final Boolean selectDSWithNoTag) {
return (targetRoot, query, cb) -> {
final SetJoin<JpaDistributionSet, JpaDistributionSetTag> tags = targetRoot.join(JpaDistributionSet_.tags,
JoinType.LEFT);
final Predicate predicate = getPredicate(tags, tagNames, selectDSWithNoTag, cb);
query.distinct(true);
return predicate;
};
}
private static Predicate getPredicate(final SetJoin<JpaDistributionSet, JpaDistributionSetTag> tags,
final Collection<String> tagNames, final Boolean selectDSWithNoTag, final CriteriaBuilder cb) {
tags.get(JpaDistributionSetTag_.name);
final Path<String> exp = tags.get(JpaDistributionSetTag_.name);
if (selectDSWithNoTag != null && selectDSWithNoTag) {
if (tagNames != null) {
return cb.or(exp.isNull(), exp.in(tagNames));
} else {
return exp.isNull();
}
} else {
return exp.in(tagNames);
}
}
/**
* returns query criteria {@link Specification} comparing case insensitive
* "NAME == AND VERSION ==".
*
* @param name
* to be filtered on
* @param version
* to be filtered on
* @return the {@link Specification}
*/
public static Specification<JpaDistributionSet> equalsNameAndVersionIgnoreCase(final String name,
final String version) {
return (targetRoot, query, cb) -> cb.and(
cb.equal(cb.lower(targetRoot.<String> get(JpaDistributionSet_.name)), name.toLowerCase()),
cb.equal(cb.lower(targetRoot.<String> get(JpaDistributionSet_.version)), version.toLowerCase()));
}
/**
* {@link Specification} for retrieving {@link DistributionSet} with given
* {@link DistributionSet#getType()}.
*
* @param type
* to search
* @return the {@link DistributionSet} {@link Specification}
*/
public static Specification<JpaDistributionSet> byType(final DistributionSetType type) {
return (targetRoot, query, cb) -> cb.equal(targetRoot.<JpaDistributionSetType> get(JpaDistributionSet_.type),
type);
}
/**
* @param installedTargetId
* the targetID which is installed to a distribution set to
* search for.
* @return the specification to search for a distribution set which is
* installed to the given targetId
*/
public static Specification<JpaDistributionSet> installedTarget(final String installedTargetId) {
return (dsRoot, query, cb) -> {
final ListJoin<JpaDistributionSet, JpaTargetInfo> installedTargetJoin = dsRoot
.join(JpaDistributionSet_.installedAtTargets, JoinType.INNER);
final Join<JpaTargetInfo, JpaTarget> targetJoin = installedTargetJoin.join(JpaTargetInfo_.target);
return cb.equal(targetJoin.get(JpaTarget_.controllerId), installedTargetId);
};
}
/**
* @param assignedTargetId
* the targetID which is assigned to a distribution set to search
* for.
* @return the specification to search for a distribution set which is
* assigned to the given targetId
*/
public static Specification<JpaDistributionSet> assignedTarget(final String assignedTargetId) {
return (dsRoot, query, cb) -> {
final ListJoin<JpaDistributionSet, JpaTarget> assignedTargetJoin = dsRoot
.join(JpaDistributionSet_.assignedToTargets, JoinType.INNER);
return cb.equal(assignedTargetJoin.get(JpaTarget_.controllerId), assignedTargetId);
};
}
}

View File

@@ -0,0 +1,79 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa.specifications;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetType;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetType_;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.springframework.data.jpa.domain.Specification;
/**
* Specifications class for {@link DistributionSetType}s. The class provides
* Spring Data JPQL Specifications.
*/
public final class DistributionSetTypeSpecification {
private DistributionSetTypeSpecification() {
// utility class
}
/**
* {@link Specification} for retrieving {@link DistributionSetType}s by its
* DELETED attribute.
*
* @param isDeleted
* TRUE/FALSE are compared to the attribute DELETED. If NULL the
* attribute is ignored
* @return the {@link DistributionSetType} {@link Specification}
*/
public static Specification<JpaDistributionSetType> isDeleted(final Boolean isDeleted) {
return (targetRoot, query, cb) -> cb.equal(targetRoot.<Boolean> get(JpaDistributionSetType_.deleted),
isDeleted);
}
/**
* {@link Specification} for retrieving {@link DistributionSetType} with
* given {@link DistributionSetType#getId()} including fetching the elements
* list.
*
* @param distid
* to search
* @return the {@link DistributionSet} {@link Specification}
*/
public static Specification<JpaDistributionSetType> byId(final Long distid) {
return (targetRoot, query, cb) -> cb.equal(targetRoot.<Long> get(JpaDistributionSetType_.id), distid);
}
/**
* {@link Specification} for retrieving {@link DistributionSetType} with
* given {@link DistributionSetType#getName())} including fetching the
* elements list.
*
* @param name
* to search
* @return the {@link DistributionSet} {@link Specification}
*/
public static Specification<JpaDistributionSetType> byName(final String name) {
return (targetRoot, query, cb) -> cb.equal(targetRoot.<String> get(JpaDistributionSetType_.name), name);
}
/**
* {@link Specification} for retrieving {@link DistributionSetType} with
* given {@link DistributionSetType#getKey()} including fetching the
* elements list.
*
* @param key
* to search
* @return the {@link DistributionSet} {@link Specification}
*/
public static Specification<JpaDistributionSetType> byKey(final String key) {
return (targetRoot, query, cb) -> cb.equal(targetRoot.<String> get(JpaDistributionSetType_.key), key);
}
}

View File

@@ -0,0 +1,82 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa.specifications;
import javax.persistence.criteria.Predicate;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModuleType;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule_;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.springframework.data.jpa.domain.Specification;
/**
* Specifications class for {@link SoftwareModule}s. The class provides Spring
* Data JPQL Specifications
*
*/
public final class SoftwareModuleSpecification {
private SoftwareModuleSpecification() {
// utility class
}
/**
* {@link Specification} for retrieving {@link SoftwareModule}s by its ID
* attribute.
*
* @param moduleId
* to search for
* @return the {@link SoftwareModule} {@link Specification}
*/
public static Specification<JpaSoftwareModule> byId(final Long moduleId) {
return (targetRoot, query, cb) -> {
final Predicate predicate = cb.equal(targetRoot.<Long> get(JpaSoftwareModule_.id), moduleId);
targetRoot.fetch(JpaSoftwareModule_.type);
return predicate;
};
}
/**
* {@link Specification} for retrieving {@link SoftwareModule}s where its
* DELETED attribute is false.
*
* @return the {@link SoftwareModule} {@link Specification}
*/
public static Specification<JpaSoftwareModule> isDeletedFalse() {
return (swRoot, query, cb) -> cb.equal(swRoot.<Boolean> get(JpaSoftwareModule_.deleted), Boolean.FALSE);
}
/**
* {@link Specification} for retrieving {@link SoftwareModule}s by
* "like name or like version".
*
* @param subString
* to be filtered on
* @return the {@link SoftwareModule} {@link Specification}
*/
public static Specification<JpaSoftwareModule> likeNameOrVersion(final String subString) {
return (targetRoot, query, cb) -> cb.or(
cb.like(cb.lower(targetRoot.<String> get(JpaSoftwareModule_.name)), subString.toLowerCase()),
cb.like(cb.lower(targetRoot.<String> get(JpaSoftwareModule_.version)), subString.toLowerCase()));
}
/**
* {@link Specification} for retrieving {@link SoftwareModule}s by
* "like name or like version".
*
* @param type
* to be filtered on
* @return the {@link SoftwareModule} {@link Specification}
*/
public static Specification<JpaSoftwareModule> equalType(final JpaSoftwareModuleType type) {
return (targetRoot, query, cb) -> cb.equal(targetRoot.<JpaSoftwareModuleType> get(JpaSoftwareModule_.type),
type);
}
}

View File

@@ -0,0 +1,46 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa.specifications;
import java.util.List;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.domain.Specifications;
/**
* Helper class to easily combine {@link Specification} instances.
*
*/
public final class SpecificationsBuilder {
private SpecificationsBuilder() {
}
/**
* Combine all given specification with and. The first specification is the
* where clause.
*
* @param specList
* all specification wich will combine
* @return <null> if the given specification list is empty
*/
public static <T> Specifications<T> combineWithAnd(final List<Specification<T>> specList) {
if (specList.isEmpty()) {
return null;
}
Specifications<T> specs = Specifications.where(specList.get(0));
specList.remove(0);
for (final Specification<T> specification : specList) {
specs = specs.and(specification);
}
return specs;
}
}

View File

@@ -0,0 +1,40 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa.specifications;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetFilterQuery;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetFilterQuery_;
import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
import org.springframework.data.jpa.domain.Specification;
/**
* Specifications class for {@link TargetFilterQuery}s. The class provides
* Spring Data JPQL Specifications.
*
*/
public final class TargetFilterQuerySpecification {
private TargetFilterQuerySpecification() {
// utility class
}
/**
* {@link Specification} for retrieving {@link JpaTargetFilterQuery}s based
* on is {@link JpaTargetFilterQuery#getName()}.
*
* @param searchText
* of the filter
* @return the {@link JpaTargetFilterQuery} {@link Specification}
*/
public static Specification<JpaTargetFilterQuery> likeName(final String searchText) {
return (targetFilterQueryRoot, query, cb) -> {
final String searchTextToLower = searchText.toLowerCase();
return cb.like(cb.lower(targetFilterQueryRoot.get(JpaTargetFilterQuery_.name)), searchTextToLower);
};
}
}

View File

@@ -0,0 +1,228 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.jpa.specifications;
import java.util.Collection;
import java.util.List;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.SetJoin;
import javax.validation.constraints.NotNull;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet_;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetInfo;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetInfo_;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetTag;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetTag_;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget_;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetInfo;
import org.eclipse.hawkbit.repository.model.TargetTag;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
import org.springframework.data.jpa.domain.Specification;
/**
* Specifications class for {@link Target}s. The class provides Spring Data JPQL
* Specifications.
*
*/
public final class TargetSpecifications {
private TargetSpecifications() {
// utility class
}
/**
* {@link Specification} for retrieving {@link Target}s including
* {@link TargetTag}s and {@link TargetStatus}.
*
* @param controllerIDs
* to search for
*
* @return the {@link Target} {@link Specification}
*/
public static Specification<JpaTarget> byControllerIdWithStatusAndTagsInJoin(
final Collection<String> controllerIDs) {
return (targetRoot, query, cb) -> {
final Predicate predicate = targetRoot.get(JpaTarget_.controllerId).in(controllerIDs);
targetRoot.fetch(JpaTarget_.tags, JoinType.LEFT);
query.distinct(true);
return predicate;
};
}
/**
* {@link Specification} for retrieving {@link Target}s including {
* {@link TargetStatus}.
*
* @param controllerIDs
* to search for
*
* @return the {@link Target} {@link Specification}
*/
public static Specification<JpaTarget> byControllerIdWithStatusAndAssignedInJoin(
final Collection<String> controllerIDs) {
return (targetRoot, query, cb) -> {
final Predicate predicate = targetRoot.get(JpaTarget_.controllerId).in(controllerIDs);
targetRoot.fetch(JpaTarget_.assignedDistributionSet);
return predicate;
};
}
/**
* {@link Specification} for retrieving {@link Target}s by "equal to given
* {@link TargetUpdateStatus}".
*
* @param updateStatus
* to be filtered on
* @param fetch
* {@code true} to fetch the {@link TargetInfo} otherwise only
* join it.
* @return the {@link Target} {@link Specification}
*/
public static Specification<JpaTarget> hasTargetUpdateStatus(final Collection<TargetUpdateStatus> updateStatus,
final boolean fetch) {
return (targetRoot, query, cb) -> {
if (!query.getResultType().isAssignableFrom(Long.class)) {
if (fetch) {
targetRoot.fetch(JpaTarget_.targetInfo);
} else {
targetRoot.join(JpaTarget_.targetInfo);
}
return targetRoot.get(JpaTarget_.targetInfo).get(JpaTargetInfo_.updateStatus).in(updateStatus);
}
final Join<JpaTarget, JpaTargetInfo> targetInfoJoin = targetRoot.join(JpaTarget_.targetInfo);
return targetInfoJoin.get(JpaTargetInfo_.updateStatus).in(updateStatus);
};
}
/**
* {@link Specification} for retrieving {@link Target}s by
* "like controllerId or like description or like ip address".
*
* @param searchText
* to be filtered on
* @return the {@link Target} {@link Specification}
*/
public static Specification<JpaTarget> likeNameOrDescriptionOrIp(final String searchText) {
return (targetRoot, query, cb) -> {
final String searchTextToLower = searchText.toLowerCase();
return cb.or(cb.like(cb.lower(targetRoot.get(JpaTarget_.name)), searchTextToLower),
cb.like(cb.lower(targetRoot.get(JpaTarget_.description)), searchTextToLower));
};
}
/**
* {@link Specification} for retrieving {@link Target}s by
* "like controllerId".
*
* @param distributionId
* to be filtered on
* @return the {@link Target} {@link Specification}
*/
public static Specification<JpaTarget> hasInstalledOrAssignedDistributionSet(@NotNull final Long distributionId) {
return (targetRoot, query, cb) -> {
final Join<JpaTarget, JpaTargetInfo> targetInfoJoin = targetRoot.join(JpaTarget_.targetInfo);
return cb.or(
cb.equal(targetInfoJoin.get(JpaTargetInfo_.installedDistributionSet).get(JpaDistributionSet_.id),
distributionId),
cb.equal(targetRoot.<JpaDistributionSet> get(JpaTarget_.assignedDistributionSet)
.get(JpaDistributionSet_.id), distributionId));
};
}
/**
* Finds all targets by given {@link Target#getControllerId()}s and which
* are not yet assigned to given {@link DistributionSet}.
*
* @param tIDs
* to search for.
* @param distributionId
* set that is not yet assigned
* @return the {@link Target} {@link Specification}
*/
public static Specification<JpaTarget> hasControllerIdAndAssignedDistributionSetIdNot(final List<String> tIDs,
@NotNull final Long distributionId) {
return (targetRoot, query, cb) -> cb.and(targetRoot.get(JpaTarget_.controllerId).in(tIDs),
cb.or(cb.notEqual(targetRoot.<JpaDistributionSet> get(JpaTarget_.assignedDistributionSet)
.get(JpaDistributionSet_.id), distributionId),
cb.isNull(targetRoot.<JpaDistributionSet> get(JpaTarget_.assignedDistributionSet))));
}
/**
* {@link Specification} for retrieving {@link Target}s by
* "has no tag names"or "has at least on of the given tag names".
*
* @param tagNames
* to be filtered on
* @param selectTargetWithNoTag
* flag to get targets with no tag assigned
* @return the {@link Target} {@link Specification}
*/
public static Specification<JpaTarget> hasTags(final String[] tagNames, final Boolean selectTargetWithNoTag) {
return (targetRoot, query, cb) -> {
final Predicate predicate = getPredicate(targetRoot, cb, selectTargetWithNoTag, tagNames);
query.distinct(true);
return predicate;
};
}
private static Predicate getPredicate(final Root<JpaTarget> targetRoot, final CriteriaBuilder cb,
final Boolean selectTargetWithNoTag, final String[] tagNames) {
final SetJoin<JpaTarget, JpaTargetTag> tags = targetRoot.join(JpaTarget_.tags, JoinType.LEFT);
final Path<String> exp = tags.get(JpaTargetTag_.name);
if (selectTargetWithNoTag) {
if (tagNames != null) {
return cb.or(exp.isNull(), exp.in(tagNames));
} else {
return exp.isNull();
}
} else {
return exp.in(tagNames);
}
}
/**
* {@link Specification} for retrieving {@link Target}s by assigned
* distribution set.
*
* @param distributionSetId
* the ID of the distribution set which must be assigned
* @return the {@link Target} {@link Specification}
*/
public static Specification<JpaTarget> hasAssignedDistributionSet(final Long distributionSetId) {
return (targetRoot, query, cb) -> cb.equal(
targetRoot.<JpaDistributionSet> get(JpaTarget_.assignedDistributionSet).get(JpaDistributionSet_.id),
distributionSetId);
}
/**
* {@link Specification} for retrieving {@link Target}s by assigned
* distribution set.
*
* @param distributionSetId
* the ID of the distribution set which must be assigned
* @return the {@link Target} {@link Specification}
*/
public static Specification<JpaTarget> hasInstalledDistributionSet(final Long distributionSetId) {
return (targetRoot, query, cb) -> {
final Join<JpaTarget, JpaTargetInfo> targetInfoJoin = targetRoot.join(JpaTarget_.targetInfo);
return cb.equal(targetInfoJoin.get(JpaTargetInfo_.installedDistributionSet).get(JpaDistributionSet_.id),
distributionSetId);
};
}
}

View File

@@ -0,0 +1,54 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.model.helper;
import org.eclipse.hawkbit.eventbus.EntityPropertyChangeListener;
import org.eclipse.hawkbit.executor.AfterTransactionCommitExecutor;
import org.springframework.beans.factory.annotation.Autowired;
/**
* A singleton bean which holds the {@link AfterTransactionCommitExecutor} to
* have to the cache manager in beans not instantiated by spring e.g. JPA
* entities or {@link EntityPropertyChangeListener} which cannot be autowired.
*
*/
public final class AfterTransactionCommitExecutorHolder {
private static final AfterTransactionCommitExecutorHolder SINGLETON = new AfterTransactionCommitExecutorHolder();
@Autowired
private AfterTransactionCommitExecutor afterCommit;
private AfterTransactionCommitExecutorHolder() {
}
/**
* @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

@@ -0,0 +1,58 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.model.helper;
import org.eclipse.hawkbit.eventbus.CacheFieldEntityListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.CacheManager;
/**
* A singleton bean which holds the {@link CacheManager} to have to the cache
* manager in beans not instantiated by spring e.g. JPA entities or
* {@link CacheFieldEntityListener} which cannot be autowired.
*
*
*
*/
public final class CacheManagerHolder {
private static final CacheManagerHolder SINGLETON = new CacheManagerHolder();
@Autowired
private CacheManager cacheManager;
private CacheManagerHolder() {
}
/**
* @return the cache manager holder singleton instance
*/
public static CacheManagerHolder getInstance() {
return SINGLETON;
}
/**
* @return the cacheManager
*/
public CacheManager getCacheManager() {
return cacheManager;
}
/**
* Normally not used when using spring-boot then the cachemanager is
* autowired to the CacheManagerHolder, but for testing purposes.
*
* @param cacheManager
* the cache manager to set for the cache manager holder.
*/
public void setCacheManager(final CacheManager cacheManager) {
this.cacheManager = cacheManager;
}
}

View File

@@ -0,0 +1,55 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.repository.model.helper;
import org.eclipse.hawkbit.eventbus.CacheFieldEntityListener;
import org.springframework.beans.factory.annotation.Autowired;
import com.google.common.eventbus.EventBus;
/**
* A singleton bean which holds the {@link EventBus} to have to the cache
* manager in beans not instantiated by spring e.g. JPA entities or
* {@link CacheFieldEntityListener} which cannot be autowired.
*
*/
public final class EventBusHolder {
private static final EventBusHolder SINGLETON = new EventBusHolder();
@Autowired
private EventBus eventBus;
private EventBusHolder() {
}
/**
* @return the cache manager holder singleton instance
*/
public static EventBusHolder getInstance() {
return SINGLETON;
}
/**
* @return the eventBus
*/
public EventBus getEventBus() {
return eventBus;
}
/**
* @param eventBus
* the eventBus to set
*/
public void setEventBus(final EventBus eventBus) {
this.eventBus = eventBus;
}
}

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