Merge branch 'master' into feature_boot_13_sec_41
Conflicts: hawkbit-artifact-repository-mongo/src/test/java/org/eclipse/hawkbit/artifact/FreePortFileWriter.java hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/FreePortFileWriter.java Signed-off-by: Kai Zimmermann <kai.zimmermann@bosch-si.com>
This commit is contained in:
@@ -24,6 +24,12 @@ The simulator has user authentication enabled in **cloud profile**. Default cred
|
||||
|
||||
This can be configured/disabled by spring boot properties
|
||||
|
||||
## hawkBit APIs
|
||||
|
||||
The simulator supports `DDI` as well as the `DMF` integration APIs.
|
||||
In case there is no AMQP message broker (like rabbitMQ) running, you can disable the AMQP support for the device simulator, so the simulator is not trying to connect to an amqp message broker.
|
||||
Configuration property `hawkbit.device.simulator.amqp.enabled=true`
|
||||
|
||||
## Usage
|
||||
|
||||
### Graphical User Interface
|
||||
|
||||
@@ -17,6 +17,4 @@ applications:
|
||||
services:
|
||||
- dmf-rabbit
|
||||
env:
|
||||
SPRING_PROFILES_ACTIVE: cloud,amqp
|
||||
CF_STAGING_TIMEOUT: 15
|
||||
CF_STARTUP_TIMEOUT: 15
|
||||
SPRING_PROFILES_ACTIVE: cloud
|
||||
|
||||
@@ -118,6 +118,16 @@ public class DeviceSimulatorUpdater {
|
||||
}
|
||||
|
||||
private static final class DeviceSimulatorUpdateThread implements Runnable {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final String BUT_GOT_LOG_MESSAGE = " but got: ";
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final String DOWNLOAD_LOG_MESSAGE = "Download ";
|
||||
|
||||
private static final int MINIMUM_TOKENLENGTH_FOR_HINT = 6;
|
||||
|
||||
private static final Random rndSleep = new SecureRandom();
|
||||
@@ -187,7 +197,7 @@ public class DeviceSimulatorUpdater {
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean isErrorResponse(final UpdateStatus status) {
|
||||
private static boolean isErrorResponse(final UpdateStatus status) {
|
||||
if (status == null) {
|
||||
return false;
|
||||
}
|
||||
@@ -212,60 +222,73 @@ public class DeviceSimulatorUpdater {
|
||||
LOGGER.debug("Downloading {} with token {}, expected sha1 hash {} and size {}", url,
|
||||
hideTokenDetails(targetToken), sha1Hash, size);
|
||||
|
||||
long overallread = 0;
|
||||
try {
|
||||
final CloseableHttpClient httpclient = createHttpClientThatAcceptsAllServerCerts();
|
||||
final HttpGet request = new HttpGet(url);
|
||||
request.addHeader(HttpHeaders.AUTHORIZATION, "TargetToken " + targetToken);
|
||||
|
||||
final String sha1HashResult;
|
||||
try (final CloseableHttpResponse response = httpclient.execute(request)) {
|
||||
|
||||
if (response.getStatusLine().getStatusCode() != HttpStatus.OK.value()) {
|
||||
final String message = wrongStatusCode(url, response);
|
||||
return new UpdateStatus(ResponseStatus.ERROR, message);
|
||||
}
|
||||
|
||||
if (response.getEntity().getContentLength() != size) {
|
||||
final String message = wrongContentLength(url, size, response);
|
||||
return new UpdateStatus(ResponseStatus.ERROR, message);
|
||||
}
|
||||
|
||||
// Exception squid:S2070 - not used for hashing sensitive
|
||||
// data
|
||||
@SuppressWarnings("squid:S2070")
|
||||
final MessageDigest md = MessageDigest.getInstance("SHA-1");
|
||||
|
||||
try (final BufferedOutputStream bdos = new BufferedOutputStream(
|
||||
new DigestOutputStream(ByteStreams.nullOutputStream(), md))) {
|
||||
try (BufferedInputStream bis = new BufferedInputStream(response.getEntity().getContent())) {
|
||||
overallread = ByteStreams.copy(bis, bdos);
|
||||
}
|
||||
}
|
||||
|
||||
if (overallread != size) {
|
||||
final String message = incompleteRead(url, size, overallread);
|
||||
return new UpdateStatus(ResponseStatus.ERROR, message);
|
||||
}
|
||||
|
||||
sha1HashResult = BaseEncoding.base16().lowerCase().encode(md.digest());
|
||||
}
|
||||
|
||||
if (!sha1Hash.equalsIgnoreCase(sha1HashResult)) {
|
||||
final String message = wrongHash(url, sha1Hash, overallread, sha1HashResult);
|
||||
return new UpdateStatus(ResponseStatus.ERROR, message);
|
||||
}
|
||||
|
||||
return readAndCheckDownloadUrl(url, targetToken, sha1Hash, size);
|
||||
} catch (IOException | KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
|
||||
LOGGER.error("Failed to download" + url, e);
|
||||
return new UpdateStatus(ResponseStatus.ERROR, "Failed to download " + url + ": " + e.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static UpdateStatus readAndCheckDownloadUrl(final String url, final String targetToken,
|
||||
final String sha1Hash, final long size)
|
||||
throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException, IOException {
|
||||
long overallread;
|
||||
final CloseableHttpClient httpclient = createHttpClientThatAcceptsAllServerCerts();
|
||||
final HttpGet request = new HttpGet(url);
|
||||
request.addHeader(HttpHeaders.AUTHORIZATION, "TargetToken " + targetToken);
|
||||
|
||||
final String sha1HashResult;
|
||||
try (final CloseableHttpResponse response = httpclient.execute(request)) {
|
||||
|
||||
if (response.getStatusLine().getStatusCode() != HttpStatus.OK.value()) {
|
||||
final String message = wrongStatusCode(url, response);
|
||||
return new UpdateStatus(ResponseStatus.ERROR, message);
|
||||
}
|
||||
|
||||
if (response.getEntity().getContentLength() != size) {
|
||||
final String message = wrongContentLength(url, size, response);
|
||||
return new UpdateStatus(ResponseStatus.ERROR, message);
|
||||
}
|
||||
|
||||
// Exception squid:S2070 - not used for hashing sensitive
|
||||
// data
|
||||
@SuppressWarnings("squid:S2070")
|
||||
final MessageDigest md = MessageDigest.getInstance("SHA-1");
|
||||
|
||||
overallread = getOverallRead(response, md);
|
||||
|
||||
if (overallread != size) {
|
||||
final String message = incompleteRead(url, size, overallread);
|
||||
return new UpdateStatus(ResponseStatus.ERROR, message);
|
||||
}
|
||||
|
||||
sha1HashResult = BaseEncoding.base16().lowerCase().encode(md.digest());
|
||||
}
|
||||
|
||||
if (!sha1Hash.equalsIgnoreCase(sha1HashResult)) {
|
||||
final String message = wrongHash(url, sha1Hash, overallread, sha1HashResult);
|
||||
return new UpdateStatus(ResponseStatus.ERROR, message);
|
||||
}
|
||||
|
||||
final String message = "Downloaded " + url + " (" + overallread + " bytes)";
|
||||
LOGGER.debug(message);
|
||||
return new UpdateStatus(ResponseStatus.SUCCESSFUL, message);
|
||||
}
|
||||
|
||||
private static long getOverallRead(final CloseableHttpResponse response, final MessageDigest md)
|
||||
throws IOException {
|
||||
long overallread;
|
||||
try (final BufferedOutputStream bdos = new BufferedOutputStream(
|
||||
new DigestOutputStream(ByteStreams.nullOutputStream(), md))) {
|
||||
try (BufferedInputStream bis = new BufferedInputStream(response.getEntity().getContent())) {
|
||||
overallread = ByteStreams.copy(bis, bdos);
|
||||
}
|
||||
}
|
||||
return overallread;
|
||||
}
|
||||
|
||||
private static String hideTokenDetails(final String targetToken) {
|
||||
if (targetToken == null) {
|
||||
return "<NULL!>";
|
||||
@@ -285,29 +308,30 @@ public class DeviceSimulatorUpdater {
|
||||
|
||||
private static String wrongHash(final String url, final String sha1Hash, final long overallread,
|
||||
final String sha1HashResult) {
|
||||
final String message = "Download " + url + " failed with SHA1 hash missmatch (Expected: " + sha1Hash
|
||||
+ " but got: " + sha1HashResult + ") (" + overallread + " bytes)";
|
||||
final String message = DOWNLOAD_LOG_MESSAGE + url + " failed with SHA1 hash missmatch (Expected: "
|
||||
+ sha1Hash + BUT_GOT_LOG_MESSAGE + sha1HashResult + ") (" + overallread + " bytes)";
|
||||
LOGGER.error(message);
|
||||
return message;
|
||||
}
|
||||
|
||||
private static String incompleteRead(final String url, final long size, final long overallread) {
|
||||
final String message = "Download " + url + " is incomplete (Expected: " + size + " but got: " + overallread
|
||||
+ ")";
|
||||
final String message = DOWNLOAD_LOG_MESSAGE + url + " is incomplete (Expected: " + size
|
||||
+ BUT_GOT_LOG_MESSAGE + overallread + ")";
|
||||
LOGGER.error(message);
|
||||
return message;
|
||||
}
|
||||
|
||||
private static String wrongContentLength(final String url, final long size,
|
||||
final CloseableHttpResponse response) {
|
||||
final String message = "Download " + url + " has wrong content length (Expected: " + size + " but got: "
|
||||
+ response.getEntity().getContentLength() + ")";
|
||||
final String message = DOWNLOAD_LOG_MESSAGE + url + " has wrong content length (Expected: " + size
|
||||
+ BUT_GOT_LOG_MESSAGE + response.getEntity().getContentLength() + ")";
|
||||
LOGGER.error(message);
|
||||
return message;
|
||||
}
|
||||
|
||||
private static String wrongStatusCode(final String url, final CloseableHttpResponse response) {
|
||||
final String message = "Download " + url + " failed (" + response.getStatusLine().getStatusCode() + ")";
|
||||
final String message = DOWNLOAD_LOG_MESSAGE + url + " failed (" + response.getStatusLine().getStatusCode()
|
||||
+ ")";
|
||||
LOGGER.error(message);
|
||||
return message;
|
||||
}
|
||||
@@ -339,4 +363,5 @@ public class DeviceSimulatorUpdater {
|
||||
*/
|
||||
void updateFinished(AbstractSimulatedDevice device, final Long actionId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -72,6 +72,7 @@ public class SimulatedDeviceFactory {
|
||||
final int pollDelaySec, final URL baseEndpoint, final String gatewayToken) {
|
||||
switch (protocol) {
|
||||
case DMF_AMQP:
|
||||
spSenderService.createOrUpdateThing(tenant, id);
|
||||
return new DMFSimulatedDevice(id, tenant, spSenderService, pollDelaySec);
|
||||
case DDI_HTTP:
|
||||
final ControllerResource controllerResource = Feign.builder().logger(new Logger.ErrorLogger())
|
||||
|
||||
@@ -8,11 +8,13 @@
|
||||
*/
|
||||
package org.eclipse.hawkbit.simulator;
|
||||
|
||||
import static org.eclipse.hawkbit.simulator.amqp.AmqpProperties.CONFIGURATION_PREFIX;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
|
||||
import org.eclipse.hawkbit.simulator.AbstractSimulatedDevice.Protocol;
|
||||
import org.eclipse.hawkbit.simulator.amqp.SpSenderService;
|
||||
import org.eclipse.hawkbit.simulator.amqp.AmqpProperties;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
@@ -21,22 +23,19 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* REST endpoint for controlling the device simulator.
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
@RestController
|
||||
public class SimulationController {
|
||||
|
||||
@Autowired
|
||||
private SpSenderService spSenderService;
|
||||
|
||||
@Autowired
|
||||
private DeviceSimulatorRepository repository;
|
||||
|
||||
@Autowired
|
||||
private SimulatedDeviceFactory deviceFactory;
|
||||
|
||||
@Autowired
|
||||
private AmqpProperties amqpProperties;
|
||||
|
||||
/**
|
||||
* The start resource to start a device creation.
|
||||
*
|
||||
@@ -82,6 +81,12 @@ public class SimulationController {
|
||||
return ResponseEntity.badRequest().body("query param api only allows value of 'dmf' or 'ddi'");
|
||||
}
|
||||
|
||||
if (protocol == Protocol.DMF_AMQP && isDmfDisabled()) {
|
||||
return ResponseEntity.badRequest()
|
||||
.body("The AMQP interface has been disabled, to use DMF protocol you need to enable the AMQP interface via '"
|
||||
+ CONFIGURATION_PREFIX + ".enabled=true'");
|
||||
}
|
||||
|
||||
for (int i = 0; i < amount; i++) {
|
||||
final String deviceId = name + i;
|
||||
repository.add(deviceFactory.createSimulatedDevice(deviceId, tenant, protocol, pollDelay, new URL(endpoint),
|
||||
@@ -90,4 +95,8 @@ public class SimulationController {
|
||||
|
||||
return ResponseEntity.ok("Updated " + amount + " DMF connected targets!");
|
||||
}
|
||||
|
||||
private boolean isDmfDisabled() {
|
||||
return !amqpProperties.isEnabled();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ package org.eclipse.hawkbit.simulator;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
|
||||
import org.eclipse.hawkbit.simulator.amqp.SpSenderService;
|
||||
import org.eclipse.hawkbit.simulator.amqp.AmqpProperties;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -32,24 +32,27 @@ public class SimulatorStartup implements ApplicationListener<ContextRefreshedEve
|
||||
@Autowired
|
||||
private SimulationProperties simulationProperties;
|
||||
|
||||
@Autowired
|
||||
private SpSenderService spSenderService;
|
||||
|
||||
@Autowired
|
||||
private DeviceSimulatorRepository repository;
|
||||
|
||||
@Autowired
|
||||
private SimulatedDeviceFactory deviceFactory;
|
||||
|
||||
@Autowired
|
||||
private AmqpProperties amqpProperties;
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(final ContextRefreshedEvent event) {
|
||||
simulationProperties.getAutostarts().forEach(autostart -> {
|
||||
for (int i = 0; i < autostart.getAmount(); i++) {
|
||||
final String deviceId = autostart.getName() + i;
|
||||
try {
|
||||
repository.add(deviceFactory.createSimulatedDevice(deviceId, autostart.getTenant(),
|
||||
autostart.getApi(), autostart.getPollDelay(), new URL(autostart.getEndpoint()),
|
||||
autostart.getGatewayToken()));
|
||||
if (amqpProperties.isEnabled()) {
|
||||
repository.add(deviceFactory.createSimulatedDevice(deviceId, autostart.getTenant(),
|
||||
autostart.getApi(), autostart.getPollDelay(), new URL(autostart.getEndpoint()),
|
||||
autostart.getGatewayToken()));
|
||||
}
|
||||
|
||||
} catch (final MalformedURLException e) {
|
||||
LOGGER.error("Creation of simulated device at startup failed.", e);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
*/
|
||||
package org.eclipse.hawkbit.simulator.amqp;
|
||||
|
||||
import static org.eclipse.hawkbit.simulator.amqp.AmqpProperties.CONFIGURATION_PREFIX;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@@ -27,6 +29,7 @@ import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
|
||||
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.amqp.RabbitProperties;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
@@ -39,6 +42,7 @@ import org.springframework.retry.support.RetryTemplate;
|
||||
*/
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(AmqpProperties.class)
|
||||
@ConditionalOnProperty(prefix = CONFIGURATION_PREFIX, name = "enabled")
|
||||
public class AmqpConfiguration {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(AmqpConfiguration.class);
|
||||
|
||||
@@ -19,6 +19,17 @@ import org.springframework.stereotype.Component;
|
||||
@Component
|
||||
@ConfigurationProperties("hawkbit.device.simulator.amqp")
|
||||
public class AmqpProperties {
|
||||
|
||||
/**
|
||||
* The prefix for this configuration.
|
||||
*/
|
||||
public static final String CONFIGURATION_PREFIX = "hawkbit.device.simulator.amqp";
|
||||
|
||||
/**
|
||||
* Indicates if the AMQP interface is enabled for the device simulator.
|
||||
*/
|
||||
private boolean enabled;
|
||||
|
||||
/**
|
||||
* Queue for receiving DMF messages from update server.
|
||||
*/
|
||||
@@ -84,4 +95,12 @@ public class AmqpProperties {
|
||||
public void setDeadLetterTtl(final int deadLetterTtl) {
|
||||
this.deadLetterTtl = deadLetterTtl;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(final boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
*/
|
||||
package org.eclipse.hawkbit.simulator.amqp;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
@@ -58,7 +59,7 @@ public abstract class SenderService extends MessageService {
|
||||
}
|
||||
message.getMessageProperties().getHeaders().remove(AbstractJavaTypeMapper.DEFAULT_CLASSID_FIELD_NAME);
|
||||
final String correlationId = UUID.randomUUID().toString();
|
||||
message.getMessageProperties().setCorrelationId(correlationId.getBytes());
|
||||
message.getMessageProperties().setCorrelationId(correlationId.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
if (LOGGER.isTraceEnabled()) {
|
||||
LOGGER.trace("Sending message {} to exchange {} with correlationId {}", message, address, correlationId);
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
*/
|
||||
package org.eclipse.hawkbit.simulator.amqp;
|
||||
|
||||
import static org.eclipse.hawkbit.simulator.amqp.AmqpProperties.CONFIGURATION_PREFIX;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.hawkbit.dmf.amqp.api.EventTopic;
|
||||
@@ -22,6 +24,7 @@ import org.springframework.amqp.core.MessageProperties;
|
||||
import org.springframework.amqp.rabbit.annotation.RabbitListener;
|
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.messaging.handler.annotation.Header;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@@ -32,6 +35,7 @@ import com.google.common.collect.Lists;
|
||||
*
|
||||
*/
|
||||
@Component
|
||||
@ConditionalOnProperty(prefix = CONFIGURATION_PREFIX, name = "enabled")
|
||||
public class SpReceiverService extends ReceiverService {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ReceiverService.class);
|
||||
|
||||
@@ -67,8 +71,6 @@ public class SpReceiverService extends ReceiverService {
|
||||
* the incoming message
|
||||
* @param type
|
||||
* the action type
|
||||
* @param contentType
|
||||
* the content type in message header
|
||||
* @param thingId
|
||||
* the thing id in message header
|
||||
*/
|
||||
@@ -82,14 +84,11 @@ public class SpReceiverService extends ReceiverService {
|
||||
private void delegateMessage(final Message message, final String type, final String thingId) {
|
||||
final MessageType messageType = MessageType.valueOf(type);
|
||||
|
||||
switch (messageType) {
|
||||
case EVENT:
|
||||
if (MessageType.EVENT.equals(messageType)) {
|
||||
handleEventMessage(message, thingId);
|
||||
break;
|
||||
default:
|
||||
LOGGER.info("No valid message type property.");
|
||||
break;
|
||||
return;
|
||||
}
|
||||
LOGGER.info("No valid message type property.");
|
||||
}
|
||||
|
||||
private void handleEventMessage(final Message message, final String thingId) {
|
||||
|
||||
@@ -33,9 +33,10 @@ import com.vaadin.ui.Window;
|
||||
* Popup dialog window for setting the values of generating the simulated
|
||||
* devices, e.g. the amount.
|
||||
*
|
||||
* @author Michael Hirsch
|
||||
*
|
||||
*/
|
||||
// Vaadin Inheritance
|
||||
@SuppressWarnings("squid:MaximumInheritanceDepth")
|
||||
public class GenerateDialog extends Window {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
@@ -49,8 +50,9 @@ public class GenerateDialog extends Window {
|
||||
private final TextField pollDelayTextField;
|
||||
private final TextField pollUrlTextField;
|
||||
private final TextField gatewayTokenTextField;
|
||||
private final OptionGroup protocolGroup;
|
||||
private final Button buttonOk;
|
||||
private OptionGroup protocolGroup;
|
||||
private Button buttonOk;
|
||||
private final boolean dmfEnabled;
|
||||
|
||||
/**
|
||||
* Creates a new pop window for setting the configuration of simulating
|
||||
@@ -59,9 +61,12 @@ public class GenerateDialog extends Window {
|
||||
* @param callback
|
||||
* the callback which is called when the dialog has been
|
||||
* successfully confirmed.
|
||||
* @param dmfEnabled
|
||||
* indicates if the AMQP/DMF interface is enabled by
|
||||
* configuration and if the option DMF should be enabled or not
|
||||
*/
|
||||
public GenerateDialog(final GenerateDialogCallback callback) {
|
||||
|
||||
public GenerateDialog(final GenerateDialogCallback callback, final boolean dmfEnabled) {
|
||||
this.dmfEnabled = dmfEnabled;
|
||||
formLayout.setSpacing(true);
|
||||
formLayout.setMargin(true);
|
||||
|
||||
@@ -87,8 +92,8 @@ public class GenerateDialog extends Window {
|
||||
gatewayTokenTextField.setColumns(50);
|
||||
gatewayTokenTextField.setVisible(false);
|
||||
|
||||
protocolGroup = createProtocolGroup();
|
||||
buttonOk = createOkButton(callback);
|
||||
createProtocolGroup();
|
||||
createOkButton(callback);
|
||||
|
||||
namePrefixTextField.addValueChangeListener(event -> checkValid());
|
||||
amountTextField.addValueChangeListener(event -> checkValid());
|
||||
@@ -181,26 +186,29 @@ public class GenerateDialog extends Window {
|
||||
final URL basePollURL, final String gatewayToken, final Protocol protocol);
|
||||
}
|
||||
|
||||
private OptionGroup createProtocolGroup() {
|
||||
private void createProtocolGroup() {
|
||||
|
||||
final OptionGroup protocolGroup = new OptionGroup("Simulated Device Protocol");
|
||||
this.protocolGroup = new OptionGroup("Simulated Device Protocol");
|
||||
protocolGroup.addItem(Protocol.DMF_AMQP);
|
||||
protocolGroup.addItem(Protocol.DDI_HTTP);
|
||||
protocolGroup.select(Protocol.DMF_AMQP);
|
||||
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(event -> {
|
||||
final boolean directDeviceOptionSelected = event.getProperty().getValue().equals(Protocol.DDI_HTTP);
|
||||
pollUrlTextField.setVisible(directDeviceOptionSelected);
|
||||
gatewayTokenTextField.setVisible(directDeviceOptionSelected);
|
||||
});
|
||||
return protocolGroup;
|
||||
protocolGroup.setItemEnabled(Protocol.DMF_AMQP, dmfEnabled);
|
||||
if (!dmfEnabled) {
|
||||
protocolGroup.select(Protocol.DDI_HTTP);
|
||||
}
|
||||
}
|
||||
|
||||
private Button createOkButton(final GenerateDialogCallback callback) {
|
||||
private void createOkButton(final GenerateDialogCallback callback) {
|
||||
|
||||
final Button buttonOk = new Button("generate");
|
||||
this.buttonOk = new Button("generate");
|
||||
buttonOk.setImmediate(true);
|
||||
buttonOk.setIcon(FontAwesome.GEARS);
|
||||
buttonOk.addClickListener(event -> {
|
||||
@@ -210,14 +218,11 @@ public class GenerateDialog extends Window {
|
||||
Integer.valueOf(pollDelayTextField.getValue().replace(".", "")),
|
||||
new URL(pollUrlTextField.getValue()), gatewayTokenTextField.getValue(),
|
||||
(Protocol) protocolGroup.getValue());
|
||||
} catch (final NumberFormatException e) {
|
||||
LOGGER.info(e.getMessage(), e);
|
||||
} catch (final MalformedURLException e) {
|
||||
} catch (final NumberFormatException | MalformedURLException e) {
|
||||
LOGGER.info(e.getMessage(), e);
|
||||
}
|
||||
GenerateDialog.this.close();
|
||||
});
|
||||
return buttonOk;
|
||||
}
|
||||
|
||||
private TextField createRequiredTextfield(final String caption, final String value, final Resource icon,
|
||||
@@ -226,7 +231,7 @@ public class GenerateDialog extends Window {
|
||||
return addTextFieldValues(textField, icon, validator);
|
||||
}
|
||||
|
||||
private TextField createRequiredTextfield(final String caption, final Property dataSource, final Resource icon,
|
||||
private TextField createRequiredTextfield(final String caption, final Property<?> dataSource, final Resource icon,
|
||||
final Validator validator) {
|
||||
final TextField textField = new TextField(caption, dataSource);
|
||||
return addTextFieldValues(textField, icon, validator);
|
||||
|
||||
@@ -17,6 +17,7 @@ import org.eclipse.hawkbit.simulator.AbstractSimulatedDevice.Status;
|
||||
import org.eclipse.hawkbit.simulator.DeviceSimulatorRepository;
|
||||
import org.eclipse.hawkbit.simulator.SimulatedDeviceFactory;
|
||||
import org.eclipse.hawkbit.simulator.UpdateStatus.ResponseStatus;
|
||||
import org.eclipse.hawkbit.simulator.amqp.AmqpProperties;
|
||||
import org.eclipse.hawkbit.simulator.amqp.SpSenderService;
|
||||
import org.eclipse.hawkbit.simulator.event.InitUpdate;
|
||||
import org.eclipse.hawkbit.simulator.event.NextPollCounterUpdate;
|
||||
@@ -57,6 +58,11 @@ import com.vaadin.ui.renderers.ProgressBarRenderer;
|
||||
@SuppressWarnings("squid:MaximumInheritanceDepth")
|
||||
public class SimulatorView extends VerticalLayout implements View {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final String HTML_SPAN = ";</span>";
|
||||
|
||||
private static final String NEXT_POLL_COUNTER_SEC_COL = "nextPollCounterSec";
|
||||
|
||||
private static final String RESPONSE_STATUS_COL = "updateStatus";
|
||||
@@ -85,6 +91,9 @@ public class SimulatorView extends VerticalLayout implements View {
|
||||
@Autowired
|
||||
private transient EventBus eventbus;
|
||||
|
||||
@Autowired
|
||||
private transient AmqpProperties amqpProperties;
|
||||
|
||||
private final Label caption = new Label("DMF/DDI Simulated Devices");
|
||||
private final HorizontalLayout toolbar = new HorizontalLayout();
|
||||
private final Grid grid = new Grid();
|
||||
@@ -261,94 +270,94 @@ public class SimulatorView extends VerticalLayout implements View {
|
||||
final String deviceId = namePrefix + index;
|
||||
beanContainer.addBean(repository.add(deviceFactory.createSimulatedDevice(deviceId,
|
||||
tenant.toLowerCase(), protocol, pollDelay, basePollUrl, gatewayToken)));
|
||||
spSenderService.createOrUpdateThing(tenant, deviceId);
|
||||
}
|
||||
}));
|
||||
}, amqpProperties.isEnabled()));
|
||||
}
|
||||
|
||||
private Converter<String, Protocol> createProtocolConverter() {
|
||||
|
||||
return new Converter<String, Protocol>() {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public Protocol convertToModel(final String value, final Class<? extends Protocol> targetType,
|
||||
final Locale locale) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String convertToPresentation(final Protocol value, final Class<? extends String> 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<Protocol> getModelType() {
|
||||
return Protocol.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<String> getPresentationType() {
|
||||
return String.class;
|
||||
}
|
||||
};
|
||||
|
||||
private ProtocolConverter createProtocolConverter() {
|
||||
return new ProtocolConverter();
|
||||
}
|
||||
|
||||
private Converter<String, Status> createStatusConverter() {
|
||||
return new Converter<String, Status>() {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private StatusConverter createStatusConverter() {
|
||||
return new StatusConverter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Status convertToModel(final String value, final Class<? extends Status> targetType,
|
||||
final Locale locale) {
|
||||
return null;
|
||||
}
|
||||
public static final class ProtocolConverter implements Converter<String, Protocol> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public String convertToPresentation(final Status value, final Class<? extends String> targetType,
|
||||
final Locale locale) {
|
||||
switch (value) {
|
||||
case UNKNWON:
|
||||
return "<span class=\"v-icon grayicon\" style=\"font-family: " + FontAwesome.FONT_FAMILY
|
||||
+ ";\"color\":\"gray\";\">&#x"
|
||||
+ Integer.toHexString(FontAwesome.QUESTION_CIRCLE.getCodepoint()) + ";</span>";
|
||||
case PEDNING:
|
||||
return "<span class=\"v-icon yellowicon\" style=\"font-family: " + FontAwesome.FONT_FAMILY
|
||||
+ ";\"color\":\"yellow\";\">&#x" + Integer.toHexString(FontAwesome.REFRESH.getCodepoint())
|
||||
+ ";</span>";
|
||||
case FINISH:
|
||||
return "<span class=\"v-icon greenicon\" style=\"font-family: " + FontAwesome.FONT_FAMILY
|
||||
+ ";\"color\":\"green\";\">&#x"
|
||||
+ Integer.toHexString(FontAwesome.CHECK_CIRCLE.getCodepoint()) + ";</span>";
|
||||
case ERROR:
|
||||
return "<span class=\"v-icon redicon\" style=\"font-family: " + FontAwesome.FONT_FAMILY
|
||||
+ ";\"color\":\"red\";\">&#x"
|
||||
+ Integer.toHexString(FontAwesome.EXCLAMATION_CIRCLE.getCodepoint()) + ";</span>";
|
||||
default:
|
||||
throw new IllegalStateException("unknown value");
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public Protocol convertToModel(final String value, final Class<? extends Protocol> targetType,
|
||||
final Locale locale) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<Status> getModelType() {
|
||||
return Status.class;
|
||||
@Override
|
||||
public String convertToPresentation(final Protocol value, final Class<? extends String> 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<String> getPresentationType() {
|
||||
return String.class;
|
||||
@Override
|
||||
public Class<Protocol> getModelType() {
|
||||
return Protocol.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<String> getPresentationType() {
|
||||
return String.class;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class StatusConverter implements Converter<String, Status> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public Status convertToModel(final String value, final Class<? extends Status> targetType,
|
||||
final Locale locale) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String convertToPresentation(final Status value, final Class<? extends String> targetType,
|
||||
final Locale locale) {
|
||||
switch (value) {
|
||||
case UNKNWON:
|
||||
return "<span class=\"v-icon grayicon\" style=\"font-family: " + FontAwesome.FONT_FAMILY
|
||||
+ ";\"color\":\"gray\";\">&#x" + Integer.toHexString(FontAwesome.QUESTION_CIRCLE.getCodepoint())
|
||||
+ HTML_SPAN;
|
||||
case PEDNING:
|
||||
return "<span class=\"v-icon yellowicon\" style=\"font-family: " + FontAwesome.FONT_FAMILY
|
||||
+ ";\"color\":\"yellow\";\">&#x" + Integer.toHexString(FontAwesome.REFRESH.getCodepoint())
|
||||
+ HTML_SPAN;
|
||||
case FINISH:
|
||||
return "<span class=\"v-icon greenicon\" style=\"font-family: " + FontAwesome.FONT_FAMILY
|
||||
+ ";\"color\":\"green\";\">&#x" + Integer.toHexString(FontAwesome.CHECK_CIRCLE.getCodepoint())
|
||||
+ HTML_SPAN;
|
||||
case ERROR:
|
||||
return "<span class=\"v-icon redicon\" style=\"font-family: " + FontAwesome.FONT_FAMILY
|
||||
+ ";\"color\":\"red\";\">&#x"
|
||||
+ Integer.toHexString(FontAwesome.EXCLAMATION_CIRCLE.getCodepoint()) + HTML_SPAN;
|
||||
default:
|
||||
throw new IllegalStateException("unknown value");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<Status> getModelType() {
|
||||
return Status.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<String> getPresentationType() {
|
||||
return String.class;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#
|
||||
|
||||
## Configuration for DMF communication
|
||||
hawkbit.device.simulator.amqp.enabled=true
|
||||
hawkbit.device.simulator.amqp.receiverConnectorQueueFromSp=simulator_receiver
|
||||
hawkbit.device.simulator.amqp.deadLetterQueue=simulator_deadletter
|
||||
hawkbit.device.simulator.amqp.deadLetterExchange=simulator.deadletter
|
||||
|
||||
Reference in New Issue
Block a user