/**
 * Redistribution and use of this software and associated documentation
 * ("Software"), with or without modification, are permitted provided
 * that the following conditions are met:
 *
 * 1. Redistributions of source code must retain copyright
 *    statements and notices.  Redistributions must also contain a
 *    copy of this document.
 *
 * 2. Redistributions in binary form must reproduce the
 *    above copyright notice, this list of conditions and the
 *    following disclaimer in the documentation and/or other
 *    materials provided with the distribution.
 *
 * 3. The name "Exolab" must not be used to endorse or promote
 *    products derived from this Software without prior written
 *    permission of Exoffice Technologies.  For written permission,
 *    please contact info@exolab.org.
 *
 * 4. Products derived from this Software may not be called "Exolab"
 *    nor may "Exolab" appear in their names without prior written
 *    permission of Exoffice Technologies. Exolab is a registered
 *    trademark of Exoffice Technologies.
 *
 * 5. Due credit should be given to the Exolab Project
 *    (http://www.exolab.org/).
 *
 * THIS SOFTWARE IS PROVIDED BY EXOFFICE TECHNOLOGIES AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
 * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
 * EXOFFICE TECHNOLOGIES OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Copyright 2000-2003 (C) Exoffice Technologies Inc. All Rights Reserved.
 *
 * $Id: AdminMgr.java,v 1.1 2010/06/18 16:45:09 smhuang Exp $
 *
 * Date         Author  Changes
 */


package org.exolab.jms.tools.admin;

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.util.ArrayList;
import javax.swing.*;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeExpansionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.log4j.xml.DOMConfigurator;
import org.apache.oro.text.regex.Pattern;
import org.apache.oro.text.regex.PatternMatcherInput;
import org.apache.oro.text.regex.Perl5Compiler;
import org.apache.oro.text.regex.Perl5Matcher;
import org.exolab.jms.config.AdminConfiguration;
import org.exolab.jms.config.Configuration;
import org.exolab.jms.config.ConfigurationLoader;
import org.exolab.jms.util.CommandLine;


/**
 * This class is the Gui controller for the JMS administration. It displays data
 * as a hierarchical set of tree nodes.
 * <p/>
 * <P>The Root is all the contactable JMS servers, idealy there can be several
 * of these all on different ports, with one common admin port they all listen
 * to. A user selects a JMSServer then connects via the menu item. This allows
 * the admin GUI to connect to the server via the main port and begin displaying
 * all its Queue/Topics and registered consumers.
 * <p/>
 * If there are no queue/topics for a consumer the node will be empty. Selecting
 * a consumer allows a user to see what its details are, i.e last message
 * received and acked, whether the consumer is currently active/deactive paused
 * etc.
 * <p/>
 * <P>The Gui can also be used to add/remove queue/topics and consumer
 * registrations.
 * <p/>
 * Reliable Topics are read only, since they cannot be persisted, they simply
 * display a snapshot at the time of connection.
 * <p/>
 * <P>For the moment this is not truly dynamic, that is a refresh needs to be
 * activated on the Gui to cause an update (other than changes made through the
 * Gui istelf).
 *
 * @author <a href="mailto:mourikis@exolab.org">Jim Mourikis</a>
 * @version $Revision: 1.1 $ $Date: 2010/06/18 16:45:09 $
 */
public class AdminMgr extends JFrame {

    private JMenu _file;
    private JMenuItem _exit;
    private JMenu _connections;
    private JMenuItem _refresh;
    private JMenuItem _online;
    private JMenuItem _offline;
    private JMenuItem _disconnect;
    private JMenuItem _startup;
    private JMenuItem _shutdown;
    private JTree _serverProperties;
    private JTextField _messageArea;

    private Configuration _config;

    // If this Admin object is connected to any OpenJMS
    private boolean _connected = false;

    /**
     * The server start command
     */
    private static String _serverStart = null;

    /**
     * The server configuration file path
     */
    private static String _serverConfig = null;

    // redirect stream to local console.
    private StreamRedirect _output = null;

    /**
     * The logger.
     */
    private static final Log _log = LogFactory.getLog(AdminMgr.class);


