Merge remote-tracking branch 'origin/master' into

fix_reflect_tag_color_change_immediatly_after_popup_is_closed

Conflicts:
	hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/HawkbitEventProvider.java


Signed-off-by: Melanie Retter <melanie.retter@bosch-si.com>
This commit is contained in:
Melanie Retter
2016-05-11 11:13:16 +02:00
171 changed files with 2917 additions and 1432 deletions

0
3rd-dependencies/listDeps.sh Executable file → Normal file
View File

View File

@@ -52,19 +52,19 @@ $ java -jar ./examples/hawkbit-mgmt-api-client/target/hawkbit-mgmt-api-client-#v
* The master branch contains future development towards 0.2. We are currently focusing on:
* Rollout Management for large scale rollouts.
* Clustering capabilities for the update server.
* Upgrade of Spring Boot and Vaadin depedencies.
* Upgrade of Spring Boot and Vaadin dependencies.
* And of course tons of usability improvements and bug fixes.
# Modules
`hawkbit-core` : core elements.
`hawkbit-security-core` : core security elements.
`hawkbit-security-integration` : security integration elements to integrate security into hawkbit.
`hawkbit-artifact-repository-mongo` : artifact repository implementation to mongoDB.
`hawkbit-core` : internal interfaces and utility classes..
`hawkbit-security-core` : authentication and authorization.
`hawkbit-security-integration` : authentication and authorization integrated with the APIs.
`hawkbit-artifact-repository-mongo` : artifact repository implementation to MongoDB.
`hawkbit-autoconfigure` : spring-boot auto-configuration.
`hawkbit-dmf-api` : API for the Device Management Integration.
`hawkbit-dmf-amqp` : AMQP endpoint implementation for the DMF API.
`hawkbit-repository` : repository implemenation based on SQL for all meta-data.
`hawkbit-repository` : repository implementation based on SQL for all meta-data.
`hawkbit-http-security` : implementation for security filters for HTTP.
`hawkbit-rest-api` : API classes for the REST Management API.
`hawkbit-rest-resource` : HTTP REST endpoints for the Management and the Direct Device API.

View File

@@ -22,14 +22,17 @@ import org.springframework.context.annotation.Import;
@SpringBootApplication
@Import({ RepositoryApplicationConfiguration.class })
@EnableHawkbitManagedSecurityConfiguration
// Exception squid:S1118 - Spring boot standard behavior
@SuppressWarnings({ "squid:S1118" })
public class Start {
/**
* Main method to start the spring-boot application.
*
* @param args
* the VM arguments.
*/
// Exception squid:S2095 - Spring boot standard behavior
@SuppressWarnings({ "squid:S2095" })
public static void main(final String[] args) {
SpringApplication.run(Start.class, args);
}

View File

@@ -1,4 +1,3 @@
<?xml version="1.0"?>
<!--
Copyright (c) 2015 Bosch Software Innovations GmbH and others.
@@ -9,7 +8,8 @@
http://www.eclipse.org/legal/epl-v10.html
-->
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
<project
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
@@ -133,13 +133,17 @@
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-bom</artifactId>
<version>7.6.3</version>
<version>${vaadin.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>

View File

@@ -8,6 +8,8 @@
*/
package org.eclipse.hawkbit.simulator;
import org.eclipse.hawkbit.simulator.UpdateStatus.ResponseStatus;
/**
* The bean of a simulated device which can be stored in the
* {@link DeviceSimulatorRepository} or shown in the UI.
@@ -22,16 +24,15 @@ public abstract class AbstractSimulatedDevice {
private Status status;
private double progress;
private String swversion = "unknown";
private ResponseStatus responseStatus = ResponseStatus.SUCCESSFUL;
private UpdateStatus updateStatus = new UpdateStatus(ResponseStatus.SUCCESSFUL, "Simulation complete!");
private Protocol protocol = Protocol.DMF_AMQP;
private String targetSecurityToken;
private int nextPollCounterSec;
/**
* Enum definition of the protocol to be used for the simulated device.
*
* @author Michael Hirsch
*
*/
public enum Protocol {
/**
@@ -69,24 +70,6 @@ public abstract class AbstractSimulatedDevice {
ERROR;
}
/**
* The status to response to the hawkbit update server if an simulated
* update process should be respond with successful or failure update.
*
* @author Michael Hirsch
*
*/
public enum ResponseStatus {
/**
* updated has been successful and response the successful update.
*/
SUCCESSFUL,
/**
* updated has been not successful and response the error update.
*/
ERROR;
}
/**
* empty constructor.
*/
@@ -158,12 +141,12 @@ public abstract class AbstractSimulatedDevice {
this.swversion = swversion;
}
public ResponseStatus getResponseStatus() {
return responseStatus;
public UpdateStatus getUpdateStatus() {
return updateStatus;
}
public void setResponseStatus(final ResponseStatus responseStatus) {
this.responseStatus = responseStatus;
public void setUpdateStatus(final UpdateStatus updateStatus) {
this.updateStatus = updateStatus;
}
public Protocol getProtocol() {
@@ -177,4 +160,13 @@ public abstract class AbstractSimulatedDevice {
public void setNextPollCounterSec(final int nextPollDelayInSec) {
this.nextPollCounterSec = nextPollDelayInSec;
}
public String getTargetSecurityToken() {
return targetSecurityToken;
}
public void setTargetSecurityToken(final String targetSecurityToken) {
this.targetSecurityToken = targetSecurityToken;
}
}

View File

@@ -8,8 +8,6 @@
*/
package org.eclipse.hawkbit.simulator;
import java.util.concurrent.ScheduledExecutorService;
import org.eclipse.hawkbit.simulator.http.ControllerResource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -18,7 +16,7 @@ import com.jayway.jsonpath.JsonPath;
import com.jayway.jsonpath.PathNotFoundException;
/**
* @author Michael Hirsch
* A simulated device using the DDI API of the hawkBit update server.
*
*/
public class DDISimulatedDevice extends AbstractSimulatedDevice {
@@ -26,12 +24,12 @@ public class DDISimulatedDevice extends AbstractSimulatedDevice {
private static final Logger LOGGER = LoggerFactory.getLogger(DDISimulatedDevice.class);
private final int pollDelaySec;
private final ScheduledExecutorService pollthreadpool;
private final ControllerResource controllerResource;
private final DeviceSimulatorUpdater deviceUpdater;
private volatile boolean removed;
private volatile Long currentActionId;
private final DeviceSimulatorUpdater deviceUpdater;
/**
* @param id
@@ -42,18 +40,14 @@ public class DDISimulatedDevice extends AbstractSimulatedDevice {
* the delay of the poll interval in sec
* @param controllerResource
* the http controller resource
* @param pollthreadpool
* the threadpool for polling endpoint
* @param deviceUpdater
* the service to update devices
*/
public DDISimulatedDevice(final String id, final String tenant, final int pollDelaySec,
final ControllerResource controllerResource, final ScheduledExecutorService pollthreadpool,
final DeviceSimulatorUpdater deviceUpdater) {
final ControllerResource controllerResource, final DeviceSimulatorUpdater deviceUpdater) {
super(id, tenant, Protocol.DDI_HTTP);
this.pollDelaySec = pollDelaySec;
this.controllerResource = controllerResource;
this.pollthreadpool = pollthreadpool;
this.deviceUpdater = deviceUpdater;
setNextPollCounterSec(pollDelaySec);
}
@@ -76,27 +70,12 @@ public class DDISimulatedDevice extends AbstractSimulatedDevice {
final String basePollJson = controllerResource.get(getTenant(), getId());
try {
final String href = JsonPath.parse(basePollJson).read("_links.deploymentBase.href");
final long actionId = Long.parseLong(href.substring(href.lastIndexOf("/") + 1, href.indexOf("?")));
final long actionId = Long.parseLong(href.substring(href.lastIndexOf('/') + 1, href.indexOf('?')));
if (currentActionId == null) {
final String deploymentJson = controllerResource.getDeployment(getTenant(), getId(), actionId);
final String swVersion = JsonPath.parse(deploymentJson).read("deployment.chunks[0].version");
currentActionId = actionId;
deviceUpdater.startUpdate(getTenant(), getId(), actionId, swVersion, (device, actionId1) -> {
switch (device.getResponseStatus()) {
case SUCCESSFUL:
controllerResource.postSuccessFeedback(getTenant(), getId(),
actionId1);
break;
case ERROR:
controllerResource.postErrorFeedback(getTenant(), getId(),
actionId1);
break;
default:
throw new IllegalStateException(
"simulated device has an unknown response status + " + device.getResponseStatus());
}
currentActionId = null;
});
startDdiUpdate(actionId, swVersion);
}
} catch (final PathNotFoundException e) {
// href might not be in the json response, so ignore
@@ -106,4 +85,21 @@ public class DDISimulatedDevice extends AbstractSimulatedDevice {
}
}
private void startDdiUpdate(final long actionId, final String swVersion) {
deviceUpdater.startUpdate(getTenant(), getId(), actionId, swVersion, null, null, (device, actionId1) -> {
switch (device.getUpdateStatus().getResponseStatus()) {
case SUCCESSFUL:
controllerResource.postSuccessFeedback(getTenant(), getId(), actionId1);
break;
case ERROR:
controllerResource.postErrorFeedback(getTenant(), getId(), actionId1);
break;
default:
throw new IllegalStateException("simulated device has an unknown response status + "
+ device.getUpdateStatus().getResponseStatus());
}
currentActionId = null;
});
}
}

View File

@@ -9,10 +9,7 @@
package org.eclipse.hawkbit.simulator;
/**
* An simulated device using the DMF API of the hawkbit update server.
*
* @author Michael Hirsch
*
* A simulated device using the DMF API of the hawkBit update server.
*/
public class DMFSimulatedDevice extends AbstractSimulatedDevice {

View File

@@ -21,8 +21,6 @@ import com.vaadin.spring.annotation.EnableVaadin;
/**
* The main-method to start the Spring-Boot application.
*
*
*
*/
@SpringBootApplication
@EnableVaadin
@@ -46,6 +44,8 @@ public class DeviceSimulator {
* @param args
* the args
*/
// Exception squid:S2095 - Spring boot standard behavior
@SuppressWarnings({ "squid:S2095" })
public static void main(final String[] args) {
SpringApplication.run(DeviceSimulator.class, args);
}

View File

@@ -8,27 +8,56 @@
*/
package org.eclipse.hawkbit.simulator;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.DigestOutputStream;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContextBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.eclipse.hawkbit.dmf.json.model.Artifact;
import org.eclipse.hawkbit.dmf.json.model.SoftwareModule;
import org.eclipse.hawkbit.simulator.AbstractSimulatedDevice.Protocol;
import org.eclipse.hawkbit.simulator.UpdateStatus.ResponseStatus;
import org.eclipse.hawkbit.simulator.amqp.SpSenderService;
import org.eclipse.hawkbit.simulator.event.InitUpdate;
import org.eclipse.hawkbit.simulator.event.ProgressUpdate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import com.google.common.eventbus.EventBus;
import com.google.common.io.BaseEncoding;
import com.google.common.io.ByteStreams;
/**
* @author Michael Hirsch
* Update simulation handler.
*
*/
@Service
public class DeviceSimulatorUpdater {
private static final Logger LOGGER = LoggerFactory.getLogger(DeviceSimulatorUpdater.class);
private static final ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(4);
@@ -54,14 +83,18 @@ public class DeviceSimulatorUpdater {
* @param actionId
* the actionId from the hawkbit update server to start the
* update.
* @param swVersion
* @param modules
* the software module version from the hawkbit update server
* @param swVersion
* the software version as static value in case modules is null
* @param targetSecurityToken
* the target security token for download authentication
* @param callback
* the callback which gets called when the simulated update
* process has been finished
*/
public void startUpdate(final String tenant, final String id, final long actionId, final String swVersion,
final UpdaterCallback callback) {
final List<SoftwareModule> modules, final String targetSecurityToken, final UpdaterCallback callback) {
AbstractSimulatedDevice device = repository.get(tenant, id);
// plug and play - non existing device will be auto created
@@ -70,14 +103,26 @@ public class DeviceSimulatorUpdater {
}
device.setProgress(0.0);
device.setSwversion(swVersion);
if (modules == null || modules.isEmpty()) {
device.setSwversion(swVersion);
} else {
device.setSwversion(modules.stream().map(sm -> sm.getModuleVersion()).collect(Collectors.joining(", ")));
}
device.setTargetSecurityToken(targetSecurityToken);
eventbus.post(new InitUpdate(device));
threadPool.schedule(new DeviceSimulatorUpdateThread(device, spSenderService, actionId, eventbus, callback),
2_000, TimeUnit.MILLISECONDS);
threadPool.schedule(
new DeviceSimulatorUpdateThread(device, spSenderService, actionId, eventbus, callback, modules), 2_000,
TimeUnit.MILLISECONDS);
}
private static final class DeviceSimulatorUpdateThread implements Runnable {
/**
*
*/
private static final int MINIMUM_TOKENLENGTH_FOR_HINT = 6;
private static final Random rndSleep = new SecureRandom();
private final AbstractSimulatedDevice device;
@@ -85,38 +130,205 @@ public class DeviceSimulatorUpdater {
private final long actionId;
private final EventBus eventbus;
private final UpdaterCallback callback;
private final List<SoftwareModule> modules;
private DeviceSimulatorUpdateThread(final AbstractSimulatedDevice device, final SpSenderService spSenderService,
final long actionId, final EventBus eventbus, final UpdaterCallback callback) {
final long actionId, final EventBus eventbus, final UpdaterCallback callback,
final List<SoftwareModule> modules) {
this.device = device;
this.spSenderService = spSenderService;
this.actionId = actionId;
this.eventbus = eventbus;
this.callback = callback;
this.modules = modules;
}
@Override
public void run() {
if (device.getProgress() <= 0 && modules != null) {
device.setUpdateStatus(simulateDownloads(device.getTargetSecurityToken()));
if (isErrorResponse(device.getUpdateStatus())) {
callback.updateFinished(device, actionId);
eventbus.post(new ProgressUpdate(device));
return;
}
}
final double newProgress = device.getProgress() + 0.2;
device.setProgress(newProgress);
if (newProgress < 1.0) {
threadPool.schedule(
new DeviceSimulatorUpdateThread(device, spSenderService, actionId, eventbus, callback),
new DeviceSimulatorUpdateThread(device, spSenderService, actionId, eventbus, callback, modules),
rndSleep.nextInt(5_000), TimeUnit.MILLISECONDS);
} else {
callback.updateFinished(device, actionId);
}
eventbus.post(new ProgressUpdate(device));
}
private UpdateStatus simulateDownloads(final String targetToken) {
final List<UpdateStatus> status = new ArrayList<>();
LOGGER.info("Simulate downloads for {}", device.getId());
modules.forEach(module -> module.getArtifacts()
.forEach(artifact -> handleArtifacts(targetToken, status, artifact)));
final UpdateStatus result = new UpdateStatus(ResponseStatus.SUCCESSFUL);
result.getStatusMessages().add("Simulation complete!");
status.forEach(download -> {
result.getStatusMessages().addAll(download.getStatusMessages());
if (isErrorResponse(download)) {
result.setResponseStatus(ResponseStatus.ERROR);
}
});
LOGGER.info("Download simulations complete for {}", device.getId());
return result;
}
private boolean isErrorResponse(final UpdateStatus status) {
if (status == null) {
return false;
}
return ResponseStatus.ERROR.equals(status.getResponseStatus());
}
private static void handleArtifacts(final String targetToken, final List<UpdateStatus> status,
final Artifact artifact) {
artifact.getUrls().entrySet().forEach(entry -> {
switch (entry.getKey()) {
case HTTP:
case HTTPS:
status.add(downloadUrl(entry.getValue(), targetToken, artifact.getHashes().getSha1(),
artifact.getSize()));
break;
default:
// not supported yet
break;
}
});
}
private static UpdateStatus downloadUrl(final String url, final String targetToken, final String sha1Hash,
final long size) {
LOGGER.debug("Downloading {} with token {}, expected sha1 hash {} and size {}", url,
hideTokenDetails(targetToken), sha1Hash, size);
long overallread = 0;
try {
final CloseableHttpClient httpclient = createHttpClientThatAcceptsAllServerCerts();
final HttpGet request = new HttpGet(url);
request.addHeader(HttpHeaders.AUTHORIZATION, "TargetToken " + targetToken);
final String sha1HashResult;
try (final CloseableHttpResponse response = httpclient.execute(request)) {
if (response.getStatusLine().getStatusCode() != HttpStatus.OK.value()) {
final String message = wrongStatusCode(url, response);
return new UpdateStatus(ResponseStatus.ERROR, message);
}
if (response.getEntity().getContentLength() != size) {
final String message = wrongContentLength(url, size, response);
return new UpdateStatus(ResponseStatus.ERROR, message);
}
final File tempFile = File.createTempFile("uploadFile", null);
final MessageDigest md = MessageDigest.getInstance("SHA-1");
try (final DigestOutputStream dos = new DigestOutputStream(new FileOutputStream(tempFile), md)) {
try (final BufferedOutputStream bdos = new BufferedOutputStream(dos)) {
try (BufferedInputStream bis = new BufferedInputStream(response.getEntity().getContent())) {
overallread = ByteStreams.copy(bis, bdos);
}
}
} finally {
if (tempFile != null && !tempFile.delete()) {
LOGGER.error("Could not delete temporary file: {}", tempFile);
}
}
if (overallread != size) {
final String message = incompleteRead(url, size, overallread);
return new UpdateStatus(ResponseStatus.ERROR, message);
}
sha1HashResult = BaseEncoding.base16().lowerCase().encode(md.digest());
}
if (!sha1Hash.equalsIgnoreCase(sha1HashResult)) {
final String message = wrongHash(url, sha1Hash, overallread, sha1HashResult);
return new UpdateStatus(ResponseStatus.ERROR, message);
}
} catch (IOException | KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
LOGGER.error("Failed to download {} with {}", url, e.getMessage());
return new UpdateStatus(ResponseStatus.ERROR, "Failed to download " + url + ": " + e.getMessage());
}
final String message = "Downloaded " + url + " (" + overallread + " bytes)";
LOGGER.debug(message);
return new UpdateStatus(ResponseStatus.SUCCESSFUL, message);
}
private static String hideTokenDetails(final String targetToken) {
if (targetToken.isEmpty()) {
return "<EMTPTY!>";
}
if (targetToken.length() <= MINIMUM_TOKENLENGTH_FOR_HINT) {
return "***";
}
return targetToken.substring(0, 2) + "***"
+ targetToken.substring(targetToken.length() - 2, targetToken.length());
}
private static String wrongHash(final String url, final String sha1Hash, final long overallread,
final String sha1HashResult) {
final String message = "Download " + url + " failed with SHA1 hash missmatch (Expected: " + sha1Hash
+ " but got: " + sha1HashResult + ") (" + overallread + " bytes)";
LOGGER.error(message);
return message;
}
private static String incompleteRead(final String url, final long size, final long overallread) {
final String message = "Download " + url + " is incomplete (Expected: " + size + " but got: " + overallread
+ ")";
LOGGER.error(message);
return message;
}
private static String wrongContentLength(final String url, final long size,
final CloseableHttpResponse response) {
final String message = "Download " + url + " has wrong content length (Expected: " + size + " but got: "
+ response.getEntity().getContentLength() + ")";
LOGGER.error(message);
return message;
}
private static String wrongStatusCode(final String url, final CloseableHttpResponse response) {
final String message = "Download " + url + " failed (" + response.getStatusLine().getStatusCode() + ")";
LOGGER.error(message);
return message;
}
private static CloseableHttpClient createHttpClientThatAcceptsAllServerCerts()
throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
final SSLContextBuilder builder = new SSLContextBuilder();
builder.loadTrustMaterial(null, (chain, authType) -> true);
final SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(builder.build());
return HttpClients.custom().setSSLSocketFactory(sslsf).build();
}
}
/**
* Callback interface which is called when the simulated update process has
* been finished and the caller of starting the simulated update process can
* send the result to the hawkbit update server back.
*
* @author Michael Hirsch
*
* send the result back to the hawkBit update server.
*/
@FunctionalInterface
public interface UpdaterCallback {

View File

@@ -26,9 +26,6 @@ import com.google.common.eventbus.EventBus;
/**
* Poll time trigger which executes the {@link DDISimulatedDevice#poll()} every
* second.
*
* @author Michael Hirsch
*
*/
@Component
public class NextPollTimeController {
@@ -59,16 +56,15 @@ public class NextPollTimeController {
devices.forEach(device -> {
int nextCounter = device.getNextPollCounterSec() - 1;
if (nextCounter < 0) {
if (device instanceof DDISimulatedDevice) {
try {
pollService.submit(() -> ((DDISimulatedDevice) device).poll());
} catch (final IllegalStateException e) {
LOGGER.trace("Device could not be polled", e);
}
nextCounter = ((DDISimulatedDevice) device).getPollDelaySec();
if (nextCounter < 0 && device instanceof DDISimulatedDevice) {
try {
pollService.submit(() -> ((DDISimulatedDevice) device).poll());
} catch (final IllegalStateException e) {
LOGGER.trace("Device could not be polled", e);
}
nextCounter = ((DDISimulatedDevice) device).getPollDelaySec();
}
device.setNextPollCounterSec(nextCounter);
});
eventBus.post(new NextPollCounterUpdate(devices));

View File

@@ -9,8 +9,6 @@
package org.eclipse.hawkbit.simulator;
import java.net.URL;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import org.eclipse.hawkbit.simulator.AbstractSimulatedDevice.Protocol;
import org.eclipse.hawkbit.simulator.http.ControllerResource;
@@ -24,15 +22,9 @@ import feign.Logger;
/**
* The simulated device factory to create either {@link DMFSimulatedDevice} or
* {@link DDISimulatedDevice#}.
*
* @author Michael Hirsch
*
*/
@Service
public class SimulatedDeviceFactory {
private static final ScheduledExecutorService pollThreadPool = Executors.newScheduledThreadPool(4);
@Autowired
private DeviceSimulatorUpdater deviceUpdater;
@@ -47,7 +39,8 @@ public class SimulatedDeviceFactory {
* the protocol of the device
* @return the created simulated device
*/
public AbstractSimulatedDevice createSimulatedDevice(final String id, final String tenant, final Protocol protocol) {
public AbstractSimulatedDevice createSimulatedDevice(final String id, final String tenant,
final Protocol protocol) {
return createSimulatedDevice(id, tenant, protocol, 30, null, null);
}
@@ -80,7 +73,7 @@ public class SimulatedDeviceFactory {
final ControllerResource controllerResource = Feign.builder().logger(new Logger.ErrorLogger())
.requestInterceptor(new GatewayTokenInterceptor(gatewayToken)).logLevel(Logger.Level.BASIC)
.target(ControllerResource.class, baseEndpoint.toString());
return new DDISimulatedDevice(id, tenant, pollDelaySec, controllerResource, pollThreadPool, deviceUpdater);
return new DDISimulatedDevice(id, tenant, pollDelaySec, controllerResource, deviceUpdater);
default:
throw new IllegalArgumentException("Protocol " + protocol + " unknown");
}

View File

@@ -86,7 +86,10 @@ public class SimulationController {
final String deviceId = name + i;
repository.add(deviceFactory.createSimulatedDevice(deviceId, tenant, protocol, pollDelay, new URL(endpoint),
gatewayToken));
spSenderService.createOrUpdateThing(tenant, deviceId);
if (protocol == Protocol.DMF_AMQP) {
spSenderService.createOrUpdateThing(tenant, deviceId);
}
}
return ResponseEntity.ok("Updated " + amount + " DMF connected targets!");

View File

@@ -11,10 +11,12 @@ package org.eclipse.hawkbit.simulator;
import java.net.MalformedURLException;
import java.net.URL;
import org.eclipse.hawkbit.simulator.AbstractSimulatedDevice.Protocol;
import org.eclipse.hawkbit.simulator.amqp.SpSenderService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;
@@ -24,6 +26,7 @@ import org.springframework.stereotype.Component;
*
*/
@Component
@ConditionalOnProperty(prefix = "hawkbit.device.simulator", name = "autostart", matchIfMissing = true)
public class SimulatorStartup implements ApplicationListener<ContextRefreshedEvent> {
private static final Logger LOGGER = LoggerFactory.getLogger(SimulatorStartup.class);
@@ -52,7 +55,9 @@ public class SimulatorStartup implements ApplicationListener<ContextRefreshedEve
LOGGER.error("Creation of simulated device at startup failed.", e);
}
spSenderService.createOrUpdateThing(autostart.getTenant(), deviceId);
if (autostart.getApi() == Protocol.DMF_AMQP) {
spSenderService.createOrUpdateThing(autostart.getTenant(), deviceId);
}
}
});
}

View File

@@ -0,0 +1,78 @@
/**
* 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.simulator;
import java.util.ArrayList;
import java.util.List;
/**
* Update status of the simulated update.
*
*/
public class UpdateStatus {
private ResponseStatus responseStatus;
private List<String> statusMessages;
/**
* Constructor.
*
* @param responseStatus
* of the update
*/
public UpdateStatus(final ResponseStatus responseStatus) {
this.responseStatus = responseStatus;
}
/**
* Constructor including status message.
*
* @param responseStatus
* of the update
* @param message
* of the update status
*/
public UpdateStatus(final ResponseStatus responseStatus, final String message) {
this(responseStatus);
statusMessages = new ArrayList<>();
statusMessages.add(message);
}
public ResponseStatus getResponseStatus() {
return responseStatus;
}
public void setResponseStatus(final ResponseStatus responseStatus) {
this.responseStatus = responseStatus;
}
public List<String> getStatusMessages() {
if (statusMessages == null) {
statusMessages = new ArrayList<>();
}
return statusMessages;
}
/**
* The status to response to the hawkBit update server if an simulated
* update process should be respond with successful or failure update.
*/
public enum ResponseStatus {
/**
* Update has been successful and response the successful update.
*/
SUCCESSFUL,
/**
* Update has been not successful and response the error update.
*/
ERROR;
}
}

View File

@@ -39,6 +39,12 @@ public class AmqpProperties {
*/
private String deadLetterExchange = "simulator.deadletter";
/**
* Message time to live (ttl) for the deadletter queue. Default ttl is 1
* hour.
*/
private int deadLetterTtl = 60_000;
public String getReceiverConnectorQueueFromSp() {
return receiverConnectorQueueFromSp;
}
@@ -70,4 +76,12 @@ public class AmqpProperties {
public void setSenderForSpExchange(final String senderForSpExchange) {
this.senderForSpExchange = senderForSpExchange;
}
public int getDeadLetterTtl() {
return deadLetterTtl;
}
public void setDeadLetterTtl(final int deadLetterTtl) {
this.deadLetterTtl = deadLetterTtl;
}
}

View File

@@ -25,6 +25,8 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.stereotype.Component;
import com.google.common.collect.Lists;
/**
* Handle all incoming Messages from hawkBit update server.
*
@@ -88,6 +90,8 @@ public class SpReceiverService extends ReceiverService {
if (eventHeader == null) {
logAndThrowMessageError(message, "Event Topic is not set");
}
// Exception squid:S2259 - Checked before
@SuppressWarnings({ "squid:S2259" })
final EventTopic eventTopic = EventTopic.valueOf(eventHeader.toString());
switch (eventTopic) {
case DOWNLOAD_AND_INSTALL:
@@ -109,7 +113,7 @@ public class SpReceiverService extends ReceiverService {
final Long actionId = convertMessage(message, Long.class);
final SimulatedUpdate update = new SimulatedUpdate(tenant, thingId, actionId);
spSenderService.finishUpdateProcess(update, "Simulation canceled");
spSenderService.finishUpdateProcess(update, Lists.newArrayList("Simulation canceled"));
}
private void handleUpdateProcess(final Message message, final String thingId) {
@@ -120,19 +124,20 @@ public class SpReceiverService extends ReceiverService {
final DownloadAndUpdateRequest downloadAndUpdateRequest = convertMessage(message,
DownloadAndUpdateRequest.class);
final Long actionId = downloadAndUpdateRequest.getActionId();
final String targetSecurityToken = downloadAndUpdateRequest.getTargetSecurityToken();
deviceUpdater.startUpdate(tenant, thingId, actionId,
downloadAndUpdateRequest.getSoftwareModules().get(0).getModuleVersion(), (device, actionId1) -> {
switch (device.getResponseStatus()) {
deviceUpdater.startUpdate(tenant, thingId, actionId, null, downloadAndUpdateRequest.getSoftwareModules(),
targetSecurityToken, (device, actionId1) -> {
switch (device.getUpdateStatus().getResponseStatus()) {
case SUCCESSFUL:
spSenderService.finishUpdateProcess(
new SimulatedUpdate(device.getTenant(), device.getId(), actionId1),
"Simulation complete!");
device.getUpdateStatus().getStatusMessages());
break;
case ERROR:
spSenderService.finishUpdateProcessWithError(
new SimulatedUpdate(device.getTenant(), device.getId(), actionId1),
"Simulation complete with error!");
device.getUpdateStatus().getStatusMessages());
break;
default:
break;

View File

@@ -8,6 +8,7 @@
*/
package org.eclipse.hawkbit.simulator.amqp;
import java.util.List;
import java.util.Map;
import org.eclipse.hawkbit.dmf.amqp.api.AmqpSettings;
@@ -23,13 +24,9 @@ import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
/**
* Sender service to send message to SP.
*
*
*
* Sender service to send messages to update server.
*/
@Service
public class SpSenderService extends SenderService {
@@ -59,8 +56,9 @@ public class SpSenderService extends SenderService {
* @param description
* a description according the update process
*/
public void finishUpdateProcess(final SimulatedUpdate update, final String description) {
final Message updateResultMessage = createUpdateResultMessage(update, ActionStatus.FINISHED, description);
public void finishUpdateProcess(final SimulatedUpdate update, final List<String> updateResultMessages) {
final Message updateResultMessage = createUpdateResultMessage(update, ActionStatus.FINISHED,
updateResultMessages);
sendMessage(spExchange, updateResultMessage);
}
@@ -72,9 +70,9 @@ public class SpSenderService extends SenderService {
* @param messageDescription
* a description according the update process
*/
public void finishUpdateProcessWithError(final SimulatedUpdate update, final String messageDescription) {
sendErrorgMessage(update, messageDescription);
LOGGER.debug("Update process finished with error \"{}\" reported by thing {}", messageDescription,
public void finishUpdateProcessWithError(final SimulatedUpdate update, final List<String> updateResultMessages) {
sendErrorgMessage(update, updateResultMessages);
LOGGER.debug("Update process finished with error \"{}\" reported by thing {}", updateResultMessages,
update.getThingId());
}
@@ -88,8 +86,8 @@ public class SpSenderService extends SenderService {
* @param actionId
* the ID of the action for the error message
*/
public void sendErrorMessage(final String tenant, final String messageDescription, final Long actionId) {
final Message message = createActionStatusMessage(tenant, ActionStatus.ERROR, messageDescription, actionId);
public void sendErrorMessage(final String tenant, final List<String> updateResultMessages, final Long actionId) {
final Message message = createActionStatusMessage(tenant, ActionStatus.ERROR, updateResultMessages, actionId);
sendMessage(spExchange, message);
}
@@ -101,8 +99,8 @@ public class SpSenderService extends SenderService {
* @param warningMessage
* a warning description
*/
public void sendWarningMessage(final SimulatedUpdate update, final String warningMessage) {
final Message message = createActionStatusMessage(update, warningMessage, ActionStatus.WARNING);
public void sendWarningMessage(final SimulatedUpdate update, final List<String> updateResultMessages) {
final Message message = createActionStatusMessage(update, updateResultMessages, ActionStatus.WARNING);
sendMessage(spExchange, message);
}
@@ -119,8 +117,8 @@ public class SpSenderService extends SenderService {
* the cached value
*/
public void sendActionStatusMessage(final String tenant, final ActionStatus actionStatus,
final String actionMessage, final Long actionId) {
final Message message = createActionStatusMessage(tenant, actionStatus, actionMessage, actionId);
final List<String> updateResultMessages, final Long actionId) {
final Message message = createActionStatusMessage(tenant, actionStatus, updateResultMessages, actionId);
sendMessage(message);
}
@@ -162,11 +160,11 @@ public class SpSenderService extends SenderService {
*
* @param context
* the current context
* @param messageDescription
* a description according the update process
* @param updateResultMessages
* a list of descriptions according the update process
*/
private void sendErrorgMessage(final SimulatedUpdate update, final String messageDescription) {
final Message message = createActionStatusMessage(update, messageDescription, ActionStatus.ERROR);
private void sendErrorgMessage(final SimulatedUpdate update, final List<String> updateResultMessages) {
final Message message = createActionStatusMessage(update, updateResultMessages, ActionStatus.ERROR);
sendMessage(spExchange, message);
}
@@ -183,7 +181,7 @@ public class SpSenderService extends SenderService {
* the cacheValue value
*/
private Message createActionStatusMessage(final String tenant, final ActionStatus actionStatus,
final String actionMessage, final Long actionId) {
final List<String> updateResultMessages, final Long actionId) {
final MessageProperties messageProperties = new MessageProperties();
final Map<String, Object> headers = messageProperties.getHeaders();
final ActionUpdateStatus actionUpdateStatus = new ActionUpdateStatus();
@@ -192,15 +190,14 @@ public class SpSenderService extends SenderService {
headers.put(MessageHeaderKey.TENANT, tenant);
headers.put(MessageHeaderKey.TOPIC, EventTopic.UPDATE_ACTION_STATUS.name());
headers.put(MessageHeaderKey.CONTENT_TYPE, MessageProperties.CONTENT_TYPE_JSON);
if (!StringUtils.isEmpty(actionMessage)) {
actionUpdateStatus.getMessage().add(actionMessage);
}
actionUpdateStatus.getMessage().addAll(updateResultMessages);
actionUpdateStatus.setActionId(actionId);
return convertMessage(actionUpdateStatus, messageProperties);
}
private Message createUpdateResultMessage(final SimulatedUpdate cacheValue, final ActionStatus actionStatus,
final String updateResultMessage) {
final List<String> updateResultMessages) {
final MessageProperties messageProperties = new MessageProperties();
final Map<String, Object> headers = messageProperties.getHeaders();
final ActionUpdateStatus actionUpdateStatus = new ActionUpdateStatus();
@@ -209,14 +206,14 @@ public class SpSenderService extends SenderService {
headers.put(MessageHeaderKey.TENANT, cacheValue.getTenant());
headers.put(MessageHeaderKey.TOPIC, EventTopic.UPDATE_ACTION_STATUS.name());
headers.put(MessageHeaderKey.CONTENT_TYPE, MessageProperties.CONTENT_TYPE_JSON);
actionUpdateStatus.getMessage().add(updateResultMessage);
actionUpdateStatus.getMessage().addAll(updateResultMessages);
actionUpdateStatus.setActionId(cacheValue.getActionId());
return convertMessage(actionUpdateStatus, messageProperties);
}
private Message createActionStatusMessage(final SimulatedUpdate update, final String messageDescription,
private Message createActionStatusMessage(final SimulatedUpdate update, final List<String> updateResultMessages,
final ActionStatus status) {
return createActionStatusMessage(update.getTenant(), status, messageDescription, update.getActionId());
return createActionStatusMessage(update.getTenant(), status, updateResultMessages, update.getActionId());
}
}

View File

@@ -13,8 +13,6 @@ import org.eclipse.hawkbit.simulator.AbstractSimulatedDevice;
/**
* Event definition object which is published if the simulated device updated
* its update progress.
*
* @author Michael Hirsch
*
*/
public class ProgressUpdate {

View File

@@ -8,21 +8,19 @@
*/
package org.eclipse.hawkbit.simulator.ui;
import java.net.URL;
import java.util.List;
import java.util.Locale;
import org.eclipse.hawkbit.simulator.AbstractSimulatedDevice;
import org.eclipse.hawkbit.simulator.AbstractSimulatedDevice.Protocol;
import org.eclipse.hawkbit.simulator.AbstractSimulatedDevice.ResponseStatus;
import org.eclipse.hawkbit.simulator.AbstractSimulatedDevice.Status;
import org.eclipse.hawkbit.simulator.DeviceSimulatorRepository;
import org.eclipse.hawkbit.simulator.SimulatedDeviceFactory;
import org.eclipse.hawkbit.simulator.UpdateStatus.ResponseStatus;
import org.eclipse.hawkbit.simulator.amqp.SpSenderService;
import org.eclipse.hawkbit.simulator.event.InitUpdate;
import org.eclipse.hawkbit.simulator.event.NextPollCounterUpdate;
import org.eclipse.hawkbit.simulator.event.ProgressUpdate;
import org.eclipse.hawkbit.simulator.ui.GenerateDialog.GenerateDialogCallback;
import org.springframework.beans.factory.annotation.Autowired;
import com.google.common.collect.Lists;
@@ -52,13 +50,27 @@ import com.vaadin.ui.renderers.ProgressBarRenderer;
* Vaadin view which allows to generate devices through the DMF API and show the
* current simulated devices in a grid with their current status and update
* progress.
*
* @author Michael Hirsch
*
*/
@SpringView(name = "")
public class SimulatorView extends VerticalLayout implements View {
private static final String NEXT_POLL_COUNTER_SEC_COL = "nextPollCounterSec";
private static final String RESPONSE_STATUS_COL = "responseStatus";
private static final String PROTOCOL_COL = "protocol";
private static final String TENANT_COL = "tenant";
private static final String PROGRESS_COL = "progress";
private static final String SWVERSION_COL = "swversion";
private static final String STATUS_COL = "status";
private static final String ID_COL = "id";
private static final long serialVersionUID = 1L;
@Autowired
@@ -79,6 +91,7 @@ public class SimulatorView extends VerticalLayout implements View {
private BeanContainer<String, AbstractSimulatedDevice> beanContainer;
@SuppressWarnings("unchecked")
@Override
public void enter(final ViewChangeEvent event) {
eventbus.register(this);
@@ -91,7 +104,7 @@ public class SimulatorView extends VerticalLayout implements View {
createToolbar();
beanContainer = new BeanContainer<>(AbstractSimulatedDevice.class);
beanContainer.setBeanIdProperty("id");
beanContainer.setBeanIdProperty(ID_COL);
grid.setSizeFull();
grid.setCellStyleGenerator(new CellStyleGenerator() {
@@ -100,28 +113,28 @@ public class SimulatorView extends VerticalLayout implements View {
@Override
public String getStyle(final CellReference cellReference) {
return cellReference.getPropertyId().equals("status") ? "centeralign" : null;
return cellReference.getPropertyId().equals(STATUS_COL) ? "centeralign" : null;
}
});
grid.setSelectionMode(SelectionMode.NONE);
grid.setContainerDataSource(beanContainer);
grid.appendHeaderRow().getCell("responseStatus").setComponent(responseComboBox);
grid.setColumnOrder("id", "status", "swversion", "progress", "tenant", "protocol", "responseStatus",
"nextPollCounterSec");
grid.appendHeaderRow().getCell(RESPONSE_STATUS_COL).setComponent(responseComboBox);
grid.setColumnOrder(ID_COL, STATUS_COL, SWVERSION_COL, PROGRESS_COL, TENANT_COL, PROTOCOL_COL,
RESPONSE_STATUS_COL, NEXT_POLL_COUNTER_SEC_COL);
// header widths
grid.getColumn("status").setMaximumWidth(80);
grid.getColumn("protocol").setMaximumWidth(180);
grid.getColumn("responseStatus").setMaximumWidth(240);
grid.getColumn("nextPollCounterSec").setMaximumWidth(210);
grid.getColumn(STATUS_COL).setMaximumWidth(80);
grid.getColumn(PROTOCOL_COL).setMaximumWidth(180);
grid.getColumn(RESPONSE_STATUS_COL).setMaximumWidth(240);
grid.getColumn(NEXT_POLL_COUNTER_SEC_COL).setMaximumWidth(210);
grid.getColumn("nextPollCounterSec").setHeaderCaption("Next Poll in (sec)");
grid.getColumn("swversion").setHeaderCaption("SW Version");
grid.getColumn("responseStatus").setHeaderCaption("Response Update Status");
grid.getColumn("progress").setRenderer(new ProgressBarRenderer());
grid.getColumn("protocol").setConverter(createProtocolConverter());
grid.getColumn("status").setRenderer(new HtmlRenderer(), createStatusConverter());
grid.removeColumn("tenant");
grid.getColumn(NEXT_POLL_COUNTER_SEC_COL).setHeaderCaption("Next Poll in (sec)");
grid.getColumn(SWVERSION_COL).setHeaderCaption("SW Version");
grid.getColumn(RESPONSE_STATUS_COL).setHeaderCaption("Response Update Status");
grid.getColumn(PROGRESS_COL).setRenderer(new ProgressBarRenderer());
grid.getColumn(PROTOCOL_COL).setConverter(createProtocolConverter());
grid.getColumn(STATUS_COL).setRenderer(new HtmlRenderer(), createStatusConverter());
grid.removeColumn(TENANT_COL);
// grid combobox
responseComboBox.setItemIcon(ResponseStatus.SUCCESSFUL, FontAwesome.CHECK_CIRCLE);
@@ -129,8 +142,8 @@ public class SimulatorView extends VerticalLayout implements View {
responseComboBox.setNullSelectionAllowed(false);
responseComboBox.setValue(ResponseStatus.SUCCESSFUL);
responseComboBox.addValueChangeListener(valueChangeEvent -> {
beanContainer.getItemIds().forEach(itemId -> beanContainer.getItem(itemId).getItemProperty("responseStatus")
.setValue(valueChangeEvent.getProperty().getValue()));
beanContainer.getItemIds().forEach(itemId -> beanContainer.getItem(itemId)
.getItemProperty(RESPONSE_STATUS_COL).setValue(valueChangeEvent.getProperty().getValue()));
});
// add all components
@@ -141,7 +154,7 @@ public class SimulatorView extends VerticalLayout implements View {
setExpandRatio(grid, 1.0F);
// load beans
repository.getAll().forEach(device -> beanContainer.addBean(device));
repository.getAll().forEach(beanContainer::addBean);
}
@Override
@@ -150,21 +163,16 @@ public class SimulatorView extends VerticalLayout implements View {
eventbus.unregister(this);
}
@SuppressWarnings("unchecked")
@Subscribe
public void pollCounterUpdate(final NextPollCounterUpdate update) {
final List<AbstractSimulatedDevice> devices = update.getDevices();
this.getUI().access(new Runnable() {
@Override
public void run() {
devices.forEach(device -> {
final BeanItem<AbstractSimulatedDevice> item = beanContainer.getItem(device.getId());
if (item != null) {
item.getItemProperty("nextPollCounterSec").setValue(device.getNextPollCounterSec());
}
});
this.getUI().access(() -> devices.forEach(device -> {
final BeanItem<AbstractSimulatedDevice> item = beanContainer.getItem(device.getId());
if (item != null) {
item.getItemProperty(NEXT_POLL_COUNTER_SEC_COL).setValue(device.getNextPollCounterSec());
}
});
}));
}
/**
@@ -173,21 +181,19 @@ public class SimulatorView extends VerticalLayout implements View {
* @param update
* the update event posted on the event bus
*/
@SuppressWarnings("unchecked")
@Subscribe
public void initUpdate(final InitUpdate update) {
final AbstractSimulatedDevice device = update.getDevice();
this.getUI().access(new Runnable() {
@Override
public void run() {
final BeanItem<AbstractSimulatedDevice> item = beanContainer.getItem(device.getId());
if (item != null) {
item.getItemProperty("progress").setValue(device.getProgress());
item.getItemProperty("status").setValue(Status.PEDNING);
item.getItemProperty("swversion").setValue(device.getSwversion());
}
this.getUI().access(() -> {
final BeanItem<AbstractSimulatedDevice> item = beanContainer.getItem(device.getId());
if (item == null) {
return;
}
item.getItemProperty(PROGRESS_COL).setValue(device.getProgress());
item.getItemProperty(STATUS_COL).setValue(Status.PEDNING);
item.getItemProperty(SWVERSION_COL).setValue(device.getSwversion());
});
}
@@ -197,35 +203,37 @@ public class SimulatorView extends VerticalLayout implements View {
* @param update
* the update event posted on the event bus
*/
@SuppressWarnings("unchecked")
@Subscribe
public void progessUpdate(final ProgressUpdate update) {
final AbstractSimulatedDevice device = update.getDevice();
this.getUI().access(new Runnable() {
@Override
public void run() {
final BeanItem<AbstractSimulatedDevice> item = beanContainer.getItem(device.getId());
if (item != null) {
item.getItemProperty("progress").setValue(device.getProgress());
if (device.getProgress() >= 1) {
switch (device.getResponseStatus()) {
case SUCCESSFUL:
item.getItemProperty("status").setValue(Status.FINISH);
break;
case ERROR:
item.getItemProperty("status").setValue(Status.ERROR);
break;
default:
item.getItemProperty("status").setValue(Status.UNKNWON);
}
} else {
item.getItemProperty("status").setValue(Status.PEDNING);
}
}
this.getUI().access(() -> {
final BeanItem<AbstractSimulatedDevice> item = beanContainer.getItem(device.getId());
if (item != null) {
item.getItemProperty(PROGRESS_COL).setValue(device.getProgress());
setStatusColumn(device, item);
}
});
}
@SuppressWarnings("unchecked")
private void setStatusColumn(final AbstractSimulatedDevice device, final BeanItem<AbstractSimulatedDevice> item) {
if (device.getProgress() >= 1) {
switch (device.getUpdateStatus().getResponseStatus()) {
case SUCCESSFUL:
item.getItemProperty(STATUS_COL).setValue(Status.FINISH);
break;
case ERROR:
item.getItemProperty(STATUS_COL).setValue(Status.ERROR);
break;
default:
item.getItemProperty(STATUS_COL).setValue(Status.UNKNWON);
}
} else {
item.getItemProperty(STATUS_COL).setValue(Status.PEDNING);
}
}
private void createToolbar() {
final Button createDevicesButton = new Button("generate...");
createDevicesButton.setIcon(FontAwesome.GEARS);
@@ -246,18 +254,15 @@ public class SimulatorView extends VerticalLayout implements View {
}
private void openGenerateDialog() {
UI.getCurrent().addWindow(new GenerateDialog(new GenerateDialogCallback() {
@Override
public void okButton(final String namePrefix, final String tenant, final int amount, final int pollDelay,
final URL basePollUrl, final String gatewayToken, final Protocol protocol) {
for (int index = 0; index < amount; index++) {
final String deviceId = namePrefix + index;
beanContainer.addBean(repository.add(deviceFactory.createSimulatedDevice(deviceId,
tenant.toLowerCase(), protocol, pollDelay, basePollUrl, gatewayToken)));
spSenderService.createOrUpdateThing(tenant, deviceId);
}
}
}));
UI.getCurrent().addWindow(
new GenerateDialog((namePrefix, tenant, amount, pollDelay, basePollUrl, gatewayToken, protocol) -> {
for (int index = 0; index < amount; index++) {
final String deviceId = namePrefix + index;
beanContainer.addBean(repository.add(deviceFactory.createSimulatedDevice(deviceId,
tenant.toLowerCase(), protocol, pollDelay, basePollUrl, gatewayToken)));
spSenderService.createOrUpdateThing(tenant, deviceId);
}
}));
}
private Converter<String, Protocol> createProtocolConverter() {

View File

@@ -4,16 +4,20 @@ The hawkBit example application is a standalone spring-boot application with an
We have have described several options for you to get access to the example.
## Try out the example application in our hawkBit sandbox on Bluemix
- try out Management UI https://hawkbit.eu-gb.mybluemix.net/UI
- try out Management API https://hawkbit.eu-gb.mybluemix.net/rest/v1/targets (don't forget basic auth header)
- try out DDI API https://hawkbit.eu-gb.mybluemix.net/DEFAULT/controller/v1/MYTESTDEVICE
- try out Management UI https://hawkbit.eu-gb.mybluemix.net/UI (username: admin, passwd: admin)
- try out Management API https://hawkbit.eu-gb.mybluemix.net/rest/v1/targets (don't forget basic auth header; username: admin, passwd: admin)
- try out DDI API https://hawkbit.eu-gb.mybluemix.net/DEFAULT/controller/v1/MYTESTDEVICE (authentication disabled)
## On your own workstation
### Run
```
java -jar examples/hawkbit-example-app/target/hawkbit-example-app-*-SNAPSHOT.jar
```
_(Note: you have to add the JDBC driver also to your class path if you intend to use another database than H2.)_
Or:
```
run org eclipse.hawkbit.app.Start
```

View File

@@ -26,6 +26,8 @@ import org.springframework.context.annotation.Import;
@EnableHawkbitManagedSecurityConfiguration
@EnableRestResources
@EnableDirectDeviceApi
// Exception squid:S1118 - Spring boot standard behavior
@SuppressWarnings({ "squid:S1118" })
public class Start {
/**
@@ -34,6 +36,8 @@ public class Start {
* @param args
* the VM arguments.
*/
// Exception squid:S2095 - Spring boot standard behavior
@SuppressWarnings({ "squid:S2095" })
public static void main(final String[] args) {
SpringApplication.run(Start.class, args);
}

View File

@@ -8,3 +8,9 @@
#
vaadin.servlet.productionMode=true
hawkbit.artifact.url.coap.enabled=false
hawkbit.artifact.url.http.enabled=false
hawkbit.artifact.url.https.enabled=true
hawkbit.artifact.url.https.pattern={protocol}://{hostname}/{tenant}/controller/v1/{targetId}/softwaremodules/{softwareModuleId}/artifacts/{artifactFileName}
hawkbit.artifact.url.https.hostname=hawkbit.eu-gb.mybluemix.net

View File

@@ -7,21 +7,28 @@
# http://www.eclipse.org/legal/epl-v10.html
#
# DDI authentication configuration
hawkbit.server.ddi.security.authentication.anonymous.enabled=true
hawkbit.server.ddi.security.authentication.targettoken.enabled=false
hawkbit.server.ddi.security.authentication.gatewaytoken.enabled=false
hawkbit.server.ddi.security.authentication.targettoken.enabled=true
hawkbit.server.ddi.security.authentication.gatewaytoken.enabled=true
spring.profiles.active=amqp
# Download URL generation config
hawkbit.artifact.url.coap.enabled=false
hawkbit.artifact.url.http.enabled=true
hawkbit.artifact.url.http.port=8080
hawkbit.artifact.url.https.enabled=false
## Vaadin configuration
vaadin.servlet.productionMode=false
## Configuration for RabbitMQ integration
## Configuration for DMF/RabbitMQ integration
spring.profiles.active=amqp
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
spring.rabbitmq.virtualHost=/
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
hawkbit.dmf.rabbitmq.deadLetterQueue=dmf_connector_deadletter
hawkbit.dmf.rabbitmq.deadLetterQueue=dmf_connector_deadletter_ttl
hawkbit.dmf.rabbitmq.deadLetterExchange=dmf.connector.deadletter
hawkbit.dmf.rabbitmq.receiverQueue=dmf_receiver

View File

@@ -18,7 +18,7 @@
</parent>
<packaging>jar</packaging>
<artifactId>hawkbit-mgmt-api-client</artifactId>
<name>hawkBit Management API example client</name>
<name>hawkBit :: Management API example client</name>
<build>
<plugins>

View File

@@ -18,8 +18,9 @@ import com.google.common.collect.Lists;
/**
* Builder pattern for building {@link DistributionSetRequestBodyPost}.
*/
// Exception squid:S1701 - builder pattern
@SuppressWarnings({ "squid:S1701" })
public class DistributionSetBuilder {
private String name;
private String version;
private String type;

View File

@@ -21,8 +21,9 @@ import com.google.common.collect.Lists;
* Builder pattern for building {@link DistributionSetTypeRequestBodyPost}.
*
*/
// Exception squid:S1701 - builder pattern
@SuppressWarnings({ "squid:S1701" })
public class DistributionSetTypeBuilder {
private String key;
private String name;
private final List<SoftwareModuleTypeAssigmentRest> mandatorymodules = Lists.newArrayList();

View File

@@ -17,6 +17,8 @@ import org.eclipse.hawkbit.rest.resource.model.rollout.RolloutRestRequestBody;
* Builder pattern for building {@link RolloutRestRequestBody}.
*
*/
// Exception squid:S1701 - builder pattern
@SuppressWarnings({ "squid:S1701" })
public class RolloutBuilder {
private String name;

View File

@@ -21,6 +21,8 @@ import com.google.common.collect.Lists;
* Builder pattern for building {@link SoftwareModuleRequestBodyPost}.
*
*/
// Exception squid:S1701 - builder pattern
@SuppressWarnings({ "squid:S1701" })
public class SoftwareModuleBuilder {
private String name;

View File

@@ -21,6 +21,8 @@ import com.google.common.collect.Lists;
* Builder pattern for building {@link SoftwareModuleRequestBodyPost}.
*
*/
// Exception squid:S1701 - builder pattern
@SuppressWarnings({ "squid:S1701" })
public class SoftwareModuleTypeBuilder {
private String key;

View File

@@ -19,6 +19,8 @@ import com.google.common.collect.Lists;
* Builder pattern for building {@link TagRequestBodyPut}.
*
*/
// Exception squid:S1701 - builder pattern
@SuppressWarnings({ "squid:S1701" })
public class TagBuilder {
private String name;

View File

@@ -21,6 +21,8 @@ import com.google.common.collect.Lists;
* Builder pattern for building {@link TargetRequestBody}.
*
*/
// Exception squid:S1701 - builder pattern
@SuppressWarnings({ "squid:S1701" })
public class TargetBuilder {
private String controllerId;

View File

@@ -8,11 +8,14 @@
*/
package org.eclipse.hawkbit.artifact.repository;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.DigestOutputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
@@ -122,7 +125,11 @@ public class ArtifactStore implements ArtifactRepository {
LOGGER.debug("storing file {} of content {}", filename, contentType);
tempFile = File.createTempFile("uploadFile", null);
try (final FileOutputStream os = new FileOutputStream(tempFile)) {
return store(content, contentType, os, tempFile, hash);
try (BufferedOutputStream bos = new BufferedOutputStream(os)) {
try (BufferedInputStream bis = new BufferedInputStream(content)) {
return store(content, contentType, bos, tempFile, hash);
}
}
}
} catch (final IOException | MongoException e1) {
throw new ArtifactStoreException(e1.getMessage(), e1);
@@ -162,7 +169,7 @@ public class ArtifactStore implements ArtifactRepository {
}
private DbArtifact store(final InputStream content, final String contentType, final FileOutputStream os,
private DbArtifact store(final InputStream content, final String contentType, final OutputStream os,
final File tempFile, final DbArtifactHash hash) {
final GridFsArtifact storedArtifact;
try {
@@ -185,7 +192,8 @@ public class ArtifactStore implements ArtifactRepository {
throw new ArtifactStoreException(e.getMessage(), e);
}
if (hash != null && hash.getMd5() != null && !storedArtifact.getHashes().getMd5().equals(hash.getMd5())) {
if (hash != null && hash.getMd5() != null
&& !storedArtifact.getHashes().getMd5().equalsIgnoreCase(hash.getMd5())) {
throw new HashNotMatchException("The given md5 hash " + hash.getMd5()
+ " not matching the calculated md5 hash " + storedArtifact.getHashes().getMd5(),
HashNotMatchException.MD5);
@@ -195,14 +203,14 @@ public class ArtifactStore implements ArtifactRepository {
}
private static String computeSHA1Hash(final InputStream stream, final FileOutputStream os,
final String providedSHA1Sum) throws NoSuchAlgorithmException, IOException {
private static String computeSHA1Hash(final InputStream stream, final OutputStream os, final String providedSHA1Sum)
throws NoSuchAlgorithmException, IOException {
String sha1Hash;
// compute digest
final MessageDigest md = MessageDigest.getInstance("SHA-1");
final DigestOutputStream dos = new DigestOutputStream(os, md);
ByteStreams.copy(stream, dos);
dos.close();
try (final DigestOutputStream dos = new DigestOutputStream(os, md)) {
ByteStreams.copy(stream, dos);
}
sha1Hash = BaseEncoding.base16().lowerCase().encode(md.digest());
if (providedSHA1Sum != null && !providedSHA1Sum.equalsIgnoreCase(sha1Hash)) {
throw new HashNotMatchException(

View File

@@ -71,6 +71,8 @@ public class MongoConfiguration extends AbstractMongoConfiguration {
@Override
@Bean
@ConditionalOnMissingBean
// Closed by pre-destroy
@SuppressWarnings({ "squid:S2095" })
public Mongo mongo() throws UnknownHostException {
final MongoClientURI uri = new MongoClientURI(properties.getUri(), createBuilderOutOfOptions(options));

View File

@@ -42,6 +42,6 @@ hawkbit.controller.minPollingTime=00:00:30
# Configuration for RabbitMQ integration
hawkbit.dmf.rabbitmq.deadLetterQueue=dmf_connector_deadletter
hawkbit.dmf.rabbitmq.deadLetterQueue=dmf_connector_deadletter_ttl
hawkbit.dmf.rabbitmq.deadLetterExchange=dmf.connector.deadletter
hawkbit.dmf.rabbitmq.receiverQueue=dmf_receiver

3
hawkbit-core/README.md Normal file
View File

@@ -0,0 +1,3 @@
# hawkBit Core
Various internal interfaces and utility classes.

View File

@@ -22,11 +22,10 @@ import org.springframework.core.io.support.ResourcePatternResolver;
* This resource bundles using specified basenames, to resource loading. This
* MessageSource implementation supports more than 1 properties file with the
* same name. All properties files will be merged.
*
*
*
*/
public class DistributedResourceBundleMessageSource extends ReloadableResourceBundleMessageSource {
// Exception squid:S2387 - Follows our upper case convention
@SuppressWarnings({ "squid:S2387" })
private static final Logger LOGGER = LoggerFactory.getLogger(DistributedResourceBundleMessageSource.class);
private static final String PROPERTIES_SUFFIX = ".properties";
private ResourceLoader resourceLoader;

View File

@@ -13,7 +13,6 @@ package org.eclipse.hawkbit.api;
* URLs to specific artifacts.
*
*/
@FunctionalInterface
public interface ArtifactUrlHandler {
/**
@@ -34,4 +33,11 @@ public interface ArtifactUrlHandler {
*/
String getUrl(String controllerId, final Long softwareModuleId, final String filename, final String sha1Hash,
final UrlProtocol protocol);
/**
* @param protocol
* to check support for
* @return <code>true</code> of the handler supports given protocol.
*/
boolean protocolSupported(UrlProtocol protocol);
}

View File

@@ -78,6 +78,12 @@ public class ArtifactUrlHandlerProperties {
* @return the pattern to build the URL.
*/
String getPattern();
/**
* @return <code>true</code> if the {@link ProtocolProperties} is
* enabled.
*/
boolean isEnabled();
}
/**
@@ -93,6 +99,20 @@ public class ArtifactUrlHandlerProperties {
*/
private String pattern = "{protocol}://{hostname}:{port}/{tenant}/controller/v1/{targetId}/softwaremodules/{softwareModuleId}/artifacts/{artifactFileName}";
/**
* Enables HTTP URI generation in DDI and DMF.
*/
private boolean enabled = true;
@Override
public boolean isEnabled() {
return enabled;
}
public void setEnabled(final boolean enabled) {
this.enabled = enabled;
}
@Override
public String getHostname() {
return hostname;
@@ -143,6 +163,20 @@ public class ArtifactUrlHandlerProperties {
*/
private String pattern = "{protocol}://{hostname}:{port}/{tenant}/controller/v1/{targetId}/softwaremodules/{softwareModuleId}/artifacts/{artifactFileName}";
/**
* Enables HTTPS URI generation in DDI and DMF.
*/
private boolean enabled = true;
@Override
public boolean isEnabled() {
return enabled;
}
public void setEnabled(final boolean enabled) {
this.enabled = enabled;
}
@Override
public String getHostname() {
return hostname;
@@ -193,6 +227,20 @@ public class ArtifactUrlHandlerProperties {
*/
private String pattern = "{protocol}://{ip}:{port}/fw/{tenant}/{targetId}/sha1/{artifactSHA1}";
/**
* Enables CoAP URI generation in DMF.
*/
private boolean enabled = true;
@Override
public boolean isEnabled() {
return enabled;
}
public void setEnabled(final boolean enabled) {
this.enabled = enabled;
}
@Override
public String getHostname() {
return hostname;

View File

@@ -84,4 +84,15 @@ public class PropertyBasedArtifactUrlHandler implements ArtifactUrlHandler {
return replaceMap;
}
@Override
public boolean protocolSupported(final UrlProtocol protocol) {
final String protocolString = protocol.name().toLowerCase();
final ProtocolProperties properties = urlHandlerProperties.getProperties(protocolString);
if (properties == null || properties.getPattern() == null) {
return false;
}
return properties.isEnabled();
}
}

View File

@@ -39,6 +39,8 @@ public class TenantConfigurationPollingDurationValidator implements TenantConfig
}
@Override
// Exception squid:S1166 - Hide origin exception
@SuppressWarnings({ "squid:S1166" })
public void validate(final Object tenantConfigurationObject) {
TenantConfigurationValidator.super.validate(tenantConfigurationObject);
final String tenantConfigurationString = (String) tenantConfigurationObject;

View File

@@ -8,9 +8,6 @@
*/
package org.eclipse.hawkbit.amqp;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.hawkbit.dmf.amqp.api.AmqpSettings;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
@@ -18,6 +15,7 @@ import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
@@ -31,15 +29,31 @@ import org.springframework.context.annotation.Bean;
* {@code amqp} to use a AMQP for communication with SP enabled devices.
*
*/
@EnableConfigurationProperties(AmqpProperties.class)
@EnableConfigurationProperties({ AmqpProperties.class, AmqpDeadletterProperties.class })
public class AmqpConfiguration {
@Autowired
protected AmqpProperties amqpProperties;
@Autowired
protected AmqpDeadletterProperties amqpDeadletterProperties;
@Autowired
private ConnectionFactory connectionFactory;
/**
* Create a {@link RabbitAdmin} and ignore declaration exceptions.
* {@link RabbitAdmin#setIgnoreDeclarationExceptions(boolean)}
*
* @return the bean
*/
@Bean
public RabbitAdmin rabbitAdmin() {
final RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
rabbitAdmin.setIgnoreDeclarationExceptions(true);
return rabbitAdmin;
}
/**
* Method to set the Jackson2JsonMessageConverter.
*
@@ -59,7 +73,8 @@ public class AmqpConfiguration {
*/
@Bean
public Queue receiverQueue() {
return new Queue(amqpProperties.getReceiverQueue(), true, false, false, getDeadLetterExchangeArgs());
return new Queue(amqpProperties.getReceiverQueue(), true, false, false,
amqpDeadletterProperties.getDeadLetterExchangeArgs(amqpProperties.getDeadLetterExchange()));
}
/**
@@ -79,7 +94,7 @@ public class AmqpConfiguration {
*/
@Bean
public Queue deadLetterQueue() {
return new Queue(amqpProperties.getDeadLetterQueue());
return amqpDeadletterProperties.createDeadletterQueue(amqpProperties.getDeadLetterQueue());
}
/**
@@ -149,10 +164,4 @@ public class AmqpConfiguration {
return containerFactory;
}
private Map<String, Object> getDeadLetterExchangeArgs() {
final Map<String, Object> args = new HashMap<>();
args.put("x-dead-letter-exchange", amqpProperties.getDeadLetterExchange());
return args;
}
}

View File

@@ -0,0 +1,69 @@
/**
* 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.amqp;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import org.springframework.amqp.core.Queue;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* Bean which holds the necessary properties for configuring the AMQP deadletter
* queue.
*/
@ConfigurationProperties("hawkbit.dmf.rabbitmq.deadLetter")
public class AmqpDeadletterProperties {
/**
* Message time to live (ttl) for the deadletter queue. Default ttl is 3
* weeks.
*/
private long ttl = Duration.ofDays(21).toMillis();
/**
* Return the deadletter arguments.
*
* @param exchange
* the deadletter exchange
* @return map which holds the properties
*/
public Map<String, Object> getDeadLetterExchangeArgs(final String exchange) {
final Map<String, Object> args = new HashMap<>();
args.put("x-dead-letter-exchange", exchange);
return args;
}
/**
* Create a deadletter queue with ttl for messages
*
* @param queueName
* the deadlette queue name
* @return the deadletter queue
*/
public Queue createDeadletterQueue(final String queueName) {
return new Queue(queueName, true, false, false, getTTLArgs());
}
private Map<String, Object> getTTLArgs() {
final Map<String, Object> args = new HashMap<>();
args.put("x-message-ttl", getTtl());
return args;
}
public long getTtl() {
return ttl;
}
public void setTtl(final long ttl) {
this.ttl = ttl;
}
}

View File

@@ -82,6 +82,7 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
.getSoftwareModules();
final DownloadAndUpdateRequest downloadAndUpdateRequest = new DownloadAndUpdateRequest();
downloadAndUpdateRequest.setActionId(targetAssignDistributionSetEvent.getActionId());
downloadAndUpdateRequest.setTargetSecurityToken(targetAssignDistributionSetEvent.getTargetToken());
for (final org.eclipse.hawkbit.repository.model.SoftwareModule softwareModule : modules) {
final SoftwareModule amqpSoftwareModule = convertToAmqpSoftwareModule(controllerId, softwareModule);
@@ -154,18 +155,26 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
private Artifact convertArtifact(final String targetId, final LocalArtifact localArtifact) {
final Artifact artifact = new Artifact();
artifact.getUrls().put(Artifact.UrlProtocol.COAP,
artifactUrlHandler.getUrl(targetId, localArtifact.getSoftwareModule().getId(),
localArtifact.getFilename(), localArtifact.getSha1Hash(), UrlProtocol.COAP));
artifact.getUrls().put(Artifact.UrlProtocol.HTTP,
artifactUrlHandler.getUrl(targetId, localArtifact.getSoftwareModule().getId(),
localArtifact.getFilename(), localArtifact.getSha1Hash(), UrlProtocol.HTTP));
artifact.getUrls().put(Artifact.UrlProtocol.HTTPS,
artifactUrlHandler.getUrl(targetId, localArtifact.getSoftwareModule().getId(),
localArtifact.getFilename(), localArtifact.getSha1Hash(), UrlProtocol.HTTPS));
if (artifactUrlHandler.protocolSupported(UrlProtocol.COAP)) {
artifact.getUrls().put(Artifact.UrlProtocol.COAP,
artifactUrlHandler.getUrl(targetId, localArtifact.getSoftwareModule().getId(),
localArtifact.getFilename(), localArtifact.getSha1Hash(), UrlProtocol.COAP));
}
if (artifactUrlHandler.protocolSupported(UrlProtocol.HTTP)) {
artifact.getUrls().put(Artifact.UrlProtocol.HTTP,
artifactUrlHandler.getUrl(targetId, localArtifact.getSoftwareModule().getId(),
localArtifact.getFilename(), localArtifact.getSha1Hash(), UrlProtocol.HTTP));
}
if (artifactUrlHandler.protocolSupported(UrlProtocol.HTTPS)) {
artifact.getUrls().put(Artifact.UrlProtocol.HTTPS,
artifactUrlHandler.getUrl(targetId, localArtifact.getSoftwareModule().getId(),
localArtifact.getFilename(), localArtifact.getSha1Hash(), UrlProtocol.HTTPS));
}
artifact.setFilename(localArtifact.getFilename());
artifact.setHashes(new ArtifactHash(localArtifact.getSha1Hash(), null));
artifact.setHashes(new ArtifactHash(localArtifact.getSha1Hash(), localArtifact.getMd5Hash()));
artifact.setSize(localArtifact.getSize());
return artifact;
}

View File

@@ -166,20 +166,12 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
final LocalArtifact localArtifact = findLocalArtifactByFileResource(fileResource);
if (localArtifact == null) {
LOG.info("target {} requested file resource {} which does not exists to download",
secruityToken.getControllerId(), fileResource);
throw new EntityNotFoundException();
}
// check action for this download purposes, the method will throw an
// EntityNotFoundException in case the controller is not allowed to
// download this file because it's not assigned to an action and not
// assigned to this controller. Otherwise no controllerId is set =
// anonymous download
if (secruityToken.getControllerId() != null) {
final Action action = controllerManagement.getActionForDownloadByTargetAndSoftwareModule(
secruityToken.getControllerId(), localArtifact.getSoftwareModule());
LOG.info("Found action for download authentication request action: {}, resource: {}", action,
secruityToken.getFileResource());
}
checkIfArtifactIsAssignedToTarget(secruityToken, localArtifact);
final Artifact artifact = convertDbArtifact(artifactManagement.loadLocalArtifactBinary(localArtifact));
if (artifact == null) {
@@ -213,6 +205,35 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
return getMessageConverter().toMessage(authentificationResponse, messageProperties);
}
/**
* check action for this download purposes, the method will throw an
* EntityNotFoundException in case the controller is not allowed to download
* this file because it's not assigned to an action and not assigned to this
* controller. Otherwise no controllerId is set = anonymous download
*
* @param secruityToken
* the security token which holds the target ID to check on
* @param localArtifact
* the local artifact to verify if the given target is allowed to
* download this artifact
*/
private void checkIfArtifactIsAssignedToTarget(final TenantSecurityToken secruityToken,
final LocalArtifact localArtifact) {
final String controllerId = secruityToken.getControllerId();
if (controllerId == null) {
LOG.info("anonymous download no authentication check for artifact {}", localArtifact);
return;
}
LOG.debug("no anonymous download request, doing authentication check for target {} and artifact {}",
controllerId, localArtifact);
if (!controllerManagement.hasTargetArtifactAssigned(controllerId, localArtifact)) {
LOG.info("target {} tried to download artifact {} which is not assigned to the target", controllerId,
localArtifact);
throw new EntityNotFoundException();
}
LOG.info("download security check for target {} and artifact {} granted", controllerId, localArtifact);
}
private LocalArtifact findLocalArtifactByFileResource(final FileResource fileResource) {
if (fileResource.getSha1() != null) {
return artifactManagement.findFirstLocalArtifactsBySHA1(fileResource.getSha1());
@@ -284,7 +305,8 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
final List<SoftwareModule> softwareModuleList = controllerManagement
.findSoftwareModulesByDistributionSet(distributionSet);
eventBus.post(new TargetAssignDistributionSetEvent(target.getOptLockRevision(), target.getTenant(),
target.getControllerId(), action.getId(), softwareModuleList, target.getTargetInfo().getAddress()));
target.getControllerId(), action.getId(), softwareModuleList, target.getTargetInfo().getAddress(),
target.getSecurityToken()));
}
@@ -315,9 +337,8 @@ public class AmqpMessageHandlerService extends BaseAmqpService {
final Action action = checkActionExist(message, actionUpdateStatus);
final ActionStatus actionStatus = new ActionStatus();
final List<String> messageText = actionUpdateStatus.getMessage();
final String messageString = String.join(", ", messageText);
actionStatus.addMessage(messageString);
actionUpdateStatus.getMessage().forEach(actionStatus::addMessage);
actionStatus.setAction(action);
actionStatus.setOccurredAt(System.currentTimeMillis());

View File

@@ -21,7 +21,7 @@ public class AmqpProperties {
/**
* DMF API dead letter queue.
*/
private String deadLetterQueue = "dmf_connector_deadletter";
private String deadLetterQueue = "dmf_connector_deadletter_ttl";
/**
* DMF API dead letter exchange.

View File

@@ -8,8 +8,8 @@
*/
package org.eclipse.hawkbit.amqp;
import static org.fest.assertions.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
@@ -23,6 +23,7 @@ import static org.mockito.Mockito.when;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.eclipse.hawkbit.AbstractIntegrationTestWithMongoDB;
import org.eclipse.hawkbit.TestDataUtil;
@@ -58,6 +59,12 @@ import ru.yandex.qatools.allure.annotations.Stories;
@Stories("AmqpMessage Dispatcher Service Test")
public class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTestWithMongoDB {
private static final String TENANT = "default";
private static final URI AMQP_URI = IpUtil.createAmqpUri("vHost", "mytest");
private static final String TEST_TOKEN = "testToken";
private AmqpMessageDispatcherService amqpMessageDispatcherService;
private RabbitTemplate rabbitTemplate;
@@ -89,8 +96,7 @@ public class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTestWit
@Description("Verfies that download and install event with no software modul works")
public void testSendDownloadRequesWithEmptySoftwareModules() {
final TargetAssignDistributionSetEvent targetAssignDistributionSetEvent = new TargetAssignDistributionSetEvent(
1L, "default", CONTROLLER_ID, 1l, new ArrayList<SoftwareModule>(),
IpUtil.createAmqpUri("vHost", "mytest"));
1L, TENANT, CONTROLLER_ID, 1L, new ArrayList<SoftwareModule>(), AMQP_URI, TEST_TOKEN);
amqpMessageDispatcherService.targetAssignDistributionSet(targetAssignDistributionSetEvent);
final Message sendMessage = createArgumentCapture(targetAssignDistributionSetEvent.getTargetAdress());
final DownloadAndUpdateRequest downloadAndUpdateRequest = assertDownloadAndInstallMessage(sendMessage);
@@ -104,7 +110,7 @@ public class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTestWit
final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement,
distributionSetManagement);
final TargetAssignDistributionSetEvent targetAssignDistributionSetEvent = new TargetAssignDistributionSetEvent(
1L, "default", CONTROLLER_ID, 1l, dsA.getModules(), IpUtil.createAmqpUri("vHost", "mytest"));
1L, TENANT, CONTROLLER_ID, 1L, dsA.getModules(), AMQP_URI, TEST_TOKEN);
amqpMessageDispatcherService.targetAssignDistributionSet(targetAssignDistributionSetEvent);
final Message sendMessage = createArgumentCapture(targetAssignDistributionSetEvent.getTargetAdress());
final DownloadAndUpdateRequest downloadAndUpdateRequest = assertDownloadAndInstallMessage(sendMessage);
@@ -143,7 +149,7 @@ public class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTestWit
Mockito.when(rabbitTemplate.convertSendAndReceive(any())).thenReturn(receivedList);
final TargetAssignDistributionSetEvent targetAssignDistributionSetEvent = new TargetAssignDistributionSetEvent(
1L, "default", CONTROLLER_ID, 1l, dsA.getModules(), IpUtil.createAmqpUri("vHost", "mytest"));
1L, TENANT, CONTROLLER_ID, 1L, dsA.getModules(), AMQP_URI, TEST_TOKEN);
amqpMessageDispatcherService.targetAssignDistributionSet(targetAssignDistributionSetEvent);
final Message sendMessage = createArgumentCapture(targetAssignDistributionSetEvent.getTargetAdress());
final DownloadAndUpdateRequest downloadAndUpdateRequest = assertDownloadAndInstallMessage(sendMessage);
@@ -154,7 +160,19 @@ public class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTestWit
if (!softwareModule.getModuleId().equals(module.getId())) {
continue;
}
assertFalse("The software module artifacts should not be empty", softwareModule.getArtifacts().isEmpty());
assertThat(softwareModule.getArtifacts().size()).isEqualTo(module.getArtifacts().size()).isGreaterThan(0);
module.getArtifacts().forEach(dbArtifact -> {
final Optional<org.eclipse.hawkbit.dmf.json.model.Artifact> found = softwareModule.getArtifacts()
.stream().filter(dmfartifact -> dmfartifact.getFilename()
.equals(((LocalArtifact) dbArtifact).getFilename()))
.findFirst();
assertTrue("The artifact should exist in message", found.isPresent());
assertThat(found.get().getSize()).isEqualTo(dbArtifact.getSize());
assertThat(found.get().getHashes().getMd5()).isEqualTo(dbArtifact.getMd5Hash());
assertThat(found.get().getHashes().getSha1()).isEqualTo(dbArtifact.getSha1Hash());
});
}
}
@@ -162,7 +180,7 @@ public class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTestWit
@Description("Verfies that send cancel event works")
public void testSendCancelRequest() {
final CancelTargetAssignmentEvent cancelTargetAssignmentDistributionSetEvent = new CancelTargetAssignmentEvent(
1L, "default", CONTROLLER_ID, 1l, IpUtil.createAmqpUri("vHost", "mytest"));
1L, TENANT, CONTROLLER_ID, 1L, AMQP_URI);
amqpMessageDispatcherService
.targetCancelAssignmentToDistributionSet(cancelTargetAssignmentDistributionSetEvent);
final Message sendMessage = createArgumentCapture(cancelTargetAssignmentDistributionSetEvent.getTargetAdress());
@@ -187,13 +205,12 @@ public class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTestWit
downloadAndUpdateRequest.getActionId(), Long.valueOf(1));
assertEquals("The topic of the event shuold contain DOWNLOAD_AND_INSTALL", EventTopic.DOWNLOAD_AND_INSTALL,
sendMessage.getMessageProperties().getHeaders().get(MessageHeaderKey.TOPIC));
assertEquals("Security token of target", downloadAndUpdateRequest.getTargetSecurityToken(), TEST_TOKEN);
return downloadAndUpdateRequest;
}
/**
* @param sendMessage
*/
private void assertEventMessage(final Message sendMessage) {
assertNotNull("The message should not be null", sendMessage);

View File

@@ -313,11 +313,10 @@ public class AmqpMessageHandlerServiceTest {
// mock
final LocalArtifact localArtifactMock = mock(LocalArtifact.class);
final Action actionMock = mock(Action.class);
final DbArtifact dbArtifactMock = mock(DbArtifact.class);
when(artifactManagementMock.findFirstLocalArtifactsBySHA1(anyString())).thenReturn(localArtifactMock);
when(controllerManagementMock.getActionForDownloadByTargetAndSoftwareModule(anyObject(), anyObject()))
.thenReturn(actionMock);
when(controllerManagementMock.hasTargetArtifactAssigned(securityToken.getControllerId(), localArtifactMock))
.thenReturn(true);
when(artifactManagementMock.loadLocalArtifactBinary(localArtifactMock)).thenReturn(dbArtifactMock);
when(dbArtifactMock.getArtifactId()).thenReturn("artifactId");
when(dbArtifactMock.getSize()).thenReturn(1L);
@@ -334,20 +333,22 @@ public class AmqpMessageHandlerServiceTest {
assertThat(downloadResponse.getResponseCode()).as("Message body response code is wrong")
.isEqualTo(HttpStatus.OK.value());
assertThat(downloadResponse.getArtifact().getSize()).as("Wrong artifact size in message body").isEqualTo(1L);
assertThat(downloadResponse.getArtifact().getHashes().getSha1()).as("Wrong sha1 hash").isEqualTo("sha1");
assertThat(downloadResponse.getArtifact().getHashes().getMd5()).as("Wrong md5 hash").isEqualTo("md5");
assertThat(downloadResponse.getDownloadUrl()).as("download url is wrong")
.startsWith("http://localhost/api/v1/downloadserver/downloadId/");
}
@Test
@Description("Tests TODO")
public void lookupNextUpdateActionAfterFinished() throws IllegalArgumentException, IllegalAccessException {
public void lookupNextUpdateActionAfterFinished() throws IllegalAccessException {
// Mock
final Action action = createActionWithTarget(22L, Status.FINISHED);
when(controllerManagementMock.findActionWithDetails(Matchers.any())).thenReturn(action);
when(controllerManagementMock.addUpdateActionStatus(Matchers.any(), Matchers.any())).thenReturn(action);
// for the test the same action can be used
final List<Action> actionList = new ArrayList<Action>();
final List<Action> actionList = new ArrayList<>();
actionList.add(action);
when(controllerManagementMock.findActionByTargetAndActive(Matchers.any())).thenReturn(actionList);
@@ -373,6 +374,8 @@ public class AmqpMessageHandlerServiceTest {
assertThat(targetAssignDistributionSetEvent.getControllerId()).as("event has wrong controller id")
.isEqualTo("target1");
assertThat(targetAssignDistributionSetEvent.getTargetToken()).as("targetoken not filled correctly")
.isEqualTo(action.getTarget().getSecurityToken());
assertThat(targetAssignDistributionSetEvent.getActionId()).as("event has wrong action id").isEqualTo(22L);
assertThat(targetAssignDistributionSetEvent.getSoftwareModules()).as("event has wrong sofware modules")
.isEqualTo(softwareModuleList);
@@ -380,7 +383,7 @@ public class AmqpMessageHandlerServiceTest {
}
private ActionUpdateStatus createActionUpdateStatus(final ActionStatus status) {
return createActionUpdateStatus(status, 2l);
return createActionUpdateStatus(status, 2L);
}
private ActionUpdateStatus createActionUpdateStatus(final ActionStatus status, final Long id) {
@@ -405,15 +408,14 @@ public class AmqpMessageHandlerServiceTest {
}
private List<SoftwareModule> createSoftwareModuleList() {
final List<SoftwareModule> softwareModuleList = new ArrayList<SoftwareModule>();
final List<SoftwareModule> softwareModuleList = new ArrayList<>();
final SoftwareModule softwareModule = new SoftwareModule();
softwareModule.setId(777L);
softwareModuleList.add(softwareModule);
return softwareModuleList;
}
private Action createActionWithTarget(final Long targetId, final Status status)
throws IllegalArgumentException, IllegalAccessException {
private Action createActionWithTarget(final Long targetId, final Status status) throws IllegalAccessException {
// is needed for the creation of targets
initalizeSecurityTokenGenerator();
@@ -428,7 +430,7 @@ public class AmqpMessageHandlerServiceTest {
return action;
}
private void initalizeSecurityTokenGenerator() throws IllegalArgumentException, IllegalAccessException {
private void initalizeSecurityTokenGenerator() throws IllegalAccessException {
final SecurityTokenGeneratorHolder instance = SecurityTokenGeneratorHolder.getInstance();
final Field[] fields = instance.getClass().getDeclaredFields();
for (final Field field : fields) {

View File

@@ -52,6 +52,8 @@ public class BaseAmqpServiceTest {
final ActionUpdateStatus actionUpdateStatus = new ActionUpdateStatus();
actionUpdateStatus.setActionId(1L);
actionUpdateStatus.setSoftwareModuleId(2L);
actionUpdateStatus.getMessage().add("Message 1");
actionUpdateStatus.getMessage().add("Message 2");
final Message message = rabbitTemplate.getMessageConverter().toMessage(actionUpdateStatus,
new MessageProperties());

View File

@@ -13,10 +13,6 @@ import com.fasterxml.jackson.annotation.JsonProperty;
/**
* JSON representation of artifact hash.
*
*
*
*
*/
public class ArtifactHash {

View File

@@ -19,15 +19,16 @@ import com.fasterxml.jackson.annotation.JsonProperty;
/**
* JSON representation of download and update request.
*
*
*
*
*/
@JsonInclude(Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
public class DownloadAndUpdateRequest {
@JsonProperty
private Long actionId;
@JsonProperty
private String targetSecurityToken;
@JsonProperty
private final List<SoftwareModule> softwareModules = new LinkedList<>();
@@ -39,6 +40,14 @@ public class DownloadAndUpdateRequest {
this.actionId = correlator;
}
public String getTargetSecurityToken() {
return targetSecurityToken;
}
public void setTargetSecurityToken(final String targetSecurityToken) {
this.targetSecurityToken = targetSecurityToken;
}
public List<SoftwareModule> getSoftwareModules() {
return softwareModules;
}

View File

@@ -11,9 +11,6 @@ package org.eclipse.hawkbit;
import javax.persistence.EntityManager;
import javax.transaction.Transaction;
import org.eclipse.hawkbit.repository.RolloutManagement;
import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.repository.exception.TenantNotExistException;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.eclipse.persistence.config.PersistenceUnitProperties;
import org.springframework.beans.factory.annotation.Autowired;
@@ -38,38 +35,12 @@ public class MultiTenantJpaTransactionManager extends JpaTransactionManager {
protected void doBegin(final Object transaction, final TransactionDefinition definition) {
super.doBegin(transaction, definition);
final EntityManagerHolder emHolder = (EntityManagerHolder) TransactionSynchronizationManager
.getResource(getEntityManagerFactory());
final EntityManager em = emHolder.getEntityManager();
if (notTenantManagement(definition) && notCurrentTenantKeyGenerator(definition)
&& notRolloutScheduler(definition) && notGetOrCreateTenantMetadata(definition)) {
final String currentTenant = tenantAware.getCurrentTenant();
if (currentTenant == null) {
throw new TenantNotExistException("Tenant Unknown. Canceling transaction.");
}
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());
}
}
private boolean notGetOrCreateTenantMetadata(final TransactionDefinition definition) {
return !definition.getName()
.startsWith(SystemManagement.class.getCanonicalName() + ".getOrCreateTenantMetadata");
}
private boolean notRolloutScheduler(final TransactionDefinition definition) {
return !definition.getName().startsWith(RolloutManagement.class.getCanonicalName() + ".rolloutScheduler");
}
private boolean notCurrentTenantKeyGenerator(final TransactionDefinition definition) {
return !definition.getName()
.startsWith(SystemManagement.class.getCanonicalName() + ".currentTenantKeyGenerator");
}
private boolean notTenantManagement(final TransactionDefinition definition) {
return !definition.getName().startsWith(SystemManagement.class.getCanonicalName() + ".deleteTenant")
&& !definition.getName().startsWith(SystemManagement.class.getCanonicalName() + ".findTenants");
}
}

View File

@@ -18,8 +18,11 @@ import org.eclipse.hawkbit.repository.model.helper.AfterTransactionCommitExecuto
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;
@@ -48,14 +51,24 @@ import org.springframework.validation.beanvalidation.MethodValidationPostProcess
@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 TenantConfigurationManagement tenantConfigurationManagement() {
return TenantConfigurationManagement.getInstance();
public TenantConfigurationManagementHolder tenantConfigurationManagementHolder() {
return TenantConfigurationManagementHolder.getInstance();
}
/**

View File

@@ -90,6 +90,8 @@ public class ExceptionMappingAspectHandler implements Ordered {
+ " || 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);

View File

@@ -20,9 +20,9 @@ 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.TargetRepository;
import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity;
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;
@@ -67,6 +67,8 @@ public class EntityChangeEventListener {
* {@link ProceedingJoinPoint#proceed()}
*/
@Around("execution(* org.eclipse.hawkbit.repository.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();
@@ -92,6 +94,8 @@ public class EntityChangeEventListener {
* {@link ProceedingJoinPoint#proceed()}
*/
@Around("execution(* org.eclipse.hawkbit.repository.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();
@@ -111,8 +115,9 @@ public class EntityChangeEventListener {
* in case exception happens in the
* {@link ProceedingJoinPoint#proceed()}
*/
@SuppressWarnings("unchecked")
@Around("execution(* org.eclipse.hawkbit.repository.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();

View File

@@ -16,8 +16,6 @@ import org.eclipse.hawkbit.repository.model.SoftwareModule;
/**
* Event that gets sent when a distribution set gets assigned to a target.
*
*
*
*/
public class TargetAssignDistributionSetEvent extends AbstractEvent {
@@ -25,6 +23,7 @@ public class TargetAssignDistributionSetEvent extends AbstractEvent {
private final String controllerId;
private final Long actionId;
private final URI targetAdress;
private final String targetToken;
/**
* Creates a new {@link TargetAssignDistributionSetEvent}.
@@ -41,14 +40,18 @@ public class TargetAssignDistributionSetEvent extends AbstractEvent {
* the software modules which have been assigned to the target
* @param targetAdress
* the targetAdress of the target
* @param targetToken
* the authentication token of the target
*/
public TargetAssignDistributionSetEvent(final long revision, final String tenant, final String controllerId,
final Long actionId, final Collection<SoftwareModule> softwareModules, final URI targetAdress) {
final Long actionId, final Collection<SoftwareModule> softwareModules, final URI targetAdress,
final String targetToken) {
super(revision, tenant);
this.controllerId = controllerId;
this.actionId = actionId;
this.softwareModules = softwareModules;
this.targetAdress = targetAdress;
this.targetToken = targetToken;
}
/**
@@ -77,4 +80,7 @@ public class TargetAssignDistributionSetEvent extends AbstractEvent {
return targetAdress;
}
public String getTargetToken() {
return targetToken;
}
}

View File

@@ -29,6 +29,8 @@ public class AfterTransactionCommitDefaultServiceExecutor extends TransactionSyn
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());
@@ -60,6 +62,7 @@ public class AfterTransactionCommitDefaultServiceExecutor extends TransactionSyn
}
@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);

View File

@@ -29,13 +29,14 @@ 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)
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
public interface ActionRepository extends BaseEntityRepository<Action, Long>, JpaSpecificationExecutor<Action> {
/**
* Retrieves an Action with all lazy attributes.
@@ -172,7 +173,7 @@ public interface ActionRepository extends BaseEntityRepository<Action, Long>, Jp
* active
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Query("UPDATE Action a SET a.active = false WHERE a IN :keySet AND a.target IN :targetsIds")
void setToInactive(@Param("keySet") List<Action> keySet, @Param("targetsIds") List<Long> targetsIds);
@@ -191,7 +192,7 @@ public interface ActionRepository extends BaseEntityRepository<Action, Long>, Jp
* the current status of the actions which are affected
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Query("UPDATE Action 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);
@@ -211,7 +212,7 @@ public interface ActionRepository extends BaseEntityRepository<Action, Long>, Jp
* the current status of the actions which are affected
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Query("UPDATE Action 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") Rollout rollout,
@Param("active") boolean active, @Param("currentStatus") Action.Status currentStatus);
@@ -386,6 +387,4 @@ public interface ActionRepository extends BaseEntityRepository<Action, Long>, Jp
@Query("SELECT NEW org.eclipse.hawkbit.repository.model.TotalTargetCountActionStatus(a.rolloutGroup.id, a.status , COUNT(a.target)) FROM Action a WHERE a.rolloutGroup.id IN ?1 GROUP BY a.rolloutGroup.id, a.status")
List<TotalTargetCountActionStatus> getStatusCountByRolloutGroupId(List<Long> rolloutGroupId);
// Asha-ends here
}

View File

@@ -16,13 +16,14 @@ 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)
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
public interface ActionStatusRepository
extends BaseEntityRepository<ActionStatus, Long>, JpaSpecificationExecutor<ActionStatus> {

View File

@@ -43,17 +43,15 @@ import org.springframework.data.jpa.repository.Modifying;
import org.springframework.hateoas.Identifiable;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
/**
* service for {@link Artifact} management operations.
*
*
*
* Service for {@link Artifact} management operations.
*
*/
@Transactional(readOnly = true)
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
@Validated
@Service
public class ArtifactManagement {
@@ -108,7 +106,7 @@ public class ArtifactManagement {
* if check against provided SHA1 checksum failed
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_CREATE_REPOSITORY)
public LocalArtifact createLocalArtifact(@NotNull final InputStream stream, @NotNull final Long moduleId,
@NotEmpty final String filename, final String providedMd5Sum, final String providedSha1Sum,
@@ -138,7 +136,7 @@ public class ArtifactManagement {
return storeArtifactMetadata(softwareModule, filename, result, existing);
}
private LocalArtifact checkForExistingArtifact(final String filename, final boolean overrideExisting,
private static LocalArtifact checkForExistingArtifact(final String filename, final boolean overrideExisting,
final SoftwareModule softwareModule) {
if (softwareModule.getLocalArtifactByFilename(filename).isPresent()) {
if (overrideExisting) {
@@ -222,9 +220,7 @@ public class ArtifactManagement {
artifact.setSize(result.getSize());
LOG.debug("storing new artifact into repository {}", artifact);
final LocalArtifact artifactPersisted = localArtifactRepository.save(artifact);
return artifactPersisted;
return localArtifactRepository.save(artifact);
}
/**
@@ -242,7 +238,7 @@ public class ArtifactManagement {
* @return created {@link ExternalArtifactProvider}
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_CREATE_REPOSITORY)
public ExternalArtifactProvider createExternalArtifactProvider(@NotEmpty final String name,
final String description, @NotNull final String basePath, final String defaultUrlSuffix) {
@@ -268,16 +264,13 @@ public class ArtifactManagement {
* if {@link SoftwareModule} with given ID does not exist
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_CREATE_REPOSITORY)
public ExternalArtifact createExternalArtifact(@NotNull final ExternalArtifactProvider externalRepository,
final String urlSuffix, @NotNull final Long moduleId) {
final SoftwareModule module = getModuleAndThrowExceptionIfThatFails(moduleId);
final ExternalArtifact result = externalArtifactRepository
.save(new ExternalArtifact(externalRepository, urlSuffix, module));
return result;
return externalArtifactRepository.save(new ExternalArtifact(externalRepository, urlSuffix, module));
}
/**
@@ -290,7 +283,7 @@ public class ArtifactManagement {
*
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_DELETE_REPOSITORY)
public void deleteLocalArtifact(@NotNull final Long id) {
final LocalArtifact existing = localArtifactRepository.findOne(id);
@@ -313,7 +306,7 @@ public class ArtifactManagement {
* the related local artifact
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_DELETE_REPOSITORY)
public void deleteGridFsArtifact(@NotNull final LocalArtifact existing) {
if (existing == null) {
@@ -324,7 +317,7 @@ public class ArtifactManagement {
for (final LocalArtifact lArtifact : localArtifactRepository
.findByGridFsFileName(existing.getGridFsFileName())) {
if (!lArtifact.getSoftwareModule().isDeleted()
&& lArtifact.getSoftwareModule().getId() != existing.getSoftwareModule().getId()) {
&& Long.compare(lArtifact.getSoftwareModule().getId(), existing.getSoftwareModule().getId()) != 0) {
artifactIsOnlyUsedByOneSoftwareModule = false;
break;
}
@@ -350,7 +343,7 @@ public class ArtifactManagement {
*
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_DELETE_REPOSITORY)
public void deleteExternalArtifact(@NotNull final Long id) {
final ExternalArtifact existing = externalArtifactRepository.findOne(id);
@@ -425,17 +418,17 @@ public class ArtifactManagement {
* @param filename
* of the artifact
* @param overrideExisting
* to <code>true</code> if the artifact binary can be overdiden
* to <code>true</code> if the artifact binary can be overridden
* if it already exists
* @param contentType
* the contentType of the file
*
* @return uploaded {@link LocalArtifact}
*
* @throw ArtifactUploadFailedException if upload failes
* @throw ArtifactUploadFailedException if upload fails
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY)
public LocalArtifact createLocalArtifact(final InputStream inputStream, final Long moduleId, final String filename,
final boolean overrideExisting, final String contentType) {
@@ -461,7 +454,7 @@ public class ArtifactManagement {
* @throw ArtifactUploadFailedException if upload failes
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY)
public LocalArtifact createLocalArtifact(final InputStream inputStream, final Long moduleId, final String filename,
final boolean overrideExisting) {

View File

@@ -14,21 +14,19 @@ 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)
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
public interface BaseEntityRepository<T extends TenantAwareBaseEntity, I extends Serializable>
extends PagingAndSortingRepository<T, I> {
@@ -39,7 +37,7 @@ public interface BaseEntityRepository<T extends TenantAwareBaseEntity, I extends
* to delete data from
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
void deleteByTenantIgnoreCase(String tenant);
}

View File

@@ -28,12 +28,14 @@ import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.ActionStatus;
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.Target_;
import org.eclipse.hawkbit.repository.model.TenantConfiguration;
import org.eclipse.hawkbit.repository.specifications.ActionSpecifications;
import org.eclipse.hawkbit.security.HawkbitSecurityProperties;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey;
import org.hibernate.validator.constraints.NotEmpty;
@@ -44,6 +46,7 @@ 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.Transactional;
import org.springframework.validation.annotation.Validated;
@@ -54,13 +57,15 @@ import org.springframework.validation.annotation.Validated;
*
*
*/
@Transactional(readOnly = true)
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
@Validated
@Service
public class ControllerManagement {
private static final Logger LOG = LoggerFactory.getLogger(ControllerManagement.class);
private static final Logger LOG_DOS = LoggerFactory.getLogger("server-security.dos");
public static final String SERVER_MESSAGE_PREFIX = "Update Server: ";
@Autowired
private EntityManager entityManager;
@@ -129,7 +134,7 @@ public class ControllerManagement {
* if target with given ID could not be found
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
public Target updateLastTargetQuery(@NotEmpty final String targetid, final URI address) {
final Target target = targetRepository.findByControllerId(targetid);
@@ -166,6 +171,31 @@ public class ControllerManagement {
return action.get(0);
}
/**
* Checks if a given target has currently or has even been assigned to the
* given artifact through the action history list. This can e.g. indicate if
* a target is allowed to download a given artifact because it has currently
* assigned or had ever been assigned to the target and so it's visible to a
* specific target e.g. for downloading.
*
* @param targetId
* the ID of the target to check
* @param localArtifact
* the artifact to verify if the given target had even been
* assigned to
* @return {@code true} if the given target has currently or had ever a
* relation to the given artifact through the action history,
* otherwise {@code false}
*/
public boolean hasTargetArtifactAssigned(@NotNull final String targetId,
@NotNull final LocalArtifact localArtifact) {
final Target target = targetRepository.findByControllerId(targetId);
if (target == null) {
return false;
}
return actionRepository.count(ActionSpecifications.hasTargetAssignedArtifact(target, localArtifact)) > 0;
}
/**
* Refreshes the time of the last time the controller has been connected to
* the server.
@@ -178,7 +208,7 @@ public class ControllerManagement {
*
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
public TargetInfo updateLastTargetQuery(@NotNull final TargetInfo target, final URI address) {
return updateTargetStatus(target, null, System.currentTimeMillis(), address);
@@ -235,7 +265,7 @@ public class ControllerManagement {
* @return target reference
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
public Target findOrRegisterTargetIfItDoesNotexist(@NotEmpty final String targetid, final URI address) {
final Specification<Target> spec = (targetRoot, query, cb) -> cb.equal(targetRoot.get(Target_.controllerId),
@@ -271,7 +301,7 @@ public class ControllerManagement {
* @return the updated TargetInfo
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
public TargetInfo updateTargetStatus(@NotNull final TargetInfo targetInfo, final TargetUpdateStatus status,
final Long lastTargetQuery, final URI address) {
@@ -300,7 +330,7 @@ public class ControllerManagement {
*
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
public Action addCancelActionStatus(@NotNull final ActionStatus actionStatus, final Action action) {
@@ -314,13 +344,14 @@ public class ControllerManagement {
break;
case CANCELED:
case FINISHED:
// in case of successful cancelation we also report the success at
// in case of successful cancellation we also report the success at
// the canceled action itself.
actionStatus.addMessage("Cancelation completion is finished sucessfully.");
actionStatus.addMessage(
ControllerManagement.SERVER_MESSAGE_PREFIX + "Cancellation completion is finished sucessfully.");
deploymentManagement.successCancellation(action);
break;
case RETRIEVED:
actionStatus.addMessage("Cancelation request retrieved");
actionStatus.addMessage(ControllerManagement.SERVER_MESSAGE_PREFIX + "Cancellation request retrieved.");
break;
default:
}
@@ -346,7 +377,7 @@ public class ControllerManagement {
* inserted
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
public Action addUpdateActionStatus(@NotNull final ActionStatus actionStatus, final Action action) {
@@ -454,7 +485,7 @@ public class ControllerManagement {
*/
@Modifying
@NotNull
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
public Target updateControllerAttributes(@NotEmpty final String targetid, @NotNull final Map<String, String> data) {
final Target target = targetRepository.findByControllerId(targetid);
@@ -492,7 +523,7 @@ public class ControllerManagement {
* {@link Status#RETRIEVED}
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
public Action registerRetrieved(final Action action, final String message) {
return handleRegisterRetrieved(action, message);
@@ -556,7 +587,7 @@ public class ControllerManagement {
*/
@PreAuthorize(SpringEvalExpressions.IS_CONTROLLER)
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public void addActionStatusMessage(final ActionStatus statusMessage) {
actionStatusRepository.save(statusMessage);
}
@@ -573,7 +604,7 @@ public class ControllerManagement {
* @return the security context of the target, in case no target exists for
* the given controllerId {@code null} is returned
*/
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public String getSecurityTokenByControllerId(final String controllerId) {
final Target target = targetRepository.findByControllerId(controllerId);
return target != null ? target.getSecurityToken() : null;

View File

@@ -67,6 +67,7 @@ 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.Transactional;
import org.springframework.validation.annotation.Validated;
@@ -78,7 +79,7 @@ import com.google.common.eventbus.EventBus;
*
*
*/
@Transactional(readOnly = true)
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
@Validated
@Service
public class DeploymentManagement {
@@ -133,7 +134,7 @@ public class DeploymentManagement {
* {@link SoftwareModuleType} are not assigned as define by the
* {@link DistributionSetType}. *
*/
@Transactional
@Transactional(isolation = Isolation.READ_COMMITTED)
@Modifying
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY_AND_UPDATE_TARGET)
@CacheEvict(value = { "distributionUsageAssigned" }, allEntries = true)
@@ -167,7 +168,7 @@ public class DeploymentManagement {
* {@link DistributionSetType}.
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_COMMITTED)
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY_AND_UPDATE_TARGET)
@CacheEvict(value = { "distributionUsageAssigned" }, allEntries = true)
public DistributionSetAssignmentResult assignDistributionSet(@NotNull final Long dsID,
@@ -195,9 +196,12 @@ public class DeploymentManagement {
* {@link DistributionSetType}.
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY_AND_UPDATE_TARGET)
@CacheEvict(value = { "distributionUsageAssigned" }, allEntries = true)
// Exception squid:S2095: see
// https://jira.sonarsource.com/browse/SONARJAVA-1478
@SuppressWarnings({ "squid:S2095" })
public DistributionSetAssignmentResult assignDistributionSet(@NotNull final Long dsID, final ActionType actionType,
final long forcedTimestamp, @NotEmpty final String... targetIDs) {
return assignDistributionSet(dsID, Arrays.stream(targetIDs)
@@ -219,7 +223,7 @@ public class DeploymentManagement {
* {@link DistributionSetType}.
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_COMMITTED)
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY_AND_UPDATE_TARGET)
@CacheEvict(value = { "distributionUsageAssigned" }, allEntries = true)
public DistributionSetAssignmentResult assignDistributionSet(@NotNull final Long dsID,
@@ -243,8 +247,8 @@ public class DeploymentManagement {
* a list of all targets and their action type
* @param rollout
* the rollout for this assignment
* @param rolloutgroup
* the rolloutgroup for this assignment
* @param rolloutGroup
* the rollout group for this assignment
* @return the assignment result
*
* @throw IncompleteDistributionSetException if mandatory
@@ -252,7 +256,7 @@ public class DeploymentManagement {
* {@link DistributionSetType}.
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_COMMITTED)
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY_AND_UPDATE_TARGET)
@CacheEvict(value = { "distributionUsageAssigned" }, allEntries = true)
public DistributionSetAssignmentResult assignDistributionSet(@NotNull final Long dsID,
@@ -276,8 +280,8 @@ public class DeploymentManagement {
* a list of all targets and their action type
* @param rollout
* the rollout for this assignment
* @param rolloutgroup
* the rolloutgroup for this assignment
* @param rolloutGroup
* the rollout group for this assignment
* @return the assignment result
*
* @throw IncompleteDistributionSetException if mandatory
@@ -389,8 +393,8 @@ public class DeploymentManagement {
softwareModules));
}
private Action createTargetAction(final Map<String, TargetWithActionType> targetsWithActionMap, final Target target,
final DistributionSet set, final Rollout rollout, final RolloutGroup rolloutGroup) {
private static Action createTargetAction(final Map<String, TargetWithActionType> targetsWithActionMap,
final Target target, final DistributionSet set, final Rollout rollout, final RolloutGroup rolloutGroup) {
final Action actionForTarget = new Action();
final TargetWithActionType targetWithActionType = targetsWithActionMap.get(target.getControllerId());
actionForTarget.setActionType(targetWithActionType.getActionType());
@@ -421,13 +425,14 @@ public class DeploymentManagement {
afterCommit.afterCommit(() -> {
eventBus.post(new TargetInfoUpdateEvent(target.getTargetInfo()));
eventBus.post(new TargetAssignDistributionSetEvent(target.getOptLockRevision(), target.getTenant(),
target.getControllerId(), actionId, softwareModules, target.getTargetInfo().getAddress()));
target.getControllerId(), actionId, softwareModules, target.getTargetInfo().getAddress(),
target.getSecurityToken()));
});
}
/**
* Removes {@link UpdateAction}s that are no longer necessary and sends
* cancelations to the controller.
* cancellations to the controller.
*
* @param myTarget
* to override {@link UpdateAction}s
@@ -512,7 +517,7 @@ public class DeploymentManagement {
* action
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_COMMITTED)
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET)
public Action cancelAction(@NotNull final Action action, @NotNull final Target target) {
LOG.debug("cancelAction({}, {})", action, target);
@@ -569,7 +574,7 @@ public class DeploymentManagement {
* in case the given action is not active
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_COMMITTED)
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET)
public Action forceQuitAction(@NotNull final Action action) {
final Action mergedAction = entityManager.merge(action);
@@ -614,7 +619,7 @@ public class DeploymentManagement {
* the rolloutgroup for this action
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_COMMITTED)
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET)
public void createScheduledAction(final List<Target> targets, final DistributionSet distributionSet,
final ActionType actionType, final long forcedTime, final Rollout rollout,
@@ -648,7 +653,7 @@ public class DeploymentManagement {
* @return the action which has been started
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_COMMITTED)
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET + SpringEvalExpressions.HAS_AUTH_OR
+ SpringEvalExpressions.IS_SYSTEM_CODE)
public Action startScheduledAction(@NotNull final Action action) {
@@ -911,7 +916,7 @@ public class DeploymentManagement {
* @return the updated or the found {@link TargetAction}
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET)
public Action forceTargetAction(final Long actionId) {
final Action action = actionRepository.findOne(actionId);
@@ -923,7 +928,7 @@ public class DeploymentManagement {
}
/**
* retrieves all the {@link ActionStatus} entries of the given
* Retrieves all the {@link ActionStatus} entries of the given
* {@link Action} and {@link Target}.
*
* @param pageReq
@@ -981,11 +986,11 @@ public class DeploymentManagement {
* @param rollout
* the rollout the actions belong to
* @param rolloutGroupParent
* the parent rolloutgroup the actions should reference
* the parent rollout group 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
* rollout group in a specific status
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET + SpringEvalExpressions.HAS_AUTH_OR
+ SpringEvalExpressions.IS_SYSTEM_CODE)

View File

@@ -59,6 +59,7 @@ 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.Transactional;
import org.springframework.validation.annotation.Validated;
@@ -69,7 +70,7 @@ import com.google.common.eventbus.EventBus;
* Business facade for managing the {@link DistributionSet}s.
*
*/
@Transactional(readOnly = true)
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
@Validated
@Service
public class DistributionSetManagement {
@@ -142,7 +143,7 @@ public class DistributionSetManagement {
* the assignment outcome.
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@NotNull
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY)
public DistributionSetTagAssignmentResult toggleTagAssignment(@NotEmpty final List<DistributionSet> sets,
@@ -164,7 +165,7 @@ public class DistributionSetManagement {
* the assignment outcome.
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@NotNull
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY)
public DistributionSetTagAssignmentResult toggleTagAssignment(@NotEmpty final Collection<Long> dsIds,
@@ -229,7 +230,7 @@ public class DistributionSetManagement {
* @throw DataDependencyViolationException in case of illegal update
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY)
public DistributionSet updateDistributionSet(@NotNull final DistributionSet ds) {
checkNotNull(ds.getId());
@@ -254,7 +255,7 @@ public class DistributionSetManagement {
* to delete
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_DELETE_REPOSITORY)
public void deleteDistributionSet(@NotNull final DistributionSet set) {
deleteDistributionSet(set.getId());
@@ -269,7 +270,7 @@ public class DistributionSetManagement {
* to be deleted
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_DELETE_REPOSITORY)
public void deleteDistributionSet(@NotEmpty final Long... distributionSetIDs) {
final List<Long> toHardDelete = new ArrayList<>();
@@ -310,7 +311,7 @@ public class DistributionSetManagement {
* {@link SoftwareModule}s.
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_CREATE_REPOSITORY)
public DistributionSet createDistributionSet(@NotNull final DistributionSet dSet) {
prepareDsSave(dSet);
@@ -344,7 +345,7 @@ public class DistributionSetManagement {
* {@link SoftwareModule}s.
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_CREATE_REPOSITORY)
public List<DistributionSet> createDistributionSets(@NotNull final Iterable<DistributionSet> distributionSets) {
for (final DistributionSet ds : distributionSets) {
@@ -366,7 +367,7 @@ public class DistributionSetManagement {
* @return the updated {@link DistributionSet}.
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY)
public DistributionSet assignSoftwareModules(@NotNull final DistributionSet ds,
final Set<SoftwareModule> softwareModules) {
@@ -388,7 +389,7 @@ public class DistributionSetManagement {
* @return the updated {@link DistributionSet}.
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY)
public DistributionSet unassignSoftwareModule(@NotNull final DistributionSet ds,
final SoftwareModule softwareModule) {
@@ -413,7 +414,7 @@ public class DistributionSetManagement {
* s while the DS type is already in use.
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY)
public DistributionSetType updateDistributionSetType(@NotNull final DistributionSetType dsType) {
checkNotNull(dsType.getId());
@@ -715,7 +716,7 @@ public class DistributionSetManagement {
* @return created {@link Entity}
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_CREATE_REPOSITORY)
public DistributionSetType createDistributionSetType(@NotNull final DistributionSetType type) {
if (type.getId() != null) {
@@ -732,7 +733,7 @@ public class DistributionSetManagement {
* to delete
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_DELETE_REPOSITORY)
public void deleteDistributionSetType(@NotNull final DistributionSetType type) {
@@ -755,7 +756,7 @@ public class DistributionSetManagement {
* in case the meta data entry already exists for the specific
* key
*/
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Modifying
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY)
public DistributionSetMetadata createDistributionSetMetadata(@NotNull final DistributionSetMetadata metadata) {
@@ -780,7 +781,7 @@ public class DistributionSetManagement {
* in case one of the meta data entry already exists for the
* specific key
*/
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Modifying
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY)
public List<DistributionSetMetadata> createDistributionSetMetadata(
@@ -802,7 +803,7 @@ public class DistributionSetManagement {
* in case the meta data entry does not exists and cannot be
* updated
*/
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Modifying
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY)
public DistributionSetMetadata updateDistributionSetMetadata(@NotNull final DistributionSetMetadata metadata) {
@@ -820,7 +821,7 @@ public class DistributionSetManagement {
* @param id
* the ID of the distribution set meta data to delete
*/
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Modifying
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY)
public void deleteDistributionSetMetadata(@NotNull final DsMetadataCompositeKey id) {
@@ -913,7 +914,7 @@ public class DistributionSetManagement {
* @return created {@link Entity}
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_CREATE_REPOSITORY)
public List<DistributionSetType> createDistributionSetTypes(@NotNull final Collection<DistributionSetType> types) {
return types.stream().map(this::createDistributionSetType).collect(Collectors.toList());
@@ -926,7 +927,7 @@ public class DistributionSetManagement {
* @param softwareModules
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY)
public void checkDistributionSetAlreadyUse(final DistributionSet distributionSet) {
checkDistributionSetSoftwareModulesIsAllowedToModify(distributionSet);
@@ -992,14 +993,14 @@ public class DistributionSetManagement {
}
}
private Boolean isDSWithNoTagSelected(final DistributionSetFilter distributionSetFilter) {
private static Boolean isDSWithNoTagSelected(final DistributionSetFilter distributionSetFilter) {
if (distributionSetFilter.getSelectDSWithNoTag() != null && distributionSetFilter.getSelectDSWithNoTag()) {
return true;
}
return false;
}
private Boolean isTagsSelected(final DistributionSetFilter distributionSetFilter) {
private static Boolean isTagsSelected(final DistributionSetFilter distributionSetFilter) {
if (distributionSetFilter.getTagNames() != null && !distributionSetFilter.getTagNames().isEmpty()) {
return true;
}
@@ -1033,7 +1034,7 @@ public class DistributionSetManagement {
}
}
private void throwMetadataKeyAlreadyExists(final String metadataKey) {
private static void throwMetadataKeyAlreadyExists(final String metadataKey) {
throw new EntityAlreadyExistsException("Metadata entry with key '" + metadataKey + "' already exists");
}
@@ -1048,7 +1049,7 @@ public class DistributionSetManagement {
* @return list of assigned ds
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@NotNull
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET)
public List<DistributionSet> assignTag(@NotEmpty final Collection<Long> dsIds,
@@ -1077,7 +1078,7 @@ public class DistributionSetManagement {
* @return list of unassigned ds
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@NotNull
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET)
public List<DistributionSet> unAssignAllDistributionSetsByTag(@NotNull final DistributionSetTag tag) {
@@ -1095,7 +1096,7 @@ public class DistributionSetManagement {
* @return the unassigned ds or <null> if no ds is unassigned
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET)
public DistributionSet unAssignTag(@NotNull final Long dsId, @NotNull final DistributionSetTag distributionSetTag) {
final List<DistributionSet> allDs = findDistributionSetListWithDetails(Arrays.asList(dsId));

View File

@@ -12,6 +12,7 @@ import org.eclipse.hawkbit.repository.model.DistributionSetMetadata;
import org.eclipse.hawkbit.repository.model.DsMetadataCompositeKey;
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;
/**
@@ -20,7 +21,7 @@ import org.springframework.transaction.annotation.Transactional;
*
*
*/
@Transactional(readOnly = true)
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
public interface DistributionSetMetadataRepository
extends PagingAndSortingRepository<DistributionSetMetadata, DsMetadataCompositeKey>,
JpaSpecificationExecutor<DistributionSetMetadata> {

View File

@@ -21,6 +21,7 @@ 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;
/**
@@ -29,7 +30,7 @@ import org.springframework.transaction.annotation.Transactional;
*
*
*/
@Transactional(readOnly = true)
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
public interface DistributionSetRepository
extends BaseEntityRepository<DistributionSet, Long>, JpaSpecificationExecutor<DistributionSet> {
@@ -50,7 +51,7 @@ public interface DistributionSetRepository
* to be deleted
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Query("update DistributionSet d set d.deleted = 1 where d.id in :ids")
void deleteDistributionSet(@Param("ids") Long... ids);
@@ -62,7 +63,7 @@ public interface DistributionSetRepository
* @return number of affected/deleted records
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
// Workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=349477
@Query("DELETE FROM DistributionSet d WHERE d.id IN ?1")
int deleteByIdIn(Collection<Long> ids);
@@ -82,7 +83,7 @@ public interface DistributionSetRepository
* yet to an {@link UpdateAction}, i.e. unused.
*
* @param ids
* to searcgh for
* to search for
* @return
*/
@Query("select ac.distributionSet.id from Action ac where ac.distributionSet.id in :ids")

View File

@@ -15,15 +15,14 @@ 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)
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
public interface DistributionSetTagRepository
extends BaseEntityRepository<DistributionSetTag, Long>, JpaSpecificationExecutor<DistributionSetTag> {
/**
@@ -34,7 +33,7 @@ public interface DistributionSetTagRepository
* @return 1 if tag was deleted
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
Long deleteByName(final String tagName);
/**

View File

@@ -14,16 +14,14 @@ 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)
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
public interface DistributionSetTypeRepository
extends BaseEntityRepository<DistributionSetType, Long>, JpaSpecificationExecutor<DistributionSetType> {

View File

@@ -13,7 +13,6 @@ import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.transaction.Transactional;
import org.eclipse.hawkbit.repository.model.TargetInfo;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
@@ -21,16 +20,16 @@ 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
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
public class EclipseLinkTargetInfoRepository implements TargetInfoRepository {
@Autowired
@@ -38,6 +37,7 @@ public class EclipseLinkTargetInfoRepository implements TargetInfoRepository {
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public void setTargetUpdateStatus(final TargetUpdateStatus status, final List<Long> targets) {
final Query query = entityManager.createQuery(
"update TargetInfo ti set ti.updateStatus = :status where ti.targetId in :targets and ti.updateStatus != :status");
@@ -48,6 +48,7 @@ public class EclipseLinkTargetInfoRepository implements TargetInfoRepository {
@Override
@Modifying
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@CacheEvict(value = { "targetStatus", "distributionUsageInstalled", "targetsLastPoll" }, allEntries = true)
public <S extends TargetInfo> S save(final S entity) {
@@ -61,6 +62,7 @@ public class EclipseLinkTargetInfoRepository implements TargetInfoRepository {
@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

View File

@@ -9,16 +9,14 @@
package org.eclipse.hawkbit.repository;
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)
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
public interface ExternalArtifactProviderRepository extends BaseEntityRepository<ExternalArtifactProvider, Long> {
}

View File

@@ -11,15 +11,14 @@ package org.eclipse.hawkbit.repository;
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)
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
public interface ExternalArtifactRepository extends BaseEntityRepository<ExternalArtifact, Long> {
/**

View File

@@ -15,13 +15,14 @@ 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)
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
public interface LocalArtifactRepository extends BaseEntityRepository<LocalArtifact, Long> {
/**

View File

@@ -48,14 +48,15 @@ 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;
/**
* Service layer for generating SP reportings.
* Service layer for generating hawkBit reports.
*
*/
@Transactional(readOnly = true)
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
@Validated
@Service
public class ReportManagement {
@@ -404,7 +405,7 @@ public class ReportManagement {
return innerOuterReport;
}
private final class InnerOuter {
private static final class InnerOuter {
final DSName name;
long count;
final List<InnerOuter> outer;
@@ -433,9 +434,6 @@ public class ReportManagement {
/**
* Object contains the name and the id of an entity.
*
*
*
*
*/
private static final class DSName {
@@ -510,9 +508,6 @@ public class ReportManagement {
* Return DateTypes.
*/
public static final class DateTypes implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
private static final PerMonth PER_MONTH = new PerMonth();

View File

@@ -43,6 +43,7 @@ import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
@@ -52,7 +53,7 @@ import org.springframework.validation.annotation.Validated;
*/
@Validated
@Service
@Transactional(readOnly = true)
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
public class RolloutGroupManagement {
@Autowired
@@ -189,7 +190,7 @@ public class RolloutGroupManagement {
final ListJoin<Target, RolloutTargetGroup> rolloutTargetJoin = root.join(Target_.rolloutTargetGroup);
return criteriaBuilder.and(specification.toPredicate(root, query, criteriaBuilder),
criteriaBuilder.equal(rolloutTargetJoin.get(RolloutTargetGroup_.rolloutGroup), rolloutGroup));
} , page);
}, page);
}
/**
@@ -213,7 +214,7 @@ public class RolloutGroupManagement {
return targetRepository.findByActionsRolloutGroup(rolloutGroup, page);
}
private boolean isRolloutStatusReady(final RolloutGroup rolloutGroup) {
private static boolean isRolloutStatusReady(final RolloutGroup rolloutGroup) {
return rolloutGroup != null && RolloutStatus.READY.equals(rolloutGroup.getRollout().getStatus());
}
@@ -259,5 +260,4 @@ public class RolloutGroupManagement {
.collect(Collectors.toList());
return new PageImpl<>(targetWithActionStatus, pageRequest, totalCount);
}
}
}

View File

@@ -18,12 +18,13 @@ 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)
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
public interface RolloutGroupRepository
extends BaseEntityRepository<RolloutGroup, Long>, JpaSpecificationExecutor<RolloutGroup> {

View File

@@ -58,6 +58,8 @@ import org.springframework.security.access.prepost.PreAuthorize;
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;
@@ -72,7 +74,7 @@ import org.springframework.validation.annotation.Validated;
@Validated
@Service
@EnableScheduling
@Transactional(readOnly = true)
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
public class RolloutManagement {
private static final Logger LOGGER = LoggerFactory.getLogger(RolloutManagement.class);
@@ -134,7 +136,7 @@ public class RolloutManagement {
/**
* Retrieves all rollouts.
*
*
* @param page
* the page request to sort and limit the result
* @return a page of found rollouts
@@ -146,7 +148,7 @@ public class RolloutManagement {
/**
* Retrieves all rollouts found by the given specification.
*
*
* @param specification
* the specification to filter rollouts
* @param page
@@ -163,7 +165,7 @@ public class RolloutManagement {
/**
* Retrieves a specific rollout by its ID.
*
*
* @param rolloutId
* the ID of the rollout to retrieve
* @return the founded rollout or {@code null} if rollout with given ID does
@@ -180,13 +182,13 @@ public class RolloutManagement {
* which are effected by this rollout to create. The targets will then be
* split up into groups. The size of the groups can be defined in the
* {@code groupSize} parameter.
*
*
* The rollout is not started. Only the preparation of the rollout is done,
* persisting and creating all the necessary groups. The Rollout and the
* groups are persisted in {@link RolloutStatus#READY} and
* {@link RolloutGroupStatus#READY} so they can be started
* {@link #startRollout(Rollout)}.
*
*
* @param rollout
* the rollout entity to create
* @param amountGroup
@@ -195,11 +197,11 @@ public class RolloutManagement {
* the rolloutgroup conditions and actions which should be
* applied for each {@link RolloutGroup}
* @return the persisted rollout.
*
*
* @throws IllegalArgumentException
* in case the given groupSize is zero or lower.
*/
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Modifying
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE)
public Rollout createRollout(final Rollout rollout, final int amountGroup,
@@ -215,22 +217,22 @@ public class RolloutManagement {
* will be done synchronously and will be returned. The targets will then be
* split up into groups. The size of the groups can be defined in the
* {@code groupSize} parameter.
*
*
* The creation of the rollout groups is executed asynchronously due it
* might take some time to split up the targets into groups. The creation of
* the {@link RolloutGroup} is published as event
* {@link RolloutGroupCreatedEvent}.
*
*
* The rollout is in status {@link RolloutStatus#CREATING} until all rollout
* groups has been created and the targets are split up, then the rollout
* will change the status to {@link RolloutStatus#READY}.
*
*
* The rollout is not started. Only the preparation of the rollout is done,
* persisting and creating all the necessary groups. The Rollout and the
* groups are persisted in {@link RolloutStatus#READY} and
* {@link RolloutGroupStatus#READY} so they can be started
* {@link #startRollout(Rollout)}.
*
*
* @param rollout
* the rollout to be created
* @param amountGroup
@@ -242,7 +244,7 @@ public class RolloutManagement {
* @return the created rollout entity in state
* {@link RolloutStatus#CREATING}
*/
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Modifying
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE)
public Rollout createRolloutAsync(final Rollout rollout, final int amountGroup,
@@ -259,13 +261,7 @@ public class RolloutManagement {
entityManager.flush();
executor.execute(() -> {
try {
final DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setName("creatingRollout");
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
new TransactionTemplate(txManager, def).execute(status -> {
createRolloutGroups(amountGroup, conditions, savedRollout);
return null;
});
createRolloutGroupsInNewTransaction(amountGroup, conditions, savedRollout);
} finally {
creatingRollouts.remove(savedRollout.getName());
}
@@ -280,7 +276,7 @@ public class RolloutManagement {
return rolloutRepository.save(rollout);
}
private void verifyRolloutGroupParameter(final int amountGroup) {
private static void verifyRolloutGroupParameter(final int amountGroup) {
if (amountGroup <= 0) {
throw new IllegalArgumentException("the amountGroup must be greater than zero");
} else if (amountGroup > 500) {
@@ -288,13 +284,22 @@ public class RolloutManagement {
}
}
private Rollout createRolloutGroupsInNewTransaction(final int amountOfGroups,
final RolloutGroupConditions conditions, final Rollout 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 amountGroup
*
* @param amountOfGroups
* the amount of groups
* @param conditions
* the rollout group conditions
@@ -302,17 +307,17 @@ public class RolloutManagement {
* the rollout
* @return the rollout with created groups
*/
private Rollout createRolloutGroups(final int amountGroup, final RolloutGroupConditions conditions,
private Rollout createRolloutGroups(final int amountOfGroups, final RolloutGroupConditions conditions,
final Rollout savedRollout) {
int pageIndex = 0;
int groupIndex = 0;
final Long totalCount = savedRollout.getTotalTargets();
final int groupSize = (int) Math.ceil((double) totalCount / (double) amountGroup);
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 = amountGroup;
int amountGroupValidated = amountOfGroups;
final int amountGroupCreation = (int) (Math.ceil((double) totalCount / (double) groupSize));
if (amountGroupCreation == (amountGroup - 1)) {
if (amountGroupCreation == (amountOfGroups - 1)) {
amountGroupValidated--;
}
RolloutGroup lastSavedGroup = null;
@@ -356,17 +361,19 @@ public class RolloutManagement {
* for each affected target in the rollout. The actions of the first group
* will be started immediately {@link RolloutGroupStatus#RUNNING} as the
* other groups will be {@link RolloutGroupStatus#SCHEDULED} state.
*
*
* The rollout itself will be then also in {@link RolloutStatus#RUNNING}.
*
*
* @param rollout
* the rollout to be started
*
*
* @return started rollout
*
* @throws RolloutIllegalStateException
* if given rollout is not in {@link RolloutStatus#READY}. Only
* ready rollouts can be started.
*/
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Modifying
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE + SpringEvalExpressions.HAS_AUTH_OR
+ SpringEvalExpressions.IS_SYSTEM_CODE)
@@ -383,12 +390,14 @@ public class RolloutManagement {
* actions of the first group will be started immediately
* {@link RolloutGroupStatus#RUNNING} as the other groups will be
* {@link RolloutGroupStatus#SCHEDULED} state.
*
*
* The rollout itself will be then also in {@link RolloutStatus#RUNNING}.
*
*
* @param rollout
* the rollout to be started
*
*
* @return the started rollout
*
* @throws RolloutIllegalStateException
* if given rollout is not in {@link RolloutStatus#READY}. Only
* ready rollouts can be started.
@@ -456,19 +465,19 @@ public class RolloutManagement {
* {@link RolloutGroupStatus#SCHEDULED} will not be started and keep in
* {@link RolloutGroupStatus#SCHEDULED} state until the rollout is
* {@link RolloutManagement#resumeRollout(Rollout)}.
*
*
* Switching the rollout status to {@link RolloutStatus#PAUSED} is
* sufficient due the {@link #checkRunningRollouts(long)} will not check
* this rollout anymore.
*
*
* @param rollout
* the rollout to be paused.
*
*
* @throws RolloutIllegalStateException
* if given rollout is not in {@link RolloutStatus#RUNNING}.
* Only running rollouts can be paused.
*/
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Modifying
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE + SpringEvalExpressions.HAS_AUTH_OR
+ SpringEvalExpressions.IS_SYSTEM_CODE)
@@ -491,14 +500,14 @@ public class RolloutManagement {
* Resumes a paused rollout. The rollout switches back to
* {@link RolloutStatus#RUNNING} state which is then picked up again by the
* {@link #checkRunningRollouts(long)}.
*
*
* @param rollout
* the rollout to be resumed
* @throws RolloutIllegalStateException
* if given rollout is not in {@link RolloutStatus#PAUSED}. Only
* paused rollouts can be resumed.
*/
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Modifying
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE + SpringEvalExpressions.HAS_AUTH_OR
+ SpringEvalExpressions.IS_SYSTEM_CODE)
@@ -516,7 +525,7 @@ public class RolloutManagement {
* Checking running rollouts. Rollouts which are checked updating the
* {@link Rollout#setLastCheck(long)} to indicate that the current instance
* is handling the specific rollout. This code should run as system-code.
*
*
* <pre>
* {@code
* SystemSecurityContext.runAsSystem(new Callable<Void>() {
@@ -526,21 +535,21 @@ public class RolloutManagement {
* });
* }
* </pre>
*
*
* This method is attend to be called by a scheduler.
* {@link RolloutScheduler}. And must be running in an transaction so it's
* splitted from the scheduler.
*
*
* Rollouts which are currently running are investigated, by means the
* error- and finish condition of running groups in this rollout are
* evaluated.
*
*
* @param delayBetweenChecks
* the time in milliseconds of the delay between the further and
* this check. This check is only applied if the last check is
* less than (lastcheck-delay).
*/
@Transactional
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_UNCOMMITTED)
@Modifying
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE + SpringEvalExpressions.HAS_AUTH_OR
+ SpringEvalExpressions.IS_SYSTEM_CODE)
@@ -723,7 +732,7 @@ public class RolloutManagement {
/**
* Count rollouts by specified filter text.
*
*
* @param searchText
* name or description
* @return total count rollouts for specified filter text.
@@ -745,7 +754,7 @@ public class RolloutManagement {
/**
* * Retrieves a specific rollout by its ID.
*
*
* @param pageable
* the page request to sort and limit the result
* @param searchText
@@ -763,7 +772,7 @@ public class RolloutManagement {
/**
* Retrieves a specific rollout by its name.
*
*
* @param rolloutName
* the name of the rollout to retrieve
* @return the founded rollout or {@code null} if rollout with given name
@@ -776,14 +785,14 @@ public class RolloutManagement {
/**
* Update rollout details.
*
*
* @param rollout
* rollout to be updated
*
*
* @return Rollout updated rollout
*/
@NotNull
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Modifying
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE)
public Rollout updateRollout(@NotNull final Rollout rollout) {
@@ -793,7 +802,7 @@ public class RolloutManagement {
/**
* 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
@@ -845,7 +854,7 @@ public class RolloutManagement {
}
}
private void checkIfRolloutCanStarted(final Rollout rollout, final Rollout mergedRollout) {
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());
@@ -855,7 +864,7 @@ public class RolloutManagement {
/***
* Get finished percentage details for a specified group which is in running
* state.
*
*
* @param rolloutId
* the ID of the {@link Rollout}
* @param rolloutGroup

View File

@@ -18,12 +18,13 @@ 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)
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
public interface RolloutRepository extends BaseEntityRepository<Rollout, Long>, JpaSpecificationExecutor<Rollout> {
/**
@@ -40,7 +41,7 @@ public interface RolloutRepository extends BaseEntityRepository<Rollout, Long>,
* @return the count of the updated rows. Zero if no row has been updated
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Query("UPDATE Rollout 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);

View File

@@ -12,11 +12,14 @@ import org.eclipse.hawkbit.repository.model.RolloutTargetGroup;
import org.eclipse.hawkbit.repository.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

@@ -52,6 +52,7 @@ 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.Transactional;
import org.springframework.validation.annotation.Validated;
@@ -62,7 +63,7 @@ import com.google.common.collect.Sets;
* Business facade for managing {@link SoftwareModule}s.
*
*/
@Transactional(readOnly = true)
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
@Validated
@Service
public class SoftwareManagement {
@@ -108,7 +109,7 @@ public class SoftwareManagement {
* of {@link SoftwareModule#getId()} is <code>null</code>
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY)
public SoftwareModule updateSoftwareModule(@NotNull final SoftwareModule sm) {
checkNotNull(sm.getId());
@@ -138,7 +139,7 @@ public class SoftwareManagement {
* @return updated {@link Entity}
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY)
public SoftwareModuleType updateSoftwareModuleType(@NotNull final SoftwareModuleType sm) {
checkNotNull(sm.getId());
@@ -283,7 +284,7 @@ public class SoftwareManagement {
* is the {@link SoftwareModule} to be deleted
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_DELETE_REPOSITORY)
public void deleteSoftwareModule(@NotNull final SoftwareModule bsm) {
@@ -314,10 +315,10 @@ public class SoftwareManagement {
* Deletes {@link SoftwareModule}s which is any if the given ids.
*
* @param ids
* of the Software Moduels to be deleted
* of the Software Modules to be deleted
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_DELETE_REPOSITORY)
public void deleteSoftwareModules(@NotNull final Iterable<Long> ids) {
final List<SoftwareModule> swModulesToDelete = softwareModuleRepository.findByIdIn(ids);
@@ -579,7 +580,7 @@ public class SoftwareManagement {
return new SliceImpl<>(resultList);
}
private List<Specification<SoftwareModule>> buildSpecificationList(final String searchText,
private static List<Specification<SoftwareModule>> buildSpecificationList(final String searchText,
final SoftwareModuleType type) {
final List<Specification<SoftwareModule>> specList = new ArrayList<>();
if (!Strings.isNullOrEmpty(searchText)) {
@@ -700,7 +701,7 @@ public class SoftwareManagement {
* @return created {@link Entity}
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_CREATE_REPOSITORY)
public SoftwareModuleType createSoftwareModuleType(@NotNull final SoftwareModuleType type) {
if (type.getId() != null) {
@@ -718,7 +719,7 @@ public class SoftwareManagement {
* @return created {@link Entity}
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_CREATE_REPOSITORY)
public List<SoftwareModuleType> createSoftwareModuleType(@NotNull final Collection<SoftwareModuleType> types) {
return types.stream().map(this::createSoftwareModuleType).collect(Collectors.toList());
@@ -731,7 +732,7 @@ public class SoftwareManagement {
* to delete
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_DELETE_REPOSITORY)
public void deleteSoftwareModuleType(@NotNull final SoftwareModuleType type) {
@@ -785,7 +786,7 @@ public class SoftwareManagement {
* in case the meta data entry already exists for the specific
* key
*/
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Modifying
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY)
public SoftwareModuleMetadata createSoftwareModuleMetadata(@NotNull final SoftwareModuleMetadata metadata) {
@@ -810,7 +811,7 @@ public class SoftwareManagement {
* in case one of the meta data entry already exists for the
* specific key
*/
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Modifying
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY)
public List<SoftwareModuleMetadata> createSoftwareModuleMetadata(
@@ -832,7 +833,7 @@ public class SoftwareManagement {
* in case the meta data entry does not exists and cannot be
* updated
*/
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Modifying
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY)
public SoftwareModuleMetadata updateSoftwareModuleMetadata(@NotNull final SoftwareModuleMetadata metadata) {
@@ -851,7 +852,7 @@ public class SoftwareManagement {
* @param id
* the ID of the software module meta data to delete
*/
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Modifying
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY)
public void deleteSoftwareModuleMetadata(@NotNull final SwMetadataCompositeKey id) {
@@ -925,7 +926,7 @@ public class SoftwareManagement {
}
}
private void throwMetadataKeyAlreadyExists(final String metadataKey) {
private static void throwMetadataKeyAlreadyExists(final String metadataKey) {
throw new EntityAlreadyExistsException("Metadata entry with key '" + metadataKey + "' already exists");
}

View File

@@ -16,15 +16,14 @@ 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)
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
public interface SoftwareModuleMetadataRepository
extends PagingAndSortingRepository<SoftwareModuleMetadata, SwMetadataCompositeKey>,
JpaSpecificationExecutor<SoftwareModuleMetadata> {

View File

@@ -21,15 +21,14 @@ 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)
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
public interface SoftwareModuleRepository
extends BaseEntityRepository<SoftwareModule, Long>, JpaSpecificationExecutor<SoftwareModule> {
@@ -69,7 +68,7 @@ public interface SoftwareModuleRepository
*
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Query("UPDATE SoftwareModule 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);

View File

@@ -12,16 +12,14 @@ 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)
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
public interface SoftwareModuleTypeRepository
extends BaseEntityRepository<SoftwareModuleType, Long>, JpaSpecificationExecutor<SoftwareModuleType> {

View File

@@ -35,6 +35,7 @@ import org.springframework.context.annotation.Bean;
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;
@@ -43,7 +44,7 @@ import org.springframework.validation.annotation.Validated;
* Central system management operations of the SP server.
*
*/
@Transactional(readOnly = true)
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
@Validated
@Service
public class SystemManagement {
@@ -164,6 +165,7 @@ public class SystemManagement {
* @return the {@link CurrentTenantKeyGenerator}
*/
@Bean
@Transactional(propagation = Propagation.SUPPORTS)
public CurrentTenantKeyGenerator currentTenantKeyGenerator() {
return new CurrentTenantKeyGenerator();
}
@@ -179,7 +181,7 @@ public class SystemManagement {
* @return
*/
@Cacheable(value = "tenantMetadata", key = "#tenant.toUpperCase()")
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Modifying
@NotNull
public TenantMetaData getTenantMetadata(@NotNull final String tenant) {
@@ -206,7 +208,6 @@ public class SystemManagement {
@NotNull
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_SYSTEM_ADMIN + SpringEvalExpressions.HAS_AUTH_OR
+ SpringEvalExpressions.IS_SYSTEM_CODE)
// tenant independent
public List<String> findTenants() {
return tenantMetaDataRepository.findAll().stream().map(md -> md.getTenant()).collect(Collectors.toList());
}
@@ -218,10 +219,9 @@ public class SystemManagement {
* to delete
*/
@CacheEvict(value = { "tenantMetadata" }, key = "#tenant.toUpperCase()")
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Modifying
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_SYSTEM_ADMIN)
// tenant independent
public void deleteTenant(@NotNull final String tenant) {
cacheManager.evictCaches(tenant);
cacheManager.getCache("currentTenant").evict(currentTenantKeyGenerator().generate(null, null));
@@ -250,7 +250,7 @@ public class SystemManagement {
* @return {@link TenantMetaData} of {@link TenantAware#getCurrentTenant()}
*/
@Cacheable(value = "tenantMetadata", keyGenerator = "tenantKeyGenerator")
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Modifying
@NotNull
public TenantMetaData getTenantMetadata() {
@@ -279,7 +279,7 @@ public class SystemManagement {
// 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)
@Transactional(propagation = Propagation.NOT_SUPPORTED, isolation = Isolation.READ_UNCOMMITTED)
public String currentTenant() {
final String initialTenantCreation = createInitialTenant.get();
if (initialTenantCreation == null) {
@@ -298,7 +298,7 @@ public class SystemManagement {
* @return updated {@link TenantMetaData} entity
*/
@CachePut(value = "tenantMetadata", key = "#metaData.tenant.toUpperCase()")
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Modifying
@NotNull
public TenantMetaData updateTenantMetadata(@NotNull final TenantMetaData metaData) {
@@ -342,6 +342,8 @@ public class SystemManagement {
*/
private 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) {

View File

@@ -38,21 +38,17 @@ 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.Transactional;
import org.springframework.validation.annotation.Validated;
import com.google.common.eventbus.EventBus;
/**
*
* Mangement service class for {@link Tag}s.
*
*
*
*
* Management service class for {@link Tag}s.
*
*/
@Transactional(readOnly = true)
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
@Validated
@Service
public class TagManagement {
@@ -102,7 +98,7 @@ public class TagManagement {
* if given object already exists
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@NotNull
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_CREATE_TARGET)
public TargetTag createTargetTag(@NotNull final TargetTag targetTag) {
@@ -133,7 +129,7 @@ public class TagManagement {
* if given object has already an ID.
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@NotNull
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_CREATE_TARGET)
public List<TargetTag> createTargetTags(@NotNull final Iterable<TargetTag> targetTags) {
@@ -155,7 +151,7 @@ public class TagManagement {
* tag name of the {@link TargetTag} to be deleted
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_DELETE_TARGET)
public void deleteTargetTag(@NotEmpty final String targetTagName) {
final TargetTag tag = targetTagRepository.findByNameEquals(targetTagName);
@@ -220,7 +216,7 @@ public class TagManagement {
* @return the new {@link TargetTag}
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@NotNull
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET)
public TargetTag updateTargetTag(@NotNull final TargetTag targetTag) {
@@ -254,7 +250,7 @@ public class TagManagement {
*
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_CREATE_REPOSITORY)
public DistributionSetTag createDistributionSetTag(@NotNull final DistributionSetTag distributionSetTag) {
if (null != distributionSetTag.getId()) {
@@ -282,7 +278,7 @@ public class TagManagement {
* if a given entity already exists
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_CREATE_REPOSITORY)
public List<DistributionSetTag> createDistributionSetTags(
@NotNull final Iterable<DistributionSetTag> distributionSetTags) {
@@ -306,7 +302,7 @@ public class TagManagement {
* to be deleted
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_DELETE_REPOSITORY)
public void deleteDistributionSetTag(@NotEmpty final String tagName) {
final DistributionSetTag tag = distributionSetTagRepository.findByNameEquals(tagName);
@@ -335,7 +331,7 @@ public class TagManagement {
* of {@link DistributionSetTag#getName()} is <code>null</code>
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@NotNull
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY)
public DistributionSetTag updateDistributionSetTag(@NotNull final DistributionSetTag distributionSetTag) {

View File

@@ -26,6 +26,7 @@ import org.springframework.data.jpa.domain.Specifications;
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.Transactional;
import org.springframework.util.Assert;
import org.springframework.validation.annotation.Validated;
@@ -35,10 +36,8 @@ import com.google.common.base.Strings;
/**
* Business service facade for managing {@link TargetFilterQuery}s.
*
*
*
*/
@Transactional(readOnly = true)
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
@Validated
@Service
public class TargetFilterQueryManagement {
@@ -53,7 +52,7 @@ public class TargetFilterQueryManagement {
* @return the created {@link TargetFilterQuery}
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@NotNull
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_CREATE_TARGET)
public TargetFilterQuery createTargetFilterQuery(@NotNull final TargetFilterQuery customTargetFilter) {
@@ -71,7 +70,7 @@ public class TargetFilterQueryManagement {
* IDs of target filter query to be deleted
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_DELETE_TARGET)
public void deleteTargetFilterQuery(@NotNull final Long targetFilterQueryId) {
targetFilterQueryRepository.delete(targetFilterQueryId);
@@ -161,7 +160,7 @@ public class TargetFilterQueryManagement {
* @return the updated {@link TargetFilterQuery}
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@NotNull
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET)
public TargetFilterQuery updateTargetFilterQuery(@NotNull final TargetFilterQuery targetFilterQuery) {

View File

@@ -12,13 +12,14 @@ 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)
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
public interface TargetFilterQueryRepository
extends BaseEntityRepository<TargetFilterQuery, Long>, JpaSpecificationExecutor<TargetFilterQuery> {

View File

@@ -12,7 +12,6 @@ import java.util.Collection;
import java.util.List;
import javax.persistence.Entity;
import javax.transaction.Transactional;
import org.eclipse.hawkbit.repository.model.TargetInfo;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
@@ -20,15 +19,16 @@ 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 {
/**
@@ -41,7 +41,7 @@ public interface TargetInfoRepository {
* to set it for
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Query("update TargetInfo 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);
@@ -63,7 +63,7 @@ public interface TargetInfoRepository {
* to delete
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@CacheEvict(value = { "targetStatus", "distributionUsageInstalled", "targetsLastPoll" }, allEntries = true)
void deleteByTargetIdIn(final Collection<Long> targetIDs);
}

View File

@@ -62,6 +62,7 @@ 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.Transactional;
import org.springframework.util.Assert;
import org.springframework.validation.annotation.Validated;
@@ -74,7 +75,7 @@ import com.google.common.eventbus.EventBus;
* Business service facade for managing {@link Target}s.
*
*/
@Transactional(readOnly = true)
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
@Validated
@Service
public class TargetManagement {
@@ -260,7 +261,7 @@ public class TargetManagement {
* @return the updated {@link Target}
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@NotNull
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET + SpringEvalExpressions.HAS_AUTH_OR
+ SpringEvalExpressions.IS_CONTROLLER)
@@ -278,7 +279,7 @@ public class TargetManagement {
* @return the updated {@link Target}s
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@NotNull
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET + SpringEvalExpressions.HAS_AUTH_OR
+ SpringEvalExpressions.IS_CONTROLLER)
@@ -294,7 +295,7 @@ public class TargetManagement {
* the technical IDs of the targets to be deleted
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_DELETE_TARGET)
public void deleteTargets(@NotEmpty final Long... targetIDs) {
// we need to select the target IDs first to check the if the targetIDs
@@ -527,11 +528,11 @@ public class TargetManagement {
* @param targets
* to toggle for
* @param tag
* to toogle
* @return TagAssigmentResult with all metadata of the assigment outcome.
* to toggle
* @return TagAssigmentResult with all metadata of the assignment outcome.
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@NotNull
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET)
public TargetTagAssignmentResult toggleTagAssignment(@NotEmpty final List<Target> targets,
@@ -553,7 +554,7 @@ public class TargetManagement {
* @return TagAssigmentResult with all metadata of the assigment outcome.
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@NotNull
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET)
public TargetTagAssignmentResult toggleTagAssignment(@NotEmpty final Collection<String> targetIds,
@@ -596,7 +597,7 @@ public class TargetManagement {
* @return list of assigned targets
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@NotNull
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET)
public List<Target> assignTag(@NotEmpty final Collection<String> targetIds, @NotNull final TargetTag tag) {
@@ -635,7 +636,7 @@ public class TargetManagement {
* @return list of unassigned targets
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@NotNull
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET)
public List<Target> unAssignAllTargetsByTag(@NotNull final TargetTag tag) {
@@ -652,7 +653,7 @@ public class TargetManagement {
* @return the unassigned target or <null> if no target is unassigned
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET)
public Target unAssignTag(@NotNull final String controllerID, @NotNull final TargetTag targetTag) {
final List<Target> allTargets = targetRepository
@@ -934,7 +935,7 @@ public class TargetManagement {
* @return
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@NotNull
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_CREATE_TARGET + SpringEvalExpressions.HAS_AUTH_OR
+ SpringEvalExpressions.IS_CONTROLLER)
@@ -972,7 +973,7 @@ public class TargetManagement {
*
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@NotNull
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_CREATE_TARGET + SpringEvalExpressions.HAS_AUTH_OR
+ SpringEvalExpressions.IS_CONTROLLER)
@@ -996,7 +997,7 @@ public class TargetManagement {
* already exist.
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@NotNull
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_CREATE_TARGET)
public List<Target> createTargets(@NotNull final List<Target> targets) {
@@ -1028,7 +1029,7 @@ public class TargetManagement {
* @return newly created target
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@NotNull
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_CREATE_TARGET)
public List<Target> createTargets(@NotNull final Collection<Target> targets,

View File

@@ -27,15 +27,14 @@ 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)
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
public interface TargetRepository extends BaseEntityRepository<Target, Long>, JpaSpecificationExecutor<Target> {
/**
@@ -64,7 +63,7 @@ public interface TargetRepository extends BaseEntityRepository<Target, Long>, Jp
* to be deleted
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
// Workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=349477
@Query("DELETE FROM Target t WHERE t.id IN ?1")
void deleteByIdIn(final Collection<Long> targetIDs);
@@ -153,7 +152,7 @@ public interface TargetRepository extends BaseEntityRepository<Target, Long>, Jp
*/
@Override
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@CacheEvict(value = { "targetStatus", "distributionUsageInstalled", "targetsLastPoll" }, allEntries = true)
<S extends Target> List<S> save(Iterable<S> entities);
@@ -167,7 +166,7 @@ public interface TargetRepository extends BaseEntityRepository<Target, Long>, Jp
*/
@Override
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@CacheEvict(value = { "targetStatus", "distributionUsageInstalled", "targetsLastPoll" }, allEntries = true)
<S extends Target> S save(S entity);
@@ -276,7 +275,7 @@ public interface TargetRepository extends BaseEntityRepository<Target, Long>, Jp
* to update
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Query("UPDATE Target t SET t.assignedDistributionSet = :set, t.lastModifiedAt = :lastModifiedAt, t.lastModifiedBy = :lastModifiedBy WHERE t.id IN :targets")
void setAssignedDistributionSet(@Param("set") DistributionSet set, @Param("lastModifiedAt") Long modifiedAt,
@Param("lastModifiedBy") String modifiedBy, @Param("targets") Collection<Long> targets);

View File

@@ -13,15 +13,14 @@ import java.util.List;
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)
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
public interface TargetTagRepository
extends BaseEntityRepository<TargetTag, Long>, JpaSpecificationExecutor<TargetTag> {
@@ -33,7 +32,7 @@ public interface TargetTagRepository
* @return 1 if tag was deleted
*/
@Modifying
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
Long deleteByName(final String tagName);
/**

View File

@@ -24,18 +24,19 @@ import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.env.Environment;
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.Transactional;
import org.springframework.validation.annotation.Validated;
/**
* Central tenant configuration management operations of the SP server.
*/
@Transactional(readOnly = true)
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
@Validated
@Service
public class TenantConfigurationManagement implements EnvironmentAware {
private static final TenantConfigurationManagement INSTANCE = new TenantConfigurationManagement();
@Autowired
private TenantConfigurationRepository tenantConfigurationRepository;
@@ -46,16 +47,6 @@ public class TenantConfigurationManagement implements EnvironmentAware {
private Environment environment;
/**
* Get Singleton instance, needed for classes which are not managed in
* Spring context
*
* @return singleton instance of TenantConfigurationManagement
*/
public static TenantConfigurationManagement getInstance() {
return INSTANCE;
}
/**
* Retrieves a configuration value from the e.g. tenant overwritten
* configuration values or in case the tenant does not a have a specific
@@ -160,7 +151,8 @@ public class TenantConfigurationManagement implements EnvironmentAware {
* if the property cannot be converted to the given
* {@code propertyType}
*/
@PreAuthorize(value = SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION)
@PreAuthorize(value = SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION + SpringEvalExpressions.HAS_AUTH_OR
+ SpringEvalExpressions.IS_SYSTEM_CODE)
public TenantConfigurationValue<?> getConfigurationValue(final TenantConfigurationKey configurationKey) {
return getConfigurationValue(configurationKey, configurationKey.getDataType());
}
@@ -185,7 +177,8 @@ public class TenantConfigurationManagement implements EnvironmentAware {
* if the property cannot be converted to the given
* {@code propertyType}
*/
@PreAuthorize(value = SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION)
@PreAuthorize(value = SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION + SpringEvalExpressions.HAS_AUTH_OR
+ SpringEvalExpressions.IS_SYSTEM_CODE)
public <T> T getGlobalConfigurationValue(final TenantConfigurationKey configurationKey,
final Class<T> propertyType) {
@@ -221,7 +214,7 @@ public class TenantConfigurationManagement implements EnvironmentAware {
* if the property cannot be converted to the given
*/
@CacheEvict(value = "tenantConfiguration", key = "#configurationKey.getKeyName()")
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Modifying
@PreAuthorize(value = SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION)
public <T> TenantConfigurationValue<T> addOrUpdateConfiguration(final TenantConfigurationKey configurationKey,
@@ -264,7 +257,7 @@ public class TenantConfigurationManagement implements EnvironmentAware {
* the configuration key to be deleted
*/
@CacheEvict(value = "tenantConfiguration", key = "#configurationKey.getKeyName()")
@Transactional
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
@Modifying
@PreAuthorize(value = SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION)
public void deleteConfiguration(final TenantConfigurationKey configurationKey) {

View File

@@ -11,13 +11,14 @@ package org.eclipse.hawkbit.repository;
import java.util.List;
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)
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
public interface TenantConfigurationRepository extends BaseEntityRepository<TenantConfiguration, Long> {
/**

View File

@@ -12,16 +12,14 @@ import java.util.List;
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)
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
public interface TenantMetaDataRepository extends PagingAndSortingRepository<TenantMetaData, Long> {
/**

View File

@@ -24,13 +24,18 @@ import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import org.eclipse.persistence.annotations.ExistenceChecking;
import org.eclipse.persistence.annotations.ExistenceType;
/**
* @author Michael Hirsch
*
* 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;

View File

@@ -38,7 +38,8 @@ import javax.persistence.OneToOne;
import javax.persistence.Table;
import javax.persistence.Transient;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
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;
@@ -245,18 +246,21 @@ public class TargetInfo implements Persistable<Long>, Serializable {
if (lastTargetQuery == null) {
return null;
}
final Duration pollTime = DurationHelper.formattedStringToDuration(TenantConfigurationManagement.getInstance()
.getConfigurationValue(TenantConfigurationKey.POLLING_TIME_INTERVAL, String.class).getValue());
final Duration overdueTime = DurationHelper.formattedStringToDuration(TenantConfigurationManagement
.getInstance().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);
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);
});
}
/**

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.repository.model.helper;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.springframework.beans.factory.annotation.Autowired;
/**
* A singleton bean which holds {@link SystemSecurityContext} service and makes
* it accessible to beans which are not managed by spring, e.g. JPA entities.
*/
public final class SystemSecurityContextHolder {
private static final SystemSecurityContextHolder INSTANCE = new SystemSecurityContextHolder();
@Autowired
private SystemSecurityContext systemSecurityContext;
private SystemSecurityContextHolder() {
}
/**
* @return the singleton {@link SystemSecurityContextHolder} instance
*/
public static SystemSecurityContextHolder getInstance() {
return INSTANCE;
}
/**
* @return the {@link SystemSecurityContext} service
*/
public SystemSecurityContext getSystemSecurityContext() {
return systemSecurityContext;
}
}

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.model.helper;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.springframework.beans.factory.annotation.Autowired;
/**
* A singleton bean which holds {@link TenantConfigurationManagement} service
* and makes it accessible to beans which are not managed by spring, e.g. JPA
* entities.
*/
public final class TenantConfigurationManagementHolder {
private static final TenantConfigurationManagementHolder INSTANCE = new TenantConfigurationManagementHolder();
@Autowired
private TenantConfigurationManagement tenantConfiguration;
private TenantConfigurationManagementHolder() {
}
/**
* @return the singleton {@link TenantConfigurationManagementHolder}
* instance
*/
public static TenantConfigurationManagementHolder getInstance() {
return INSTANCE;
}
/**
* @return the {@link TenantConfigurationManagement} service
*/
public TenantConfigurationManagement getTenantConfigurationManagement() {
return tenantConfiguration;
}
}

View File

@@ -66,10 +66,6 @@ import cz.jirutka.rsql.parser.ast.RSQLVisitor;
* <li>name==targetId1,description==plugAndPlay,updateStatus==UNKNOWN</li>
* <li>name==targetId1 or description==plugAndPlay or updateStatus==UNKNOWN</li>
* </ul>
*
*
*
*
*/
public final class RSQLUtility {
@@ -279,6 +275,9 @@ public final class RSQLUtility {
}
@Override
// Exception squid:S2095 - see
// https://jira.sonarsource.com/browse/SONARJAVA-1478
@SuppressWarnings({ "squid:S2095" })
public List<Predicate> visit(final ComparisonNode node, final String param) {
A fieldName = null;
try {
@@ -304,6 +303,9 @@ public final class RSQLUtility {
return mapToPredicate(node, fieldPath, node.getArguments(), transformedValue, fieldName);
}
// Exception squid:S2095 - see
// https://jira.sonarsource.com/browse/SONARJAVA-1478
@SuppressWarnings({ "squid:S2095" })
private List<String> getExpectedFieldList() {
final List<String> expectedFieldList = Arrays.stream(enumType.getEnumConstants())
.filter(enumField -> enumField.getSubEntityAttributes().isEmpty()).map(enumField -> {
@@ -390,7 +392,9 @@ public final class RSQLUtility {
}
}
@SuppressWarnings({ "rawtypes", "unchecked" })
// Exception squid:S2095 - see
// https://jira.sonarsource.com/browse/SONARJAVA-1478
@SuppressWarnings({ "rawtypes", "unchecked", "squid:S2095" })
private Object transformEnumValue(final ComparisonNode node, final String value,
final Class<? extends Object> javaType) {
final Class<? extends Enum> tmpEnumType = (Class<? extends Enum>) javaType;

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.specifications;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.ListJoin;
import javax.persistence.criteria.SetJoin;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action_;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSet_;
import org.eclipse.hawkbit.repository.model.LocalArtifact;
import org.eclipse.hawkbit.repository.model.LocalArtifact_;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.SoftwareModule_;
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 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<Action> hasTargetAssignedArtifact(final Target target,
final LocalArtifact localArtifact) {
return (actionRoot, query, criteriaBuilder) -> {
final Join<Action, DistributionSet> dsJoin = actionRoot.join(Action_.distributionSet);
final SetJoin<DistributionSet, SoftwareModule> modulesJoin = dsJoin.join(DistributionSet_.modules);
final ListJoin<SoftwareModule, LocalArtifact> artifactsJoin = modulesJoin.join(SoftwareModule_.artifacts);
return criteriaBuilder.and(
criteriaBuilder.equal(artifactsJoin.get(LocalArtifact_.filename), localArtifact.getFilename()),
criteriaBuilder.equal(actionRoot.get(Action_.target), target));
};
}
}

View File

@@ -79,7 +79,7 @@ public class TestConfiguration implements AsyncConfigurer {
}
/**
* Bean for the downlod id cache.
* Bean for the download id cache.
*
* @return the cache
*/

View File

@@ -14,6 +14,7 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
@@ -29,6 +30,7 @@ import javax.validation.ConstraintViolationException;
import org.eclipse.hawkbit.AbstractIntegrationTest;
import org.eclipse.hawkbit.TestDataUtil;
import org.eclipse.hawkbit.WithSpringAuthorityRule;
import org.eclipse.hawkbit.WithUser;
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
import org.eclipse.hawkbit.repository.exception.TenantNotExistException;
@@ -724,4 +726,20 @@ public class TargetManagementTest extends AbstractIntegrationTest {
assertThat(25).as("Targets with no tag").isEqualTo(targetsListWithNoTag.size());
}
@Test
@Description("Tests the a target can be read with only the read target permission")
public void targetCanBeReadWithOnlyReadTargetPermission() throws Exception {
final String knownTargetControllerId = "readTarget";
controllerManagament.findOrRegisterTargetIfItDoesNotexist(knownTargetControllerId, new URI("http://127.0.0.1"));
securityRule.runAs(WithSpringAuthorityRule.withUser("bumlux", "READ_TARGET"), () -> {
final Target findTargetByControllerID = targetManagement.findTargetByControllerID(knownTargetControllerId);
assertThat(findTargetByControllerID).isNotNull();
assertThat(findTargetByControllerID.getTargetInfo()).isNotNull();
assertThat(findTargetByControllerID.getTargetInfo().getPollStatus()).isNotNull();
return null;
});
}
}

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