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

org.bidib.wizard.mvc.backup.controller.BackupController Maven / Gradle / Ivy

There is a newer version: 2.0.29
Show newest version
package org.bidib.wizard.mvc.backup.controller;

import java.io.File;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import javax.swing.SwingUtilities;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.time.StopWatch;
import org.bidib.jbidibc.core.node.ConfigurationVariable;
import org.bidib.jbidibc.core.schema.BidibFactory;
import org.bidib.jbidibc.core.schema.bidib2.BiDiB;
import org.bidib.jbidibc.exchange.vendorcv.VendorCvData;
import org.bidib.jbidibc.exchange.vendorcv.VendorCvFactory;
import org.bidib.jbidibc.messages.helpers.Context;
import org.bidib.jbidibc.messages.helpers.DefaultContext;
import org.bidib.jbidibc.messages.utils.ThreadFactoryBuilder;
import org.bidib.wizard.api.model.Macro;
import org.bidib.wizard.api.model.MacroSaveState;
import org.bidib.wizard.api.model.NodeInterface;
import org.bidib.wizard.api.model.NodeListProvider;
import org.bidib.wizard.api.model.NodeProvider;
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.api.service.node.SwitchingNodeService;
import org.bidib.wizard.api.utils.XmlLocaleUtils;
import org.bidib.wizard.client.common.view.DockKeys;
import org.bidib.wizard.client.common.view.DockUtils;
import org.bidib.wizard.client.common.view.cvdef.CvContainer;
import org.bidib.wizard.client.common.view.cvdef.CvDefinitionTreeModelRegistry;
import org.bidib.wizard.client.common.view.statusbar.StatusBar;
import org.bidib.wizard.common.labels.WizardLabelWrapper;
import org.bidib.wizard.common.model.settings.WizardSettingsInterface;
import org.bidib.wizard.common.service.SettingsService;
import org.bidib.wizard.common.utils.SearchPathUtils;
import org.bidib.wizard.core.model.connection.ConnectionRegistry;
import org.bidib.wizard.core.service.ConnectionService;
import org.bidib.wizard.mvc.backup.controller.listener.BackupControllerListener;
import org.bidib.wizard.mvc.backup.model.BackupTableModel;
import org.bidib.wizard.mvc.backup.model.NodeBackupModel;
import org.bidib.wizard.mvc.backup.view.BackupView;
import org.bidib.wizard.mvc.main.view.exchange.NodeExchangeHelper;
import org.bidib.wizard.utils.NodeUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import com.vlsolutions.swing.docking.Dockable;
import com.vlsolutions.swing.docking.DockableState;
import com.vlsolutions.swing.docking.DockingDesktop;
import com.vlsolutions.swing.docking.RelativeDockablePosition;
import com.vlsolutions.swing.docking.event.DockableStateChangeEvent;
import com.vlsolutions.swing.docking.event.DockableStateChangeListener;

import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.rxjava3.disposables.Disposable;

public class BackupController implements BackupControllerListener {
    private static final Logger LOGGER = LoggerFactory.getLogger(BackupController.class);

    private final DockingDesktop desktop;

    private DockableStateChangeListener dockableStateChangeListener;

    private final Supplier nodeProviderSupplier;

    private final NodeListProvider nodeListProvider;

    private BackupView backupView;

    private BackupTableModel backupTableModel;

    @Autowired
    private ConnectionService connectionService;

    @Autowired
    private NodeService nodeService;

    @Autowired
    private SwitchingNodeService switchingNodeService;

    @Autowired
    private StatusBar statusBar;

    @Autowired
    private SettingsService settingsService;

    // registry for CvDefinitionTreeTableModel instances
    @Autowired
    private CvDefinitionTreeModelRegistry cvDefinitionTreeModelRegistry;

    @Autowired
    private WizardLabelWrapper wizardLabelWrapper;

    private final NodeListListener nodeListListener;

    private final ScheduledExecutorService backupWorkers =
        Executors
            .newScheduledThreadPool(5, new ThreadFactoryBuilder().setNameFormat("backupWorkers-thread-%d").build());

    private CompositeDisposable compDispNodes;