    public AdminMgr(String path) throws Exception {
        _config = new ConfigurationLoader().load(path);
        initComponents(path);
        pack();
    }

    /**
     * This method is called from within the constructor to initialize the form.
     * All the GUI objects are created, and callbacks registered.
     */
    private void initComponents(String title) {
        JMenuBar menuBar = new JMenuBar();
        _file = new JMenu();
        _exit = new JMenuItem();
        JMenu actions = new JMenu();
        _connections = new JMenu();
        _refresh = new JMenuItem();
        _online = new JMenuItem();
        _offline = new JMenuItem();
        _disconnect = new JMenuItem();
        JSeparator separator = new JSeparator();
        _startup = new JMenuItem();
        _shutdown = new JMenuItem();
        JScrollPane jmsServers = new JScrollPane();
        JComboBox jmsCombo = new JComboBox();
        _serverProperties = new JTree();
        setTitle("OpenJMS Administrator: " + title);
        DefaultTreeModel serverModel =
                OpenJMSServer.createServerList(_serverProperties);
        _serverProperties.setModel(serverModel);
        AdminInfo info = new AdminInfo();
        _serverProperties.setCellRenderer(info);

        _messageArea = new JTextField();
        _file.setText("File");
        _exit.setToolTipText("Exit administration");
        _exit.setText("Exit");
        _exit.setMnemonic('x');

        _serverProperties.setRootVisible(false);
        _serverProperties.setShowsRootHandles(true);
        _serverProperties.putClientProperty("JTree.lineStyle", "Angled");
        _serverProperties.setCellEditor(new OpenJMSEditor(_serverProperties,
                jmsCombo));
        _serverProperties.setEditable(false);
        setupCallbacks();
        _file.add(_exit);
        _file.setMnemonic('F');
        menuBar.add(_file);
        actions.setText("Actions");
        actions.setMnemonic('A');
        _connections.setText("Connections");
        _connections.setMnemonic('C');
        _refresh.setToolTipText("Refresh the display");
        _online.setToolTipText("Connect to a running OpenJMS Server");
        _offline.setToolTipText("Connect directly to an OpenJMS database");
        _refresh.setText("Refresh");
        _refresh.setMnemonic('R');
        actions.add(_refresh);

        _online.setText("Online");
        _online.setMnemonic('O');
        _offline.setText("Offline");
        _offline.setMnemonic('f');
        _connections.add(_online);
        _connections.add(_offline);
        actions.add(_connections);
        _disconnect.setToolTipText
                ("Disconnect from any connected OpenJMS Servers");
        _disconnect.setText("Disconnect");
        _disconnect.setMnemonic('D');
        actions.add(_disconnect);

        actions.add(separator);
        _startup.setToolTipText("Start the OpenJMS server");
        _startup.setText("Start OpenJMS");
        _startup.setMnemonic('S');
        _shutdown.setToolTipText("Shutdown the connected OpenJMS server");
        _shutdown.setText("Shutdown OpenJMS");
        _shutdown.setMnemonic('h');
        actions.add(_startup);
        actions.add(_shutdown);
        menuBar.add(actions);

        jmsServers.setViewportView(_serverProperties);


        getContentPane().add(jmsServers, BorderLayout.CENTER);

        _messageArea.setToolTipText("Message Area");
        _messageArea.setEditable(false);
        _messageArea.setForeground(java.awt.Color.red);
        _messageArea.setText("Not Connected");
        _messageArea.setHorizontalAlignment(SwingConstants.CENTER);


        getContentPane().add(_messageArea, BorderLayout.SOUTH);
        setJMenuBar(menuBar);
        _startup.setEnabled(true);
        _shutdown.setEnabled(false);
        _refresh.setEnabled(false);
        _disconnect.setEnabled(false);
    }

    /**
     * The exit method for the application, when the user shutdowns the form.
     */
    private void exitAdmin() {
        System.exit(0);
    }

    /**
     * Exit the Application when a user selects File->Exit from the menu
     */
    private void exitForm() {
        System.exit(0);
    }

