Launcher.java 24.8 KB
Newer Older
1 2 3 4 5
/**
 * $RCSfile$
 * $Revision: 3054 $
 * $Date: 2005-11-10 21:08:33 -0300 (Thu, 10 Nov 2005) $
 *
6
 * Copyright (C) 2004-2008 Jive Software. All rights reserved.
7
 *
8 9 10 11 12 13 14 15 16 17 18
 * 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.
19 20
 */

21
package org.jivesoftware.openfire.launcher;
22

23
import java.awt.AWTException;
24 25 26 27
import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Color;
import java.awt.Cursor;
28
import java.awt.Desktop;
29 30 31 32 33 34
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
35 36 37 38
import java.awt.MenuItem;
import java.awt.PopupMenu;
import java.awt.SystemTray;
import java.awt.TrayIcon;
Gaston Dombiak's avatar
Gaston Dombiak committed
39 40
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
41 42
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
Gaston Dombiak's avatar
Gaston Dombiak committed
43 44
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
45 46 47 48 49 50 51 52 53
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
54
import java.net.URI;
Matt Tucker's avatar
Matt Tucker committed
55
import java.net.URL;
56

57 58 59 60 61 62 63 64 65 66 67
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.JTextPane;
68
import javax.swing.SwingWorker;
69 70 71 72 73 74 75 76
import javax.swing.UIManager;
import javax.swing.text.BadLocationException;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
77

78
/**
79
 * Graphical launcher for Openfire.
80 81 82 83 84 85 86
 *
 * @author Matt Tucker
 */
public class Launcher {

    private String appName;
    private File binDir;
87
    private Process openfired;
88
    private File configFile;
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
    private JPanel toolbar = new JPanel();

    private ImageIcon offIcon;
    private ImageIcon onIcon;
    private TrayIcon trayIcon;
    private JFrame frame;
    private JPanel cardPanel = new JPanel();
    private CardLayout cardLayout = new CardLayout();

    private JTextPane pane;
    private boolean freshStart = true;

    /**
     * Creates a new Launcher object.
     */
104
    public Launcher() throws AWTException {
105 106 107
        // Initialize the SystemTray now (to avoid a bug!)
        SystemTray tray = null;
        try {
108
            tray = SystemTray.getSystemTray();
109 110 111 112 113
        }
        catch (Throwable e) {
            // Log to System error instead of standard error log.
            System.err.println("Error loading system tray library, system tray support disabled.");
        }
Matt Tucker's avatar
Matt Tucker committed
114

115 116 117 118 119 120 121 122 123 124 125 126
        // Use the native look and feel.
        try {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        }
        catch (Exception e) {
            e.printStackTrace();
        }

        if (System.getProperty("app.name") != null) {
            appName = System.getProperty("app.name");
        }
        else {
127
            appName = "Openfire";
128 129 130 131 132 133 134 135
        }

        binDir = new File("").getAbsoluteFile();
        // See if the appdir property is set. If so, use it to find the executable.
        if (System.getProperty("appdir") != null) {
            binDir = new File(System.getProperty("appdir"));
        }

136
        configFile = new File(new File(binDir.getParent(), "conf"), "openfire.xml");
137 138

        frame = new DroppableFrame() {
139 140
            @Override
			public void fileDropped(File file) {
141 142 143 144 145 146 147 148 149
                String fileName = file.getName();
                if (fileName.endsWith(".jar") || fileName.endsWith(".war")) {
                    installPlugin(file);
                }
            }
        };

        frame.setTitle(appName);

Gaston Dombiak's avatar
Gaston Dombiak committed
150
        ImageIcon splash;
151 152 153 154 155 156 157 158
        JPanel mainPanel = new JPanel();
        JLabel splashLabel = null;

        cardPanel.setLayout(cardLayout);

        // Set the icon.
        try {
            splash = new ImageIcon(getClass().getClassLoader().getResource("splash.gif"));
Matt Tucker's avatar
Matt Tucker committed
159
            splashLabel = new JLabel("", splash, JLabel.CENTER);
160

161 162
            onIcon = new ImageIcon(getClass().getClassLoader().getResource("openfire_on-16x16.gif"));
            offIcon = new ImageIcon(getClass().getClassLoader().getResource("openfire_off-16x16.gif"));
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198
            frame.setIconImage(offIcon.getImage());
        }
        catch (Exception e) {
            e.printStackTrace();
        }

        mainPanel.setLayout(new BorderLayout());
        cardPanel.setBackground(Color.white);

        // Add buttons
        final JButton startButton = new JButton("Start");
        startButton.setActionCommand("Start");

        final JButton stopButton = new JButton("Stop");
        stopButton.setActionCommand("Stop");

        final JButton browserButton = new JButton("Launch Admin");
        browserButton.setActionCommand("Launch Admin");

        final JButton quitButton = new JButton("Quit");
        quitButton.setActionCommand("Quit");

        toolbar.setLayout(new GridBagLayout());
        toolbar.add(startButton, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0,
                GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0));
        toolbar.add(stopButton, new GridBagConstraints(1, 0, 1, 1, 0.0, 0.0,
                GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0));
        toolbar.add(browserButton, new GridBagConstraints(2, 0, 1, 1, 0.0, 0.0,
                GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0));
        toolbar.add(quitButton, new GridBagConstraints(3, 0, 1, 1, 0.0, 0.0,
                GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0));