    public BackupController(final DockingDesktop desktop, final Supplier nodeProviderSupplier,
        final NodeListProvider nodeListProvider) {
        this.desktop = desktop;
        this.nodeProviderSupplier = nodeProviderSupplier;
        this.nodeListProvider = nodeListProvider;

        // 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() {
        LOGGER.info("Start the BackupController.");

        // check if the booster table view is already opened
        String searchKey = DockKeys.BACKUP_VIEW;
        LOGGER.info("Search for view with key: {}", searchKey);
        Dockable view = desktop.getContext().getDockableByKey(searchKey);
        if (view != null) {
            LOGGER.info("Select the existing backup view.");
            DockUtils.selectWindow(view);
            return;
        }

        createDockable();
    }

    public Dockable createDockable() {

        LOGGER.info("Create new BackupView.");

        if (this.backupView != null) {
            LOGGER.info("Select the existing booster table view.");
            DockUtils.selectWindow(this.backupView);
            return this.backupView;
        }

        this.compDispNodes = new CompositeDisposable();

        this.backupTableModel = new BackupTableModel((text, duration) -> {
            if (duration != null) {
                statusBar.setStatusText(text, duration);
            }
            else {
                statusBar.setStatusText(text);
            }
        });

        this.backupView = new BackupView(this, this.backupTableModel, this.settingsService, (text, duration) -> {
            if (duration != null) {
                statusBar.setStatusText(text, duration);
            }
            else {
                statusBar.setStatusText(text);
            }
        });

        if (desktop.getDockables().length > 1) {

            for (DockableState dockableState : desktop.getDockables()) {

                Dockable dock = dockableState.getDockable();
                if (dock.getDockKey().equals(DockKeys.DOCKKEY_TAB_PANEL)) {
                    LOGGER.info("Found tabPanel and add backup view next to tabPanel.");
                    desktop.createTab(dock, this.backupView, 2, true);
                    break;
                }
            }
        }
        else {
            LOGGER.info("No dockables found. Add backup view to right.");
            desktop.addDockable(this.backupView, RelativeDockablePosition.RIGHT);
        }

        // register as nodeList listener at the main controller
        this.nodeListProvider.addNodeListListener(this.nodeListListener);

        this.dockableStateChangeListener = new DockableStateChangeListener() {

            @Override
            public void dockableStateChanged(DockableStateChangeEvent event) {
                if (event.getNewState().getDockable().equals(BackupController.this.backupView)
                    && event.getNewState().isClosed()) {
                    LOGGER.info("BackupView was closed, free resources.");

                    try {
                        desktop.removeDockableStateChangeListener(dockableStateChangeListener);
                    }
                    catch (Exception ex) {
                        LOGGER
                            .warn("Remove dockableStateChangeListener from desktop failed: "
                                + dockableStateChangeListener, ex);
                    }
                    finally {
                        dockableStateChangeListener = null;
                    }

                    try {
                        BackupController.this.nodeListProvider
                            .removeNodeListListener(BackupController.this.nodeListListener);
                    }
                    catch (Exception ex) {
                        LOGGER.warn("Remove nodeList listener failed.", ex);
                    }

                    if (BackupController.this.compDispNodes != null) {
                        LOGGER.info("Dispose the node subscription.");
                        BackupController.this.compDispNodes.dispose();

                        BackupController.this.compDispNodes = null;
                    }

                    BackupController.this.backupView = null;
                    BackupController.this.backupTableModel = null;

                }
            }
        };

        desktop.addDockableStateChangeListener(this.dockableStateChangeListener);

        try {
            Disposable disp = 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.");

                            List nodeBackups = new LinkedList<>(backupTableModel.getNodes());
                            for (NodeBackupModel nodeBackup : nodeBackups) {
                                backupTableModel.removeNode(nodeBackup.getNode());
                            }
                            break;
                        default:
                            break;
                    }
                }

            }, error -> {
                LOGGER.warn("The connection status change caused an error.", error);
            }, () -> {
                LOGGER.info("The subscription to connection status changes has completed.");
            });

            this.compDispNodes.add(disp);

            final NodeProvider nodeProvider = this.nodeProviderSupplier.get();

            if (CollectionUtils.isNotEmpty(nodeProvider.getNodes())) {
                LOGGER.info("Add nodes to backup model.");
                final List nodes = new LinkedList<>(nodeProvider.getNodes());
                for (NodeInterface node : nodes) {

                    this.backupTableModel.addNode(node, wizardLabelWrapper);
                }
            }
        }
        catch (Exception ex) {
            LOGGER.warn("Register controller as connection status listener failed.", ex);
        }

        return this.backupView;
    }

    private void nodeNew(final NodeInterface node) {
        LOGGER.info("New node in system detected: {}", node);
        if (SwingUtilities.isEventDispatchThread()) {
            internalNewNode(node);
        }
        else {
            SwingUtilities.invokeLater(() -> internalNewNode(node));
        }
    }

    private void internalNewNode(final NodeInterface node) {
        LOGGER.info("Add new node to table: {}", node);

        backupTableModel.addNode(node, wizardLabelWrapper);
    }

    private void nodeLost(final NodeInterface node) {
        LOGGER.info("Remove node from model: {}", node);
        if (SwingUtilities.isEventDispatchThread()) {

            backupTableModel.removeNode(node);
        }
        else {
            SwingUtilities.invokeLater(() -> backupTableModel.removeNode(node));
        }
    }

    @Override
    public CompletableFuture performBackup(
        final String backupDir, final List nodeBackupModels) {
        LOGGER.info("Perform backup, backupDir: {}", backupDir);

        final List> futures = new LinkedList<>();

        final WizardSettingsInterface wizardSettings = settingsService.getWizardSettings();

        final NodeExchangeHelper helper = new NodeExchangeHelper();
        final String lang = XmlLocaleUtils.getXmlLocaleVendorCV();
        final boolean loadCvs = true;

        for (NodeBackupModel nodeBackupModel : nodeBackupModels) {

            final CompletableFuture future = new CompletableFuture<>();

            // schedule for backup
            Callable backup = () -> {
                final NodeInterface node = nodeBackupModel.getNode();
                LOGGER.info("Perform backup for node: {}", node);

                SwingUtilities.invokeLater(() -> nodeBackupModel.setProgress(5));

                // load CVs
                if (loadCvs) {
                    LOGGER.info("Load the CVs of the node before export: {}", node);

                    long uniqueId = node.getUniqueId();
                    CvContainer cvContainer = cvDefinitionTreeModelRegistry.getCvContainer(uniqueId);

                    if (cvContainer == null) {
                        if (node.getVendorCV() == null) {

                            SwingUtilities.invokeLater(() -> nodeBackupModel.setProgress(7));

                            // try to load cv definition
                            loadCvDefinition(node);
                        }

                        SwingUtilities.invokeLater(() -> nodeBackupModel.setProgress(20));

                        if (node.getVendorCV() != null) {
                            LOGGER
                                .info(
                                    "No cvContainer found but vendorCV available. Prepare the vendorCV tree for node: {}",
                                    node);
                            final VendorCvData vendorCV = node.getVendorCV();
                            cvDefinitionTreeModelRegistry.prepareVendorCVTree(node, vendorCV, false);

                            cvContainer = cvDefinitionTreeModelRegistry.getCvContainer(uniqueId);
                        }
                    }

                    List configurationVariables = null;
                    if (cvContainer != null) {
                        configurationVariables = cvContainer.getConfigVariables();
                    }

                    if (CollectionUtils.isNotEmpty(configurationVariables)) {
                        // use the CV list from the model

                        configurationVariables =
                            configurationVariables.stream().distinct().collect(Collectors.toList());

                        // sort the CV variables by number
                        ConfigurationVariable.sortCvVariables(configurationVariables);

                        LOGGER.info("Load CV from node");

                        try {
                            // must fetch the CV from the nodes
                            List queriedConfigurationVariables =
                                nodeService
                                    .queryConfigVariables(ConnectionRegistry.CONNECTION_ID_MAIN, node,
                                        configurationVariables);

                            LOGGER.info("Current queried configurationVariables: {}", queriedConfigurationVariables);
                            node.setConfigVariables(queriedConfigurationVariables);
                        }
                        catch (Exception ex) {
                            LOGGER.warn("Query configuration variables from node failed.", ex);

                            future.completeExceptionally(ex);

                            return nodeBackupModel;
                        }
                    }
                    else {
                        LOGGER.warn("No configuration variables available to load from node: {}", node);
                    }
                }
                else {
                    LOGGER.warn("The CV values of the node are not exported!");
                }

                // check if the macros are loaded
                if (node.hasUnloadedMacros()) {
                    LOGGER.info("The current node has unloaded macros: {}", node);

                    SwingUtilities.invokeLater(() -> nodeBackupModel.setProgress(30));

                    try {
                        for (Macro macro : node.getMacros()) {
                            if (macro.getMacroSaveState() != MacroSaveState.PERMANENTLY_STORED_ON_NODE) {
                                LOGGER.info("Load macro content for macro: {}", macro);
                                NodeUtils
                                    .loadMacroContentFromNode(ConnectionRegistry.CONNECTION_ID_MAIN, nodeService,
                                        switchingNodeService, node, macro);
                            }
                        }
                    }
                    catch (Exception ex) {
                        LOGGER.warn("Load macro content from node failed.", ex);

                        future.completeExceptionally(ex);

                        return nodeBackupModel;
                    }
                }
                else {
                    LOGGER.info("The current node has no unloaded macros: {}", node);
                }

                try {
                    final BiDiB bidib =
                        helper.prepareBiDiB(node, cvDefinitionTreeModelRegistry, lang, !loadCvs, wizardLabelWrapper);
                    String fileName = NodeExchangeHelper.prepareFileName(wizardSettings, node);
                    LOGGER.info("Prepared backup fileName: {}, backupDir: {}", fileName, backupDir);

                    SwingUtilities.invokeLater(() -> nodeBackupModel.setProgress(50));

                    // store bidib to file
                    File backupFile = new File(backupDir, fileName);
                    BidibFactory.saveBiDiB(bidib, backupFile, false);

                    LOGGER.info("Save node state passed, fileName: {}", fileName);

                    SwingUtilities.invokeLater(() -> nodeBackupModel.setProgress(100));
                }
                catch (Exception ex) {
                    LOGGER.warn("Failed to prepare backup and save to file.", ex);

                    future.completeExceptionally(ex);

                    return nodeBackupModel;
                }

                future.complete(nodeBackupModel);

                return nodeBackupModel;
            };

            futures.add(future);

            this.backupWorkers.submit(backup);
        }

        final CompletableFuture[] cfs = futures.toArray(new CompletableFuture[0]);

        CompletableFuture result =
            CompletableFuture
                .allOf(cfs).thenApply(ignored -> futures
                    .stream().map(CompletableFuture::join).collect(Collectors.toList()));

        CompletableFuture res = new CompletableFuture<>();

        // wait maximum for 30s
        // try {
        // result.get(30, TimeUnit.SECONDS);
        result.whenComplete((o, th) -> {
            LOGGER.info("The backup has finished.");

            if (th != null) {
                LOGGER.info("The backup result has signalled an error.");
                res.completeExceptionally(th);
            }
            else {
                res.complete("ok");
            }

        }).join();

        LOGGER.info("The backup has finished with result: {}", result);
        // }
        // catch (InterruptedException | TimeoutException ex) {
        // LOGGER.warn("Get result failed.", ex);
        // }

        // LOGGER.info("The backup has finished.");
        //
        // if (result.isCompletedExceptionally()) {
        // LOGGER.info("The backup result has signalled an error.");
        // res.complete("error");
        // }
        // else {
        // res.complete("ok");
        // }
        LOGGER.info("Return with result: {}", res);
        return res;
    }

    private void loadCvDefinition(final NodeInterface node) {
        VendorCvData vendorCV = null;

        if (node != null) {

            final Context context = new DefaultContext();

            try {
                final StopWatch sw = StopWatch.createStarted();

                String installationPath = this.settingsService.getInstallationPath();
                LOGGER.info("Current installation path: {}", installationPath);

                File file = new File(installationPath);
                file = new File(file.getAbsoluteFile(), "data/BiDiBNodeVendorData");

                String userHome = System.getProperty("user.home");
                File searchPathUserHomeWizard = new File(userHome, ".BiDiBWizard/data/BiDiBNodeVendorData");

                String labelPath = settingsService.getMiscSettings().getBidibConfigDir();
                File searchPathLabelPath = new File(labelPath, "data/BiDiBNodeVendorData");

                vendorCV =
                    VendorCvFactory
                        .getCvDefinition(node.getNode(), context, SearchPathUtils::lookupFilesInClasspath,
                            searchPathLabelPath.getAbsolutePath(), file.getAbsolutePath(), "classpath:/bidib",
                            searchPathUserHomeWizard.getAbsolutePath());

                sw.stop();

                LOGGER.info("Load vendorCV data took: {}", sw.toString());
            }
            catch (Exception ex) {
                LOGGER.warn("Get CV definition for node failed.", ex);
            }

            if (vendorCV != null) {
                LOGGER.info("Set the vendorCv for node: {}", node);

                node.setVendorCV(vendorCV);
            }
        }
    }

}