    /**
     * Refresh the display, and repaint all items.
     */
    private void refresh() {
        if (AbstractAdminConnection.instance() instanceof OnlineConnection) {
            setConnected(false, null);
            setConnected(true, "Connected - Online Mode");
            _startup.setEnabled(false);
            _shutdown.setEnabled(true);

        } else {
            ((OpenJMSServer) (_serverProperties.getModel().getRoot()
            )).refresh();
        }
    }

    /**
     * Start the OpenJMSServer.
     */
    private void startup() {

        try {
            String args[] = getStartCommand();

            System.out.print("running ");
            for (int i = 0, j = args.length; i < j; i++) {
                System.out.print(args[i] + " ");
            }
            System.out.println();

            if (_output != null) {
                // Stop the old redirect if any.
                _output.interrupt();
            }
            Process proc = Runtime.getRuntime().exec(args);
            // Redirect output
            _output = new StreamRedirect(proc.getInputStream());
            // kick it off
            _output.start();
        } catch (Exception err) {
            JOptionPane.showMessageDialog
                    (this, "Failed to Startup OpenJMSServer:\n" + err
                            + "\nCheck config file",
                            "OpenJMSServer Startup", JOptionPane.ERROR_MESSAGE);
        }
    }

    /**
     * When a user wishes to connect to all known OpenJMSServers. Attempt to
     * create an RMI connection to the OpenJMSServer. If the server is not
     * running, this will fail. The user can start the server through the start
     * server command, and attempt to re-connect, or use the offline method
     * below.
     */
    private void onlineConnect() {
        try {
            // if online.
            new OnlineConnection(this, _config);
            _startup.setEnabled(false);
            _shutdown.setEnabled(true);
            setConnected(true, "Connected - Online Mode");
        } catch (Exception err) {
            JOptionPane.showMessageDialog
                    (this, err.getMessage(), "Online Connection Error",
                            JOptionPane.ERROR_MESSAGE);
        }
    }


    /**
     * Connect to the database in offline mode.
     */
    private void offlineConnect() {
        try {
            // if online.
            new OfflineConnection(this, _config);
            _startup.setEnabled(false);
            _shutdown.setEnabled(false);
            setConnected(true, "Connected - OFFLine Mode");
        } catch (Exception err) {
            error("Database error", err.getMessage(), err);
        }
    }

    /**
     * Disconnect from a connected OpenJMSServer. Close the database, set the
     * connected flag to false, stop displaying the OpenJMS folder.
     */
    private void disconnect() {
        try {
            AbstractAdminConnection.instance().close();
            setConnected(false, null);
        } catch (Exception e) {
            JOptionPane.showMessageDialog
                    (this, e.getMessage(), "Database Close Error",
                            JOptionPane.ERROR_MESSAGE);
        }
    }

    /**
     * A conveniance routine to open/close all database connections, and fix up
     * the display.
     * <p/>
     * <P>When connecting, show the root object, get any persistent queue/topic
     * names currently in the db, and display them. Turn off the connection
     * menu, enable the disconnection and shutdown and the context menus. Set
     * the message area text to connected.
     * <p/>
     * <P>When disconnecting, turn off the root, destroy all Gui objects close
     * the db connection, turn off all context sensitive menus, disable
     * disconnection menu and enable connection. Set the message text to
     * disconnected.
     *
     * @param c a flag inidication if this is a connection or disconnection.
     */
    private void setConnected(boolean c, String st) {
        if (c) {
            _serverProperties.setRootVisible(true);
            ((OpenJMSServer)
                    (_serverProperties.getModel().getRoot())).displayConnections();
            _connections.setEnabled(false);
            _refresh.setEnabled(true);
            // _shutdown.setEnabled(true);
            _disconnect.setEnabled(true);
            // _startup.setEnabled(false);
            _messageArea.setForeground(java.awt.Color.green.darker().darker());
            _messageArea.setText(st);
            _connected = true;
        } else {
            _serverProperties.setRootVisible(false);
            OpenJMSServer root =
                    (OpenJMSServer) _serverProperties.getModel().getRoot();
            root.removeAllChildren();
            DefaultTreeModel model =
                    (DefaultTreeModel) _serverProperties.getModel();
            model.nodeStructureChanged((DefaultMutableTreeNode) root);
            _connections.setEnabled(true);
            _startup.setEnabled(true);
            _shutdown.setEnabled(false);
            _refresh.setEnabled(false);
            _disconnect.setEnabled(false);
            _messageArea.setForeground(java.awt.Color.red);
            _messageArea.setText("Not Connected");
            _connected = false;
        }
    }