        mainPanel.add(cardPanel, BorderLayout.CENTER);
        mainPanel.add(toolbar, BorderLayout.SOUTH);

        // create the main menu of the system tray icon
199
        PopupMenu menu = new PopupMenu(appName + " Menu");
200

201
        final MenuItem showMenuItem = new MenuItem("Hide");
202 203 204
        showMenuItem.setActionCommand("Hide/Show");
        menu.add(showMenuItem);

205
        final MenuItem startMenuItem = new MenuItem("Start");
206 207 208
        startMenuItem.setActionCommand("Start");
        menu.add(startMenuItem);

209
        final MenuItem stopMenuItem = new MenuItem("Stop");
210 211 212
        stopMenuItem.setActionCommand("Stop");
        menu.add(stopMenuItem);

213
        final MenuItem browserMenuItem = new MenuItem("Launch Admin");
214 215 216 217 218
        browserMenuItem.setActionCommand("Launch Admin");
        menu.add(browserMenuItem);

        menu.addSeparator();

219
        final MenuItem quitMenuItem = new MenuItem("Quit");
220 221 222 223 224 225 226 227 228
        quitMenuItem.setActionCommand("Quit");
        menu.add(quitMenuItem);

        browserButton.setEnabled(false);
        stopButton.setEnabled(false);
        browserMenuItem.setEnabled(false);
        stopMenuItem.setEnabled(false);

        ActionListener actionListener = new ActionListener() {
229
            @Override
230 231 232 233 234 235 236 237 238 239 240 241 242 243
            public void actionPerformed(ActionEvent e) {
                if ("Start".equals(e.getActionCommand())) {
                    frame.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
                    // Adjust button and menu items.
                    startButton.setEnabled(false);
                    stopButton.setEnabled(true);
                    startMenuItem.setEnabled(false);
                    stopMenuItem.setEnabled(true);

                    // Startup Application
                    startApplication();

                    // Change to the "on" icon.
                    frame.setIconImage(onIcon.getImage());
244
                    trayIcon.setImage(onIcon.getImage());
245 246 247

                    // Start a thread to enable the admin button after 8 seconds.
                    Thread thread = new Thread() {
248 249
                        @Override
						public void run() {
250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270
                            try {
                                sleep(8000);
                            }
                            catch (InterruptedException ie) {
                                // Ignore.
                            }
                            // Enable the Launch Admin button/menu item only if the
                            // server has started.
                            if (stopButton.isEnabled()) {
                                browserButton.setEnabled(true);
                                browserMenuItem.setEnabled(true);
                                frame.setCursor(Cursor.getDefaultCursor());
                            }
                        }
                    };
                    thread.start();
                }
                else if ("Stop".equals(e.getActionCommand())) {
                    stopApplication();
                    // Change to the "off" button.
                    frame.setIconImage(offIcon.getImage());
271
                    trayIcon.setImage(offIcon.getImage());
272 273 274 275 276 277 278 279 280 281 282
                    // Adjust buttons and menu items.
                    frame.setCursor(Cursor.getDefaultCursor());
                    browserButton.setEnabled(false);
                    startButton.setEnabled(true);
                    stopButton.setEnabled(false);
                    browserMenuItem.setEnabled(false);
                    startMenuItem.setEnabled(true);
                    stopMenuItem.setEnabled(false);
                }
                else if ("Launch Admin".equals(e.getActionCommand())) {
                    launchBrowser();
283
                } else if ("Quit".equals(e.getActionCommand())) {
284 285 286 287
                    stopApplication();
                    System.exit(0);
                }
                else if ("Hide/Show".equals(e.getActionCommand()) || "PressAction".equals(e.getActionCommand())) {
288
                    toggleVisibility(showMenuItem);
289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306
                }
            }
        };

