From 380370c02653ad3ac08c3ff0232ed7b419f5e784 Mon Sep 17 00:00:00 2001 From: Michael Hirsch Date: Thu, 21 Jan 2016 17:56:26 +0100 Subject: [PATCH 1/7] extend device simulator of create DDI and DMF devices and a Vaadin UI for manage them. Signed-off-by: Michael Hirsch --- examples/hawkbit-device-simulator/README.md | 19 + examples/hawkbit-device-simulator/pom.xml | 44 ++- .../src/main/images/generateScreenshot.png | Bin 0 -> 55292 bytes .../main/images/updateProcessScreenshot.png | Bin 0 -> 48274 bytes .../images/updateResultOverviewScreenshot.png | Bin 0 -> 70522 bytes .../simulator/AbstractSimulatedDevice.java | 180 +++++++++ .../hawkbit/simulator/DDISimulatedDevice.java | 111 ++++++ .../hawkbit/simulator/DMFSimulatedDevice.java | 29 ++ .../hawkbit/simulator/DeviceSimulator.java | 18 +- .../simulator/DeviceSimulatorRepository.java | 123 +++++++ .../simulator/DeviceSimulatorUpdater.java | 122 +++++++ .../simulator/NextPollTimeController.java | 78 ++++ .../simulator/SimulatedDeviceFactory.java | 88 +++++ .../simulator/SimulationController.java | 11 +- .../simulator/amqp/AmqpConfiguration.java | 3 + .../simulator/amqp/SpReceiverService.java | 39 +- .../hawkbit/simulator/event/InitUpdate.java | 40 ++ .../event/NextPollCounterUpdate.java | 42 +++ .../simulator/event/ProgressUpdate.java | 41 +++ .../simulator/http/ControllerResource.java | 88 +++++ .../http/GatewayTokenInterceptor.java | 36 ++ .../hawkbit/simulator/ui/GenerateDialog.java | 233 ++++++++++++ .../hawkbit/simulator/ui/SimulatorUI.java | 62 ++++ .../hawkbit/simulator/ui/SimulatorView.java | 341 ++++++++++++++++++ .../src/main/resources/application.properties | 4 +- .../VAADIN/themes/simulator/styles.scss | 32 ++ 26 files changed, 1767 insertions(+), 17 deletions(-) create mode 100644 examples/hawkbit-device-simulator/src/main/images/generateScreenshot.png create mode 100644 examples/hawkbit-device-simulator/src/main/images/updateProcessScreenshot.png create mode 100644 examples/hawkbit-device-simulator/src/main/images/updateResultOverviewScreenshot.png create mode 100644 examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/AbstractSimulatedDevice.java create mode 100644 examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DDISimulatedDevice.java create mode 100644 examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DMFSimulatedDevice.java create mode 100644 examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DeviceSimulatorRepository.java create mode 100644 examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DeviceSimulatorUpdater.java create mode 100644 examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/NextPollTimeController.java create mode 100644 examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/SimulatedDeviceFactory.java create mode 100644 examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/event/InitUpdate.java create mode 100644 examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/event/NextPollCounterUpdate.java create mode 100644 examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/event/ProgressUpdate.java create mode 100644 examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/http/ControllerResource.java create mode 100644 examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/http/GatewayTokenInterceptor.java create mode 100644 examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/ui/GenerateDialog.java create mode 100644 examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/ui/SimulatorUI.java create mode 100644 examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/ui/SimulatorView.java create mode 100644 examples/hawkbit-device-simulator/src/main/webapp/VAADIN/themes/simulator/styles.scss diff --git a/examples/hawkbit-device-simulator/README.md b/examples/hawkbit-device-simulator/README.md index b5db3d7ef..610a1d256 100644 --- a/examples/hawkbit-device-simulator/README.md +++ b/examples/hawkbit-device-simulator/README.md @@ -20,6 +20,25 @@ The simulator has user authentication enabled by default. Default credentials: This can be configured/disabled by spring boot properties ## Usage + +### Graphical User Interface +The device simulator comes with a graphical user interface which makes it very easy to generate dummy devices handled by the device simulator. +The status and the update progress of the simulated device are shown in the UI. +The UI can be accessed via the URL: +``` +http://localhost:8083 +``` + +`Basic Authentication Credentials are admin / admin` + + ![](src/main/images/generateScreenshot.png) + + ![](src/main/images/updateProcessScreenshot.png) + + ![](src/main/images/updateResultOverviewScreenshot.png) + + +### REST API The device simulator exposes an REST-API which can be used to trigger device creation. Optional parameters: diff --git a/examples/hawkbit-device-simulator/pom.xml b/examples/hawkbit-device-simulator/pom.xml index 9e9fc86dc..eaec9b91e 100644 --- a/examples/hawkbit-device-simulator/pom.xml +++ b/examples/hawkbit-device-simulator/pom.xml @@ -80,10 +80,52 @@ org.springframework.boot spring-boot-starter-log4j2 + + com.vaadin + vaadin-spring-boot-starter + 1.0.0 + + + com.vaadin + vaadin-push + org.springframework.boot spring-boot-autoconfigure + + org.springframework.boot + spring-boot-autoconfigure + + + com.google.guava + guava + 19.0 + + + com.netflix.feign + feign-jackson + 8.14.1 + + + com.netflix.feign + feign-core + 8.12.1 + + + com.jayway.jsonpath + json-path + - + + + + com.vaadin + vaadin-bom + 7.5.5 + pom + import + + + diff --git a/examples/hawkbit-device-simulator/src/main/images/generateScreenshot.png b/examples/hawkbit-device-simulator/src/main/images/generateScreenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..6c31c2d7d975a0958373359149269c477d0d9f5f GIT binary patch literal 55292 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 M$6$BH&Xd3WAAXgOOaK4? literal 0 HcmV?d00001 diff --git a/examples/hawkbit-device-simulator/src/main/images/updateResultOverviewScreenshot.png b/examples/hawkbit-device-simulator/src/main/images/updateResultOverviewScreenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..78e16758f09e4949e8173cba51276ff2e9e322cd GIT binary patch literal 70522 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 HbnAZsIjSJU literal 0 HcmV?d00001 diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/AbstractSimulatedDevice.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/AbstractSimulatedDevice.java new file mode 100644 index 000000000..474acb6c4 --- /dev/null +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/AbstractSimulatedDevice.java @@ -0,0 +1,180 @@ +/** + * 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; + +/** + * The bean of a simulated device which can be stored in the + * {@link DeviceSimulatorRepository} or shown in the UI. + * + * @author Michael Hirsch + * + */ +public abstract class AbstractSimulatedDevice { + + private String id; + private String tenant; + private Status status; + private double progress; + private String swversion = "unknown"; + private ResponseStatus responseStatus = ResponseStatus.SUCCESSFUL; + private Protocol protocol = Protocol.DMF_AMQP; + + private int nextPollCounterSec; + + /** + * Enum definition of the protocol to be used for the simulated device. + * + * @author Michael Hirsch + * + */ + public enum Protocol { + /** + * Device Management Federation API via AMQP, push mechanism. + */ + DMF_AMQP, + /** + * Direct Device Interface via HTTP, poll mechanism. + */ + DDI_HTTP; + } + + /** + * The current status of the simulated device. + * + * @author Michael Hirsch + * + */ + public enum Status { + /** + * device is in status unknown. + */ + UNKNWON, + /** + * device is in status pending which represents is updating software. + */ + PEDNING, + /** + * device has been updated successfully. + */ + FINISH, + /** + * device has been updated with an error. + */ + 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. + */ + AbstractSimulatedDevice() { + + } + + /** + * Creates a new simulated device. + * + * @param id + * the ID of the simulated device + * @param tenant + * the tenant of the simulated device + */ + AbstractSimulatedDevice(final String id, final String tenant, final Protocol protocol) { + this.id = id; + this.tenant = tenant; + this.status = Status.UNKNWON; + this.progress = 0.0; + this.protocol = protocol; + } + + /** + * Method to clean-up resource e.g. when the simulated device has been + * removed from the repository. + */ + public void clean() { + + } + + public String getId() { + return id; + } + + public Status getStatus() { + return status; + } + + public double getProgress() { + return progress; + } + + public String getTenant() { + return tenant; + } + + public void setId(final String id) { + this.id = id; + } + + public void setTenant(final String tenant) { + this.tenant = tenant; + } + + public void setStatus(final Status status) { + this.status = status; + } + + public void setProgress(final double progress) { + this.progress = progress; + } + + public String getSwversion() { + return swversion; + } + + public void setSwversion(final String swversion) { + this.swversion = swversion; + } + + public ResponseStatus getResponseStatus() { + return responseStatus; + } + + public void setResponseStatus(final ResponseStatus responseStatus) { + this.responseStatus = responseStatus; + } + + public Protocol getProtocol() { + return protocol; + } + + public int getNextPollCounterSec() { + return nextPollCounterSec; + } + + public void setNextPollCounterSec(final int nextPollDelayInSec) { + this.nextPollCounterSec = nextPollDelayInSec; + } +} diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DDISimulatedDevice.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DDISimulatedDevice.java new file mode 100644 index 000000000..1417c3153 --- /dev/null +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DDISimulatedDevice.java @@ -0,0 +1,111 @@ +/** + * 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.concurrent.ScheduledExecutorService; + +import org.eclipse.hawkbit.simulator.DeviceSimulatorUpdater.UpdaterCallback; +import org.eclipse.hawkbit.simulator.http.ControllerResource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.jayway.jsonpath.JsonPath; +import com.jayway.jsonpath.PathNotFoundException; + +/** + * @author Michael Hirsch + * + */ +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 volatile boolean removed; + private volatile Long currentActionId; + private final DeviceSimulatorUpdater deviceUpdater; + + /** + * @param id + * the ID of the device + * @param tenant + * the tenant of the simulated device + * @param pollDelaySec + * 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) { + super(id, tenant, Protocol.DDI_HTTP); + this.pollDelaySec = pollDelaySec; + this.controllerResource = controllerResource; + this.pollthreadpool = pollthreadpool; + this.deviceUpdater = deviceUpdater; + setNextPollCounterSec(pollDelaySec); + } + + @Override + public void clean() { + super.clean(); + removed = true; + } + + public int getPollDelaySec() { + return pollDelaySec; + } + + /** + * Polls the base URL for the DDI API interface. + */ + public void poll() { + if (!removed) { + 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("?"))); + 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, new UpdaterCallback() { + @Override + public void updateFinished(final AbstractSimulatedDevice device, final Long actionId) { + switch (device.getResponseStatus()) { + case SUCCESSFUL: + controllerResource.postSuccessFeedback(getTenant(), getId(), actionId); + break; + case ERROR: + controllerResource.postErrorFeedback(getTenant(), getId(), actionId); + break; + default: + throw new IllegalStateException("simulated device has an unknown response status + " + + device.getResponseStatus()); + } + currentActionId = null; + } + }); + } + } catch (final PathNotFoundException e) { + // href might not be in the json response, so ignore + // exception here. + LOGGER.trace("Response does not contain a deploymentbase href link, ignoring."); + } + + } + } +} diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DMFSimulatedDevice.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DMFSimulatedDevice.java new file mode 100644 index 000000000..b9fdc827c --- /dev/null +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DMFSimulatedDevice.java @@ -0,0 +1,29 @@ +/** + * 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; + +/** + * An simulated device using the DMF API of the hawkbit update server. + * + * @author Michael Hirsch + * + */ +public class DMFSimulatedDevice extends AbstractSimulatedDevice { + + /** + * @param id + * the ID of the device + * @param tenant + * the tenant of the simulated device + */ + public DMFSimulatedDevice(final String id, final String tenant) { + super(id, tenant, Protocol.DMF_AMQP); + } + +} diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DeviceSimulator.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DeviceSimulator.java index 37812a19d..944ba1d07 100644 --- a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DeviceSimulator.java +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DeviceSimulator.java @@ -8,8 +8,15 @@ */ package org.eclipse.hawkbit.simulator; +import java.util.concurrent.Executors; + import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; + +import com.google.common.eventbus.AsyncEventBus; +import com.google.common.eventbus.EventBus; +import com.vaadin.spring.annotation.EnableVaadin; /** * The main-method to start the Spring-Boot application. @@ -18,12 +25,21 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; * */ @SpringBootApplication +@EnableVaadin public class DeviceSimulator { - private DeviceSimulator() { + public DeviceSimulator() { // utility class } + /** + * @return an asynchronous event bus to publish and retrieve events. + */ + @Bean + public EventBus eventBus() { + return new AsyncEventBus(Executors.newFixedThreadPool(4)); + } + /** * Start the Spring Boot Application. * diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DeviceSimulatorRepository.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DeviceSimulatorRepository.java new file mode 100644 index 000000000..68db9df45 --- /dev/null +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DeviceSimulatorRepository.java @@ -0,0 +1,123 @@ +/** + * 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.Collection; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * An in-memory simulated device repository to hold the simulated device in + * memory and be able to retrieve them again. + * + * @author Michael Hirsch + * + */ +@Service +public class DeviceSimulatorRepository { + + private final Map devices = new LinkedHashMap<>(); + + @Autowired + private SimulatedDeviceFactory deviceFactory; + + /** + * Adds a simulated device to the repository. + * + * @param simulatedDevice + * the device to add + * @return the device which has been added to the repository + */ + public AbstractSimulatedDevice add(final AbstractSimulatedDevice simulatedDevice) { + devices.put(new DeviceKey(simulatedDevice.getTenant().toLowerCase(), simulatedDevice.getId()), simulatedDevice); + return simulatedDevice; + } + + /** + * @return all simulated devices + */ + public Collection getAll() { + return devices.values(); + } + + /** + * Retrieves a single simulated devices or {@code null} if device does not + * exists. + * + * @param tenant + * the tenant of the simulated device + * @param id + * the ID of the device + * @return a simulated device from the repository or {@code null} if device + * does not exixts. + */ + public AbstractSimulatedDevice get(final String tenant, final String id) { + return devices.get(new DeviceKey(tenant.toLowerCase(), id)); + } + + /** + * Clears all stored devices. + */ + public void clear() { + devices.values().forEach(device -> device.clean()); + devices.clear(); + } + + private static final class DeviceKey { + private final String tenant; + private final String id; + + private DeviceKey(final String tenant, final String id) { + this.tenant = tenant; + this.id = id; + } + + @Override + public int hashCode() {// NOSONAR - as this is generated + final int prime = 31; + int result = 1; + result = prime * result + ((id == null) ? 0 : id.hashCode()); + result = prime * result + ((tenant == null) ? 0 : tenant.hashCode()); + return result; + } + + @Override + public boolean equals(final Object obj) {// NOSONAR - as this is + // generated + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final DeviceKey other = (DeviceKey) obj; + if (id == null) { + if (other.id != null) { + return false; + } + } else if (!id.equals(other.id)) { + return false; + } + if (tenant == null) { + if (other.tenant != null) { + return false; + } + } else if (!tenant.equals(other.tenant)) { + return false; + } + return true; + } + } +} diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DeviceSimulatorUpdater.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DeviceSimulatorUpdater.java new file mode 100644 index 000000000..6e93b7d04 --- /dev/null +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DeviceSimulatorUpdater.java @@ -0,0 +1,122 @@ +/** + * 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.Random; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import org.eclipse.hawkbit.simulator.amqp.SpSenderService; +import org.eclipse.hawkbit.simulator.event.InitUpdate; +import org.eclipse.hawkbit.simulator.event.ProgressUpdate; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.google.common.eventbus.EventBus; + +/** + * @author Michael Hirsch + * + */ +@Service +public class DeviceSimulatorUpdater { + + private static final ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(4); + + @Autowired + private SpSenderService spSenderService; + + @Autowired + private EventBus eventbus; + + @Autowired + private DeviceSimulatorRepository repository; + + /** + * Starting an simulated update process of an simulated device. + * + * @param tenant + * the tenant of the device + * @param id + * the ID of the simulated device + * @param actionId + * the actionId from the hawkbit update server to start the + * update. + * @param swVersion + * the software module version from the hawkbit update server + * @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 AbstractSimulatedDevice device = repository.get(tenant, id); + device.setProgress(0.0); + device.setSwversion(swVersion); + eventbus.post(new InitUpdate(device)); + threadPool.schedule(new DeviceSimulatorUpdateThread(device, spSenderService, actionId, eventbus, callback), + 2000, TimeUnit.MILLISECONDS); + } + + private static final class DeviceSimulatorUpdateThread implements Runnable { + private static final Random rndSleep = new Random(); + + private final AbstractSimulatedDevice device; + private final SpSenderService spSenderService; + private final long actionId; + private final EventBus eventbus; + private final UpdaterCallback callback; + + private DeviceSimulatorUpdateThread(final AbstractSimulatedDevice device, + final SpSenderService spSenderService, final long actionId, final EventBus eventbus, + final UpdaterCallback callback) { + this.device = device; + this.spSenderService = spSenderService; + this.actionId = actionId; + this.eventbus = eventbus; + this.callback = callback; + } + + @Override + public void run() { + final double newProgress = device.getProgress() + 0.2; + device.setProgress(newProgress); + if (newProgress < 1.0) { + threadPool.schedule(new DeviceSimulatorUpdateThread(device, spSenderService, actionId, eventbus, + callback), rndSleep.nextInt(3000), TimeUnit.MILLISECONDS); + } else { + callback.updateFinished(device, actionId); + } + eventbus.post(new ProgressUpdate(device)); + } + } + + /** + * 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 + * + */ + @FunctionalInterface + public interface UpdaterCallback { + /** + * Callback method to indicate that the simulated update process has + * been finished. + * + * @param device + * the device which has been updated + * @param actionId + * the ID of the action from the hawkbit update server + */ + void updateFinished(AbstractSimulatedDevice device, final Long actionId); + } +} diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/NextPollTimeController.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/NextPollTimeController.java new file mode 100644 index 000000000..81acf897e --- /dev/null +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/NextPollTimeController.java @@ -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.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +import org.eclipse.hawkbit.simulator.event.NextPollCounterUpdate; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import com.google.common.eventbus.EventBus; + +/** + * Poll time trigger which executes the {@link DDISimulatedDevice#poll()} every + * second. + * + * @author Michael Hirsch + * + */ +@Component +public class NextPollTimeController { + + private static final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1); + private static final ExecutorService pollService = Executors.newFixedThreadPool(1); + + @Autowired + private DeviceSimulatorRepository repository; + + @Autowired + private EventBus eventBus; + + /** + * Constructor which schedules the poll trigger runnable every second. + */ + public NextPollTimeController() { + executorService.scheduleWithFixedDelay(new NextPollUpdaterRunnable(), 1, 1, TimeUnit.SECONDS); + } + + private class NextPollUpdaterRunnable implements Runnable { + @Override + public void run() { + final List devices = repository.getAll().stream() + .filter(device -> device instanceof DDISimulatedDevice).collect(Collectors.toList()); + + devices.forEach(device -> { + int nextCounter = device.getNextPollCounterSec() - 1; + if (nextCounter < 0) { + if (device instanceof DDISimulatedDevice) { + try { + pollService.submit(new Runnable() { + @Override + public void run() { + ((DDISimulatedDevice) device).poll(); + } + }); + } catch (final Exception e) { + + } + nextCounter = ((DDISimulatedDevice) device).getPollDelaySec(); + } + } + device.setNextPollCounterSec(nextCounter); + }); + eventBus.post(new NextPollCounterUpdate(devices)); + } + } +} diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/SimulatedDeviceFactory.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/SimulatedDeviceFactory.java new file mode 100644 index 000000000..d3e080806 --- /dev/null +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/SimulatedDeviceFactory.java @@ -0,0 +1,88 @@ +/** + * 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.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; +import org.eclipse.hawkbit.simulator.http.GatewayTokenInterceptor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import feign.Feign; +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; + + /** + * Creating a simulated devices. + * + * @param id + * the ID of the simulated device + * @param tenant + * the tenant of the simulated device + * @param protocol + * the protocol of the device + * @return the created simulated device + */ + public AbstractSimulatedDevice createSimulatedDevice(final String id, final String tenant, final Protocol protocol) { + return createSimulatedDevice(id, tenant, protocol, 30, null, null); + } + + /** + * Creating a simulated device. + * + * @param id + * the ID of the simulated device + * @param tenant + * the tenant of the simulated device + * @param protocol + * the protocol which should be used be the simulated device + * @param pollDelaySec + * the poll delay time in seconds which should be used for + * {@link DDISimulatedDevice}s + * @param baseEndpoint + * the http base endpoint which should be used for + * {@link DDISimulatedDevice}s + * @param gatewayToken + * the gatewayToken to be used to authenticate + * {@link DDISimulatedDevice}s at the endpoint + * @return the created simulated device + */ + public AbstractSimulatedDevice createSimulatedDevice(final String id, final String tenant, final Protocol protocol, + final int pollDelaySec, final URL baseEndpoint, final String gatewayToken) { + switch (protocol) { + case DMF_AMQP: + return new DMFSimulatedDevice(id, tenant); + case DDI_HTTP: + 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); + default: + throw new IllegalArgumentException("Protocol " + protocol + " unknown"); + } + } +} diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/SimulationController.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/SimulationController.java index ab8847cb1..6f94bd319 100644 --- a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/SimulationController.java +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/SimulationController.java @@ -8,6 +8,7 @@ */ package org.eclipse.hawkbit.simulator; +import org.eclipse.hawkbit.simulator.AbstractSimulatedDevice.Protocol; import org.eclipse.hawkbit.simulator.amqp.SpSenderService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; @@ -26,6 +27,12 @@ public class SimulationController { @Autowired private SpSenderService spSenderService; + @Autowired + private DeviceSimulatorRepository repository; + + @Autowired + private SimulatedDeviceFactory deviceFactory; + /** * The start resource to start a device creation. * @@ -43,7 +50,9 @@ public class SimulationController { @RequestParam(value = "tenant", defaultValue = "DEFAULT") final String tenant) { for (int i = 0; i < amount; i++) { - spSenderService.createOrUpdateThing(tenant, name + i); + final String deviceId = name + i; + repository.add(deviceFactory.createSimulatedDevice(deviceId, tenant, Protocol.DMF_AMQP)); + spSenderService.createOrUpdateThing(tenant, deviceId); } return "Updated " + amount + " DMF connected targets!"; diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/amqp/AmqpConfiguration.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/amqp/AmqpConfiguration.java index c4968c849..492bb3857 100644 --- a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/amqp/AmqpConfiguration.java +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/amqp/AmqpConfiguration.java @@ -132,6 +132,9 @@ public class AmqpConfiguration { final SimpleRabbitListenerContainerFactory containerFactory = new SimpleRabbitListenerContainerFactory(); containerFactory.setDefaultRequeueRejected(false); containerFactory.setConnectionFactory(connectionFactory); + containerFactory.setConcurrentConsumers(20); + containerFactory.setMaxConcurrentConsumers(20); + containerFactory.setPrefetchCount(20); return containerFactory; } diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/amqp/SpReceiverService.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/amqp/SpReceiverService.java index 5884a2645..6f0ac732e 100644 --- a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/amqp/SpReceiverService.java +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/amqp/SpReceiverService.java @@ -15,6 +15,9 @@ import org.eclipse.hawkbit.dmf.amqp.api.MessageHeaderKey; import org.eclipse.hawkbit.dmf.amqp.api.MessageType; import org.eclipse.hawkbit.dmf.json.model.ActionStatus; import org.eclipse.hawkbit.dmf.json.model.DownloadAndUpdateRequest; +import org.eclipse.hawkbit.simulator.AbstractSimulatedDevice; +import org.eclipse.hawkbit.simulator.DeviceSimulatorUpdater; +import org.eclipse.hawkbit.simulator.DeviceSimulatorUpdater.UpdaterCallback; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.amqp.core.Message; @@ -40,6 +43,8 @@ public class SpReceiverService extends ReceiverService { private final SpSenderService spSenderService; + private final DeviceSimulatorUpdater deviceUpdater; + /** * Constructor. * @@ -51,12 +56,15 @@ public class SpReceiverService extends ReceiverService { * the lwm2mSenderService * @param spSenderService * the spSenderService + * @param deviceUpdater + * the updater service to simulate update process */ @Autowired public SpReceiverService(final RabbitTemplate rabbitTemplate, final AmqpProperties amqpProperties, - final SpSenderService spSenderService) { + final SpSenderService spSenderService, final DeviceSimulatorUpdater deviceUpdater) { super(rabbitTemplate, amqpProperties); this.spSenderService = spSenderService; + this.deviceUpdater = deviceUpdater; } @@ -139,16 +147,23 @@ public class SpReceiverService extends ReceiverService { spSenderService.sendActionStatusMessage(tenant, ActionStatus.RUNNING, "device Simulator retrieved update request. proceeding with simulation.", actionId); - - final SimulatedUpdate update = new SimulatedUpdate(tenant, thingId, actionId); - - try { - Thread.sleep(1_000); - } catch (final InterruptedException e) { - LOGGER.error("Sleep interrupted", e); - } - - spSenderService.finishUpdateProcess(update, "Simulation complete!"); + deviceUpdater.startUpdate(tenant, thingId, actionId, downloadAndUpdateRequest.getSoftwareModules().get(0) + .getModuleVersion(), new UpdaterCallback() { + @Override + public void updateFinished(final AbstractSimulatedDevice device, final Long actionId) { + switch (device.getResponseStatus()) { + case SUCCESSFUL: + spSenderService.finishUpdateProcess(new SimulatedUpdate(device.getTenant(), device.getId(), + actionId), "Simulation complete!"); + break; + case ERROR: + spSenderService.finishUpdateProcessWithError(new SimulatedUpdate(device.getTenant(), + device.getId(), actionId), "Simulation complete with error!"); + break; + default: + break; + } + } + }); } - } diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/event/InitUpdate.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/event/InitUpdate.java new file mode 100644 index 000000000..1cf6dbbda --- /dev/null +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/event/InitUpdate.java @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.simulator.event; + +import org.eclipse.hawkbit.simulator.AbstractSimulatedDevice; + +/** + * Event description which indicates the initialization of an update. + * + * @author Michael Hirsch + * + */ +public class InitUpdate { + + private final AbstractSimulatedDevice device; + + /** + * Creates new progress update event. + * + * @param device + * the device which progress has been updated + */ + public InitUpdate(final AbstractSimulatedDevice device) { + this.device = device; + } + + /** + * @return the device of the event + */ + public AbstractSimulatedDevice getDevice() { + return device; + } + +} diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/event/NextPollCounterUpdate.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/event/NextPollCounterUpdate.java new file mode 100644 index 000000000..b9d7b9027 --- /dev/null +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/event/NextPollCounterUpdate.java @@ -0,0 +1,42 @@ +/** + * 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.event; + +import java.util.List; + +import org.eclipse.hawkbit.simulator.AbstractSimulatedDevice; + +/** + * Event description which indicates an poll time update. + * + * @author Michael Hirsch + * + */ +public class NextPollCounterUpdate { + + private final List devices; + + /** + * Creates poll timer update event. + * + * @param devices + * the devices which progress has been updated + */ + public NextPollCounterUpdate(final List devices) { + this.devices = devices; + } + + /** + * @return the devices of the event + */ + public List getDevices() { + return devices; + } + +} diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/event/ProgressUpdate.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/event/ProgressUpdate.java new file mode 100644 index 000000000..3e34a0fa1 --- /dev/null +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/event/ProgressUpdate.java @@ -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.simulator.event; + +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 { + + private final AbstractSimulatedDevice device; + + /** + * Creates new progress update event. + * + * @param device + * the device which progress has been updated + */ + public ProgressUpdate(final AbstractSimulatedDevice device) { + this.device = device; + } + + /** + * @return the device of the event + */ + public AbstractSimulatedDevice getDevice() { + return device; + } + +} diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/http/ControllerResource.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/http/ControllerResource.java new file mode 100644 index 000000000..1dac4c80b --- /dev/null +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/http/ControllerResource.java @@ -0,0 +1,88 @@ +/** + * 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.http; + +import org.eclipse.hawkbit.simulator.DDISimulatedDevice; + +import feign.Body; +import feign.Headers; +import feign.Param; +import feign.RequestLine; + +/** + * A feign based controller resource interface declaration for + * {@link DDISimulatedDevice}s using over HTTP. + * + * @author Michael Hirsch + * + */ +public interface ControllerResource { + + /** + * The base poll URL for the devices to retrieve if there is an update + * available. + * + * @param tenant + * the tenant of the device + * @param controllerId + * the ID of the device + * @return the plain json response of the http request + */ + @RequestLine("GET /{tenant}/controller/v1/{controllerId}") + @Headers({ "Content-Type: application/json" }) + String get(@Param("tenant") final String tenant, @Param("controllerId") final String controllerId); + + /** + * Retrieving the deployment job response from the hawkbit update server. + * + * @param tenant + * the tenant for the simulated device + * @param controllerId + * the ID of the device + * @param actionId + * the ID of the action to retrieve + * @return the json response of the http request + */ + @RequestLine("GET /{tenant}/controller/v1/{controllerId}/deploymentBase/{actionId}") + @Headers({ "Content-Type: application/json" }) + String getDeployment(@Param("tenant") final String tenant, @Param("controllerId") final String controllerId, + @Param("actionId") final long actionId); + + /** + * Post a success update feedback to the hawkbit update server + * + * @param tenant + * the tenant of the device + * @param controllerId + * the ID of the device + * @param actionId + * the ID of the action to post feedback back + */ + @RequestLine("POST /{tenant}/controller/v1/{controllerId}/deploymentBase/{actionId}/feedback") + @Headers("Content-Type: application/json") + @Body("%7B\"id\":{actionId},\"time\":\"20140511T121314\",\"status\":%7B\"execution\":\"closed\",\"result\":%7B\"finished\":\"success\",\"progress\":%7B%7D%7D%7D%7D") + void postSuccessFeedback(@Param("tenant") final String tenant, @Param("controllerId") final String controllerId, + @Param("actionId") final long actionId); + + /** + * Post a failure update feedback to the hawkbit update server + * + * @param tenant + * the tenant of the device + * @param controllerId + * the ID of the device + * @param actionId + * the ID of the action to post feedback back + */ + @RequestLine("POST /{tenant}/controller/v1/{controllerId}/deploymentBase/{actionId}/feedback") + @Headers("Content-Type: application/json") + @Body("%7B\"id\":{actionId},\"time\":\"20140511T121314\",\"status\":%7B\"execution\":\"closed\",\"result\":%7B\"finished\":\"failure\",\"progress\":%7B%7D%7D%7D%7D") + void postErrorFeedback(@Param("tenant") final String tenant, @Param("controllerId") final String controllerId, + @Param("actionId") final long actionId); +} diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/http/GatewayTokenInterceptor.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/http/GatewayTokenInterceptor.java new file mode 100644 index 000000000..3381481de --- /dev/null +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/http/GatewayTokenInterceptor.java @@ -0,0 +1,36 @@ +/** + * 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.http; + +import feign.RequestInterceptor; +import feign.RequestTemplate; + +/** + * A feign interceptor to apply the gateway-token header to each http-request. + * + * @author Michael Hirsch + * + */ +public class GatewayTokenInterceptor implements RequestInterceptor { + + private final String gatewayToken; + + /** + * @param gatewayToken + * the gatwway token to be used in the http-header + */ + public GatewayTokenInterceptor(final String gatewayToken) { + this.gatewayToken = gatewayToken; + } + + @Override + public void apply(final RequestTemplate template) { + template.header("Authorization", "GatewayToken " + gatewayToken); + } +} diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/ui/GenerateDialog.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/ui/GenerateDialog.java new file mode 100644 index 000000000..0389045b0 --- /dev/null +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/ui/GenerateDialog.java @@ -0,0 +1,233 @@ +/** + * 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.ui; + +import java.net.MalformedURLException; +import java.net.URL; + +import org.eclipse.hawkbit.simulator.AbstractSimulatedDevice.Protocol; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.vaadin.data.Property.ValueChangeEvent; +import com.vaadin.data.Property.ValueChangeListener; +import com.vaadin.data.util.ObjectProperty; +import com.vaadin.data.validator.NullValidator; +import com.vaadin.data.validator.RangeValidator; +import com.vaadin.data.validator.RegexpValidator; +import com.vaadin.server.FontAwesome; +import com.vaadin.ui.Button; +import com.vaadin.ui.Button.ClickEvent; +import com.vaadin.ui.Button.ClickListener; +import com.vaadin.ui.FormLayout; +import com.vaadin.ui.OptionGroup; +import com.vaadin.ui.TextField; +import com.vaadin.ui.Window; + +/** + * Popup dialog window for setting the values of generating the simulated + * devices, e.g. the amount. + * + * @author Michael Hirsch + * + */ +public class GenerateDialog extends Window { + + private static final long serialVersionUID = 1L; + private static final Logger LOGGER = LoggerFactory.getLogger(GenerateDialog.class); + + private final FormLayout formLayout = new FormLayout(); + + /** + * Creates a new pop window for setting the configuration of simulating + * devices. + * + * @param callback + * the callback which is called when the dialog has been + * successfully confirmed. + */ + public GenerateDialog(final GenerateDialogCallback callback) { + + formLayout.setSpacing(true); + formLayout.setMargin(true); + + final TextField tf1 = new TextField("name prefix", "dmfSimulated"); + tf1.setIcon(FontAwesome.INFO); + tf1.setRequired(true); + tf1.addValidator(new NullValidator("Must be given", false)); + + final TextField tf2 = new TextField("amount", new ObjectProperty(10)); + tf2.setIcon(FontAwesome.GEAR); + tf2.setRequired(true); + tf2.addValidator(new RangeValidator("Must be between 1 and 1000", Integer.class, 1, 1000)); + + final TextField tf3 = new TextField("tenant", "default"); + tf3.setIcon(FontAwesome.USER); + tf3.setRequired(true); + tf3.addValidator(new NullValidator("Must be given", false)); + + final TextField tf4 = new TextField("poll delay (sec)", new ObjectProperty(10)); + tf4.setIcon(FontAwesome.CLOCK_O); + tf4.setRequired(true); + tf4.setVisible(false); + tf4.addValidator(new RangeValidator("Must be between 1 and 60", Integer.class, 1, 60)); + + final TextField tf5 = new TextField("base poll URL endpoint", "http://localhost:8080"); + tf5.setColumns(50); + tf5.setIcon(FontAwesome.FLAG_O); + tf5.setRequired(true); + tf5.setVisible(false); + tf5.addValidator(new RegexpValidator( + "^(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]", "is not an URL")); + + final TextField tf6 = new TextField("gateway token", ""); + tf6.setColumns(50); + tf6.setIcon(FontAwesome.FLAG_O); + tf6.setRequired(true); + tf6.setVisible(false); + + final OptionGroup protocolGroup = new OptionGroup("Simulated Device Protocol"); + protocolGroup.addItem(Protocol.DMF_AMQP); + protocolGroup.addItem(Protocol.DDI_HTTP); + protocolGroup.setItemCaption(Protocol.DMF_AMQP, "Device Management Federation API (AMQP push)"); + protocolGroup.setItemCaption(Protocol.DDI_HTTP, "Direct Device Interface (HTTP poll)"); + protocolGroup.setNullSelectionAllowed(false); + protocolGroup.select(Protocol.DMF_AMQP); + protocolGroup.addValueChangeListener(new ValueChangeListener() { + private static final long serialVersionUID = 1L; + + @Override + public void valueChange(final ValueChangeEvent event) { + if (event.getProperty().getValue().equals(Protocol.DDI_HTTP)) { + tf4.setVisible(true); + tf5.setVisible(true); + tf6.setVisible(true); + } else { + tf4.setVisible(false); + tf5.setVisible(false); + tf6.setVisible(false); + } + } + }); + + final Button buttonOk = new Button("generate"); + buttonOk.setImmediate(true); + buttonOk.setIcon(FontAwesome.GEARS); + buttonOk.addClickListener(new ClickListener() { + private static final long serialVersionUID = 1L; + + @Override + public void buttonClick(final ClickEvent event) { + try { + callback.okButton(tf1.getValue(), tf3.getValue(), Integer.valueOf(tf2.getValue().replace(".", "")), + Integer.valueOf(tf4.getValue().replace(".", "")), new URL(tf5.getValue()), tf6.getValue(), + (Protocol) protocolGroup.getValue()); + } catch (final NumberFormatException e) { + LOGGER.info(e.getMessage(), e); + } catch (final MalformedURLException e) { + LOGGER.info(e.getMessage(), e); + } + GenerateDialog.this.close(); + } + }); + + tf1.addValueChangeListener(event -> checkValid(tf1, tf2, tf3, tf4, buttonOk)); + tf2.addValueChangeListener(event -> checkValid(tf1, tf2, tf3, tf4, buttonOk)); + tf3.addValueChangeListener(event -> checkValid(tf1, tf2, tf3, tf4, buttonOk)); + + formLayout.addComponent(tf1); + formLayout.addComponent(tf2); + formLayout.addComponent(tf3); + formLayout.addComponent(protocolGroup); + formLayout.addComponent(tf4); + formLayout.addComponent(tf5); + formLayout.addComponent(tf6); + formLayout.addComponent(buttonOk); + + setCaption("Simulate Devices"); + setContent(formLayout); + setResizable(false); + center(); + } + + private void checkValid(final TextField tf1, final TextField tf2, final TextField tf3, final TextField tf4, + final Button buttonOk) { + if (tf1.isValid() && tf2.isValid() && tf3.isValid() && tf4.isValid()) { + buttonOk.setEnabled(true); + } else { + buttonOk.setEnabled(false); + } + } + + @Override + public int hashCode() {// NOSONAR - as this is generated + final int prime = 31; + int result = super.hashCode(); + result = prime * result + ((formLayout == null) ? 0 : formLayout.hashCode()); + return result; + } + + @Override + public boolean equals(final Object obj) {// NOSONAR - as this is generated + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!super.equals(obj)) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final GenerateDialog other = (GenerateDialog) obj; + if (formLayout == null) { + if (other.formLayout != null) { + return false; + } + } else if (!formLayout.equals(other.formLayout)) { + return false; + } + return true; + } + + /** + * Callback interface to retrieve the result from the dialog window. + * + * @author Michael Hirsch + * + */ + @FunctionalInterface + interface GenerateDialogCallback { + /** + * Callback method which is called when dialog closes with the OK + * button. + * + * @param namePrefix + * the parameter for name prefix for the simulated devices + * @param tenant + * the tenant for the simulated devices + * @param amount + * the number of simulated devices to be created + * @param pollDelay + * the delay poll time in seconds for DDI devices + * @param basePollURL + * the base http URL endpoint for DDI devices + * @param gatewayToken + * the gateway token header for authentication for DDI + * devices + * @param protocol + * the protocol to be used for the simulated devices to be + * generated + */ + void okButton(final String namePrefix, final String tenant, final int amount, final int pollDelay, + final URL basePollURL, final String gatewayToken, final Protocol protocol); + } +} diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/ui/SimulatorUI.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/ui/SimulatorUI.java new file mode 100644 index 000000000..1c09a702d --- /dev/null +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/ui/SimulatorUI.java @@ -0,0 +1,62 @@ +/** + * 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.ui; + +import org.springframework.beans.factory.annotation.Autowired; + +import com.vaadin.annotations.Push; +import com.vaadin.annotations.Theme; +import com.vaadin.annotations.Title; +import com.vaadin.navigator.Navigator; +import com.vaadin.server.VaadinRequest; +import com.vaadin.shared.communication.PushMode; +import com.vaadin.shared.ui.ui.Transport; +import com.vaadin.spring.annotation.SpringUI; +import com.vaadin.spring.navigator.SpringViewProvider; +import com.vaadin.ui.Panel; +import com.vaadin.ui.UI; +import com.vaadin.ui.VerticalLayout; + +/** + * The vaadin simulator UI which allows to generate simulated devices and show + * their current status and update progress. + * + * @author Michael Hirsch + * + */ +@SpringUI(path = "") +@Title("hawkBit Device Simulator") +@Theme(value = "simulator") +@Push(value = PushMode.AUTOMATIC, transport = Transport.WEBSOCKET) +public class SimulatorUI extends UI { + + private static final long serialVersionUID = 1L; + + private final VerticalLayout rootLayout = new VerticalLayout(); + + @Autowired + private SpringViewProvider viewProvider; + + @Override + protected void init(final VaadinRequest request) { + + rootLayout.setSizeFull(); + + final Panel viewContainer = new Panel(); + viewContainer.setSizeFull(); + rootLayout.addComponent(viewContainer); + rootLayout.setExpandRatio(viewContainer, 1.0F); + + final Navigator navigator = new Navigator(this, viewContainer); + navigator.addProvider(viewProvider); + + setContent(rootLayout); + } + +} diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/ui/SimulatorView.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/ui/SimulatorView.java new file mode 100644 index 000000000..25498cea7 --- /dev/null +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/ui/SimulatorView.java @@ -0,0 +1,341 @@ +/** + * 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.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.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; +import com.google.common.eventbus.EventBus; +import com.google.common.eventbus.Subscribe; +import com.vaadin.data.Property.ValueChangeEvent; +import com.vaadin.data.Property.ValueChangeListener; +import com.vaadin.data.util.BeanContainer; +import com.vaadin.data.util.BeanItem; +import com.vaadin.data.util.converter.Converter; +import com.vaadin.navigator.View; +import com.vaadin.navigator.ViewChangeListener.ViewChangeEvent; +import com.vaadin.server.FontAwesome; +import com.vaadin.spring.annotation.SpringView; +import com.vaadin.ui.Button; +import com.vaadin.ui.ComboBox; +import com.vaadin.ui.Grid; +import com.vaadin.ui.Grid.CellReference; +import com.vaadin.ui.Grid.CellStyleGenerator; +import com.vaadin.ui.Grid.SelectionMode; +import com.vaadin.ui.HorizontalLayout; +import com.vaadin.ui.Label; +import com.vaadin.ui.UI; +import com.vaadin.ui.VerticalLayout; +import com.vaadin.ui.renderers.HtmlRenderer; +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 long serialVersionUID = 1L; + + @Autowired + private transient SpSenderService spSenderService; + @Autowired + private transient DeviceSimulatorRepository repository; + @Autowired + private transient SimulatedDeviceFactory deviceFactory; + + @Autowired + private transient EventBus eventbus; + + private final Label caption = new Label("DMF/DDI Simulated Devices"); + private final HorizontalLayout toolbar = new HorizontalLayout(); + private final Grid grid = new Grid(); + private final ComboBox responseComboBox = new ComboBox("", Lists.newArrayList(ResponseStatus.SUCCESSFUL, + ResponseStatus.ERROR)); + + private BeanContainer beanContainer; + + @Override + public void enter(final ViewChangeEvent event) { + eventbus.register(this); + setSizeFull(); + + // caption + caption.addStyleName("h2"); + + // toolbar + createToolbar(); + + beanContainer = new BeanContainer<>(AbstractSimulatedDevice.class); + beanContainer.setBeanIdProperty("id"); + + grid.setSizeFull(); + grid.setCellStyleGenerator(new CellStyleGenerator() { + @Override + public String getStyle(final CellReference cellReference) { + return cellReference.getPropertyId().equals("status") ? "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"); + // header widths + grid.getColumn("status").setMaximumWidth(80); + grid.getColumn("protocol").setMaximumWidth(180); + grid.getColumn("responseStatus").setMaximumWidth(240); + grid.getColumn("nextPollCounterSec").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(new Converter() { + @Override + public Protocol convertToModel(final String value, final Class targetType, + final Locale locale) { + return null; + } + + @Override + public String convertToPresentation(final Protocol value, final Class targetType, + final Locale locale) { + switch (value) { + case DDI_HTTP: + return "DDI API (http)"; + case DMF_AMQP: + return "DMF API (amqp)"; + default: + return "unknown"; + } + } + + @Override + public Class getModelType() { + return Protocol.class; + } + + @Override + public Class getPresentationType() { + return String.class; + } + }); + grid.getColumn("status").setRenderer(new HtmlRenderer(), new Converter() { + private static final long serialVersionUID = 1L; + + @Override + public Status convertToModel(final String value, final Class targetType, + final Locale locale) { + return null; + } + + @Override + public String convertToPresentation(final Status value, final Class targetType, + final Locale locale) { + String style = null; + switch (value) { + case UNKNWON: + style = "&#x" + + Integer.toHexString(FontAwesome.QUESTION_CIRCLE.getCodepoint()) + ";"; + break; + case PEDNING: + style = "&#x" + Integer.toHexString(FontAwesome.REFRESH.getCodepoint()) + + ";"; + break; + case FINISH: + style = "&#x" + + Integer.toHexString(FontAwesome.CHECK_CIRCLE.getCodepoint()) + ";"; + break; + case ERROR: + style = "&#x" + + Integer.toHexString(FontAwesome.EXCLAMATION_CIRCLE.getCodepoint()) + ";"; + break; + default: + throw new IllegalStateException("unknown value"); + } + return style; + } + + @Override + public Class getModelType() { + return Status.class; + } + + @Override + public Class getPresentationType() { + return String.class; + } + }); + grid.removeColumn("tenant"); + + // grid combobox + responseComboBox.setItemIcon(ResponseStatus.SUCCESSFUL, FontAwesome.CHECK_CIRCLE); + responseComboBox.setItemIcon(ResponseStatus.ERROR, FontAwesome.EXCLAMATION_CIRCLE); + responseComboBox.setNullSelectionAllowed(false); + responseComboBox.setValue(ResponseStatus.SUCCESSFUL); + responseComboBox.addValueChangeListener(new ValueChangeListener() { + @Override + public void valueChange(final ValueChangeEvent event) { + beanContainer.getItemIds().forEach( + itemId -> beanContainer.getItem(itemId).getItemProperty("responseStatus") + .setValue(event.getProperty().getValue())); + } + }); + + // add all components + addComponent(caption); + addComponent(toolbar); + addComponent(grid); + + setExpandRatio(grid, 1.0F); + + // load beans + repository.getAll().forEach(device -> beanContainer.addBean(device)); + } + + @Override + public void detach() { + super.detach(); + eventbus.unregister(this); + } + + @Subscribe + public void pollCounterUpdate(final NextPollCounterUpdate update) { + final List devices = update.getDevices(); + this.getUI().access(new Runnable() { + @Override + public void run() { + devices.forEach(device -> { + final BeanItem item = beanContainer.getItem(device.getId()); + if (item != null) { + item.getItemProperty("nextPollCounterSec").setValue(device.getNextPollCounterSec()); + } + }); + } + }); + } + + /** + * Method to retrieve {@link InitUpdate} events from the event bus. + * + * @param update + * the update event posted on the event bus + */ + @Subscribe + public void initUpdate(final InitUpdate update) { + final AbstractSimulatedDevice device = update.getDevice(); + this.getUI().access(new Runnable() { + @Override + public void run() { + final BeanItem 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()); + } + + } + }); + } + + /** + * Method to retrieve {@link ProgressUpdate} events from the event bus. + * + * @param update + * the update event posted on the event bus + */ + @Subscribe + public void progessUpdate(final ProgressUpdate update) { + final AbstractSimulatedDevice device = update.getDevice(); + this.getUI().access(new Runnable() { + @Override + public void run() { + final BeanItem 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); + } + } + + } + }); + } + + private void createToolbar() { + final Button createDevicesButton = new Button("generate..."); + createDevicesButton.setIcon(FontAwesome.GEARS); + createDevicesButton.addClickListener(event -> openGenerateDialog()); + + final Button clearDevicesButton = new Button("clear"); + clearDevicesButton.setIcon(FontAwesome.ERASER); + clearDevicesButton.addClickListener(event -> clearSimulatedDevices()); + + toolbar.addComponent(createDevicesButton); + toolbar.addComponent(clearDevicesButton); + toolbar.setSpacing(true); + } + + private void clearSimulatedDevices() { + repository.clear(); + beanContainer.removeAllItems(); + } + + 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); + } + } + })); + } +} diff --git a/examples/hawkbit-device-simulator/src/main/resources/application.properties b/examples/hawkbit-device-simulator/src/main/resources/application.properties index 19dd2cb77..402f71bfe 100644 --- a/examples/hawkbit-device-simulator/src/main/resources/application.properties +++ b/examples/hawkbit-device-simulator/src/main/resources/application.properties @@ -25,7 +25,7 @@ spring.rabbitmq.virtualHost=/ spring.rabbitmq.host=localhost spring.rabbitmq.port=5672 spring.rabbitmq.dynamic=true - +spring.rabbitmq.listener.prefetch=100 # SECURITY (SecurityProperties) security.user.name=${BASIC_USERNAME:admin} @@ -44,6 +44,6 @@ security.headers.frame=false security.headers.content-type=false security.headers.hsts=all security.sessions=stateless -security.ignored= +security.ignored=/VAADIN/** server.port=8083 diff --git a/examples/hawkbit-device-simulator/src/main/webapp/VAADIN/themes/simulator/styles.scss b/examples/hawkbit-device-simulator/src/main/webapp/VAADIN/themes/simulator/styles.scss new file mode 100644 index 000000000..56ed48c34 --- /dev/null +++ b/examples/hawkbit-device-simulator/src/main/webapp/VAADIN/themes/simulator/styles.scss @@ -0,0 +1,32 @@ +/** + * 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 + */ +// Import valo after setting the parameters +@import "../valo/valo"; + + +.simulator{ + @include valo; + + .yellowicon { + color: orange; + } + .greenicon { + color: green; + } + .grayicon { + color: gray; + } + .redicon { + color: red; + } + + .v-grid-cell.centeralign { + text-align: center; + } +} \ No newline at end of file From c57e810d672c22d794e7d66a1b81ae4f4c6518d4 Mon Sep 17 00:00:00 2001 From: Michael Hirsch Date: Mon, 25 Jan 2016 17:26:38 +0100 Subject: [PATCH 2/7] move vaadin folder to src/main/resources due then it will be automatically included in the fat jar. Signed-off-by: Michael Hirsch --- .../{webapp => resources}/VAADIN/themes/simulator/styles.scss | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename examples/hawkbit-device-simulator/src/main/{webapp => resources}/VAADIN/themes/simulator/styles.scss (100%) diff --git a/examples/hawkbit-device-simulator/src/main/webapp/VAADIN/themes/simulator/styles.scss b/examples/hawkbit-device-simulator/src/main/resources/VAADIN/themes/simulator/styles.scss similarity index 100% rename from examples/hawkbit-device-simulator/src/main/webapp/VAADIN/themes/simulator/styles.scss rename to examples/hawkbit-device-simulator/src/main/resources/VAADIN/themes/simulator/styles.scss From ccbab665507449ddaffe623b11e73dcdc05bec18 Mon Sep 17 00:00:00 2001 From: Michael Hirsch Date: Tue, 26 Jan 2016 11:22:31 +0100 Subject: [PATCH 3/7] increase the generate simulated device amount from 1000 to 30000. Signed-off-by: Michael Hirsch --- .../java/org/eclipse/hawkbit/simulator/ui/GenerateDialog.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/ui/GenerateDialog.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/ui/GenerateDialog.java index 0389045b0..1400ec0e1 100644 --- a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/ui/GenerateDialog.java +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/ui/GenerateDialog.java @@ -65,7 +65,7 @@ public class GenerateDialog extends Window { final TextField tf2 = new TextField("amount", new ObjectProperty(10)); tf2.setIcon(FontAwesome.GEAR); tf2.setRequired(true); - tf2.addValidator(new RangeValidator("Must be between 1 and 1000", Integer.class, 1, 1000)); + tf2.addValidator(new RangeValidator("Must be between 1 and 30000", Integer.class, 1, 30000)); final TextField tf3 = new TextField("tenant", "default"); tf3.setIcon(FontAwesome.USER); From fbcc0b8de0c19a18a1318e990e12630d953f3cc5 Mon Sep 17 00:00:00 2001 From: Kai Zimmermann Date: Wed, 27 Jan 2016 11:45:53 +0100 Subject: [PATCH 4/7] Removed unused private methods --- .../dstable/DistributionBeanQuery.java | 23 ++++--------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/dstable/DistributionBeanQuery.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/dstable/DistributionBeanQuery.java index fa7130d23..f3b68bce6 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/dstable/DistributionBeanQuery.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/dstable/DistributionBeanQuery.java @@ -8,9 +8,6 @@ */ package org.eclipse.hawkbit.ui.management.dstable; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -39,8 +36,6 @@ import com.google.common.base.Strings; * Simple implementation of generics bean query which dynamically loads a batch * of beans. * - * - * */ public class DistributionBeanQuery extends AbstractBeanQuery { @@ -55,7 +50,7 @@ public class DistributionBeanQuery extends AbstractBeanQuery /** * Bean query for retrieving beans/objects of type. - * + * * @param definition * query definition * @param queryConfig @@ -92,8 +87,9 @@ public class DistributionBeanQuery extends AbstractBeanQuery /** * Load all the Distribution set. - * - * @parm startIndex as page start + * + * @param startIndex + * as page start * @param count * as total data */ @@ -193,15 +189,4 @@ public class DistributionBeanQuery extends AbstractBeanQuery return distributionSetManagement; } - private void writeObject(final ObjectOutputStream out) throws IOException { - out.defaultWriteObject(); - out.writeObject(firstPageDistributionSets); - } - - @SuppressWarnings("unchecked") - private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { - in.defaultReadObject(); - firstPageDistributionSets = (Page) in.readObject(); - } - } From ffacf8cf68900984547c02e02ba87c7c540ef380 Mon Sep 17 00:00:00 2001 From: Kai Zimmermann Date: Wed, 27 Jan 2016 11:56:38 +0100 Subject: [PATCH 5/7] Renamed field to not match method name. Close mongoconnection. --- .../repository/MongoConfiguration.java | 31 ++++++++++++------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/hawkbit-artifact-repository-mongo/src/main/java/org/eclipse/hawkbit/artifact/repository/MongoConfiguration.java b/hawkbit-artifact-repository-mongo/src/main/java/org/eclipse/hawkbit/artifact/repository/MongoConfiguration.java index 3f203f94f..347abea1e 100644 --- a/hawkbit-artifact-repository-mongo/src/main/java/org/eclipse/hawkbit/artifact/repository/MongoConfiguration.java +++ b/hawkbit-artifact-repository-mongo/src/main/java/org/eclipse/hawkbit/artifact/repository/MongoConfiguration.java @@ -51,7 +51,7 @@ public class MongoConfiguration extends AbstractMongoConfiguration { @Autowired(required = false) private MongoClientOptions options; - private Mongo mongo; + private Mongo mongoConnection; @Override public String getDatabaseName() { @@ -63,8 +63,8 @@ public class MongoConfiguration extends AbstractMongoConfiguration { */ @PreDestroy public void close() { - if (this.mongo != null) { - this.mongo.close(); + if (this.mongoConnection != null) { + this.mongoConnection.close(); } } @@ -74,16 +74,25 @@ public class MongoConfiguration extends AbstractMongoConfiguration { public Mongo mongo() throws UnknownHostException { final MongoClientURI uri = new MongoClientURI(properties.getUri(), createBuilderOutOfOptions(options)); - if (properties.getPort() != null) { - LOG.debug("Create mongo by properties (host: {}, port: {})", uri.getHosts().get(0), properties.getPort()); - this.mongo = new MongoClient(Arrays.asList(new ServerAddress(uri.getHosts().get(0), properties.getPort())), - uri.getOptions()); - } else { - LOG.debug("Create mongo by URI : {}", uri); - this.mongo = new MongoClient(uri); + try { + if (properties.getPort() != null) { + LOG.debug("Create mongo by properties (host: {}, port: {})", uri.getHosts().get(0), + properties.getPort()); + this.mongoConnection = new MongoClient( + Arrays.asList(new ServerAddress(uri.getHosts().get(0), properties.getPort())), + uri.getOptions()); + } else { + LOG.debug("Create mongo by URI : {}", uri); + this.mongoConnection = new MongoClient(uri); + } + } finally { + if (this.mongoConnection != null) { + this.mongoConnection.close(); + } + } - return this.mongo; + return this.mongoConnection; } /* From b51113af895bda885819cb6719ccbaf576ae72a4 Mon Sep 17 00:00:00 2001 From: Kai Zimmermann Date: Wed, 27 Jan 2016 11:56:59 +0100 Subject: [PATCH 6/7] Try to make sonar happy about generated methods --- .../eclipse/hawkbit/repository/model/Action.java | 14 +++++++------- .../repository/model/DistributionSetTag.java | 6 +++--- .../hawkbit/repository/model/ExternalArtifact.java | 8 ++++---- .../repository/model/ExternalArtifactProvider.java | 8 ++++---- .../hawkbit/repository/model/LocalArtifact.java | 8 ++++---- .../hawkbit/repository/model/SoftwareModule.java | 4 ++-- .../hawkbit/ui/artifacts/state/CustomFile.java | 10 +++++----- 7 files changed, 29 insertions(+), 29 deletions(-) diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/Action.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/Action.java index eb70f235b..80624054a 100644 --- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/Action.java +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/Action.java @@ -233,7 +233,7 @@ public class Action extends BaseEntity implements Comparable { * checks if the {@link #forcedTime} is hit by the given * {@code hitTimeMillis}, by means if the given milliseconds are greater * than the forcedTime. - * + * * @param hitTimeMillis * the milliseconds, mostly the * {@link System#currentTimeMillis()} @@ -274,7 +274,7 @@ public class Action extends BaseEntity implements Comparable { /* * (non-Javadoc) - * + * * @see java.lang.Object#toString() */ @Override @@ -284,11 +284,11 @@ public class Action extends BaseEntity implements Comparable { /* * (non-Javadoc) - * + * * @see java.lang.Object#hashCode() */ @Override - public int hashCode() { + public int hashCode() { // NOSONAR - as this is generated final int prime = 31; int result = super.hashCode(); result = prime * result + ((actionType == null) ? 0 : actionType.hashCode()); @@ -301,12 +301,12 @@ public class Action extends BaseEntity implements Comparable { /* * (non-Javadoc) - * + * * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(final Object obj) { // NOSONAR - as this is generated - // code + if (this == obj) { return true; } @@ -384,7 +384,7 @@ public class Action extends BaseEntity implements Comparable { /** * The action type for this action relation. - * + * * * * diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/DistributionSetTag.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/DistributionSetTag.java index 3028be775..63a858a7d 100644 --- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/DistributionSetTag.java +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/DistributionSetTag.java @@ -71,7 +71,7 @@ public class DistributionSetTag extends Tag { /* * (non-Javadoc) - * + * * @see java.lang.Object#hashCode() */ @Override @@ -84,11 +84,11 @@ public class DistributionSetTag extends Tag { /* * (non-Javadoc) - * + * * @see java.lang.Object#equals(java.lang.Object) */ @Override - public boolean equals(final Object obj) { + public boolean equals(final Object obj) { // NOSONAR - as this is generated if (this == obj) { return true; } diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/ExternalArtifact.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/ExternalArtifact.java index d6664ac59..35e0c4e99 100644 --- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/ExternalArtifact.java +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/ExternalArtifact.java @@ -129,11 +129,11 @@ public class ExternalArtifact extends Artifact { /* * (non-Javadoc) - * + * * @see java.lang.Object#hashCode() */ @Override - public int hashCode() { + public int hashCode() { // NOSONAR - as this is generated final int prime = 31; int result = super.hashCode(); result = prime * result + this.getClass().getName().hashCode(); @@ -142,11 +142,11 @@ public class ExternalArtifact extends Artifact { /* * (non-Javadoc) - * + * * @see java.lang.Object#equals(java.lang.Object) */ @Override - public boolean equals(final Object obj) { + public boolean equals(final Object obj) { // NOSONAR - as this is generated if (this == obj) { return true; } diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/ExternalArtifactProvider.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/ExternalArtifactProvider.java index f6ed40e79..56d92c8e1 100644 --- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/ExternalArtifactProvider.java +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/ExternalArtifactProvider.java @@ -92,11 +92,11 @@ public class ExternalArtifactProvider extends NamedEntity { /* * (non-Javadoc) - * + * * @see java.lang.Object#hashCode() */ @Override - public int hashCode() { + public int hashCode() { // NOSONAR - as this is generated final int prime = 31; int result = super.hashCode(); result = prime * result + this.getClass().getName().hashCode(); @@ -105,11 +105,11 @@ public class ExternalArtifactProvider extends NamedEntity { /* * (non-Javadoc) - * + * * @see java.lang.Object#equals(java.lang.Object) */ @Override - public boolean equals(final Object obj) { + public boolean equals(final Object obj) { // NOSONAR - as this is generated if (this == obj) { return true; } diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/LocalArtifact.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/LocalArtifact.java index a5ad493b9..baa4ee1f0 100644 --- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/LocalArtifact.java +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/LocalArtifact.java @@ -75,11 +75,11 @@ public class LocalArtifact extends Artifact { /* * (non-Javadoc) - * + * * @see java.lang.Object#hashCode() */ @Override - public int hashCode() { + public int hashCode() { // NOSONAR - as this is generated final int prime = 31; int result = super.hashCode(); result = prime * result + this.getClass().getName().hashCode(); @@ -88,11 +88,11 @@ public class LocalArtifact extends Artifact { /* * (non-Javadoc) - * + * * @see java.lang.Object#equals(java.lang.Object) */ @Override - public boolean equals(final Object obj) { + public boolean equals(final Object obj) { // NOSONAR - as this is generated if (this == obj) { return true; } diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/SoftwareModule.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/SoftwareModule.java index a3685df66..49e73f749 100644 --- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/SoftwareModule.java +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/SoftwareModule.java @@ -257,7 +257,7 @@ public class SoftwareModule extends NamedVersionedEntity { * @see java.lang.Object#hashCode() */ @Override - public int hashCode() { + public int hashCode() { // NOSONAR - as this is generated final int prime = 31; int result = super.hashCode(); result = prime * result + this.getClass().getName().hashCode(); @@ -270,7 +270,7 @@ public class SoftwareModule extends NamedVersionedEntity { * @see java.lang.Object#equals(java.lang.Object) */ @Override - public boolean equals(final Object obj) { + public boolean equals(final Object obj) { // NOSONAR - as this is generated if (this == obj) { return true; } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/state/CustomFile.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/state/CustomFile.java index 9b29c780a..ff350511a 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/state/CustomFile.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/state/CustomFile.java @@ -72,7 +72,7 @@ public class CustomFile implements Serializable { /** * Initialize details. - * + * * @param fileName * uploaded file name * @param baseSoftwareModuleName @@ -138,7 +138,7 @@ public class CustomFile implements Serializable { } /** - * + * * @return the isValid */ public Boolean getIsValid() { @@ -170,11 +170,11 @@ public class CustomFile implements Serializable { /* * (non-Javadoc) - * + * * @see java.lang.Object#hashCode() */ @Override - public int hashCode() { + public int hashCode() { // NOSONAR - as this is generated final int prime = 31; int result = 1; result = prime * result + (fileName == null ? 0 : fileName.hashCode()); @@ -183,7 +183,7 @@ public class CustomFile implements Serializable { /* * (non-Javadoc) - * + * * @see java.lang.Object#equals(java.lang.Object) */ @Override From bed3f0c8c906b93a0f2ecd48b6763c6bab053ae3 Mon Sep 17 00:00:00 2001 From: Kai Zimmermann Date: Wed, 27 Jan 2016 14:16:24 +0100 Subject: [PATCH 7/7] Fixed stream handling and some sonar issues --- .../api/client/DistributionSetResource.java | 3 +- .../repository/MongoConfiguration.java | 25 +++---- .../push/AsyncVaadinServletConfiguration.java | 1 + .../targettable/BulkUploadHandler.java | 72 +++++++++---------- 4 files changed, 43 insertions(+), 58 deletions(-) diff --git a/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/api/client/DistributionSetResource.java b/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/api/client/DistributionSetResource.java index 747432cd0..62c987ae8 100644 --- a/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/api/client/DistributionSetResource.java +++ b/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/api/client/DistributionSetResource.java @@ -20,10 +20,11 @@ import feign.RequestLine; /** * Client binding for the Distribution resource of the management API. */ +@FunctionalInterface public interface DistributionSetResource { /** - * Creates a list of distrbution sets. + * Creates a list of distribution sets. * * @param sets * the request body java bean containing the necessary attributes diff --git a/hawkbit-artifact-repository-mongo/src/main/java/org/eclipse/hawkbit/artifact/repository/MongoConfiguration.java b/hawkbit-artifact-repository-mongo/src/main/java/org/eclipse/hawkbit/artifact/repository/MongoConfiguration.java index 347abea1e..02fb22725 100644 --- a/hawkbit-artifact-repository-mongo/src/main/java/org/eclipse/hawkbit/artifact/repository/MongoConfiguration.java +++ b/hawkbit-artifact-repository-mongo/src/main/java/org/eclipse/hawkbit/artifact/repository/MongoConfiguration.java @@ -59,7 +59,7 @@ public class MongoConfiguration extends AbstractMongoConfiguration { } /** - * Closes mongo client when destroyd. + * Closes mongo client when destroyed. */ @PreDestroy public void close() { @@ -74,22 +74,13 @@ public class MongoConfiguration extends AbstractMongoConfiguration { public Mongo mongo() throws UnknownHostException { final MongoClientURI uri = new MongoClientURI(properties.getUri(), createBuilderOutOfOptions(options)); - try { - if (properties.getPort() != null) { - LOG.debug("Create mongo by properties (host: {}, port: {})", uri.getHosts().get(0), - properties.getPort()); - this.mongoConnection = new MongoClient( - Arrays.asList(new ServerAddress(uri.getHosts().get(0), properties.getPort())), - uri.getOptions()); - } else { - LOG.debug("Create mongo by URI : {}", uri); - this.mongoConnection = new MongoClient(uri); - } - } finally { - if (this.mongoConnection != null) { - this.mongoConnection.close(); - } - + if (properties.getPort() != null) { + LOG.debug("Create mongo by properties (host: {}, port: {})", uri.getHosts().get(0), properties.getPort()); + this.mongoConnection = new MongoClient( + Arrays.asList(new ServerAddress(uri.getHosts().get(0), properties.getPort())), uri.getOptions()); + } else { + LOG.debug("Create mongo by URI : {}", uri); + this.mongoConnection = new MongoClient(uri); } return this.mongoConnection; diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/push/AsyncVaadinServletConfiguration.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/push/AsyncVaadinServletConfiguration.java index 6eb6761e6..89ff3413e 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/push/AsyncVaadinServletConfiguration.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/push/AsyncVaadinServletConfiguration.java @@ -32,6 +32,7 @@ import com.vaadin.spring.boot.internal.VaadinServletConfigurationProperties; @Import(VaadinServletConfiguration.class) public class AsyncVaadinServletConfiguration extends VaadinServletConfiguration { + @Override @Bean protected ServletRegistrationBean vaadinServletRegistration() { return createServletRegistrationBean(); diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/BulkUploadHandler.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/BulkUploadHandler.java index 8ba60ec51..fa4022a3e 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/BulkUploadHandler.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/BulkUploadHandler.java @@ -14,6 +14,7 @@ import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.nio.charset.Charset; @@ -62,15 +63,13 @@ import com.vaadin.ui.Upload.SucceededListener; /** * Bulk target upload handler. - * - * * */ public class BulkUploadHandler extends CustomComponent implements SucceededListener, FailedListener, Receiver, StartedListener { /** - * * + * */ private static final long serialVersionUID = -1273494705754674501L; private static final Logger LOG = LoggerFactory.getLogger(BulkUploadHandler.class); @@ -103,7 +102,7 @@ public class BulkUploadHandler extends CustomComponent final TargetBulkUpdateWindowLayout targetBulkUpdateWindowLayout; /** - * + * * @param targetBulkUpdateWindowLayout * @param targetManagement * @param managementUIState @@ -152,7 +151,7 @@ public class BulkUploadHandler extends CustomComponent /* * (non-Javadoc) - * + * * @see com.vaadin.ui.Upload.Receiver#receiveUpload(java.lang.String, * java.lang.String) */ @@ -173,7 +172,7 @@ public class BulkUploadHandler extends CustomComponent /* * (non-Javadoc) - * + * * @see * com.vaadin.ui.Upload.FailedListener#uploadFailed(com.vaadin.ui.Upload. * FailedEvent) @@ -185,7 +184,7 @@ public class BulkUploadHandler extends CustomComponent /* * (non-Javadoc) - * + * * @see * com.vaadin.ui.Upload.SucceededListener#uploadSucceeded(com.vaadin.ui. * Upload.SucceededEvent) @@ -199,7 +198,7 @@ public class BulkUploadHandler extends CustomComponent final SucceededEvent event; /** - * + * * @param event */ public UploadAsync(final SucceededEvent event) { @@ -208,15 +207,18 @@ public class BulkUploadHandler extends CustomComponent @Override public void run() { - BufferedReader reader = null; long innerCounter = 0; String line; - if (tempFile != null) { - try { + if (tempFile == null) { + return; + } + + try (InputStream tempStream = new FileInputStream(tempFile)) { + try (BufferedReader reader = new BufferedReader( + new InputStreamReader(tempStream, Charset.defaultCharset()))) { LOG.info("Bulk file upload started"); final double totalFileSize = getTotalNumberOfLines(); - reader = new BufferedReader( - new InputStreamReader(new FileInputStream(tempFile), Charset.defaultCharset())); + /** * Once control is in upload succeeded method automatically * upload button is re-enabled. To disable the button firing @@ -232,21 +234,16 @@ public class BulkUploadHandler extends CustomComponent // Clearing after assignments are done managementUIState.getTargetTableFilters().getBulkUpload().getTargetsCreated().clear(); - } catch (final FileNotFoundException e) { - LOG.error("File not found with name {}", tempFile.getName(), e); } catch (final IOException e) { LOG.error("Error reading file {}", tempFile.getName(), e); } finally { - try { - if (null != reader) { - reader.close(); - resetCounts(); - deleteFile(); - } - } catch (final IOException e) { - LOG.error("Error while reading file ", e); - } + resetCounts(); + deleteFile(); } + } catch (final FileNotFoundException e) { + LOG.error("Temporary file not found with name {}", tempFile.getName(), e); + } catch (final IOException e) { + LOG.error("Error while opening temorary file ", e); } } @@ -307,24 +304,19 @@ public class BulkUploadHandler extends CustomComponent } private double getTotalNumberOfLines() { - InputStreamReader inputStreamReader; - BufferedReader readerForSize = null; + double totalFileSize = 0; - try { - inputStreamReader = new InputStreamReader(new FileInputStream(tempFile), Charset.defaultCharset()); - readerForSize = new BufferedReader(inputStreamReader); - totalFileSize = readerForSize.lines().count(); + try (InputStreamReader inputStreamReader = new InputStreamReader(new FileInputStream(tempFile), + Charset.defaultCharset())) { + try (BufferedReader readerForSize = new BufferedReader(inputStreamReader)) { + totalFileSize = readerForSize.lines().count(); + } } catch (final FileNotFoundException e) { LOG.error("Error reading file {}", tempFile.getName(), e); - } finally { - if (readerForSize != null) { - try { - readerForSize.close(); - } catch (final IOException e) { - LOG.error("Error while closing reader of file {}", tempFile.getName(), e); - } - } + } catch (final IOException e) { + LOG.error("Error while closing reader of file {}", tempFile.getName(), e); } + return totalFileSize; } @@ -449,7 +441,7 @@ public class BulkUploadHandler extends CustomComponent private static class NullOutputStream extends OutputStream { /** * null output stream. - * + * * @param i * byte */ @@ -468,7 +460,7 @@ public class BulkUploadHandler extends CustomComponent /* * (non-Javadoc) - * + * * @see * com.vaadin.ui.Upload.StartedListener#uploadStarted(com.vaadin.ui.Upload * .StartedEvent)