    /**
     * Set up all Action menu callbacks, and mouse events for the tree and its
     * nodes. Check all mose 2 key presses on a node, select the node, then call
     * the nodes appropriate display methos to display its specific popup
     * menus.
     * <p/>
     * <P>When first connected, all queue/topics displayed are not expaned. This
     * is just a performance saver, since their could potentially be hundreds of
     * objects. A callback is set up, so that when a queue/topic is expanded, it
     * is lokked up only then to determine what consumers are registered with
     * it.
     */
    private void setupCallbacks() {

        addWindowListener(new WindowAdapter() {

            public void windowClosing(WindowEvent evt) {
                exitForm();
            }
        }
        );


        _serverProperties.addMouseListener(new MouseAdapter() {

            public void mousePressed(MouseEvent e) {
                if (!_connected) {
                    return;
                }

                if (SwingUtilities.isRightMouseButton(e)) {
                    int selRow = _serverProperties.getRowForLocation
                            (e.getX(), e.getY());

                    _serverProperties.setSelectionRow(selRow);
                    Object loc =
                            _serverProperties.getLastSelectedPathComponent();
                    if (loc instanceof OpenJMSNode) {
                        OpenJMSNode node = (OpenJMSNode) loc;
                        node.displayCommands
                                (_serverProperties.getRowBounds(selRow));
                    } else if (loc instanceof OpenJMSServer) {
                        ((OpenJMSServer) loc).displayCommands
                                (_serverProperties.getRowBounds(selRow));
                    }
                }
            }
        }
        );

        _serverProperties.addTreeExpansionListener(new TreeExpansionListener() {

            public void treeCollapsed(TreeExpansionEvent e) {
                // todo Anything.....
            }

            public void treeExpanded(TreeExpansionEvent e) {
                TreePath path = e.getPath();
                Object loc = path.getLastPathComponent();
                if (loc instanceof OpenJMSNode) {
                    OpenJMSNode node = (OpenJMSNode) loc;
                    node.update();
                }
            }
        }
        );

        /**
         _serverProperties.addTreeSelectionListener(new TreeSelectionListener()
         {
         public void valueChanged(TreeSelectionEvent e)
         {
         TreePath path = e.getPath();
         Object loc = path.getLastPathComponent();
         if (loc instanceof OpenJMSNode)
         {
         OpenJMSNode node = (OpenJMSNode)loc;
         System.out.println(node);
         }
         }
         }
         );

         **/
        _exit.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent evt) {
                exitAdmin();
            }
        }
        );


        _refresh.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent evt) {
                refresh();
            }
        }
        );


        _online.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent evt) {
                onlineConnect();
            }
        }
        );

        _offline.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent evt) {
                offlineConnect();
            }
        }
        );

        _disconnect.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent evt) {
                disconnect();
            }
        }
        );

        _startup.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent evt) {
                startup();
            }
        }
        );

        _shutdown.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent evt) {
                try {
                    AbstractAdminConnection.instance().stopServer();
                    setConnected(false, null);
                } catch (NullPointerException err) {
                    JOptionPane.showMessageDialog
                            (_file, "Must connect with online mode \nto "
                                    + "shutdown server", "Shutdown Error",
                                    JOptionPane.ERROR_MESSAGE);
                }
            }
        }
        );
    }

    /**
     * The main entry point for this admin gui. The main form and any support
     * dialogs are created. An initial size is given, and the gui placed in the
     * middle of the screen
     *
     * @param args the command line arguments
     */
    public static void main(String args[]) {
        try {
            CommandLine cmdline = new CommandLine(args);

            boolean helpSet = cmdline.exists("help");
            boolean configSet = cmdline.exists("config");
            boolean stopServer = cmdline.exists("stopServer");
            String username = cmdline.value("u");
            String password = cmdline.value("p");

            if (helpSet) {
                usage();
            } else if (!configSet && !stopServer && args.length != 0) {
                // invalid argument specified
                usage();
            } else {
                String configFile = cmdline.value("config");
                if (configFile == null) {
                    String home = getOpenJMSHome();
                    configFile = home + "/config/openjms.xml";
                }
                Configuration config = new ConfigurationLoader().load(configFile);
                String path = config.getLoggerConfiguration().getFile();
                if (path != null) {
                    DOMConfigurator.configure(path);
                }
                AdminConfiguration adminConfig = null;

                adminConfig = config.getAdminConfiguration();
                _serverStart = adminConfig.getScript();
                _serverConfig = adminConfig.getConfig();
                if (_serverConfig == null) {
                    _serverConfig = configFile;
                }

                if (stopServer) {
                    // this is a special mode that will just attempt
                    // a connection to the server and stop it. No GUI

                    new OnlineConnection(username, password, config);
                    AbstractAdminConnection.instance().stopServer();
                } else {
                    AdminMgr admin = new AdminMgr(configFile);
                    QueryDialog.create(admin);
                    CreateQueueDialog.create(admin);
                    CreateTopicDialog.create(admin);
                    CreateLogonDialog.create(admin);
                    CreateUserDialog.create(admin);
                    ChangePasswordDialog.create(admin);
                    Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
                    // About center of screen
                    admin.setLocation(screen.width / 2 - 150, screen.height / 2 - 150);
                    admin.setSize(300, 300);
                    admin.invalidate();
                    admin.show();
                }
            }
        } catch (Exception err) {
            err.printStackTrace();
            System.err.println("Failed to initialize AdminMgr.\nExiting....");
        }
    }

    /**
     * Print out information on running this sevice
     */
    static protected void usage() {
        PrintStream out = System.out;

        out.println("\n\n");
        out.println("=====================================================");
        out.println("Usage information for " + AdminMgr.class.getName());
        out.println("=====================================================");
        out.println("\n" + AdminMgr.class.getName());
        out.println("    [-help | -config <xml config file>]\n");
        out.println("\t-help   displays this screen\n");
        out.println("\t-config file name of xml-based config file\n");
    }

    /**
     * A simple class to re-direct the output stream from JMS to the local
     * console
     */
    class StreamRedirect extends Thread {

        InputStream is_;

        StreamRedirect(InputStream is) {
            is_ = is;
        }

        public void run() {
            try {
                InputStreamReader isr = new InputStreamReader(is_);
                BufferedReader br = new BufferedReader(isr);
                String line = null;
                while ((line = br.readLine()) != null) {
                    System.out.println(line);
                }
            } catch (IOException ioe) {
                ioe.printStackTrace();
            }
        }
    }

    private String[] getStartCommand() throws Exception {
        ArrayList args = new ArrayList();

        if (_serverStart != null) {
            Perl5Compiler compiler = new Perl5Compiler();
            Pattern pattern = compiler.compile("'.*'|[^\\s]*");
            Perl5Matcher matcher = new Perl5Matcher();
            PatternMatcherInput input = new PatternMatcherInput(_serverStart);

            while (matcher.contains(input, pattern)) {
                String arg = matcher.getMatch().toString();
                if (arg.startsWith("'") && arg.endsWith("'")) {
                    arg = arg.substring(1, arg.length() - 1);
                }
                args.add(arg);
            }
        }

        args.add("-config");
        args.add(_serverConfig);

        return (String[]) args.toArray(new String[0]);
    }

    /**
     * Returns the value of the openjms.home environment variable
     */
    private static String getOpenJMSHome() {
        return System.getProperty("openjms.home",
                System.getProperty("user.dir"));
    }

    /**
     * Display and log an error
     *
     * @param title
     * @param message
     * @param exception
     */
    private void error(String title, String message, Exception exception) {
        _log.error(message, exception);
        JOptionPane.showMessageDialog(this, message, title,
                JOptionPane.ERROR_MESSAGE);
    }

} //-- AdminMgr

