All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.bidib.wizard.mvc.ping.controller.PingTableController Maven / Gradle / Ivy

There is a newer version: 2.0.29
Show newest version
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);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy