Initial check in accordance with Parallel IP
This commit is contained in:
2
examples/hawkbit-device-simulator/.gitignore
vendored
Executable file
2
examples/hawkbit-device-simulator/.gitignore
vendored
Executable file
@@ -0,0 +1,2 @@
|
||||
/bin/
|
||||
/target/
|
||||
18
examples/hawkbit-device-simulator/README.md
Executable file
18
examples/hawkbit-device-simulator/README.md
Executable file
@@ -0,0 +1,18 @@
|
||||
# hawkBit Device Simulator
|
||||
|
||||
The device simulator handles software update commands from the update server.
|
||||
|
||||
## Run
|
||||
```
|
||||
java -jar examples/hawkbit-device-simulator/target/hawkbit-device-simulator-*-SNAPSHOT.jar
|
||||
```
|
||||
Or:
|
||||
```
|
||||
run org.eclipse.hawkbit.simulator.DeviceSimulator
|
||||
```
|
||||
|
||||
## Usage
|
||||
The device simulator exposes an REST-API which can be used to trigger device creation.
|
||||
```
|
||||
http://localhost:8083/start?amount=10
|
||||
```
|
||||
61
examples/hawkbit-device-simulator/pom.xml
Executable file
61
examples/hawkbit-device-simulator/pom.xml
Executable file
@@ -0,0 +1,61 @@
|
||||
<?xml version="1.0"?>
|
||||
<!--
|
||||
|
||||
Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
|
||||
All rights reserved. This program and the accompanying materials
|
||||
are made available under the terms of the Eclipse Public License v1.0
|
||||
which accompanies this distribution, and is available at
|
||||
http://www.eclipse.org/legal/epl-v10.html
|
||||
|
||||
-->
|
||||
|
||||
<project
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.eclipse.hawkbit</groupId>
|
||||
<version>0.1.0-SNAPSHOT</version>
|
||||
<artifactId>hawkbit-examples-parent</artifactId>
|
||||
</parent>
|
||||
|
||||
<artifactId>hawkbit-device-simulator</artifactId>
|
||||
<name>hawkBit :: Device Simulator</name>
|
||||
<description>Device Management Federation API based simulator</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.hawkbit</groupId>
|
||||
<artifactId>hawkbit-dmf-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.amqp</groupId>
|
||||
<artifactId>spring-rabbit</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-logging</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-log4j2</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-autoconfigure</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -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
|
||||
*/
|
||||
package org.eclipse.hawkbit.simulator;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
/**
|
||||
* The main-method to start the Spring-Boot application.
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class DeviceSimulator {
|
||||
|
||||
/**
|
||||
* Start the Spring Boot Application.
|
||||
*
|
||||
* @param args
|
||||
* the args
|
||||
*/
|
||||
public static void main(final String[] args) {
|
||||
SpringApplication.run(DeviceSimulator.class, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* 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 org.eclipse.hawkbit.simulator.amqp.SpSenderService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* REST endpoint for controlling the device simulator.
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
@RestController
|
||||
public class SimulationController {
|
||||
|
||||
@Autowired
|
||||
private SpSenderService spSenderService;
|
||||
|
||||
/**
|
||||
* The start resource to start a device creation.
|
||||
*
|
||||
* @param name
|
||||
* the name prefix of the generated device naming
|
||||
* @param amount
|
||||
* the amount of devices to be created
|
||||
* @param tenant
|
||||
* the tenant to create the device to
|
||||
* @return a response string that devices has been created
|
||||
*/
|
||||
@RequestMapping("/start")
|
||||
String start(@RequestParam(value = "name", defaultValue = "dmfSimulated") final String name,
|
||||
@RequestParam(value = "amount", defaultValue = "20") final int amount,
|
||||
@RequestParam(value = "tenant", defaultValue = "DEFAULT") final String tenant) {
|
||||
|
||||
for (int i = 0; i < amount; i++) {
|
||||
spSenderService.createOrUpdateThing(tenant, name + i);
|
||||
}
|
||||
|
||||
return "Updated " + amount + " DMF connected targets!";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
/**
|
||||
* 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.amqp;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.amqp.core.Binding;
|
||||
import org.springframework.amqp.core.BindingBuilder;
|
||||
import org.springframework.amqp.core.FanoutExchange;
|
||||
import org.springframework.amqp.core.Queue;
|
||||
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
|
||||
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
|
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
|
||||
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
|
||||
import org.springframework.amqp.support.converter.MessageConverter;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* The spring AMQP configuration to use a AMQP for communication with SP update
|
||||
* server.
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(AmqpProperties.class)
|
||||
public class AmqpConfiguration {
|
||||
|
||||
@Autowired
|
||||
protected AmqpProperties amqpProperties;
|
||||
|
||||
@Autowired
|
||||
private ConnectionFactory connectionFactory;
|
||||
|
||||
@Autowired
|
||||
private RabbitTemplate rabbitTemplate;
|
||||
|
||||
/**
|
||||
* Create jackson message converter bean.
|
||||
*
|
||||
* @return the jackson message converter
|
||||
*/
|
||||
@Bean
|
||||
public MessageConverter jsonMessageConverter() {
|
||||
final Jackson2JsonMessageConverter jackson2JsonMessageConverter = new Jackson2JsonMessageConverter();
|
||||
rabbitTemplate.setMessageConverter(jackson2JsonMessageConverter);
|
||||
return jackson2JsonMessageConverter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the receiver queue from sp. Receive messages from sp.
|
||||
*
|
||||
* @return the queue
|
||||
*/
|
||||
@Bean
|
||||
public Queue receiverConnectorQueueFromSp() {
|
||||
return new Queue(amqpProperties.getReceiverConnectorQueueFromSp(), true, false, false,
|
||||
getDeadLetterExchangeArgs());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the recevier exchange. Sp send messages to this exchange.
|
||||
*
|
||||
* @return the exchange
|
||||
*/
|
||||
@Bean
|
||||
public FanoutExchange exchangeQueueToConnector() {
|
||||
return new FanoutExchange(amqpProperties.getSenderForSpExchange());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the Binding
|
||||
* {@link AmqpConfiguration#receiverConnectorQueueFromSp()} to
|
||||
* {@link AmqpConfiguration#exchangeQueueToConnector()}.
|
||||
*
|
||||
* @return the binding and create the queue and exchange
|
||||
*/
|
||||
@Bean
|
||||
public Binding bindReceiverQueueToSpExchange() {
|
||||
return BindingBuilder.bind(receiverConnectorQueueFromSp()).to(exchangeQueueToConnector());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create dead letter queue.
|
||||
*
|
||||
* @return the queue
|
||||
*/
|
||||
@Bean
|
||||
public Queue deadLetterQueue() {
|
||||
return new Queue(amqpProperties.getDeadLetterQueue(), true, false, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the dead letter fanout exchange.
|
||||
*
|
||||
* @return the fanout exchange
|
||||
*/
|
||||
@Bean
|
||||
public FanoutExchange exchangeDeadLetter() {
|
||||
return new FanoutExchange(amqpProperties.getDeadLetterExchange());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the Binding deadLetterQueue to exchangeDeadLetter.
|
||||
*
|
||||
* @return the binding
|
||||
*/
|
||||
@Bean
|
||||
public Binding bindDeadLetterQueueToLwm2mExchange() {
|
||||
return BindingBuilder.bind(deadLetterQueue()).to(exchangeDeadLetter());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Listener factory.
|
||||
*
|
||||
* @return the {@link SimpleMessageListenerContainer} that gets used receive
|
||||
* AMQP messages
|
||||
*/
|
||||
@Bean(name = { "listenerContainerFactory" })
|
||||
public SimpleRabbitListenerContainerFactory listenerContainerFactory() {
|
||||
final SimpleRabbitListenerContainerFactory containerFactory = new SimpleRabbitListenerContainerFactory();
|
||||
containerFactory.setDefaultRequeueRejected(false);
|
||||
containerFactory.setConnectionFactory(connectionFactory);
|
||||
return containerFactory;
|
||||
}
|
||||
|
||||
private Map<String, Object> getDeadLetterExchangeArgs() {
|
||||
final Map<String, Object> args = new HashMap<String, Object>();
|
||||
args.put("x-dead-letter-exchange", amqpProperties.getDeadLetterExchange());
|
||||
return args;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.simulator.amqp;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* Bean which holds the necessary properties for configuring the AMQP
|
||||
* connection.
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
@ConfigurationProperties("hawkbit.device.simulator.amqp")
|
||||
public class AmqpProperties {
|
||||
|
||||
private String receiverConnectorQueueFromSp;
|
||||
private String senderForSpExchange;
|
||||
|
||||
private String deadLetterQueue;
|
||||
private String deadLetterExchange;
|
||||
|
||||
public String getReceiverConnectorQueueFromSp() {
|
||||
return receiverConnectorQueueFromSp;
|
||||
}
|
||||
|
||||
public void setReceiverConnectorQueueFromSp(final String receiverConnectorQueueFromSp) {
|
||||
this.receiverConnectorQueueFromSp = receiverConnectorQueueFromSp;
|
||||
}
|
||||
|
||||
public String getDeadLetterExchange() {
|
||||
return deadLetterExchange;
|
||||
}
|
||||
|
||||
public void setDeadLetterExchange(final String deadLetterExchange) {
|
||||
this.deadLetterExchange = deadLetterExchange;
|
||||
}
|
||||
|
||||
public String getDeadLetterQueue() {
|
||||
return deadLetterQueue;
|
||||
}
|
||||
|
||||
public void setDeadLetterQueue(final String deadLetterQueue) {
|
||||
this.deadLetterQueue = deadLetterQueue;
|
||||
}
|
||||
|
||||
public String getSenderForSpExchange() {
|
||||
return senderForSpExchange;
|
||||
}
|
||||
|
||||
public void setSenderForSpExchange(final String senderForSpExchange) {
|
||||
this.senderForSpExchange = senderForSpExchange;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.simulator.amqp;
|
||||
|
||||
/**
|
||||
* The constants for the caching.
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
public final class MessageHandlerCacheConstant {
|
||||
|
||||
/**
|
||||
* The name of the cache.
|
||||
*/
|
||||
public static final String CACHE_NAME = "hawkbit.device.simulator.states";
|
||||
|
||||
private MessageHandlerCacheConstant() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.simulator.amqp;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.amqp.core.Message;
|
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
import org.springframework.amqp.support.converter.AbstractJavaTypeMapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
/**
|
||||
* Abstract class for sender and receiver service.
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
public class MessageService {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(MessageService.class);
|
||||
|
||||
protected RabbitTemplate rabbitTemplate;
|
||||
|
||||
protected AmqpProperties amqpProperties;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param rabbitTemplate
|
||||
* the rabbit template
|
||||
* @param amqpProperties
|
||||
* the amqp properties
|
||||
* @param messageConverter
|
||||
* the message converter
|
||||
*/
|
||||
@Autowired
|
||||
public MessageService(final RabbitTemplate rabbitTemplate, final AmqpProperties amqpProperties) {
|
||||
this.rabbitTemplate = rabbitTemplate;
|
||||
this.amqpProperties = amqpProperties;
|
||||
}
|
||||
|
||||
public void setAmqpProperties(final AmqpProperties amqpProperties) {
|
||||
this.amqpProperties = amqpProperties;
|
||||
}
|
||||
|
||||
public void setRabbitTemplate(final RabbitTemplate rabbitTemplate) {
|
||||
this.rabbitTemplate = rabbitTemplate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to call when error emerges.
|
||||
*
|
||||
* @param message
|
||||
* the message that triggered the error
|
||||
* @param error
|
||||
* the error
|
||||
*/
|
||||
public void logAndThrowMessageError(final Message message, final String error) {
|
||||
LOGGER.error("Error \"{}\" reported by message {}", error, message.getMessageProperties().getMessageId());
|
||||
throw new IllegalArgumentException(error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a message body to a given class and set the message header
|
||||
* AbstractJavaTypeMapper.DEFAULT_CLASSID_FIELD_NAME for Jackson converter.
|
||||
*
|
||||
* @param message
|
||||
* which body will converted
|
||||
* @param clazz
|
||||
* the body class
|
||||
* @return the converted body
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T convertMessage(final Message message, final Class<T> clazz) {
|
||||
message.getMessageProperties().getHeaders().put(AbstractJavaTypeMapper.DEFAULT_CLASSID_FIELD_NAME,
|
||||
clazz.getTypeName());
|
||||
return (T) rabbitTemplate.getMessageConverter().fromMessage(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to verify if lwm2m header is set.
|
||||
*
|
||||
* @param message
|
||||
* the message with the header
|
||||
* @param header
|
||||
* the header to verify
|
||||
*/
|
||||
public void checkIfLwm2mHeaderEmpty(final Message message, final String header) {
|
||||
final Object headerObject = message.getMessageProperties().getHeaders().get(header);
|
||||
if (null == headerObject) {
|
||||
logAndThrowMessageError(message, "Header of " + header + "empty.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.simulator.amqp;
|
||||
|
||||
import org.springframework.amqp.core.Message;
|
||||
import org.springframework.amqp.core.MessageProperties;
|
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
/**
|
||||
* Abstract class for all receiver objects.
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
public abstract class ReceiverService extends MessageService {
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param rabbitTemplate
|
||||
* RabbitTemplate
|
||||
* @param amqpProperties
|
||||
* AmqpProperties
|
||||
* @param messageConverter
|
||||
* MessageConverter
|
||||
*/
|
||||
@Autowired
|
||||
public ReceiverService(final RabbitTemplate rabbitTemplate, final AmqpProperties amqpProperties) {
|
||||
super(rabbitTemplate, amqpProperties);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to validate if content type is set in the message properties.
|
||||
*
|
||||
* @param message
|
||||
* the message to get validated
|
||||
*/
|
||||
protected void checkContentTypeJson(final Message message) {
|
||||
if (message.getBody().length == 0) {
|
||||
return;
|
||||
}
|
||||
final MessageProperties messageProperties = message.getMessageProperties();
|
||||
final String headerContentType = (String) messageProperties.getHeaders().get("content-type");
|
||||
if (null != headerContentType) {
|
||||
messageProperties.setContentType(headerContentType);
|
||||
}
|
||||
final String contentType = messageProperties.getContentType();
|
||||
if (contentType != null && contentType.contains("json")) {
|
||||
return;
|
||||
}
|
||||
throw new IllegalArgumentException("Content-Type is not JSON compatible");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* 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.amqp;
|
||||
|
||||
import org.springframework.amqp.core.Message;
|
||||
import org.springframework.amqp.core.MessageProperties;
|
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
/**
|
||||
* Abstract calls for sender service objects.
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
public abstract class SenderService extends MessageService {
|
||||
|
||||
/**
|
||||
* Constructor for sender service.
|
||||
*
|
||||
* @param rabbitTemplate
|
||||
* the rabbit template
|
||||
* @param amqpProperties
|
||||
* the amqp properties
|
||||
* @param cacheManager
|
||||
* the cache manager
|
||||
*/
|
||||
@Autowired
|
||||
public SenderService(final RabbitTemplate rabbitTemplate, final AmqpProperties amqpProperties) {
|
||||
super(rabbitTemplate, amqpProperties);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message if the message is not null.
|
||||
*
|
||||
* @param adress
|
||||
* the exchange name
|
||||
* @param message
|
||||
* the amqp message which will be send if its not null
|
||||
*/
|
||||
public void sendMessage(final String adress, final Message message) {
|
||||
if (message == null) {
|
||||
return;
|
||||
}
|
||||
rabbitTemplate.setExchange(adress);
|
||||
rabbitTemplate.send(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert object and message properties to message.
|
||||
*
|
||||
* @param object
|
||||
* to get converted
|
||||
* @param messageProperties
|
||||
* to get converted
|
||||
* @return converted message
|
||||
*/
|
||||
public Message convertMessage(final Object object, final MessageProperties messageProperties) {
|
||||
return rabbitTemplate.getMessageConverter().toMessage(object, messageProperties);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* 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.amqp;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* Object for holding attributes for a simulated update for the device
|
||||
* simulator.
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
public class SimulatedUpdate implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
protected final String tenant;
|
||||
protected final String thingId;
|
||||
|
||||
protected final Long actionId;
|
||||
|
||||
protected LocalDateTime startCacheTime;
|
||||
|
||||
/**
|
||||
* Constructor of the class.
|
||||
*
|
||||
* @param tenant
|
||||
* the tenant name.
|
||||
* @param thingId
|
||||
* the thing id.
|
||||
* @param actionId
|
||||
* the action id
|
||||
* @param softwareModuleId
|
||||
* software modul id
|
||||
* @param urls
|
||||
* the artifact download urls.
|
||||
*/
|
||||
public SimulatedUpdate(final String tenant, final String thingId, final Long actionId) {
|
||||
this.tenant = tenant;
|
||||
this.thingId = thingId;
|
||||
this.actionId = actionId;
|
||||
this.startCacheTime = LocalDateTime.now();
|
||||
}
|
||||
|
||||
public String getTenant() {
|
||||
return tenant;
|
||||
}
|
||||
|
||||
public String getThingId() {
|
||||
return thingId;
|
||||
}
|
||||
|
||||
public Long getActionId() {
|
||||
return actionId;
|
||||
}
|
||||
|
||||
public LocalDateTime getStartCacheTime() {
|
||||
return startCacheTime;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
/**
|
||||
* 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.amqp;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.hawkbit.dmf.amqp.api.EventTopic;
|
||||
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.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.amqp.core.Message;
|
||||
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.messaging.handler.annotation.Header;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Handle all incoming Messages from SP.
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
@Component
|
||||
public class SpReceiverService extends ReceiverService {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ReceiverService.class);
|
||||
|
||||
public static final String SOFTWARE_MODULE_FIRMWARE = "firmware";
|
||||
|
||||
private final SpSenderService spSenderService;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param rabbitTemplate
|
||||
* the rabbit template
|
||||
* @param amqpProperties
|
||||
* the amqp properties
|
||||
* @param lwm2mSenderService
|
||||
* the lwm2mSenderService
|
||||
* @param spSenderService
|
||||
* the spSenderService
|
||||
*/
|
||||
@Autowired
|
||||
public SpReceiverService(final RabbitTemplate rabbitTemplate, final AmqpProperties amqpProperties,
|
||||
final SpSenderService spSenderService) {
|
||||
super(rabbitTemplate, amqpProperties);
|
||||
this.spSenderService = spSenderService;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the incoming Message from Queue with the property
|
||||
* (com.bosch.sp.lwm2m.connector.amqp.receiverConnectorQueueFromSp).
|
||||
*
|
||||
* @param message
|
||||
* the incoming message
|
||||
* @param type
|
||||
* the action type
|
||||
* @param contentType
|
||||
* the content type in message header
|
||||
* @param thingId
|
||||
* the thing id in message header
|
||||
*/
|
||||
@RabbitListener(queues = "${hawkbit.device.simulator.amqp.receiverConnectorQueueFromSp}", containerFactory = "listenerContainerFactory")
|
||||
public void recieveMessageSp(final Message message, @Header(MessageHeaderKey.TYPE) final String type,
|
||||
@Header(MessageHeaderKey.THING_ID) final String thingId) {
|
||||
checkContentTypeJson(message);
|
||||
delegateMessage(message, type, thingId);
|
||||
}
|
||||
|
||||
private void delegateMessage(final Message message, final String type, final String thingId) {
|
||||
final MessageType messageType = MessageType.valueOf(type);
|
||||
|
||||
switch (messageType) {
|
||||
case EVENT:
|
||||
handleEventMessage(message, thingId);
|
||||
break;
|
||||
default:
|
||||
LOGGER.info("No valid message type property.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void handleEventMessage(final Message message, final String thingId) {
|
||||
final Object eventHeader = message.getMessageProperties().getHeaders().get(MessageHeaderKey.TOPIC);
|
||||
if (eventHeader == null) {
|
||||
logAndThrowMessageError(message, "Event Topic is not set");
|
||||
}
|
||||
final EventTopic eventTopic = EventTopic.valueOf(eventHeader.toString());
|
||||
switch (eventTopic) {
|
||||
case DOWNLOAD_AND_INSTALL:
|
||||
handleUpdateProcess(message, thingId);
|
||||
break;
|
||||
case CANCEL_DOWNLOAD:
|
||||
handleCancelDownloadAction(message, thingId);
|
||||
break;
|
||||
default:
|
||||
LOGGER.info("No valid event property.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void handleCancelDownloadAction(final Message message, final String thingId) {
|
||||
final MessageProperties messageProperties = message.getMessageProperties();
|
||||
final Map<String, Object> headers = messageProperties.getHeaders();
|
||||
final String tenant = (String) headers.get(MessageHeaderKey.TENANT);
|
||||
final Long actionId = convertMessage(message, Long.class);
|
||||
|
||||
final SimulatedUpdate update = new SimulatedUpdate(tenant, thingId, actionId);
|
||||
spSenderService.finishUpdateProcess(update, "Simulation canceled");
|
||||
}
|
||||
|
||||
private void handleUpdateProcess(final Message message, final String thingId) {
|
||||
final MessageProperties messageProperties = message.getMessageProperties();
|
||||
final Map<String, Object> headers = messageProperties.getHeaders();
|
||||
final String tenant = (String) headers.get(MessageHeaderKey.TENANT);
|
||||
|
||||
final DownloadAndUpdateRequest downloadAndUpdateRequest = convertMessage(message,
|
||||
DownloadAndUpdateRequest.class);
|
||||
final Long actionId = downloadAndUpdateRequest.getActionId();
|
||||
|
||||
spSenderService.sendActionStatusMessage(tenant, ActionStatus.RUNNING,
|
||||
"device Simulator retrieved update request. proceeding with simulation.", actionId);
|
||||
|
||||
final SimulatedUpdate update = new SimulatedUpdate(tenant, thingId, actionId);
|
||||
|
||||
spSenderService.finishUpdateProcess(update, "Simulation complete!");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
/**
|
||||
* 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.amqp;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.hawkbit.dmf.amqp.api.AmqpSettings;
|
||||
import org.eclipse.hawkbit.dmf.amqp.api.EventTopic;
|
||||
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.ActionUpdateStatus;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.amqp.core.Message;
|
||||
import org.springframework.amqp.core.MessageProperties;
|
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Sender service to send message to SP.
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
@Service
|
||||
public class SpSenderService extends SenderService {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(SpSenderService.class);
|
||||
|
||||
private final String spExchange;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param rabbitTemplate
|
||||
* the rabbit template
|
||||
* @param amqpProperties
|
||||
* the amqp properties
|
||||
*/
|
||||
@Autowired
|
||||
public SpSenderService(final RabbitTemplate rabbitTemplate, final AmqpProperties amqpProperties) {
|
||||
super(rabbitTemplate, amqpProperties);
|
||||
this.spExchange = AmqpSettings.DMF_EXCHANGE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finish the update process. This will send a action status to SP.
|
||||
*
|
||||
* @param update
|
||||
* the simulated update object
|
||||
* @param description
|
||||
* a description according the update process
|
||||
*/
|
||||
public void finishUpdateProcess(final SimulatedUpdate update, final String description) {
|
||||
final Message updateResultMessage = createUpdateResultMessage(update, ActionStatus.FINISHED, description);
|
||||
sendMessage(spExchange, updateResultMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finish update process with error and send error to SP.
|
||||
*
|
||||
* @param update
|
||||
* the simulated update object
|
||||
* @param messageDescription
|
||||
* a description according the update process
|
||||
*/
|
||||
public void finishUpdateProcessWithError(final SimulatedUpdate update, final String messageDescription) {
|
||||
sendErrorgMessage(update, messageDescription);
|
||||
LOGGER.debug("Update process finished with error \"{}\" reported by thing {}", messageDescription,
|
||||
update.getThingId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an error message to SP.
|
||||
*
|
||||
* @param tenant
|
||||
* the tenant
|
||||
* @param messageDescription
|
||||
* the error message description to send
|
||||
* @param actionId
|
||||
* the ID of the action for the error message
|
||||
*/
|
||||
public void sendErrorMessage(final String tenant, final String messageDescription, final Long actionId) {
|
||||
final Message message = createActionStatusMessage(tenant, ActionStatus.ERROR, messageDescription, actionId);
|
||||
sendMessage(spExchange, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a warning message to SP.
|
||||
*
|
||||
* @param update
|
||||
* the simulated update object
|
||||
* @param warningMessage
|
||||
* a warning description
|
||||
*/
|
||||
public void sendWarningMessage(final SimulatedUpdate update, final String warningMessage) {
|
||||
final Message message = createActionStatusMessage(update, warningMessage, ActionStatus.WARNING);
|
||||
sendMessage(spExchange, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to send a action status to SP.
|
||||
*
|
||||
* @param tenant
|
||||
* the tenant
|
||||
* @param actionStatus
|
||||
* the action status
|
||||
* @param actionMessage
|
||||
* the message to get send
|
||||
* @param actionId
|
||||
* the cached value
|
||||
*/
|
||||
public void sendActionStatusMessage(final String tenant, final ActionStatus actionStatus,
|
||||
final String actionMessage, final Long actionId) {
|
||||
final Message message = createActionStatusMessage(tenant, actionStatus, actionMessage, actionId);
|
||||
sendMessage(message);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new thing created message and send to SP.
|
||||
*
|
||||
* @param tenant
|
||||
* the tenant to create the target
|
||||
* @param targetId
|
||||
* the ID of the target to create or update
|
||||
*/
|
||||
public void createOrUpdateThing(final String tenant, final String targetId) {
|
||||
|
||||
final MessageProperties messagePropertiesForSP = new MessageProperties();
|
||||
messagePropertiesForSP.setHeader(MessageHeaderKey.TYPE, MessageType.THING_CREATED.name());
|
||||
messagePropertiesForSP.setHeader(MessageHeaderKey.TENANT, tenant);
|
||||
messagePropertiesForSP.setHeader(MessageHeaderKey.THING_ID, targetId);
|
||||
messagePropertiesForSP.setHeader(MessageHeaderKey.SENDER, "simulator");
|
||||
messagePropertiesForSP.setContentType(MessageProperties.CONTENT_TYPE_JSON);
|
||||
messagePropertiesForSP.setReplyTo(amqpProperties.getSenderForSpExchange());
|
||||
final Message convertedMessage = new Message(null, messagePropertiesForSP);
|
||||
sendMessage(spExchange, convertedMessage);
|
||||
LOGGER.debug("Created thing created message and send to SP for Thing \"{}\"", targetId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a created message to SP.
|
||||
*
|
||||
* @param message
|
||||
* the message to get send
|
||||
*/
|
||||
private void sendMessage(final Message message) {
|
||||
sendMessage(spExchange, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send error message to SP.
|
||||
*
|
||||
* @param context
|
||||
* the current context
|
||||
* @param messageDescription
|
||||
* a description according the update process
|
||||
*/
|
||||
private void sendErrorgMessage(final SimulatedUpdate update, final String messageDescription) {
|
||||
final Message message = createActionStatusMessage(update, messageDescription, ActionStatus.ERROR);
|
||||
sendMessage(spExchange, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a action status message.
|
||||
*
|
||||
* @param actionStatus
|
||||
* the ActionStatus
|
||||
* @param actionMessage
|
||||
* the message description
|
||||
* @param actionId
|
||||
* the action id
|
||||
* @param cacheValue
|
||||
* the cacheValue value
|
||||
*/
|
||||
private Message createActionStatusMessage(final String tenant, final ActionStatus actionStatus,
|
||||
final String actionMessage, final Long actionId) {
|
||||
final MessageProperties messageProperties = new MessageProperties();
|
||||
final Map<String, Object> headers = messageProperties.getHeaders();
|
||||
final ActionUpdateStatus actionUpdateStatus = new ActionUpdateStatus();
|
||||
actionUpdateStatus.setActionStatus(actionStatus);
|
||||
headers.put(MessageHeaderKey.TYPE, MessageType.EVENT.name());
|
||||
headers.put(MessageHeaderKey.TENANT, tenant);
|
||||
headers.put(MessageHeaderKey.TOPIC, EventTopic.UPDATE_ACTION_STATUS.name());
|
||||
headers.put(MessageHeaderKey.CONTENT_TYPE, MessageProperties.CONTENT_TYPE_JSON);
|
||||
if (!StringUtils.isEmpty(actionMessage)) {
|
||||
actionUpdateStatus.getMessage().add(actionMessage);
|
||||
}
|
||||
actionUpdateStatus.setActionId(actionId);
|
||||
return convertMessage(actionUpdateStatus, messageProperties);
|
||||
}
|
||||
|
||||
private Message createUpdateResultMessage(final SimulatedUpdate cacheValue, final ActionStatus actionStatus,
|
||||
final String updateResultMessage) {
|
||||
final MessageProperties messageProperties = new MessageProperties();
|
||||
final Map<String, Object> headers = messageProperties.getHeaders();
|
||||
final ActionUpdateStatus actionUpdateStatus = new ActionUpdateStatus();
|
||||
actionUpdateStatus.setActionStatus(actionStatus);
|
||||
headers.put(MessageHeaderKey.TYPE, MessageType.EVENT.name());
|
||||
headers.put(MessageHeaderKey.TENANT, cacheValue.getTenant());
|
||||
headers.put(MessageHeaderKey.TOPIC, EventTopic.UPDATE_ACTION_STATUS.name());
|
||||
headers.put(MessageHeaderKey.CONTENT_TYPE, MessageProperties.CONTENT_TYPE_JSON);
|
||||
actionUpdateStatus.getMessage().add(updateResultMessage);
|
||||
actionUpdateStatus.setActionId(cacheValue.getActionId());
|
||||
return convertMessage(actionUpdateStatus, messageProperties);
|
||||
}
|
||||
|
||||
private Message createActionStatusMessage(final SimulatedUpdate update, final String messageDescription,
|
||||
final ActionStatus status) {
|
||||
final Message sendMessage = createActionStatusMessage(update.getTenant(), status, messageDescription,
|
||||
update.getActionId());
|
||||
return sendMessage;
|
||||
}
|
||||
|
||||
}
|
||||
101
examples/hawkbit-device-simulator/src/main/resources/application.properties
Executable file
101
examples/hawkbit-device-simulator/src/main/resources/application.properties
Executable file
@@ -0,0 +1,101 @@
|
||||
#
|
||||
# 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
|
||||
#
|
||||
|
||||
|
||||
#########################################################################################
|
||||
# PUBLIC configuration, i.e. can be changed by users at runtime (defaults provided here)
|
||||
#########################################################################################
|
||||
## Configuration for RabbitMQ communication
|
||||
hawkbit.device.simulator.amqp.receiverConnectorQueueFromSp=sp_connector_receiver
|
||||
hawkbit.device.simulator.amqp.deadLetterQueue=simulator_deadletter
|
||||
hawkbit.device.simulator.amqp.deadLetterExchange=simulator.deadletter
|
||||
hawkbit.device.simulator.amqp.senderForSpExchange=simulator.replyTo
|
||||
|
||||
|
||||
## Configuration for RabbitMQ integration
|
||||
spring.rabbitmq.username=guest
|
||||
spring.rabbitmq.password=guest
|
||||
spring.rabbitmq.virtualHost=/
|
||||
spring.rabbitmq.host=localhost
|
||||
spring.rabbitmq.port=5672
|
||||
spring.rabbitmq.dynamic=true
|
||||
|
||||
|
||||
#disable expose jmx beans
|
||||
spring.jmx.enabled=false
|
||||
# the context path of the monitor spring actuator which offers following endpoints
|
||||
# /autoconfig, /beans, /configprops, /dump, /env, /health, /info, /metrics, /mappings, /trace
|
||||
management.context-path=/system
|
||||
management.security.enabled=true
|
||||
management.security.sessions=stateless
|
||||
# ENDPOINTS
|
||||
endpoints.autoconfig.id=autoconfig
|
||||
endpoints.autoconfig.sensitive=true
|
||||
endpoints.autoconfig.enabled=false
|
||||
endpoints.beans.id=beans
|
||||
endpoints.beans.sensitive=true
|
||||
endpoints.beans.enabled=false
|
||||
endpoints.configprops.id=configprops
|
||||
endpoints.configprops.sensitive=true
|
||||
endpoints.configprops.enabled=true
|
||||
#endpoints.configprops.keys-to-sanitize=password,secret,key # suffix or regex
|
||||
endpoints.dump.id=dump
|
||||
endpoints.dump.sensitive=true
|
||||
endpoints.dump.enabled=false
|
||||
endpoints.env.id=env
|
||||
endpoints.env.sensitive=true
|
||||
endpoints.env.enabled=true
|
||||
#endpoints.env.keys-to-sanitize=password,secret,key # suffix or regex
|
||||
endpoints.health.id=health
|
||||
endpoints.health.sensitive=true
|
||||
endpoints.health.enabled=true
|
||||
endpoints.info.id=info
|
||||
endpoints.info.sensitive=true
|
||||
endpoints.info.enabled=true
|
||||
endpoints.metrics.id=metrics
|
||||
endpoints.metrics.sensitive=true
|
||||
endpoints.metrics.enabled=true
|
||||
endpoints.shutdown.id=shutdown
|
||||
endpoints.shutdown.sensitive=true
|
||||
endpoints.shutdown.enabled=false
|
||||
endpoints.trace.id=trace
|
||||
endpoints.trace.sensitive=true
|
||||
endpoints.trace.enabled=true
|
||||
|
||||
# HEALTH INDICATORS (previously health.*)
|
||||
management.health.db.enabled=true
|
||||
management.health.diskspace.enabled=true
|
||||
management.health.mongo.enabled=true
|
||||
management.health.rabbit.enabled=true
|
||||
management.health.redis.enabled=false
|
||||
management.health.solr.enabled=false
|
||||
management.health.diskspace.path=.
|
||||
management.health.diskspace.threshold=10485760
|
||||
management.health.status.order=DOWN, OUT_OF_SERVICE, UNKNOWN, UP
|
||||
|
||||
# SECURITY (SecurityProperties)
|
||||
security.user.name=${BASIC_USERNAME:admin}
|
||||
security.user.password=${BASIC_PASSWORD:admin}
|
||||
security.user.role=USER
|
||||
security.require-ssl=false
|
||||
security.enable-csrf=false
|
||||
security.basic.enabled=true
|
||||
security.basic.realm=DeviceSimulator
|
||||
security.basic.path= /system/**
|
||||
security.basic.authorize-mode=ROLE
|
||||
security.filter-order=0
|
||||
security.headers.xss=false
|
||||
security.headers.cache=false
|
||||
security.headers.frame=false
|
||||
security.headers.content-type=false
|
||||
security.headers.hsts=all
|
||||
security.sessions=stateless
|
||||
security.ignored=
|
||||
|
||||
server.port=8083
|
||||
Reference in New Issue
Block a user