org.bidib.wizard.mvc.ping.controller.PingTableController Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of bidibwizard-client Show documentation
Show all versions of bidibwizard-client Show documentation
jBiDiB BiDiB Wizard Client Application POM
package org.bidib.wizard.mvc.ping.controller;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.apache.commons.collections4.CollectionUtils;
import org.bidib.jbidibc.messages.enums.IdentifyState;
import org.bidib.jbidibc.messages.utils.ByteUtils;
import org.bidib.jbidibc.messages.utils.NodeUtils;
import org.bidib.jbidibc.messages.utils.ThreadFactoryBuilder;
import org.bidib.wizard.api.model.NodeInterface;
import org.bidib.wizard.api.model.connection.AbstractMessageEvent;
import org.bidib.wizard.api.model.connection.event.SysIdentifyStateMessageEvent;
import org.bidib.wizard.api.model.connection.event.SysPongMessageEvent;
import org.bidib.wizard.api.model.listener.DefaultNodeListListener;
import org.bidib.wizard.api.model.listener.NodeListListener;
import org.bidib.wizard.api.service.node.NodeService;
import org.bidib.wizard.client.common.uils.SwingUtils;
import org.bidib.wizard.client.common.view.DockKeys;
import org.bidib.wizard.client.common.view.DockUtils;
import org.bidib.wizard.common.exception.ConnectionException;
import org.bidib.wizard.common.model.settings.MiscSettingsInterface;
import org.bidib.wizard.core.model.connection.ConnectionRegistry;
import org.bidib.wizard.core.model.connection.MessageAdapter;
import org.bidib.wizard.core.model.connection.MessageEventConsumer;
import org.bidib.wizard.core.model.settings.MiscSettings;
import org.bidib.wizard.core.service.ConnectionService;
import org.bidib.wizard.mvc.main.controller.MainControllerInterface;
import org.bidib.wizard.mvc.ping.model.DefaultPingTablePreferences;
import org.bidib.wizard.mvc.ping.model.NodePingModel;
import org.bidib.wizard.mvc.ping.model.NodePingState;
import org.bidib.wizard.mvc.ping.model.PingTableModel;
import org.bidib.wizard.mvc.ping.model.PingTablePreferences;
import org.bidib.wizard.mvc.ping.view.PingTableView;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import com.vlsolutions.swing.docking.Dockable;
import com.vlsolutions.swing.docking.DockableState;
import com.vlsolutions.swing.docking.DockingConstants;
import com.vlsolutions.swing.docking.DockingDesktop;
import com.vlsolutions.swing.docking.DockingUtilities;
import com.vlsolutions.swing.docking.RelativeDockablePosition;
import com.vlsolutions.swing.docking.TabbedDockableContainer;
import com.vlsolutions.swing.docking.event.DockableStateChangeEvent;
import com.vlsolutions.swing.docking.event.DockableStateChangeListener;
public class PingTableController {
private static final Logger LOGGER = LoggerFactory.getLogger(PingTableController.class);
private final MainControllerInterface mainController;
private DockableStateChangeListener dockableStateChangeListener;
private PingTableView pingTableView;
private PingTableModel pingTableModel;
@Autowired
private ConnectionService connectionService;
@Autowired
private NodeService nodeService;
@Autowired
private MiscSettingsInterface miscSettings;
private MessageAdapter messageAdapter;
private PropertyChangeListener pingIntervalListener;
private PropertyChangeListener nodePingStatusListener;
private final ApplicationEventPublisher applicationEventPublisher;
private final DockingDesktop desktop;
/**
* the ping workers are used to send ping requests to the nodes
*/
private ScheduledExecutorService pingWorkers;
private final NodeListListener nodeListListener;
public PingTableController(final MainControllerInterface mainController,
final ApplicationEventPublisher applicationEventPublisher, final DockingDesktop desktop) {
this.mainController = mainController;
this.applicationEventPublisher = applicationEventPublisher;
this.desktop = desktop;
// create the nodeList listener
this.nodeListListener = new DefaultNodeListListener() {
@Override
public void listNodeAdded(NodeInterface node) {
LOGGER.info("The nodelist has a new node: {}", node);
nodeNew(node);
}
@Override
public void listNodeRemoved(NodeInterface node) {
LOGGER.info("The nodelist has a node removed: {}", node);
nodeLost(node);
}
};
}
public void start() {
// check if the ping view is already opened
String searchKey = DockKeys.PING_TABLE_VIEW;
LOGGER.info("Search for view with key: {}", searchKey);
Dockable view = desktop.getContext().getDockableByKey(searchKey);
if (view != null) {
LOGGER.info("Select the existing ping table view.");
DockUtils.selectWindow(view);
return;
}
LOGGER.info("Create new PingTableView.");
final PingTablePreferences pingTablePreferences = loadPingTablePreferences();
int pingInterval = this.miscSettings.getPingInterval();
pingTableModel = new PingTableModel(pingTablePreferences);
pingTableModel.setDefaultPingInterval(pingInterval);
pingTableView = new PingTableView(pingTableModel);
// add the ping table panel next to the booster panel
DockableState[] dockables = desktop.getDockables();
LOGGER.info("Current dockables: {}", new Object[] { dockables });
if (dockables.length > 1) {
DockableState boosterTableView = null;
// search the booster table view
for (DockableState dockable : dockables) {
if (DockKeys.DOCKKEY_BOOSTER_TABLE_VIEW.equals(dockable.getDockable().getDockKey())) {
LOGGER.info("Found the booster table view dockable.");
boosterTableView = dockable;
break;
}
}
Dockable dock = desktop.getDockables()[1].getDockable();
if (boosterTableView != null) {
LOGGER.info("Add the ping table view to the booster table view panel.");
dock = boosterTableView.getDockable();
TabbedDockableContainer container = DockingUtilities.findTabbedDockableContainer(dock);
int order = 0;
if (container != null) {
order = container.getTabCount();
}
LOGGER.info("Add new ping table panel at order: {}", order);
desktop.createTab(dock, pingTableView, order, true);
}
else {
desktop.split(dock, pingTableView, DockingConstants.SPLIT_RIGHT);
}
}
else {
desktop.addDockable(pingTableView, RelativeDockablePosition.RIGHT);
}
// Add listener for changes of ping interval
pingIntervalListener = evt -> {
int currentPingInterval = miscSettings.getPingInterval();
pingTableModel.setDefaultPingInterval(currentPingInterval);
};
this.miscSettings.addPropertyChangeListener(MiscSettings.PROPERTY_PING_INTERVAL, pingIntervalListener);
// register as nodeList listener at the main controller
mainController.addNodeListListener(nodeListListener);
try {
connectionService.subscribeConnectionStatusChanges(connectionInfo -> {
if (connectionInfo.getConnectionId().equals(ConnectionRegistry.CONNECTION_ID_MAIN)) {
LOGGER.info("Current state: {}", connectionInfo.getConnectionState());
switch (connectionInfo.getConnectionState().getActualPhase()) {
case CONNECTED:
LOGGER.info("The communication was opened.");
break;
case DISCONNECTED:
LOGGER.info("The communication was closed. Remove all nodes from the ping table.");
// stop the ping timer
if (pingWorkers != null) {
LOGGER.info("Shutdown and free the ping workers.");
pingWorkers.shutdownNow();
try {
pingWorkers.awaitTermination(2, TimeUnit.SECONDS);
}
catch (InterruptedException ex) {
LOGGER.warn("Wait for termination of ping workers failed.", ex);
}
pingWorkers = null;
}
final List pingNodes = new LinkedList<>(pingTableModel.getNodes());
SwingUtils.executeInEDT(() -> {
for (NodePingModel pingNode : pingNodes) {
pingTableModel.removeNode(pingNode.getNode());
}
});
break;
default:
break;
}
}
}, error -> {
LOGGER.warn("The connection status change caused an error.", error);
});
}
catch (Exception ex) {
LOGGER.warn("Register controller as connection status listener failed.", ex);
}
this.messageAdapter = new MessageAdapter(connectionService) {
@Override
protected void prepareMessageMap(
Map, MessageEventConsumer> messageActionMap) {
LOGGER.info("Prepare the message map.");
}
@Override
protected void handleBidibMessageEvent(final AbstractMessageEvent event) {
LOGGER.debug("Handle the message event: {}", event);
// super.handleBidibMessageEvent(event);
if (event instanceof SysPongMessageEvent) {
SysPongMessageEvent evt = (SysPongMessageEvent) event;
final byte[] address = evt.getAddress();
final int marker = evt.getMarker();
LOGGER.info("Set the pong marker: {}", marker);
pingTableModel.setPongMarker(address, marker, cme -> {
PingTableController.this.applicationEventPublisher.publishEvent(cme);
});
}
else if (event instanceof SysIdentifyStateMessageEvent) {
SysIdentifyStateMessageEvent evt = (SysIdentifyStateMessageEvent) event;
final byte[] address = evt.getAddress();
IdentifyState identifyState = evt.getIdentifyState();
LOGGER
.info("Received the identifyState: {}, address: {}", identifyState,
NodeUtils.formatAddress(address));
pingTableModel.checkIdentifyWaitTime(address);
}
}
@Override
protected void onDisconnect() {
super.onDisconnect();
}
};
messageAdapter.start();
try {
// get the current nodes
List nodes = nodeService.getAllNodes(ConnectionRegistry.CONNECTION_ID_MAIN);
if (CollectionUtils.isNotEmpty(nodes)) {
for (NodeInterface node : nodes) {
LOGGER.info("Initially add node.");
nodeNew(node);
}
}
}
catch (ConnectionException ex) {
LOGGER.warn("Get the nodes from the main connection failed: {}", ex.getMessage());
}
catch (Exception ex) {
LOGGER.warn("Get the nodes from the main connection failed.", ex);
}
this.nodePingStatusListener = evt -> {
LOGGER.info("The node ping state has changed, node: {}", evt.getNewValue());
final NodeInterface node = (NodeInterface) evt.getNewValue();
final NodePingModel nodePingModel =
pingTableModel.getNodes().stream().filter(npm -> npm.getNode().equals(node)).findFirst().orElse(null);
if (nodePingModel != null) {
NodePingState nodePingState = nodePingModel.getNodePingState();
switch (nodePingState) {
case ON:
startPingWorker(nodePingModel);
break;
default:
stopPingWorker(nodePingModel);
break;
}
}
};
this.pingTableModel
.addPropertyChangeListener(PingTableModel.PROPERTY_NODE_PING_STATUS, this.nodePingStatusListener);
// listen on window close
this.dockableStateChangeListener = new DockableStateChangeListener() {
@Override
public void dockableStateChanged(DockableStateChangeEvent event) {
if (event.getNewState().getDockable().equals(pingTableView) && event.getNewState().isClosed()) {
LOGGER.info("PingTableView was closed, free resources.");
cleanup();
}
}
};
desktop.addDockableStateChangeListener(this.dockableStateChangeListener);
}
private void cleanup() {
LOGGER.info("Cleanup.");
if (this.desktop != null) {
try {
desktop.removeDockableStateChangeListener(dockableStateChangeListener);
}
catch (Exception ex) {
LOGGER
.warn("Remove dockableStateChangeListener from desktop failed: " + dockableStateChangeListener, ex);
}
finally {
dockableStateChangeListener = null;
}
}
if (PingTableController.this.pingIntervalListener != null) {
try {
PingTableController.this.miscSettings
.removePropertyChangeListener(MiscSettings.PROPERTY_PING_INTERVAL,
PingTableController.this.pingIntervalListener);
}
catch (Exception ex) {
LOGGER.warn("Remove pingInterval listener failed.", ex);
}
PingTableController.this.pingIntervalListener = null;
}
if (PingTableController.this.nodePingStatusListener != null) {
try {
PingTableController.this.pingTableModel
.removePropertyChangeListener(PingTableModel.PROPERTY_NODE_PING_STATUS,
PingTableController.this.nodePingStatusListener);
}
catch (Exception ex) {
LOGGER.warn("Remove nodePingStatus listener failed.", ex);
}
PingTableController.this.nodePingStatusListener = null;
}
try {
// remove node listener from communication factory
if (nodeListListener != null) {
mainController.removeNodeListListener(nodeListListener);
}
if (messageAdapter != null) {
messageAdapter.dispose();
}
}
catch (Exception ex) {
LOGGER.warn("Register controller as node listener failed.", ex);
}
if (pingWorkers != null) {
LOGGER.info("Shutdown and free the ping workers because the ping table view is closed.");
pingWorkers.shutdownNow();
try {
pingWorkers.awaitTermination(2, TimeUnit.SECONDS);
}
catch (InterruptedException ex) {
LOGGER.warn("Wait for termination of ping workers failed.", ex);
}
pingWorkers = null;
}
}
private PingTablePreferences loadPingTablePreferences() {
LOGGER.info("Load the ping table preferences.");
String pingTablePreferencesPath = new File(miscSettings.getBidibConfigDir(), "/data/misc").getPath();
LOGGER.info("Set the location of the ping table preferences: {}", pingTablePreferencesPath);
final Path path = Paths.get(pingTablePreferencesPath);
try {
Files.createDirectories(path);
}
catch (IOException ex) {
LOGGER.warn("Create directories to load the ping table preferences failed.", ex);
throw new IllegalStateException("Create directories to load the ping table preferences failed.");
}
final File pingTablePreferencesFile = new File(pingTablePreferencesPath, ".wizard2PingTablePreferences");
LOGGER.info("Load pingTablePreferences from file: {}", pingTablePreferencesFile);
final DefaultPingTablePreferences pingTablePreferences =
new DefaultPingTablePreferences(pingTablePreferencesFile);
try {
pingTablePreferences.load();
}
catch (Exception ex) {
LOGGER
.warn("Load pingTablePreferences failed. Current pingTablePreferences file: {}",
pingTablePreferencesFile.getPath(), ex);
}
return pingTablePreferences;
}
private Map> scheduledPingWorkers = new HashMap<>();
private void startPingWorker(final NodePingModel nodePingModel) {
if (pingWorkers == null) {
LOGGER.info("Create the ping workers thread pool.");
pingWorkers =
Executors
.newScheduledThreadPool(0,
new ThreadFactoryBuilder().setNameFormat("pingWorkers-thread-%d").build());
}
stopPingWorker(nodePingModel);
long period = nodePingModel.getPingInterval();
if (period < 100) {
period = 100;
}
final Runnable command = () -> {
// perform a ping
try {
long now = System.currentTimeMillis();
int additionalTotalBytesCount = nodePingModel.getAdditionalTotalBytesCount();
byte[] payload = nodePingModel.getNextIncData();
int sentMessageLen = 3 + nodePingModel.getNode().getAddr().length + payload.length;
int totalRepetitions = additionalTotalBytesCount > 0 ? (additionalTotalBytesCount / sentMessageLen) : 0;
// manipulate the ping index
int nextPingIndex = nodePingModel.getData() + totalRepetitions;
nodePingModel.setData(nextPingIndex);
LOGGER
.info(
"Ping the node: {}, additionalTotalBytesCount: {}, payload: {}, totalRepetitions: {}, nextPingIndex: {}",
nodePingModel, additionalTotalBytesCount, ByteUtils.bytesToHex(payload), totalRepetitions,
nextPingIndex);
nodeService
.ping(ConnectionRegistry.CONNECTION_ID_MAIN, nodePingModel.getNode(), payload,
additionalTotalBytesCount);
nodePingModel.setLastPingTimestamp(now);
}
catch (Exception ex) {
LOGGER.warn("Ping node failed: {}", nodePingModel, ex);
}
};
ScheduledFuture> pingFuture = pingWorkers.scheduleAtFixedRate(command, 500, period, TimeUnit.MILLISECONDS);
scheduledPingWorkers.put(nodePingModel.getNode(), pingFuture);
}
private void stopPingWorker(final NodePingModel nodePingModel) {
ScheduledFuture> oldWorker = scheduledPingWorkers.remove(nodePingModel.getNode());
if (oldWorker != null) {
LOGGER.info("Cancel the ping worker: {}", oldWorker);
oldWorker.cancel(true);
}
}
private void nodeLost(final NodeInterface node) {
LOGGER.info("Remove node from model: {}", node);
if (node != null) {
SwingUtils.executeInEDT(() -> pingTableModel.removeNode(node));
}
}
private void nodeNew(final NodeInterface node) {
LOGGER.info("New node in system detected: {}", node);
SwingUtils.executeInEDT(() -> internalNewNode(node));
}
private void internalNewNode(final NodeInterface node) {
if (node != null) {
pingTableModel.addNode(node);
}
}
}