        // Register a listener for the radio buttons.
        startButton.addActionListener(actionListener);
        stopButton.addActionListener(actionListener);
        browserButton.addActionListener(actionListener);
        quitButton.addActionListener(actionListener);

        // Register a listener for the menu items.
        quitMenuItem.addActionListener(actionListener);
        browserMenuItem.addActionListener(actionListener);
        stopMenuItem.addActionListener(actionListener);
        startMenuItem.addActionListener(actionListener);
        showMenuItem.addActionListener(actionListener);

        // Set the system tray icon with the menu
307 308
        trayIcon = new TrayIcon(offIcon.getImage(), appName, menu);
        trayIcon.setImageAutoSize(true);
309
        trayIcon.addActionListener(actionListener);
310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327
        trayIcon.addMouseListener(new MouseListener() {
            @Override
            public void mouseClicked(MouseEvent e) {
                // Left click
                if (e.getButton() == 1) {
                    toggleVisibility(showMenuItem);
                }
            }

            @Override
            public void mousePressed(MouseEvent e) {

            }

            @Override
            public void mouseReleased(MouseEvent e) {

            }
328

329 330 331 332 333 334 335 336 337 338
            @Override
            public void mouseEntered(MouseEvent e) {

            }

            @Override
            public void mouseExited(MouseEvent e) {

            }
        });
339
        if (tray != null) {
340
            tray.add(trayIcon);
341 342 343
        }

        frame.addWindowListener(new WindowAdapter() {
344 345
            @Override
			public void windowClosing(WindowEvent e) {
346 347 348 349
                stopApplication();
                System.exit(0);
            }

350 351
            @Override
			public void windowIconified(WindowEvent e) {
352 353
                // Make the window disappear when minimized
                frame.setVisible(false);
354
                showMenuItem.setLabel("Show");
355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371
            }
        });

        cardPanel.add("main", splashLabel);
        frame.getContentPane().add(mainPanel, BorderLayout.CENTER);
        frame.pack();

        frame.setSize(400, 300);
        frame.setResizable(true);

        GraphicUtils.centerWindowOnScreen(frame);

        frame.setVisible(true);

        // Setup command area
        final ImageIcon icon = new ImageIcon(getClass().getClassLoader().getResource("splash2.gif"));
        pane = new DroppableTextPane() {
372 373
            @Override
			public void paintComponent(Graphics g) {
374 375 376 377 378 379 380 381 382 383 384 385 386 387
                final Dimension size = pane.getSize();

                int x = (size.width - icon.getIconWidth()) / 2;
                int y = (size.height - icon.getIconHeight()) / 2;
                //  Approach 1: Dispaly image at at full size
                g.setColor(Color.white);
                g.fillRect(0, 0, size.width, size.height);
                g.drawImage(icon.getImage(), x, y, null);


                setOpaque(false);
                super.paintComponent(g);
            }

388 389
            @Override
			public void fileDropped(File file) {
390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412
                String fileName = file.getName();
                if (fileName.endsWith(".jar") || fileName.endsWith(".war")) {
                    installPlugin(file);
                }
            }
        };

        pane.setEditable(false);

        final JPanel bevelPanel = new JPanel();
        bevelPanel.setBackground(Color.white);
        bevelPanel.setLayout(new BorderLayout());
        bevelPanel.setBorder(BorderFactory.createLoweredBevelBorder());
        bevelPanel.add(new JScrollPane(pane), BorderLayout.CENTER);
        cardPanel.add("running", bevelPanel);

        // Start the app.
        startButton.doClick();
    }

    /**
     * Creates a new GUI launcher instance.
     */
413
    public static void main(String[] args) throws AWTException {
414 415 416
        new Launcher();
    }

