/* * Copyright (C) 2004-2008 Jive Software. All rights reserved. * * 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.jivesoftware.openfire.launcher; import java.awt.AWTException; import java.awt.BorderLayout; import java.awt.CardLayout; import java.awt.Color; import java.awt.Cursor; import java.awt.Desktop; import java.awt.Dimension; import java.awt.Frame; import java.awt.Graphics; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.MenuItem; import java.awt.PopupMenu; import java.awt.SystemTray; import java.awt.TrayIcon; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; 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; import java.net.URI; import java.net.URL; 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; import javax.swing.SwingWorker; 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; /** * Graphical launcher for Openfire. * * @author Matt Tucker */ public class Launcher { private String appName; private File binDir; private Process openfired; private File configFile; 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. */ public Launcher() throws AWTException { // Initialize the SystemTray now (to avoid a bug!) SystemTray tray = null; try { tray = SystemTray.getSystemTray(); } catch (Throwable e) { // Log to System error instead of standard error log. System.err.println("Error loading system tray library, system tray support disabled."); } // 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 { appName = "Openfire"; } 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")); } configFile = new File(new File(binDir.getParent(), "conf"), "openfire.xml"); frame = new DroppableFrame() { @Override public void fileDropped(File file) { String fileName = file.getName(); if (fileName.endsWith(".jar") || fileName.endsWith(".war")) { installPlugin(file); } } }; frame.setTitle(appName); ImageIcon splash; JPanel mainPanel = new JPanel(); JLabel splashLabel = null; cardPanel.setLayout(cardLayout); // Set the icon. try { splash = new ImageIcon(getClass().getClassLoader().getResource("splash.gif")); splashLabel = new JLabel("", splash, JLabel.CENTER); onIcon = new ImageIcon(getClass().getClassLoader().getResource("openfire_on-16x16.gif")); offIcon = new ImageIcon(getClass().getClassLoader().getResource("openfire_off-16x16.gif")); 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 PopupMenu menu = new PopupMenu(appName + " Menu"); final MenuItem showMenuItem = new MenuItem("Hide"); showMenuItem.setActionCommand("Hide/Show"); menu.add(showMenuItem); final MenuItem startMenuItem = new MenuItem("Start"); startMenuItem.setActionCommand("Start"); menu.add(startMenuItem); final MenuItem stopMenuItem = new MenuItem("Stop"); stopMenuItem.setActionCommand("Stop"); menu.add(stopMenuItem); final MenuItem browserMenuItem = new MenuItem("Launch Admin"); browserMenuItem.setActionCommand("Launch Admin"); menu.add(browserMenuItem); menu.addSeparator(); final MenuItem quitMenuItem = new MenuItem("Quit"); quitMenuItem.setActionCommand("Quit"); menu.add(quitMenuItem); browserButton.setEnabled(false); stopButton.setEnabled(false); browserMenuItem.setEnabled(false); stopMenuItem.setEnabled(false); ActionListener actionListener = new ActionListener() { @Override 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()); trayIcon.setImage(onIcon.getImage()); // Start a thread to enable the admin button after 8 seconds. Thread thread = new Thread() { @Override public void run() { 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()); trayIcon.setImage(offIcon.getImage()); // 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(); } else if ("Quit".equals(e.getActionCommand())) { stopApplication(); System.exit(0); } else if ("Hide/Show".equals(e.getActionCommand()) || "PressAction".equals(e.getActionCommand())) { toggleVisibility(showMenuItem); } } }; // 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 trayIcon = new TrayIcon(offIcon.getImage(), appName, menu); trayIcon.setImageAutoSize(true); trayIcon.addActionListener(actionListener); 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) { } @Override public void mouseEntered(MouseEvent e) { } @Override public void mouseExited(MouseEvent e) { } }); if (tray != null) { tray.add(trayIcon); } frame.addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { stopApplication(); System.exit(0); } @Override public void windowIconified(WindowEvent e) { // Make the window disappear when minimized frame.setVisible(false); showMenuItem.setLabel("Show"); } }); 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() { @Override public void paintComponent(Graphics g) { 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); } @Override public void fileDropped(File file) { 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. */ public static void main(String[] args) throws AWTException { new Launcher(); } 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"); } } private synchronized void startApplication() { if (openfired == null) { try { File windowsExe = new File(binDir, "openfired.exe"); File unixExe = new File(binDir, "openfired"); if (windowsExe.exists()) { openfired = Runtime.getRuntime().exec(new String[]{windowsExe.toString()}); } else if (unixExe.exists()) { openfired = Runtime.getRuntime().exec(new String[]{unixExe.toString()}); } 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(); openfired = Runtime.getRuntime().exec(new String[]{ "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(); SwingWorker<String, Void> inputWorker = new SwingWorker<String, Void>() { @Override public String doInBackground() { if (openfired != null) { // Get the input stream and read from it try (InputStream in = openfired.getInputStream()) { 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"; } }; inputWorker.execute(); SwingWorker<String, Void> errorWorker = new SwingWorker<String, Void>() { @Override public String doInBackground() { if (openfired != null) { // Get the input stream and read from it try (InputStream in = openfired.getErrorStream()) { 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"; } }; errorWorker.execute(); 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() { if (openfired != null) { try { // attempt to perform a graceful shutdown by sending // an "exit" command to the process (via stdin) try (Writer out = new OutputStreamWriter( new BufferedOutputStream(openfired.getOutputStream()))) { out.write("exit\n"); } final Thread waiting = Thread.currentThread(); Thread waiter = new Thread() { @Override public void run() { try { // wait for the openfire server to stop openfired.waitFor(); waiting.interrupt(); } catch (InterruptedException ie) { /* ignore */ } } }; waiter.start(); try { // wait for a maximum of ten seconds Thread.sleep(10000); waiter.interrupt(); openfired.destroy(); } catch (InterruptedException ie) { /* ignore */ } cardLayout.show(cardPanel, "main"); } catch (Exception e) { e.printStackTrace(); } } openfired = null; } private synchronized void launchBrowser() { try { // 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(); } if ("-1".equals(port)) { Desktop.getDesktop().browse(URI.create("https://127.0.0.1:" + securePort + "/index.html")); } else { Desktop.getDesktop().browse(URI.create("http://127.0.0.1:" + port + "/index.html")); } } catch (Exception e) { // Make sure to print the exception e.printStackTrace(System.out); 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); final SwingWorker<File, Void> installerThread = new SwingWorker<File, Void>() { @Override public File doInBackground() { 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); copy(plugin.toURI().toURL(), tempPluginsFile); // If successfull, rename to real plugin name. tempPluginsFile.renameTo(realPluginsFile); } catch (Exception e) { e.printStackTrace(); } return realPluginsFile; } @Override public void done() { dialog.setVisible(false); } }; // Start installation installerThread.execute(); dialog.setLocationRelativeTo(frame); dialog.setVisible(true); } private static void copy(URL src, File dst) throws IOException { try (InputStream in = src.openStream()) { try (OutputStream out = new FileOutputStream(dst)) { dst.mkdirs(); copy(in, out); } } } /** * 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); } } }