org.nebulaframework.ui.swing.node.NodeMainUI.java Source code

Java tutorial

Introduction

Here is the source code for org.nebulaframework.ui.swing.node.NodeMainUI.java

Source

/*
 * Copyright (C) 2008 Yohan Liyanage. 
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at 
 * http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and 
 * limitations under the License.
 */
package org.nebulaframework.ui.swing.node;

import java.awt.AWTException;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.HeadlessException;
import java.awt.Image;
import java.awt.MenuItem;
import java.awt.PopupMenu;
import java.awt.SystemTray;
import java.awt.Toolkit;
import java.awt.TrayIcon;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.swing.AbstractListModel;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JTextPane;
import javax.swing.JWindow;
import javax.swing.KeyStroke;
import javax.swing.ListSelectionModel;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingUtilities;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.nebulaframework.discovery.DiscoveryFailureException;
import org.nebulaframework.grid.Grid;
import org.nebulaframework.grid.cluster.node.GridNode;
import org.nebulaframework.grid.service.event.ServiceEventsSupport;
import org.nebulaframework.grid.service.event.ServiceHookCallback;
import org.nebulaframework.grid.service.message.ServiceMessage;
import org.nebulaframework.grid.service.message.ServiceMessageType;
import org.nebulaframework.ui.swing.AboutDialog;
import org.nebulaframework.ui.swing.UISupport;
import org.nebulaframework.util.log4j.JLabelAppender;
import org.nebulaframework.util.log4j.JTextPaneAppender;
import org.nebulaframework.util.net.NetUtils;
import org.nebulaframework.util.profiling.TimeUtils;

/**
 * The Swing UI for GridNode. This UI exposes basic functionality of 
 * GridNode, and allows users to easily manage the Node.
 * <p>
 * However, for more advanced uses, such as embedding
 * GridNodes, consider using the API.
 * 
 * @author Yohan Liyanage
 * @version 1.0
 */
public class NodeMainUI extends JFrame {

    private static final long serialVersionUID = 1574154489795768861L;

    private static final Log log = LogFactory.getLog(NodeMainUI.class);

    private static final int WIDTH = 600;
    private static final int HEIGHT = 575;

    // Components
    private static Map<String, JComponent> components = new HashMap<String, JComponent>();

    // Job History List
    private JobHistoryListModel historyList = new JobHistoryListModel();

    // Total Execution Time
    private long executionTime = 0L;

    // Auto Discovery Enabled
    private boolean autodiscover = true;

    // Last Discovery Attempt Time stamp
    private Long lastDiscoveryAttempt = System.currentTimeMillis();

    // Currently Active JobId
    private String activeJobId = null;

    private TrayIcon trayIcon;

    private Image idleIcon;
    private Image activeIcon;

    /**
     * Constructs a GridNode UI.
     * 
     * @throws HeadlessException if UI not supported.
     */
    public NodeMainUI() throws HeadlessException {
        super();
        setupUI();
    }

    /**
     * UI Setup operations.
     */
    private void setupUI() {
        setTitle("Nebula Grid - Execution Node");
        setSize(WIDTH, HEIGHT);

        setJMenuBar(setupMenu());
        setLayout(new BorderLayout());

        JTabbedPane tabs = new JTabbedPane();
        add(tabs, BorderLayout.CENTER);

        tabs.addTab("Control Center", setupGeneral());
        tabs.addTab("History", setupHistory());

        resetActiveJobInfo();
        setStatus("Not Connected");
        updateGridInfo();
        updateExecutionTime();

        setupTrayIcon(this);
    }

    /**
     * Setup System Tray Icon.
     * 
     * @param frame owner frame
     */
    private void setupTrayIcon(final JFrame frame) {

        idleIcon = Toolkit.getDefaultToolkit()
                .getImage(ClassLoader.getSystemResource("META-INF/resources/node_inactive.png"));

        activeIcon = Toolkit.getDefaultToolkit()
                .getImage(ClassLoader.getSystemResource("META-INF/resources/node_active.png"));

        frame.setIconImage(idleIcon);

        // If system tray is supported by OS
        if (SystemTray.isSupported()) {
            trayIcon = new TrayIcon(idleIcon, "Nebula Grid Node", createTrayPopup());
            trayIcon.setImageAutoSize(true);
            trayIcon.addMouseListener(new MouseAdapter() {

                @Override
                public void mouseClicked(MouseEvent e) {
                    if (e.getButton() == MouseEvent.BUTTON1) {
                        if (!frame.isVisible()) {
                            frame.setVisible(true);
                        }

                        frame.setExtendedState(JFrame.NORMAL);
                        frame.requestFocus();
                        frame.toFront();
                    }
                }

            });

            try {
                SystemTray.getSystemTray().add(trayIcon);
            } catch (AWTException ae) {
                log.debug("[UI] Unable to Initialize Tray Icon");
                return;
            }

            frame.addWindowListener(new WindowAdapter() {

                @Override
                public void windowIconified(WindowEvent e) {
                    // Hide (can be shown using tray icon)
                    frame.setVisible(false);
                }

            });
        }

    }