417 418 419 420 421 422 423 424 425 426 427 428 429
    private void toggleVisibility(MenuItem showMenuItem) {
        // Hide/Unhide the window if the user clicked in the system tray icon or
        // selected the menu option
        if (frame.isVisible()) {
            frame.setVisible(false);
            showMenuItem.setLabel("Show");
        } else {
            frame.setVisible(true);
            frame.setState(Frame.NORMAL);
            showMenuItem.setLabel("Hide");
        }
    }

430
    private synchronized void startApplication() {
431
        if (openfired == null) {
432
            try {
433 434
                File windowsExe = new File(binDir, "openfired.exe");
                File unixExe = new File(binDir, "openfired");
435
                if (windowsExe.exists()) {
436
                    openfired = Runtime.getRuntime().exec(new String[]{windowsExe.toString()});
437 438
                }
                else if (unixExe.exists()) {
439
                    openfired = Runtime.getRuntime().exec(new String[]{unixExe.toString()});
440 441 442 443 444 445 446 447 448
                }
                else {
                    throw new FileNotFoundException();
                }
            }
            catch (Exception e) {
                // Try one more time using the jar and hope java is on the path
                try {
                    File libDir = new File(binDir.getParentFile(), "lib").getAbsoluteFile();
449
                    openfired = Runtime.getRuntime().exec(new String[]{
450 451 452 453 454 455 456 457 458 459 460 461
                        "java", "-jar", new File(libDir, "startup.jar").toString()
                    });
                }
                catch (Exception ex) {
                    ex.printStackTrace();
                    JOptionPane.showMessageDialog(null,
                            "Launcher could not start,\n" + appName,
                            "File not found", JOptionPane.ERROR_MESSAGE);
                }
            }

            final SimpleAttributeSet styles = new SimpleAttributeSet();
462
            SwingWorker<String, Void> inputWorker = new SwingWorker<String, Void>() {
463
                @Override
464
				public String doInBackground() {
465
                    if (openfired != null) {
466 467
                        // Get the input stream and read from it
                        try (InputStream in = openfired.getInputStream()) {
468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486
                            int c;
                            while ((c = in.read()) != -1) {
                                try {
                                    StyleConstants.setFontFamily(styles, "courier new");
                                    pane.getDocument().insertString(pane.getDocument().getLength(),
                                            "" + (char)c, styles);
                                }
                                catch (BadLocationException e) {
                                    // Ignore.
                                }
                            }
                        }
                        catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                    return "ok";
                }
            };
487
            inputWorker.execute();
488 489


490
            SwingWorker<String, Void> errorWorker = new SwingWorker<String, Void>() {
491
                @Override
492
				public String doInBackground() {
493
                    if (openfired != null) {
494 495
                        // Get the input stream and read from it
                        try (InputStream in = openfired.getErrorStream()) {
496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513
                            int c;
                            while ((c = in.read()) != -1) {
                                try {
                                    StyleConstants.setForeground(styles, Color.red);
                                    pane.getDocument().insertString(pane.getDocument().getLength(), "" + (char)c, styles);
                                }
                                catch (BadLocationException e) {
                                    // Ignore.
                                }
                            }
                        }
                        catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                    return "ok";
                }
            };
514
            errorWorker.execute();
515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535

            if (freshStart) {
                try {
                    Thread.sleep(1000);
                    cardLayout.show(cardPanel, "running");
                }
                catch (Exception ex) {
                    // Ignore.
                }
                freshStart = false;
            }
            else {
                 // Clear Text
                pane.setText("");
                cardLayout.show(cardPanel, "running");
            }

        }
    }

    private synchronized void stopApplication() {
536
        if (openfired != null) {
537
            try {
538 539
            	// attempt to perform a graceful shutdown by sending
            	// an "exit" command to the process (via stdin)
540 541 542 543 544
                try (Writer out = new OutputStreamWriter(
                        new BufferedOutputStream(openfired.getOutputStream()))) {
                    out.write("exit\n");
                }
                final Thread waiting = Thread.currentThread();
545
            	Thread waiter = new Thread() {
546
            		@Override
547 548 549 550 551 552 553 554 555 556
            		public void run() {
                        try {
                        	// wait for the openfire server to stop
                        	openfired.waitFor();
                        	waiting.interrupt();
                        }
                        catch (InterruptedException ie) { /* ignore */ }
            		}
            	};
            	waiter.start();
557
            	try {
558 559 560 561 562 563
            		// wait for a maximum of ten seconds
            		Thread.sleep(10000);
            		waiter.interrupt();
            		openfired.destroy();
            	}
            	catch (InterruptedException ie) { /* ignore */ }
564 565 566 567 568 569 570
                cardLayout.show(cardPanel, "main");
            }
            catch (Exception e) {
                e.printStackTrace();
            }
        }

571
        openfired = null;
572 573 574 575
    }

    private synchronized void launchBrowser() {
        try {
Matt Tucker's avatar
Matt Tucker committed
576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591
            // Note, we use standard DOM to read in the XML. This is necessary so that
            // Launcher has fewer dependencies.
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            Document document = factory.newDocumentBuilder().parse(configFile);
            Element rootElement = document.getDocumentElement();
            Element adminElement = (Element)rootElement.getElementsByTagName("adminConsole").item(0);
            String port = "-1";
            String securePort = "-1";
            Element portElement = (Element)adminElement.getElementsByTagName("port").item(0);
            if (portElement != null) {
                port = portElement.getTextContent();
            }
            Element securePortElement = (Element)adminElement.getElementsByTagName("securePort").item(0);
            if (securePortElement != null) {
                securePort = securePortElement.getTextContent();
            }
592
            if ("-1".equals(port)) {
593
                Desktop.getDesktop().browse(URI.create("https://127.0.0.1:" + securePort + "/index.html"));
594
            } else {
595
                Desktop.getDesktop().browse(URI.create("http://127.0.0.1:" + port + "/index.html"));
596 597 598
            }
        }
        catch (Exception e) {
599 600
            // Make sure to print the exception
            e.printStackTrace(System.out);
601 602 603 604 605 606 607 608 609 610 611 612 613 614 615
            JOptionPane.showMessageDialog(new JFrame(), configFile + " " + e.getMessage());
        }
    }

    private void installPlugin(final File plugin) {
        final JDialog dialog = new JDialog(frame, "Installing Plugin", true);
        dialog.getContentPane().setLayout(new BorderLayout());
        JProgressBar bar = new JProgressBar();
        bar.setIndeterminate(true);
        bar.setString("Installing Plugin.  Please wait...");
        bar.setStringPainted(true);
        dialog.getContentPane().add(bar, BorderLayout.CENTER);
        dialog.pack();
        dialog.setSize(225, 55);

616
        final SwingWorker<File, Void> installerThread = new SwingWorker<File, Void>() {
617
            @Override
618
			public File doInBackground() {
619 620 621 622 623 624 625 626 627 628 629
                File pluginsDir = new File(binDir.getParentFile(), "plugins");
                String tempName = plugin.getName() + ".part";
                File tempPluginsFile = new File(pluginsDir, tempName);

                File realPluginsFile = new File(pluginsDir, plugin.getName());

                // Copy Plugin into Dir.
                try {
                    // Just for fun. Show no matter what for two seconds.
                    Thread.sleep(2000);

630
                    copy(plugin.toURI().toURL(), tempPluginsFile);
631 632 633 634 635 636 637 638 639 640

                    // If successfull, rename to real plugin name.
                    tempPluginsFile.renameTo(realPluginsFile);
                }
                catch (Exception e) {
                    e.printStackTrace();
                }
                return realPluginsFile;
            }

641
            @Override
642
			public void done() {
643 644 645 646 647
                dialog.setVisible(false);
            }
        };

        // Start installation
648
        installerThread.execute();
649 650 651 652

        dialog.setLocationRelativeTo(frame);
        dialog.setVisible(true);
    }
Matt Tucker's avatar
Matt Tucker committed
653 654

    private static void copy(URL src, File dst) throws IOException {
655 656 657 658
        try (InputStream in = src.openStream()) {
            try (OutputStream out = new FileOutputStream(dst)) {
                dst.mkdirs();
                copy(in, out);
Matt Tucker's avatar
Matt Tucker committed
659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677
            }
        }
    }

    /**
     * Common code for copy routines.  By convention, the streams are
     * closed in the same method in which they were opened.  Thus,
     * this method does not close the streams when the copying is done.
     */
    private static void copy(InputStream in, OutputStream out) throws IOException {
        byte[] buffer = new byte[4096];
        while (true) {
            int bytesRead = in.read(buffer);
            if (bytesRead < 0) {
                break;
            }
            out.write(buffer, 0, bytesRead);
        }
    }
678
}