From 380370c02653ad3ac08c3ff0232ed7b419f5e784 Mon Sep 17 00:00:00 2001 From: Michael Hirsch Date: Thu, 21 Jan 2016 17:56:26 +0100 Subject: [PATCH 01/17] 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 02/17] 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 03/17] 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 04/17] 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 05/17] 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 06/17] 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 07/17] 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) From 474792f12415219033e4f7950fc815d263d63ef7 Mon Sep 17 00:00:00 2001 From: Kai Zimmermann Date: Thu, 28 Jan 2016 17:30:52 +0100 Subject: [PATCH 08/17] Fixed parent version --- examples/hawkbit-device-simulator/pom.xml | 2 +- examples/hawkbit-example-app/pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/hawkbit-device-simulator/pom.xml b/examples/hawkbit-device-simulator/pom.xml index eaec9b91e..9a84d13f5 100644 --- a/examples/hawkbit-device-simulator/pom.xml +++ b/examples/hawkbit-device-simulator/pom.xml @@ -14,7 +14,7 @@ 4.0.0 org.eclipse.hawkbit - 0.1.0-SNAPSHOT + 0.2.0-SNAPSHOT hawkbit-examples-parent diff --git a/examples/hawkbit-example-app/pom.xml b/examples/hawkbit-example-app/pom.xml index cd238945a..33624ffd7 100644 --- a/examples/hawkbit-example-app/pom.xml +++ b/examples/hawkbit-example-app/pom.xml @@ -14,7 +14,7 @@ org.eclipse.hawkbit hawkbit-examples-parent - 0.1.0-SNAPSHOT + 0.2.0-SNAPSHOT hawkbit-example-app hawkBit :: Example App From 38c3498a7342638f1dd5895c01d01237a138bb99 Mon Sep 17 00:00:00 2001 From: Kai Zimmermann Date: Thu, 28 Jan 2016 17:32:27 +0100 Subject: [PATCH 09/17] Fixed parent version --- examples/hawkbit-mgmt-api-client/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/hawkbit-mgmt-api-client/pom.xml b/examples/hawkbit-mgmt-api-client/pom.xml index cd0977cf5..cced3dfbb 100644 --- a/examples/hawkbit-mgmt-api-client/pom.xml +++ b/examples/hawkbit-mgmt-api-client/pom.xml @@ -14,7 +14,7 @@ org.eclipse.hawkbit hawkbit-examples-parent - 0.1.0-SNAPSHOT + 0.2.0-SNAPSHOT hawkbit-mgmt-api-client hawkBit Management API example client From 0395ed42f19261c022a1c3e7ceea0548d8c1b6a9 Mon Sep 17 00:00:00 2001 From: Kai Zimmermann Date: Thu, 28 Jan 2016 18:30:53 +0100 Subject: [PATCH 10/17] Added build status --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 95cacbc9f..c9ec53685 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # hawkbit-update-server +Build: [![Circle CI](https://circleci.com/gh/eclipse/hawkbit.svg?style=svg)](https://circleci.com/gh/eclipse/hawkbit) + Want to chat with the team behind hawkBit? [![Join the chat at https://gitter.im/eclipse/hawkbit](https://badges.gitter.im/eclipse/hawkbit.svg)](https://gitter.im/eclipse/hawkbit?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [hawkBit](https://projects.eclipse.org/projects/iot.hawkbit) is an domain independent back end solution for rolling out software updates to constrained edge devices as well as more powerful controllers and gateways connected to IP based networking infrastructure. From 9cf345b15d01afb516a5251bdecfe0bc7c9b8e44 Mon Sep 17 00:00:00 2001 From: Kai Zimmermann Date: Fri, 29 Jan 2016 10:52:58 +0100 Subject: [PATCH 11/17] Fix sonar link --- pom.xml | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/pom.xml b/pom.xml index 84a223b64..2c8e004be 100644 --- a/pom.xml +++ b/pom.xml @@ -94,31 +94,31 @@ https://sonar.eu-gb.mybluemix.net - bsinno/hawkbit-update-server + eclipse/hawkbit jacoco 0.7.2.201409121644 - 1.4 reuseReports - ${project.basedir}/../target/ jacoco-ut.exec - ${jacoco.outputDir}/${jacoco.out.ut.file} jacoco-it.exec - ${jacoco.outputDir}/${jacoco.out.it.file} 19.0 @@ -360,9 +360,9 @@ vaadin-push ${vaadin.version} - com.vaadin @@ -501,7 +501,7 @@ - com.fasterxml From afd38cb6b858de008fe7f8d0a64862bec9b03487 Mon Sep 17 00:00:00 2001 From: Kai Zimmermann Date: Fri, 29 Jan 2016 11:06:33 +0100 Subject: [PATCH 12/17] Added further sonar inks --- pom.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pom.xml b/pom.xml index 2c8e004be..a9d303782 100644 --- a/pom.xml +++ b/pom.xml @@ -96,6 +96,8 @@ https://sonar.eu-gb.mybluemix.net eclipse/hawkbit jacoco + https://projects.eclipse.org/projects/iot.hawkbit + https://circleci.com/gh/eclipse/hawkbit 0.7.2.201409121644