org.bidib.wizard.server.controllers.SystemController Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of bidibwizard-server Show documentation
Show all versions of bidibwizard-server Show documentation
jBiDiB BiDiB Wizard Server POM
package org.bidib.wizard.server.controllers;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.bidib.api.json.types.ConnectionPhase;
import org.bidib.api.json.types.NodeStatusInformation;
import org.bidib.api.json.types.NodeStatusInformation.StatusLevel;
import org.bidib.api.json.types.SerialPortInfo;
import org.bidib.api.json.types.StatusInfo;
import org.bidib.api.json.types.SystemError;
import org.bidib.api.json.types.system.SystemAvailablePortsResponse;
import org.bidib.api.json.types.system.SystemInfoResponse;
import org.bidib.jbidibc.messages.helpers.Context;
import org.bidib.jbidibc.messages.helpers.DefaultContext;
import org.bidib.wizard.api.LookupService;
import org.bidib.wizard.api.model.common.CommPort;
import org.bidib.wizard.api.model.connection.BidibConnection;
import org.bidib.wizard.api.model.connection.ConnectionIdentifier;
import org.bidib.wizard.api.model.connection.ConnectionState;
import org.bidib.wizard.api.notification.ConnectionInfo;
import org.bidib.wizard.api.version.ApiVersion;
import org.bidib.wizard.common.exception.ConnectionException;
import org.bidib.wizard.core.model.connection.ConnectionRegistry;
import org.bidib.wizard.core.service.ConnectionService;
import org.bidib.wizard.core.service.LogFileService;
import org.bidib.wizard.core.service.SystemInfoService;
import org.bidib.wizard.server.aspect.LogExecutionTime;
import org.bidib.wizard.server.config.StompDestinations;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.messaging.handler.annotation.MessageExceptionHandler;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.messaging.simp.annotation.SendToUser;
import org.springframework.util.StreamUtils;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import io.reactivex.rxjava3.core.Observer;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.rxjava3.disposables.Disposable;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
@CrossOrigin(origins = "*")
@RestController
@RequestMapping("/api/system")
@Tag(name = "system", description = "the system API")
public class SystemController {
private static final Logger LOGGER = LoggerFactory.getLogger(SystemController.class);
@Autowired
private ConnectionService connectionService;
@Autowired
private LogFileService logFileService;
@Autowired
private SystemInfoService systemInfoService;
// The SimpMessagingTemplate is used to send Stomp over WebSocket messages.
@Autowired
private SimpMessagingTemplate messagingTemplate;
@Autowired
private LookupService lookupService;
public SystemController() {
}
@PostConstruct
public void initialize() {
String versionInfo = getClass().getPackage().getImplementationVersion();
LOGGER.info("Initialize the SystemController, versionInfo: {}", versionInfo);
final CompositeDisposable compDispNodeStatus = new CompositeDisposable();
connectionService.subscribeNodeStatusChanges(new Observer() {
@Override
public void onSubscribe(Disposable d) {
LOGGER.info("Subscribed to connection registry changes, disposed: {}", d.isDisposed());
compDispNodeStatus.add(d);
}
@Override
public void onNext(NodeStatusInformation evt) {
LOGGER.info("New status received from connection registry: {}", evt);
publishStatusInformation(evt);
}
@Override
public void onError(Throwable e) {
LOGGER
.info("Subscription to status information from connection registry changes has thrown an error: {}",
e);
compDispNodeStatus.dispose();
}
@Override
public void onComplete() {
LOGGER
.info("The subscription to status information from for connection registry changes has finished.");
compDispNodeStatus.dispose();
}
});
final CompositeDisposable compDispConnectionStatus = new CompositeDisposable();
connectionService.subscribeConnectionStatusChanges(new Observer() {
@Override
public void onSubscribe(Disposable d) {
LOGGER.info("Subscribed to connection status changes, disposed: {}", d.isDisposed());
compDispConnectionStatus.add(d);
}
@Override
public void onNext(ConnectionInfo connectionInfo) {
LOGGER.info("Publish the connection info: {}", connectionInfo);
try {
boolean notifyStatus = false;
// Publish only connected and disconnected to web client
switch (connectionInfo.getConnectionState().getActualPhase()) {
case CONNECTED:
case DISCONNECTED:
notifyStatus = true;
break;
default:
break;
}
if (notifyStatus) {
// create the JSON connection info
org.bidib.api.json.types.ConnectionInfo jsonConnectionInfo =
toJsonConnectionInfo(connectionInfo.getConnectionId(),
connectionInfo.getConnectionState().getActualPhase());
messagingTemplate
.convertAndSend(StompDestinations.PUBLISH_SYSTEM_CONNECTIONSTATE_DESTINATION,
jsonConnectionInfo);
}
}
catch (Exception ex) {
LOGGER.warn("Send the connection state failed: {}", connectionInfo, ex);
}
}
@Override
public void onError(Throwable e) {
LOGGER.info("Subscription to connection status information changes has thrown an error: {}", e);
compDispConnectionStatus.dispose();
}
@Override
public void onComplete() {
LOGGER.info("The subscription to connection status information changes has finished.");
compDispConnectionStatus.dispose();
}
});
systemInfoService.subscribeUsbPortEvents(upe -> {
LOGGER.info("Publish the USB port event: {}", upe);
messagingTemplate.convertAndSend(StompDestinations.SYSTEM_USBPORTCHANGE_DESTINATION, upe);
}, error -> {
LOGGER.warn("The USB port event signalled a failure: {}", error);
});
}
@GetMapping(path = "/info")
public ResponseEntity getSystemInfo() {
try {
String apiVersion = ApiVersion.getVersion();
String versionNumber = ApiVersion.getBuildNumber();
LOGGER.info("Current version: {}, versionNumber: {}", apiVersion, versionNumber);
return new ResponseEntity<>(new SystemInfoResponse(Integer.parseInt(versionNumber), apiVersion),
HttpStatus.OK);
}
catch (RuntimeException ex) {
LOGGER.warn("No system info available.", ex);
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
@MessageMapping("/info")
@SendToUser(destinations = StompDestinations.SYSTEM_INFO_DESTINATION, broadcast = false)
@LogExecutionTime
public SystemInfoResponse msgGetSystemInfo() {
String apiVersion = ApiVersion.getVersion();
String versionNumber = ApiVersion.getBuildNumber();
LOGGER.info("Current version: {}, versionNumber: {}", apiVersion, versionNumber);
return new SystemInfoResponse(Integer.parseInt(versionNumber), apiVersion);
}
@GetMapping(path = "/availablePorts")
public ResponseEntity getAvailablePorts() {
try {
LOGGER.info("Get the currently available serial ports.");
List serialPorts = new ArrayList<>();
for (CommPort commPort : lookupService.getDetectedComPorts()) {
SerialPortInfo pi =
new SerialPortInfo(commPort.getName(), commPort.getSerialNumber(), commPort.getVendorId(),
commPort.getProductId(), commPort.getDescription(), commPort.getManufacturerString());
serialPorts.add(pi);
}
return new ResponseEntity<>(new SystemAvailablePortsResponse(serialPorts), HttpStatus.OK);
}
catch (RuntimeException ex) {
LOGGER.warn("No system info available.", ex);
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
@MessageMapping("/availablePorts")
@SendToUser(destinations = StompDestinations.SYSTEM_AVAILABLEPORTS_DESTINATION, broadcast = false)
@LogExecutionTime
public SystemAvailablePortsResponse msgGetAvailablePorts() {
LOGGER.info("Get the currently available serial ports.");
List serialPorts = new ArrayList<>();
for (CommPort commPort : lookupService.getDetectedComPorts()) {
SerialPortInfo pi =
new SerialPortInfo(commPort.getName(), commPort.getSerialNumber(), commPort.getVendorId(),
commPort.getProductId(), commPort.getDescription(), commPort.getManufacturerString());
serialPorts.add(pi);
}
return new SystemAvailablePortsResponse(serialPorts);
}
@GetMapping(path = "/currentState")
public ResponseEntity getCurrentState() {
String name = ConnectionRegistry.CONNECTION_ID_MAIN;
LOGGER.debug("Get connection status for name: {}", name);
return getCurrentState(name);
}
@GetMapping(path = "/currentState/{connectionId}")
public ResponseEntity getCurrentState(
@PathVariable(value = "connectionId") String connectionId) {
LOGGER.info("Get connection status for name: {}", connectionId);
try {
ConnectionState connectionState = connectionService.getCurrentState(connectionId);
org.bidib.api.json.types.ConnectionInfo.ConnectionState jsonState = toJsonState(connectionState);
return new ResponseEntity<>(jsonState, HttpStatus.OK);
}
catch (RuntimeException ex) {
LOGGER.warn("No connection found with name: {}", connectionId, ex);
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
private org.bidib.api.json.types.ConnectionInfo.ConnectionState toJsonState(ConnectionState connectionState) {
org.bidib.api.json.types.ConnectionInfo.ConnectionState jsonState = null;
switch (connectionState.getActualPhase()) {
case CONNECTED:
case DISCONNECTING:
jsonState = org.bidib.api.json.types.ConnectionInfo.ConnectionState.CONNECTED;
break;
case CONNECTING:
case DISCONNECTED:
jsonState = org.bidib.api.json.types.ConnectionInfo.ConnectionState.DISCONNECTED;
break;
case STALL:
jsonState = org.bidib.api.json.types.ConnectionInfo.ConnectionState.STALL;
break;
default:
jsonState = org.bidib.api.json.types.ConnectionInfo.ConnectionState.UNKNOWN;
break;
}
return jsonState;
}
@MessageMapping(StompDestinations.SYSTEM_CONNECTIONSTATE_DESTINATION)
@SendTo(StompDestinations.PUBLISH_SYSTEM_CONNECTIONSTATE_DESTINATION)
@LogExecutionTime
public org.bidib.api.json.types.ConnectionInfo msgGetCurrentState(ConnectionIdentifier connectionIdentifier) {
LOGGER.info("Get connection status for name: {}", connectionIdentifier);
String connectionId = connectionIdentifier.getConnectionId();
if (StringUtils.isBlank(connectionId)) {
connectionId = ConnectionRegistry.CONNECTION_ID_MAIN;
}
org.bidib.api.json.types.ConnectionInfo connectionInfo = null;
try {
ConnectionState connectionState = connectionService.getCurrentState(connectionId);
org.bidib.api.json.types.ConnectionInfo.ConnectionState jsonState = toJsonState(connectionState);
connectionInfo =
new org.bidib.api.json.types.ConnectionInfo(connectionId, jsonState, connectionState.getError());
}
catch (ConnectionException ex) {
LOGGER.warn("No connection found with name: {}", connectionId);
connectionInfo =
new org.bidib.api.json.types.ConnectionInfo(connectionId,
org.bidib.api.json.types.ConnectionInfo.ConnectionState.DISCONNECTED, null);
}
catch (RuntimeException ex) {
LOGGER.warn("No connection found with name: {}", connectionId, ex);
throw new RuntimeException("Find existing connection failed with name: " + connectionId);
}
return connectionInfo;
}
@PostMapping(path = "/connect")
@LogExecutionTime
public ResponseEntity connectInterface() {
String name = ConnectionRegistry.CONNECTION_ID_MAIN;
LOGGER.info("Connect to default interface with name: {}", name);
return connectInterface(name);
}
@PostMapping(path = "/disconnect")
@LogExecutionTime
public ResponseEntity disconnectInterface() {
String name = ConnectionRegistry.CONNECTION_ID_MAIN;
LOGGER.info("Disonnect from interface with name: {}", name);
return disconnectInterface(name);
}
@PutMapping(path = "/connect/{connectionId}")
@LogExecutionTime
public ResponseEntity connectInterface(@PathVariable(value = "connectionId") String connectionId) {
LOGGER.info("Connect to interface with connectionId: {}", connectionId);
// create the context
final Context context = new DefaultContext();
try {
final BidibConnection connection =
connectionService.connect(connectionId, conn -> conn, conn -> conn, context);
return new ResponseEntity<>(connection.getConnectionState(), HttpStatus.OK);
}
catch (ConnectionException ex) {
LOGGER.warn("Open connection failed.", ex);
throw ex;
}
catch (RuntimeException ex) {
LOGGER.warn("Find existing connection failed.", ex);
}
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
@MessageMapping("/connect")
@SendToUser(destinations = StompDestinations.PUBLISH_SYSTEM_CONNECTIONSTATE_DESTINATION, broadcast = true)
@LogExecutionTime
public org.bidib.api.json.types.ConnectionInfo msgConnectInterface(ConnectionIdentifier connectionIdentifier) {
LOGGER.info("Connect to interface with name: {}", connectionIdentifier);
String connectionId = connectionIdentifier.getConnectionId();
if (StringUtils.isBlank(connectionId)) {
connectionId = ConnectionRegistry.CONNECTION_ID_MAIN;
}
// create the context
final Context context = new DefaultContext();
try {
LOGGER.info("Try to connect the connection with id: {}", connectionId);
BidibConnection connection = connectionService.connect(connectionId, conn -> conn, conn -> conn, context);
LOGGER.info("Created connection: {}", connection);
final org.bidib.api.json.types.ConnectionInfo connectionInfo =
toJsonConnectionInfo(connectionId, ConnectionPhase.CONNECTED);
return connectionInfo;
}
catch (ConnectionException ex) {
LOGGER.warn("Open connection failed, connectionId: {}, uri: {}", connectionId, ex.getUri(), ex);
LOGGER.info("Prepare the connection state with the error.");
handleException(ex);
final org.bidib.api.json.types.ConnectionInfo connectionInfo =
toJsonConnectionInfo(connectionId, ConnectionPhase.DISCONNECTED, "bidib-connect-failed");
return connectionInfo;
}
catch (RuntimeException ex) {
LOGGER.warn("Find existing connection failed.", ex);
}
throw new RuntimeException("Find existing connection failed.");
}
private org.bidib.api.json.types.ConnectionInfo toJsonConnectionInfo(
String connectionId, ConnectionPhase connectionPhase) {
return toJsonConnectionInfo(connectionId, connectionPhase, null);
}
private org.bidib.api.json.types.ConnectionInfo toJsonConnectionInfo(
String connectionId, ConnectionPhase connectionPhase, String error) {
org.bidib.api.json.types.ConnectionInfo.ConnectionState connectionState =
org.bidib.api.json.types.ConnectionInfo.ConnectionState.valueOf(connectionPhase.name());
final org.bidib.api.json.types.ConnectionInfo connectionInfo = new org.bidib.api.json.types.ConnectionInfo();
connectionInfo.setId(connectionId);
connectionInfo.setState(connectionState);
if (StringUtils.isNotBlank(error)) {
connectionInfo.setError(error);
}
return connectionInfo;
}
@Operation(summary = "Disconnect the interface with the provided name.")
@PutMapping(path = "/disconnect/{connectionId}")
@LogExecutionTime
public ResponseEntity disconnectInterface(
@PathVariable(value = "connectionId") String connectionId) {
LOGGER.info("Disonnect from interface with connectionId: {}", connectionId);
try {
BidibConnection connection = connectionService.disconnect(connectionId);
return new ResponseEntity<>(connection.getConnectionState(), HttpStatus.OK);
}
catch (RuntimeException ex) {
LOGGER.warn("Find existing connection failed.", ex);
}
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
@Operation(summary = "Disconnect the interface with the provided connection identifier.")
@PutMapping(path = "/disconnect/{connectionIdentifier}")
@MessageMapping("/disconnect")
@SendToUser(destinations = StompDestinations.PUBLISH_SYSTEM_CONNECTIONSTATE_DESTINATION, broadcast = true)
@LogExecutionTime
public org.bidib.api.json.types.ConnectionInfo msgDisconnectInterface(
@PathVariable ConnectionIdentifier connectionIdentifier) {
LOGGER.info("Disonnect from interface with connectionId: {}", connectionIdentifier);
String connectionId = connectionIdentifier.getConnectionId();
if (StringUtils.isBlank(connectionId)) {
connectionId = ConnectionRegistry.CONNECTION_ID_MAIN;
}
try {
BidibConnection connection = connectionService.disconnect(connectionId);
final org.bidib.api.json.types.ConnectionInfo connectionInfo =
toJsonConnectionInfo(connectionId, connection.getConnectionState().getActualPhase());
return connectionInfo;
}
catch (RuntimeException ex) {
LOGGER.warn("Find existing connection failed.", ex);
}
throw new RuntimeException("Find existing connection failed.");
}
@GetMapping("/download/log")
public void downloadFile(HttpServletResponse response) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd-HHmm");
String zipFileName = "bidib-server-logs-" + sdf.format(new Date()) + ".zip";
response.setContentType("application/octet-stream");
response.setHeader("Content-Disposition", "attachment;filename=" + zipFileName);
response.setStatus(HttpServletResponse.SC_OK);
List fileNames = logFileService.getFileNames();
LOGGER.info("Total number of files: ", fileNames.size());
try (ZipOutputStream zippedOut = new ZipOutputStream(response.getOutputStream())) {
for (String file : fileNames) {
FileSystemResource resource = new FileSystemResource(file);
ZipEntry e = new ZipEntry(resource.getFilename());
// Configure the zip entry, the properties of the file
e.setSize(resource.contentLength());
e.setTime(System.currentTimeMillis());
// etc.
zippedOut.putNextEntry(e);
// And the content of the resource:
StreamUtils.copy(resource.getInputStream(), zippedOut);
zippedOut.closeEntry();
}
zippedOut.finish();
}
catch (Exception ex) {
LOGGER.warn("The download of logfiles caused an error.", ex);
}
}
/**
* Send notification to users subscribed on channel "/topic/system/statusInfo".
*
* @param statusInformation
* the statusInformation.
*/
private void publishStatusInformation(NodeStatusInformation statusInformation) {
LOGGER.info("Notify new statusInformation: {}", statusInformation);
// provide the connection name
String connectionName = statusInformation.getConnectionId();
try {
if (statusInformation.getStatusLevel() == null) {
statusInformation.setStatusLevel(StatusLevel.Info);
}
switch (statusInformation.getStatusLevel()) {
case Error:
final SystemError se = new SystemError();
se.setMsgKey(statusInformation.getMsgKey());
se.setMsgKeyArgs(statusInformation.getMsgKeyArgs());
se.setTimestamp(LocalDateTime.now());
se.setConnectionId(connectionName);
messagingTemplate.convertAndSend(StompDestinations.SYSTEM_ERROR_DESTINATION, se);
break;
default:
final StatusInfo si =
new StatusInfo(connectionName, statusInformation.getStatusLevel(),
statusInformation.getMsgKey(), statusInformation.getMsgKeyArgs(), 20);
messagingTemplate.convertAndSend(StompDestinations.SYSTEM_STATEINFO_DESTINATION, si);
break;
}
}
catch (Exception ex) {
LOGGER.warn("Send the status information failed: {}", statusInformation, ex);
}
}
@MessageExceptionHandler
public String handleException(ConnectionException exception) {
LOGGER.error("Error detected:", exception);
String msgKey = exception.getMsgKey();
List