    /**
     * System Tray Icon Pop Up Menu
     * 
     * @return PopupMenu
     */
    private PopupMenu createTrayPopup() {
        PopupMenu trayPopup = new PopupMenu();

        // About
        MenuItem aboutItem = new MenuItem("About");
        aboutItem.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                showAbout();
            }

        });
        trayPopup.add(aboutItem);

        trayPopup.addSeparator();

        // Shutdown Node
        MenuItem shutdownItem = new MenuItem("Shutdown");
        shutdownItem.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                doShutdownNode();
            }

        });
        trayPopup.add(shutdownItem);

        return trayPopup;
    }

    /**
     * Displays busy icon in System tray
     */
    private void showBusyIcon() {
        if (trayIcon != null)
            trayIcon.setImage(activeIcon);
    }

    /**
     * Displays Idle icon in System tray
     */
    private void showIdleIcon() {
        if (trayIcon != null)
            trayIcon.setImage(idleIcon);
    }

    /**
     * Removes System Tray icon.
     */
    private void removeIcon() {
        if (SystemTray.isSupported()) {
            SystemTray.getSystemTray().remove(trayIcon);
        }
    }

    /**
     * Setup Menu Bar
     * @return JMenu Bar
     */
    private JMenuBar setupMenu() {
        JMenuBar menuBar = new JMenuBar();

        /* -- GridNode Menu -- */

        JMenu gridNodeMenu = new JMenu("GridNode");
        gridNodeMenu.setMnemonic(KeyEvent.VK_N);
        menuBar.add(gridNodeMenu);

        // Discover 
        JMenuItem clusterDiscoverItem = new JMenuItem("Disover and Connect Clusters");
        clusterDiscoverItem.setMnemonic(KeyEvent.VK_D);
        clusterDiscoverItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F2, 0));
        gridNodeMenu.add(clusterDiscoverItem);
        clusterDiscoverItem.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                doDiscover(false);
                ((JCheckBoxMenuItem) getUIElement("menu.node.autodiscover")).setSelected(false);
            }
        });
        addUIElement("menu.node.discover", clusterDiscoverItem); // Add to components map

        // Auto-Discovery
        final JCheckBoxMenuItem autodiscoveryItem = new JCheckBoxMenuItem("Auto Discover");
        autodiscoveryItem.setMnemonic(KeyEvent.VK_A);
        autodiscoveryItem.setSelected(true);
        gridNodeMenu.add(autodiscoveryItem);
        autodiscoveryItem.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                autodiscover = autodiscoveryItem.isSelected();
            }
        });
        addUIElement("menu.node.autodiscover", autodiscoveryItem); // Add to components map

        gridNodeMenu.addSeparator();

        // Cluster-> Shutdown
        JMenuItem nodeShutdownItem = new JMenuItem("Shutdown", 'u');
        nodeShutdownItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F6, 0));
        nodeShutdownItem.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                doShutdownNode();
            }
        });
        gridNodeMenu.add(nodeShutdownItem);
        addUIElement("menu.node.shutdown", nodeShutdownItem); // Add to components map

        /* -- Options Menu -- */
        JMenu optionsMenu = new JMenu("Options");
        optionsMenu.setMnemonic(KeyEvent.VK_O);
        menuBar.add(optionsMenu);

        // Configuration
        JMenuItem optionsConfigItem = new JMenuItem("Configuration...", 'C');
        optionsConfigItem.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                showConfiguration();
            }
        });
        optionsMenu.add(optionsConfigItem);
        optionsConfigItem.setEnabled(false); // TODO Create Configuration Options

        /* -- Help Menu -- */
        JMenu helpMenu = new JMenu("Help");
        helpMenu.setMnemonic(KeyEvent.VK_H);
        menuBar.add(helpMenu);

        // Help Contents
        JMenuItem helpContentsItem = new JMenuItem("Help Contents", 'H');
        helpContentsItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F1, 0));
        helpContentsItem.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                showHelp();
            }
        });
        helpMenu.add(helpContentsItem);

        helpMenu.addSeparator();

        JMenuItem helpAboutItem = new JMenuItem("About", 'A');
        helpAboutItem.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                showAbout();
            }
        });
        helpMenu.add(helpAboutItem);

        return menuBar;
    }

    /**
     * Setup General (Control Center) Tab
     * 
     * @return JPanel for Control Center
     */
    private JPanel setupGeneral() {

        JPanel generalPanel = new JPanel();
        generalPanel.setLayout(new BorderLayout());

        /* -- Stats Panel -- */
        JPanel statsPanel = new JPanel();
        generalPanel.add(statsPanel, BorderLayout.NORTH);

        statsPanel.setLayout(new GridLayout(0, 2, 10, 10));

        JPanel eastPanel = new JPanel();
        statsPanel.add(eastPanel, BorderLayout.EAST);
        eastPanel.setLayout(new BorderLayout());

        JPanel westPanel = new JPanel();
        statsPanel.add(westPanel, BorderLayout.WEST);
        westPanel.setLayout(new BorderLayout());

        // Grid Information Panel
        JPanel gridInfoPanel = new JPanel();
        eastPanel.add(gridInfoPanel, BorderLayout.NORTH);

        gridInfoPanel.setBorder(BorderFactory.createTitledBorder("Grid Information"));
        gridInfoPanel.setLayout(new GridLayout(0, 2, 10, 10));

        JLabel nodeIdLabel = new JLabel("Node ID :");
        gridInfoPanel.add(nodeIdLabel);
        JLabel nodeId = new JLabel("#nodeid#");
        gridInfoPanel.add(nodeId);
        addUIElement("general.stats.nodeid", nodeId); // Add to components map

        JLabel nodeIpLabel = new JLabel("Node IP :");
        gridInfoPanel.add(nodeIpLabel);
        JLabel nodeIp = new JLabel("#nodeip#");
        gridInfoPanel.add(nodeIp);
        addUIElement("general.stats.nodeip", nodeIp); // Add to components map

        JLabel clusterIdLabel = new JLabel("Cluster ID :");
        gridInfoPanel.add(clusterIdLabel);
        JLabel clusterId = new JLabel("#clusterid#");
        gridInfoPanel.add(clusterId);
        addUIElement("general.stats.clusterid", clusterId); // Add to components map

        JLabel clusterServiceLabel = new JLabel("Cluster Service :");
        gridInfoPanel.add(clusterServiceLabel);
        JLabel clusterService = new JLabel("#clusterservice#");
        gridInfoPanel.add(clusterService);
        addUIElement("general.stats.clusterservice", clusterService); // Add to components map

        // Node Status Panel 
        JPanel nodeStatusPanel = new JPanel();
        eastPanel.add(nodeStatusPanel, BorderLayout.SOUTH);

        nodeStatusPanel.setBorder(BorderFactory.createTitledBorder("GridNode Status"));
        nodeStatusPanel.setLayout(new GridLayout(0, 2, 10, 10));

        JLabel statusLabel = new JLabel("Status :");
        nodeStatusPanel.add(statusLabel);
        JLabel status = new JLabel("#status#");
        nodeStatusPanel.add(status);
        addUIElement("general.stats.status", status); // Add to components map

        JLabel uptimeLabel = new JLabel("Node Up Time :");
        nodeStatusPanel.add(uptimeLabel);
        JLabel uptime = new JLabel("#uptime#");
        nodeStatusPanel.add(uptime);
        addUIElement("general.stats.uptime", uptime); // Add to components map

        JLabel execTimeLabel = new JLabel("Execution Time :");
        nodeStatusPanel.add(execTimeLabel);
        JLabel execTime = new JLabel("#exectime#");
        nodeStatusPanel.add(execTime);
        addUIElement("general.stats.exectime", execTime); // Add to components map

        // Execution Statistics Panel
        JPanel execStatsPanel = new JPanel();
        westPanel.add(execStatsPanel, BorderLayout.NORTH);

        execStatsPanel.setLayout(new GridLayout(0, 2, 10, 10));
        execStatsPanel.setBorder(BorderFactory.createTitledBorder("Execution Statistics"));

        JLabel totalJobsLabel = new JLabel("Total Jobs :");
        execStatsPanel.add(totalJobsLabel);
        JLabel totalJobs = new JLabel("0");
        execStatsPanel.add(totalJobs);
        addUIElement("general.stats.totaljobs", totalJobs); // Add to components map

        JLabel totalTasksLabel = new JLabel("Total Tasks :");
        execStatsPanel.add(totalTasksLabel);
        JLabel totalTasks = new JLabel("0");
        execStatsPanel.add(totalTasks);
        addUIElement("general.stats.totaltasks", totalTasks); // Add to components map

        JLabel totalBansLabel = new JLabel("Banments :");
        execStatsPanel.add(totalBansLabel);
        JLabel totalBans = new JLabel("0");
        execStatsPanel.add(totalBans);
        addUIElement("general.stats.totalbans", totalBans); // Add to components map

        // Execution Active Job Panel
        JPanel activeJobPanel = new JPanel();
        westPanel.add(activeJobPanel, BorderLayout.SOUTH);

        activeJobPanel.setLayout(new GridLayout(0, 2, 10, 10));
        activeJobPanel.setBorder(BorderFactory.createTitledBorder("Active Job"));

        JLabel jobNameLabel = new JLabel("GridJob Name :");
        activeJobPanel.add(jobNameLabel);
        JLabel jobName = new JLabel("#jobname#");
        activeJobPanel.add(jobName);
        addUIElement("general.stats.jobname", jobName); // Add to components map

        JLabel durationLabel = new JLabel("Duration :");
        activeJobPanel.add(durationLabel);
        JLabel duration = new JLabel("#duration#");
        activeJobPanel.add(duration);
        addUIElement("general.stats.duration", duration); // Add to components map

        JLabel tasksLabel = new JLabel("Tasks Executed :");
        activeJobPanel.add(tasksLabel);
        JLabel tasks = new JLabel("#xyz#");
        activeJobPanel.add(tasks);
        addUIElement("general.stats.tasks", tasks); // Add to components map

        JLabel failuresLabel = new JLabel("Failures :");
        activeJobPanel.add(failuresLabel);
        JLabel failures = new JLabel("#failures#");
        activeJobPanel.add(failures);
        addUIElement("general.stats.failures", failures); // Add to components map

        /* -- Log Panel -- */
        JPanel logPanel = new JPanel();
        generalPanel.add(logPanel, BorderLayout.CENTER);

        logPanel.setLayout(new BorderLayout());
        logPanel.setBorder(BorderFactory.createTitledBorder("Log Output"));
        JTextPane logTextPane = new JTextPane();
        logTextPane.setEditable(false);
        logTextPane.setBackground(Color.BLACK);
        logTextPane.setForeground(Color.WHITE);

        logPanel.add(new JScrollPane(logTextPane, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS,
                ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED), BorderLayout.CENTER);
        addUIElement("general.log", logTextPane); // Add to component map

        JPanel logOptionsPanel = new JPanel();
        logPanel.add(logOptionsPanel, BorderLayout.SOUTH);
        logOptionsPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));

        final JCheckBox logScrollCheckbox = new JCheckBox("Auto-Scroll Log");
        logScrollCheckbox.setSelected(true);
        logScrollCheckbox.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                JTextPaneAppender.setAutoScroll(logScrollCheckbox.isSelected());
            }

        });
        logOptionsPanel.add(logScrollCheckbox);

        // Enable Logging
        JTextPaneAppender.setTextPane(logTextPane);

        /* -- Buttons Panel -- */
        JPanel buttonsPanel = new JPanel();
        generalPanel.add(buttonsPanel, BorderLayout.SOUTH);

        buttonsPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));

        // Shutdown Button
        JButton shutdownButton = new JButton("Shutdown");
        buttonsPanel.add(shutdownButton);
        shutdownButton.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                doShutdownNode();
            }

        });

        // Start Up time Thread
        Thread t = new Thread(new Runnable() {
            public void run() {

                long start = System.currentTimeMillis();

                while (true) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        log.warn("Interrupted Exception in Up Time Thread", e);
                    }

                    final String uptime = TimeUtils.timeDifference(start);

                    SwingUtilities.invokeLater(new Runnable() {

                        public void run() {
                            JLabel upTime = getUIElement("general.stats.uptime");
                            upTime.setText(uptime);
                        }

                    });
                }

            }
        });
        t.setDaemon(true);
        t.start();

        // Auto-Discovery Thread
        Thread autoDiscovery = new Thread(new Runnable() {
            public void run() {

                while (true) {
                    try {

                        // Attempt every 30 seconds
                        Thread.sleep(30000);

                    } catch (InterruptedException e) {
                        log.warn("Interrupted Exception in Up Time Thread", e);
                    }

                    if (autodiscover && (!Grid.isNode())) {
                        // 30 Second Intervals
                        doDiscover(true);
                    }

                }
            }
        });
        autoDiscovery.setDaemon(true);
        autoDiscovery.start();

        return generalPanel;
    }

    /**
     * Setup Job History Tab Pane
     * 
     * @return JPanel for History tab
     */
    private JPanel setupHistory() {

        JPanel historyPanel = new JPanel();
        historyPanel.setLayout(new BorderLayout(10, 10));

        /* -- Job List  -- */
        JPanel jobListPanel = new JPanel();
        historyPanel.add(jobListPanel, BorderLayout.CENTER);

        jobListPanel.setLayout(new BorderLayout(10, 10));
        jobListPanel.setBorder(BorderFactory.createTitledBorder("Grid Jobs"));

        final JList jobList = new JList(historyList);
        JScrollPane jobListScroll = new JScrollPane(jobList);
        jobListPanel.add(jobListScroll, BorderLayout.CENTER);
        jobListScroll.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
        jobListScroll.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);

        addUIElement("history.joblist", jobList);

        jobList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        jobList.addListSelectionListener(new ListSelectionListener() {

            @Override
            public void valueChanged(ListSelectionEvent e) {

                // Ignore intermediate events
                if (e.getValueIsAdjusting())
                    return;

                displayJobInfo(jobList.getSelectedValue());
            }

        });

        JPanel jobInfoPanel = new JPanel();
        historyPanel.add(jobInfoPanel, BorderLayout.SOUTH);

        jobInfoPanel.setLayout(new BorderLayout(10, 10));
        jobInfoPanel.setBorder(BorderFactory.createTitledBorder("Job Information"));

        JPanel centerPanel = new JPanel();

        jobInfoPanel.add(centerPanel, BorderLayout.CENTER);

        centerPanel.setLayout(new GridLayout(0, 4, 10, 10));

        JLabel jobIdLabel = new JLabel("GridJob ID :");
        centerPanel.add(jobIdLabel);
        JLabel jobId = new JLabel("N/A");
        centerPanel.add(jobId);
        addUIElement("history.jobid", jobId); // Add to components map

        JLabel jobNameLabel = new JLabel("GridJob Name :");
        centerPanel.add(jobNameLabel);
        JLabel jobName = new JLabel("N/A");
        centerPanel.add(jobName);
        addUIElement("history.jobname", jobName); // Add to components map

        JLabel startTimeLabel = new JLabel("Start Time :");
        centerPanel.add(startTimeLabel);
        JLabel startTime = new JLabel("N/A");
        centerPanel.add(startTime);
        addUIElement("history.starttime", startTime); // Add to components map

        JLabel durationLabel = new JLabel("Duration :");
        centerPanel.add(durationLabel);
        JLabel duration = new JLabel("N/A");
        centerPanel.add(duration);
        addUIElement("history.duration", duration); // Add to components map

        JLabel tasksLabel = new JLabel("Tasks Executed :");
        centerPanel.add(tasksLabel);
        JLabel tasks = new JLabel("N/A");
        centerPanel.add(tasks);
        addUIElement("history.tasks", tasks); // Add to components map

        JLabel failuresLabel = new JLabel("Failures :");
        centerPanel.add(failuresLabel);
        JLabel failures = new JLabel("N/A");
        centerPanel.add(failures);
        addUIElement("history.failures", failures); // Add to components map

        // Place Holders
        centerPanel.add(new JLabel());
        centerPanel.add(new JLabel());

        return historyPanel;
    }

    /**
     * Resets the active Job Info fields
     */
    protected void resetActiveJobInfo() {

        JLabel jobName = getUIElement("general.stats.jobname");
        JLabel duration = getUIElement("general.stats.duration");
        JLabel tasks = getUIElement("general.stats.tasks");
        JLabel failure = getUIElement("general.stats.failures");

        jobName.setText("N/A");
        duration.setText("N/A");
        tasks.setText("N/A");
        failure.setText("N/A");

    }

    /**
     * Updates the GridNode Status to given status
     * @param status status text
     */
    protected void setStatus(String status) {
        ((JLabel) getUIElement("general.stats.status")).setText(status);
    }

    /**
     * Displays the Job info for given {@link JobHistoryElement} in the
     * History Tab Pane's fields.
     *  
     * @param obj JobHistoryElement
     */
    protected void displayJobInfo(Object obj) {

        if (!(obj instanceof JobHistoryElement))
            return;
        JobHistoryElement element = (JobHistoryElement) obj;

        JLabel jobId = getUIElement("history.jobid");
        JLabel jobName = getUIElement("history.jobname");
        JLabel starttime = getUIElement("history.starttime");
        JLabel duration = getUIElement("history.duration");
        JLabel tasks = getUIElement("history.tasks");
        JLabel failures = getUIElement("history.failures");

        jobId.setText(element.getJobId());
        jobName.setText(element.getJobName());
        starttime.setText(element.getStartTime());
        duration.setText(element.getDuration());
        tasks.setText(String.valueOf(element.getTasks()));
        failures.setText(String.valueOf(element.getFailures()));

    }

    /**
     * Updates GridInfo in Control Center
     */
    private void updateGridInfo() {

        JLabel nodeId = getUIElement("general.stats.nodeid");
        JLabel nodeIp = getUIElement("general.stats.nodeip");
        JLabel clusterId = getUIElement("general.stats.clusterid");
        JLabel clusterService = getUIElement("general.stats.clusterservice");

        if (Grid.isNode()) {
            // If connected
            GridNode instance = GridNode.getInstance();

            nodeId.setText(instance.getId().toString());
            nodeIp.setText(NetUtils.getLocalHostAddress());
            clusterId.setText(instance.getClusterId().toString());
            clusterService.setText(instance.getClusterUrl());

            getUIElement("menu.node.discover").setEnabled(false);

            registerHooks(instance);
            setStatus("Idle");

        } else {
            // If not connected
            nodeId.setText("Not Connected");
            nodeIp.setText("Not Connected");
            clusterId.setText("Not Connected");
            clusterService.setText("Not Connected");
            setStatus("Not Connected");
            getUIElement("menu.node.discover").setEnabled(true);
        }
    }

    /**
     * Registers Service Event Hooks to update fields, once
     * connected
     * 
     * @param instance GridNode instance
     */
    private void registerHooks(GridNode instance) {

        // Disconnection Hook
        ServiceEventsSupport.addServiceHook(new ServiceHookCallback() {

            @Override
            public void onServiceEvent(ServiceMessage message) {
                SwingUtilities.invokeLater(new Runnable() {

                    @Override
                    public void run() {
                        updateGridInfo();
                    }

                });
            }

        }, instance.getClusterId().toString(), ServiceMessageType.NODE_DISCONNECTED);

        // Job Start Hook
        ServiceEventsSupport.addServiceHook(new ServiceHookCallback() {

            @Override
            public void onServiceEvent(ServiceMessage message) {

                final String jobId = message.getMessage();
                final String jobName = GridNode.getInstance().getJobExecutionService().getJobName(jobId);

                activeJobId = jobId;

                final long timeStart = System.currentTimeMillis();

                SwingUtilities.invokeLater(new Runnable() {

                    @Override
                    public void run() {

                        showBusyIcon();
                        setStatus("Executing Job");

                        ((JLabel) getUIElement("general.stats.jobname")).setText(jobName);

                        ((JLabel) getUIElement("general.stats.tasks")).setText("0");
                        ((JLabel) getUIElement("general.stats.failures")).setText("0");
                    }

                });

                // Duration Update Thread
                new Thread(new Runnable() {

                    @Override
                    public void run() {

                        while (jobId.equals(activeJobId)) {

                            SwingUtilities.invokeLater(new Runnable() {

                                @Override
                                public void run() {
                                    ((JLabel) getUIElement("general.stats.duration"))
                                            .setText(TimeUtils.timeDifference(timeStart));
                                }

                            });

                            try {
                                Thread.sleep(1000);
                            } catch (InterruptedException e) {
                                log.warn("Interrupted", e);
                            }

                        }
                    }

                }).start();

                // Job End Hook
                ServiceEventsSupport.addServiceHook(new ServiceHookCallback() {

                    @Override
                    public void onServiceEvent(ServiceMessage message) {

                        activeJobId = null;

                        SwingUtilities.invokeLater(new Runnable() {

                            @Override
                            public void run() {

                                try {

                                    JLabel durationLabel = getUIElement("general.stats.duration");
                                    JLabel jobnameLabel = getUIElement("general.stats.jobname");
                                    JLabel tasksLabel = getUIElement("general.stats.tasks");
                                    JLabel failuresLabel = getUIElement("general.stats.failures");
                                    JLabel totalJobsLabel = getUIElement("general.stats.totaljobs");

                                    int tasks = Integer.parseInt(tasksLabel.getText());
                                    int failures = Integer.parseInt(failuresLabel.getText());

                                    // Create and enter Job History Element
                                    JobHistoryElement element = new JobHistoryElement(jobName, jobId,
                                            TimeUtils.formatDate(timeStart), durationLabel.getText(), tasks,
                                            failures);

                                    historyList.addJobHistoryElement(element);

                                    // Update Job Info Fields
                                    durationLabel.setText("N/A");
                                    jobnameLabel.setText("N/A");
                                    tasksLabel.setText("N/A");
                                    failuresLabel.setText("N/A");

                                    // Update Total Jobs Count
                                    int totalJobs = Integer.parseInt(totalJobsLabel.getText()) + 1;
                                    totalJobsLabel.setText(String.valueOf(totalJobs));

                                    showIdleIcon();
                                    setStatus("Idle");

                                } catch (Exception e) {
                                    log.warn("[UI] Exception ", e);
                                }

                            }

                        });
                    }

                }, jobId, ServiceMessageType.LOCAL_JOBFINISHED);
            }

        }, ServiceMessageType.LOCAL_JOBSTARTED);

        // Task Executed Hook
        ServiceEventsSupport.addServiceHook(new ServiceHookCallback() {

            public void onServiceEvent(final ServiceMessage message) {

                SwingUtilities.invokeLater(new Runnable() {

                    public void run() {

                        try {
                            // Total Tasks
                            JLabel totalTasksLabel = getUIElement("general.stats.totaltasks");
                            int totalTasks = Integer.parseInt(totalTasksLabel.getText()) + 1;
                            totalTasksLabel.setText(String.valueOf(totalTasks));

                            // If active job, update task count
                            if (message.getMessage().equals(activeJobId)) {
                                JLabel tasksLabel = getUIElement("general.stats.tasks");
                                int tasks = Integer.parseInt(tasksLabel.getText()) + 1;
                                tasksLabel.setText(String.valueOf(tasks));
                            }
                        } catch (Exception e) {
                            log.warn("[UI] Exception ", e);
                        }
                    }
                });
            }

        }, ServiceMessageType.LOCAL_TASKDONE);

        // Task Failed Hook
        ServiceEventsSupport.addServiceHook(new ServiceHookCallback() {

            public void onServiceEvent(final ServiceMessage message) {

                SwingUtilities.invokeLater(new Runnable() {

                    public void run() {

                        try {
                            // If active job, update task count
                            if (message.getMessage().equals(activeJobId)) {
                                JLabel failuresLabel = getUIElement("general.stats.failures");
                                int fails = Integer.parseInt(failuresLabel.getText()) + 1;
                                failuresLabel.setText(String.valueOf(fails));
                            }
                        } catch (Exception e) {
                            log.warn("[UI] Exception ", e);
                        }
                    }
                });
            }
        }, ServiceMessageType.LOCAL_TASKFAILED);

        // Task Execution Time Hook
        ServiceEventsSupport.addServiceHook(new ServiceHookCallback() {

            public void onServiceEvent(final ServiceMessage message) {

                try {
                    executionTime += Long.parseLong(message.getMessage());
                } catch (Exception e) {
                    log.warn("[UI] Exception ", e);
                }

                SwingUtilities.invokeLater(new Runnable() {

                    public void run() {
                        updateExecutionTime();
                    }
                });
            }
        }, ServiceMessageType.LOCAL_TASKEXEC);

        // Node Banned Hook
        ServiceEventsSupport.addServiceHook(new ServiceHookCallback() {

            public void onServiceEvent(final ServiceMessage message) {

                // If not relevant to node, ignore
                if (!message.getMessage().startsWith(GridNode.getInstance().getId().toString())) {
                    return;
                }

                SwingUtilities.invokeLater(new Runnable() {

                    public void run() {

                        try {
                            // If active job, update task count
                            JLabel bansLabel = getUIElement("general.stats.totalbans");
                            int bans = Integer.parseInt(bansLabel.getText()) + 1;
                            bansLabel.setText(String.valueOf(bans));
                        } catch (Exception e) {
                            log.warn("[UI] Exception ", e);
                        }
                    }
                });
            }
        }, ServiceMessageType.NODE_BANNED);

    }

    /**
     * Updates the total execution time field
     */
    private void updateExecutionTime() {
        ((JLabel) getUIElement("general.stats.exectime")).setText(TimeUtils.buildTimeString(executionTime));
    }

    /**
     * Adds a Component to the components map of this object.
     * 
     * @param identifier Component Identifier
     * @param component Component
     */
    protected void addUIElement(String identifier, JComponent component) {
        components.put(identifier, component);
    }

    /**
     * Removes a Component from the components map of this object.
     * 
     * @param identifier Component Identifier
     */
    protected void removeUIElement(String identifier) {
        components.remove(identifier);
    }

    /**
     * Returns the UI Element for given Identifier.
     * 
     * @param <T> Expected Type of UI Element
     * @param identifier Element Identifier
     * 
     * @return UI Element Instance
     * 
     * @throws IllegalArgumentException if invalid identifier
     * @throws ClassCastException if invalid type
     */
    @SuppressWarnings("unchecked")
    protected <T extends JComponent> T getUIElement(String identifier)
            throws IllegalArgumentException, ClassCastException {
        if (!components.containsKey(identifier))
            throw new IllegalArgumentException("Invalid Identifier");
        return (T) components.get(identifier);
    }

    /* -- ACTIONS --*/
    /**
     * Shutdowns the GridNode
     */
    protected void doShutdownNode() {

        int result = JOptionPane.showConfirmDialog(this, "Are You Sure to Shutdown Node ?", "Nebula Grid",
                JOptionPane.YES_NO_OPTION);

        // If user chose no, abort shutdown
        if (result == JOptionPane.NO_OPTION)
            return;

        new Thread(new Runnable() {

            @Override
            public void run() {
                try {

                    // If connected to Cluster, unregister
                    if (Grid.isNode()) {
                        GridNode.getInstance().shutdown();
                    }
                    removeIcon();
                    System.exit(0);
                } catch (Exception e) {
                    log.fatal("[GridNode] Exception while Shutting Down", e);
                    System.exit(1);
                }
            }

        }).start();
    }

    /**
     * Attempts to discover a cluster.
     * 
     * @param silent if silent, no message will be displayed
     */
    protected void doDiscover(final boolean silent) {
        new Thread(new Runnable() {

            public void run() {

                synchronized (lastDiscoveryAttempt) {
                    try {
                        showBusyIcon();

                        getUIElement("menu.node.discover").setEnabled(false);
                        getUIElement("menu.node.autodiscover").setEnabled(false);

                        // Attempt start GridNode
                        Grid.startGridNode();

                    } catch (DiscoveryFailureException e) {
                        if (!silent) {
                            JOptionPane.showMessageDialog(NodeMainUI.this, "Unable to discover any Cluster");
                        }
                    } catch (Exception e) {
                        if (!silent) {
                            JOptionPane.showMessageDialog(NodeMainUI.this,
                                    "Exception while attempting Cluster Connection");
                        }
                        log.error("[GridNode] Exception while connecting", e);
                    } finally {

                        getUIElement("menu.node.discover").setEnabled(true);
                        getUIElement("menu.node.autodiscover").setEnabled(true);

                        updateGridInfo();

                        showIdleIcon();

                        lastDiscoveryAttempt = System.currentTimeMillis();
                    }
                }
            }

        }).start();
    }

    /**
     * Displays About Dialog
     */
    protected void showAbout() {
        try {
            new AboutDialog(this);
        } catch (Exception e) {
            log.error(e);
            JOptionPane.showMessageDialog(this, "Unable to display About dialog");
        }
    }

    /**
     * Displays Help Contents
     */
    protected void showHelp() {
        UISupport.displayHelp(this);
    }

    /**
     * Displays Configuration dialog
     */
    protected void showConfiguration() {
        // TODO Implement
        JOptionPane.showMessageDialog(this, "This feature is not implemented");
    }

    /**
     * Invoked on Shutdown Event
     */
    protected void onShutdown() {
        showBusyIcon();
        doShutdownNode();
        showIdleIcon();

    }

    /**
     * Class which represents a Job History Element, inserted into the
     * JobHistory List Model.
     * 
     * @author Yohan Liyanage
     * @version 1.0
     */
    protected static class JobHistoryElement {

        private String jobName;
        private String jobId;
        private String startTime;
        private String duration;
        private int tasks;
        private int failures;

        /**
         * Constructs a JobHistoryElement with given field
         * values.
         * 
         * @param jobName job name
         * @param jobId job id
         * @param startTime job start time
         * @param duration job duration
         * @param tasks tasks executed for job on this node
         * @param failures total failures for job on this node
         */
        public JobHistoryElement(String jobName, String jobId, String startTime, String duration, int tasks,
                int failures) {
            super();
            this.jobName = jobName;
            this.jobId = jobId;
            this.startTime = startTime;
            this.duration = duration;
            this.tasks = tasks;
            this.failures = failures;
        }

        /**
         * Returns the user friendly name for Job
         * 
         * @return name
         */
        public String getJobName() {
            return jobName;
        }

        /**
         * Returns the JobId for Job
         * @return job id
         */
        public String getJobId() {
            return jobId;
        }

        /**
         * Returns the start time for job
         * @return start time
         */
        public String getStartTime() {
            return startTime;
        }

        /**
         * Returns the duration for job
         * @return duration
         */
        public String getDuration() {
            return duration;
        }

        /**
         * Returns the number of tasks executed for job on this node
         * @return tasks
         */
        public int getTasks() {
            return tasks;
        }

        /**
         * Returns the number of tasks failed for job on this node
         * @return tasks
         */
        public int getFailures() {
            return failures;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean equals(Object obj) {
            if (!(obj instanceof JobHistoryElement))
                return false;

            JobHistoryElement element = (JobHistoryElement) obj;
            return this.jobId.equals(element.getJobId());
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public int hashCode() {
            return this.jobId.hashCode();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public String toString() {
            return "[" + this.startTime + "] " + this.jobName;
        }

    }

    /**
     * ListModel for JobHistory List.
     * 
     * @author Yohan Liyanage
     * @version 1.0
     */
    protected static class JobHistoryListModel extends AbstractListModel {

        private static final long serialVersionUID = 4872462645102776860L;

        // Job History Elements
        private List<JobHistoryElement> elements = new ArrayList<JobHistoryElement>();

        /**
         * Adds a new JobHistoryElement.
         * 
         * @param element new element
         */
        public void addJobHistoryElement(JobHistoryElement element) {
            elements.add(element);
            fireIntervalAdded(this, this.getSize() - 1, this.getSize() - 1);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public Object getElementAt(int index) {
            return elements.get(index);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public int getSize() {
            return elements.size();
        }

    }

    /**
     * Displays Splash Screen.
     * 
     * @return Splash Screen reference
     */
    public static JWindow showSplash() {

        JWindow splash = new JWindow();
        splash.setSize(400, 250);
        splash.setLayout(null);

        JLabel status = new JLabel("Developed by Yohan Liyanage, 2008");
        JLabelAppender.setLabel(status);
        status.setFont(new Font("sansserif", Font.PLAIN, 10));
        status.setSize(350, 30);
        status.setLocation(10, 220);
        splash.add(status);

        JLabel lbl = new JLabel(
                new ImageIcon(ClassLoader.getSystemResource("META-INF/resources/nebula-startup.png")));
        lbl.setSize(400, 250);
        lbl.setLocation(0, 0);
        splash.add(lbl);

        splash.setVisible(true);
        splash.setLocationRelativeTo(null);

        return splash;
    }

    /**
     * Creates a GridNode UI
     * @return UI
     */
    public static NodeMainUI create() {

        final NodeMainUI ui = new NodeMainUI();
        ui.setLocationRelativeTo(null);

        ui.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);

        ui.addWindowListener(new WindowAdapter() {

            @Override
            public void windowClosing(WindowEvent e) {
                ui.onShutdown();
            }

        });

        return ui;
    }

}