/* * eXist Open Source Native XML Database * Copyright (C) 2012-2015 The eXist-db Project * http://exist-db.org * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ package org.exist.launcher; import org.apache.commons.configuration2.MapConfiguration; import org.apache.commons.lang3.SystemUtils; import org.exist.EXistException; import org.exist.jetty.JettyStart; import org.exist.repo.ExistRepository; import org.exist.security.PermissionDeniedException; import org.exist.start.Main; import org.exist.storage.BrokerPool; import org.exist.storage.DBBroker; import org.exist.util.ConfigurationHelper; import org.exist.util.FileUtils; import org.exist.util.SystemExitCodes; import org.exist.xquery.XPathException; import org.exist.xquery.XQuery; import org.exist.xquery.value.Sequence; import org.exist.xquery.value.SequenceIterator; import org.rzo.yajsw.wrapper.WrappedService; import javax.imageio.ImageIO; import javax.swing.*; import javax.swing.Timer; import java.awt.*; import java.awt.event.*; import java.awt.image.BufferedImage; import java.io.*; import java.net.URI; import java.net.URISyntaxException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.*; import java.util.List; import java.util.function.BiConsumer; import java.util.function.Consumer; /** * A launcher for the eXist-db server integrated with the desktop. * Shows a splash screen during startup and registers a tray icon * in the system bar. * * @author Wolfgang Meier */ public class Launcher extends Observable implements Observer { private MenuItem stopItem; private MenuItem startItem; private MenuItem dashboardItem; private MenuItem eXideItem; private MenuItem monexItem; private MenuItem installServiceItem; private MenuItem uninstallServiceItem; private MenuItem showServices; private MenuItem quitItem; public final static String PACKAGE_DASHBOARD = "http://exist-db.org/apps/dashboard"; public final static String PACKAGE_EXIDE = "http://exist-db.org/apps/eXide"; public final static String PACKAGE_MONEX = "http://exist-db.org/apps/monex"; public static void main(final String[] args) { final String os = System.getProperty("os.name", ""); // Switch to native look and feel except for Linux (ugly) if (!"Linux".equals(os)) { final String nativeLF = UIManager.getSystemLookAndFeelClassName(); try { UIManager.setLookAndFeel(nativeLF); } catch (final Exception e) { // can be safely ignored } } /* Turn off metal's use of bold fonts */ //UIManager.put("swing.boldMetal", Boolean.FALSE); //Schedule a job for the event-dispatching thread: SwingUtilities.invokeLater(() -> new Launcher(args)); } private SystemTray tray = null; private TrayIcon trayIcon = null; private Optional<WrappedService> runningAsService; private SplashScreen splash; private Optional<JettyStart> jetty = Optional.empty(); private final Path jettyConfig; private Properties wrapperProperties; private boolean inServiceInstall = false; private UtilityPanel utilityPanel; private ConfigurationDialog configDialog; private boolean canUseServices = false; public Launcher(final String[] args) { if (SystemTray.isSupported()) { tray = SystemTray.getSystemTray(); } captureConsole(); this.jettyConfig = getJettyConfig(); final Optional<Path> eXistHome = ConfigurationHelper.getExistHome(); final Path wrapperConfig; if (eXistHome.isPresent()) { wrapperConfig = eXistHome.get().resolve("tools/yajsw/conf/wrapper.conf"); } else { wrapperConfig = Paths.get("tools/yajsw/conf/wrapper.conf"); } wrapperProperties = new Properties(); wrapperProperties.setProperty("wrapper.working.dir", eXistHome.orElse(Paths.get(".")).toString()); wrapperProperties.setProperty("wrapper.config", wrapperConfig.toString()); System.setProperty("wrapper.config", wrapperConfig.toString()); installedAsService(); boolean initSystemTray = true; if (isSystemTraySupported()) { initSystemTray = initSystemTray(); } configDialog = new ConfigurationDialog(this::shutdown); splash = new SplashScreen(this); splash.addWindowListener(new WindowAdapter() { @Override public void windowOpened(WindowEvent windowEvent) { setServiceState(); if (runningAsService.isPresent()) { splash.setStatus("eXist-db is already installed as service! Attaching to it ..."); final Timer timer = new Timer(3000, (event) -> splash.setVisible(false)); timer.setRepeats(false); timer.start(); } else { if (ConfigurationUtility.isFirstStart()) { splash.setVisible(false); configDialog.open(true); configDialog.requestFocus(); } else { startJetty(); } } } }); final boolean systemTrayReady = tray != null && initSystemTray && tray.getTrayIcons().length > 0; SwingUtilities.invokeLater(() -> utilityPanel = new UtilityPanel(Launcher.this, systemTrayReady)); } protected void startJetty() { new Thread() { @Override public void run() { try { if (!jetty.isPresent()) { jetty = Optional.of(new JettyStart()); jetty.get().run(new String[]{jettyConfig.toAbsolutePath().toString()}, splash); } } catch (final Exception e) { showMessageAndExit("Error Occurred", "An error occurred during eXist-db startup. Please check console output and logs.", true); System.exit(SystemExitCodes.CATCH_ALL_GENERAL_ERROR_EXIT_CODE); } } }.start(); } public boolean isSystemTraySupported() { return tray != null; } private boolean initSystemTray() { final Dimension iconDim = tray.getTrayIconSize(); BufferedImage image = null; try { image = ImageIO.read(getClass().getResource("icon32.png")); } catch (final IOException e) { showMessageAndExit("Launcher failed", "Failed to read system tray icon.", false); } trayIcon = new TrayIcon(image.getScaledInstance(iconDim.width, iconDim.height, Image.SCALE_SMOOTH), "eXist-db Launcher"); final JDialog hiddenFrame = new JDialog(); hiddenFrame.setUndecorated(true); hiddenFrame.setIconImage(image); final PopupMenu popup = createMenu(); trayIcon.setPopupMenu(popup); trayIcon.addActionListener(actionEvent -> { trayIcon.displayMessage(null, "Right click for menu", TrayIcon.MessageType.INFO); setServiceState(); } ); // add listener for left click on system tray icon. doesn't work well on linux though. if (!SystemUtils.IS_OS_LINUX) { trayIcon.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent mouseEvent) { if (mouseEvent.getButton() == MouseEvent.BUTTON1) { setServiceState(); hiddenFrame.add(popup); popup.show(hiddenFrame, mouseEvent.getXOnScreen(), mouseEvent.getYOnScreen()); } } }); } try { hiddenFrame.setResizable(false); hiddenFrame.pack(); hiddenFrame.setVisible(true); tray.add(trayIcon); } catch (final AWTException e) { return false; } return true; } private PopupMenu createMenu() { final PopupMenu popup = new PopupMenu(); startItem = new MenuItem("Start server"); popup.add(startItem); startItem.addActionListener(actionEvent -> { if (jetty.isPresent()) { jetty.ifPresent(server -> { if (server.isStarted()) { showTrayMessage("Server already started", TrayIcon.MessageType.WARNING); } else { server.run(new String[]{jettyConfig.toAbsolutePath().toString()}, null); if (server.isStarted()) { showTrayMessage("eXist-db server running on port " + server.getPrimaryPort(), TrayIcon .MessageType.INFO); } } setServiceState(); }); } else if (runningAsService.isPresent()){ showTrayMessage("Starting the eXistdb service. Please wait...", TrayIcon.MessageType.INFO); if (runningAsService.get().start()) { showTrayMessage("eXistdb service started", TrayIcon.MessageType.INFO); } else { showTrayMessage("Starting eXistdb service failed", TrayIcon.MessageType.ERROR); } setServiceState(); } }); stopItem = new MenuItem("Stop server"); popup.add(stopItem); stopItem.addActionListener(actionEvent -> { if (jetty.isPresent()) { jetty.get().shutdown(); setServiceState(); showTrayMessage("eXist-db stopped", TrayIcon.MessageType.INFO); } else if (runningAsService.isPresent()) { if (runningAsService.get().stop()) { showTrayMessage("eXistdb service stopped", TrayIcon.MessageType.INFO); } else { showTrayMessage("Stopping eXistdb service failed", TrayIcon.MessageType.ERROR); } setServiceState(); } }); popup.addSeparator(); final MenuItem configItem = new MenuItem("System Configuration"); popup.add(configItem); configItem.addActionListener(e -> EventQueue.invokeLater(() -> { configDialog.open(false); configDialog.toFront(); configDialog.repaint(); configDialog.requestFocus(); })); if (SystemUtils.IS_OS_WINDOWS) { canUseServices = true; } else { isRoot((root) -> canUseServices = root); } final String requiresRootMsg; if (canUseServices) { requiresRootMsg = ""; } else { requiresRootMsg = " (requires root)"; } installServiceItem = new MenuItem("Install as service" + requiresRootMsg); popup.add(installServiceItem); installServiceItem.setEnabled(canUseServices); installServiceItem.addActionListener(e -> SwingUtilities.invokeLater(this::installAsService)); uninstallServiceItem = new MenuItem("Uninstall service" + requiresRootMsg); popup.add(uninstallServiceItem); uninstallServiceItem.setEnabled(canUseServices); uninstallServiceItem.addActionListener(e -> SwingUtilities.invokeLater(this::uninstallService)); if (SystemUtils.IS_OS_WINDOWS) { showServices = new MenuItem("Show services console"); popup.add(showServices); showServices.addActionListener(e -> SwingUtilities.invokeLater(this::showServicesConsole)); } popup.addSeparator(); final MenuItem toolbar = new MenuItem("Show tool window"); popup.add(toolbar); toolbar.addActionListener(actionEvent -> EventQueue.invokeLater(() -> { utilityPanel.toFront(); utilityPanel.setVisible(true); })); MenuItem item; if (Desktop.isDesktopSupported()) { popup.addSeparator(); final Desktop desktop = Desktop.getDesktop(); if (desktop.isSupported(Desktop.Action.BROWSE)) { dashboardItem = new MenuItem("Open Dashboard"); popup.add(dashboardItem); dashboardItem.addActionListener(actionEvent -> dashboard(desktop)); eXideItem = new MenuItem("Open eXide"); popup.add(eXideItem); eXideItem.addActionListener(actionEvent -> eXide(desktop)); item = new MenuItem("Open Java Admin Client"); popup.add(item); item.addActionListener(actionEvent -> client()); monexItem = new MenuItem("Open Monitoring and Profiling"); popup.add(monexItem); monexItem.addActionListener(actionEvent -> monex(desktop)); } if (desktop.isSupported(Desktop.Action.OPEN)) { popup.addSeparator(); item = new MenuItem("Open exist.log"); popup.add(item); item.addActionListener(new LogActionListener()); } popup.addSeparator(); quitItem = new MenuItem("Quit (and stop server)"); popup.add(quitItem); quitItem.addActionListener(actionEvent -> shutdown(false)); setServiceState(); } return popup; } protected void installAsService() { jetty.ifPresent(server -> { if (server.isStarted()) { showTrayMessage("Stopping eXistdb...", TrayIcon.MessageType.INFO); server.shutdown(); } }); jetty = Optional.empty(); showTrayMessage("Installing service and starting eXistdb ...", TrayIcon.MessageType.INFO); final WrappedService service = new WrappedService(); service.setLocalConfiguration(new MapConfiguration(wrapperProperties)); service.init(); final boolean installed = service.install(); if (installed) { if (SystemUtils.IS_OS_WINDOWS) { service.start(); } runningAsService = Optional.of(service); setServiceState(); showTrayMessage("Service installed and started", TrayIcon.MessageType.INFO); } else { JOptionPane.showMessageDialog(null, "Failed to install service. ", "Install Service Failed", JOptionPane .ERROR_MESSAGE); runningAsService = Optional.empty(); } inServiceInstall = false; } protected void uninstallService() { if (runningAsService.isPresent()) { final boolean uninstalled = runningAsService.get().uninstall(); if (uninstalled) { showTrayMessage("Service removed and db stopped", TrayIcon.MessageType.INFO); runningAsService = Optional.empty(); } else { JOptionPane.showMessageDialog(null, "Removing the service failed.", "Removing Service Failed", JOptionPane.ERROR_MESSAGE); } setServiceState(); } } private void setServiceState() { if (runningAsService.isPresent()) { final WrappedService service = runningAsService.get(); final boolean serverRunning = service.isRunning() || service.isStarting(); if (canUseServices) { final boolean serviceInstalled = service.isInstalled(); if (installServiceItem != null && uninstallServiceItem != null) { installServiceItem.setEnabled(!serviceInstalled); uninstallServiceItem.setEnabled(serviceInstalled); } } quitItem.setLabel("Quit"); stopItem.setEnabled(serverRunning); startItem.setEnabled(!serverRunning); if (dashboardItem != null) { dashboardItem.setEnabled(serverRunning); monexItem.setEnabled(serverRunning); eXideItem.setEnabled(serverRunning); } } else { final boolean serverRunning = jetty.isPresent() && jetty.get().isStarted(); if (canUseServices) { if (installServiceItem != null && uninstallServiceItem != null) { installServiceItem.setEnabled(true); uninstallServiceItem.setEnabled(false); } } quitItem.setLabel("Quit (and stop server)"); stopItem.setEnabled(serverRunning); startItem.setEnabled(!serverRunning); if (dashboardItem != null) { dashboardItem.setEnabled(serverRunning); monexItem.setEnabled(serverRunning); eXideItem.setEnabled(serverRunning); } } } private void isRoot(Consumer<Boolean> consumer) { final List<String> args = new ArrayList<>(2); args.add("id"); args.add("-u"); run(args, (code, output) -> { consumer.accept("0".equals(output.trim())); }); } private void showServicesConsole() { final List<String> args = new ArrayList<>(2); args.add("cmd"); args.add("/c"); args.add("services.msc"); run(args, null); } protected static void run(List<String> args, BiConsumer<Integer, String> consumer) { final ProcessBuilder pb = new ProcessBuilder(args); final Optional<Path> home = ConfigurationHelper.getExistHome(); if (home.isPresent()) { pb.directory(home.get().toFile()); } pb.redirectErrorStream(true); try { final Process process = pb.start(); if (consumer != null) { final StringBuilder output = new StringBuilder(); try (final BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), "UTF-8"))) { String line; while ((line = reader.readLine()) != null) { output.append('\n').append(line); } } final int exitValue = process.waitFor(); consumer.accept(exitValue, output.toString()); } } catch (IOException | InterruptedException e) { JOptionPane.showMessageDialog(null, e.getMessage(), "Error Running Process", JOptionPane.ERROR_MESSAGE); } } protected void shutdown(final boolean restart) { utilityPanel.setStatus("Shutting down ..."); SwingUtilities.invokeLater(() -> { if (restart && runningAsService.isPresent()) { final WrappedService service = runningAsService.get(); if (service.isRunning() || service.isStarting()) { if (service.stop()) { while (service.isRunning()) { try { wait(500); } catch (InterruptedException e) { break; } } if (!service.isRunning() && service.start()) { trayIcon.displayMessage(null, "Database restarted", TrayIcon.MessageType.INFO); } else { trayIcon.displayMessage(null, "Failed to restart. Please start service manually.", TrayIcon.MessageType.INFO); } } } } else { if (tray != null) { tray.remove(trayIcon); } if (jetty.isPresent()) { jetty.get().shutdown(); if (restart) { final LauncherWrapper wrapper = new LauncherWrapper(Launcher.class.getName()); wrapper.launch(); } } System.exit(SystemExitCodes.OK_EXIT_CODE); } }); } protected void dashboard(Desktop desktop) { utilityPanel.setStatus("Opening dashboard in browser ..."); final int port = jetty.isPresent() ? jetty.get().getPrimaryPort() : 8080; try { final URI url = new URI("http://localhost:" + port + "/exist/apps/dashboard/"); desktop.browse(url); } catch (final URISyntaxException e) { if (isSystemTraySupported()) {trayIcon.displayMessage(null, "Failed to open URL", TrayIcon.MessageType.ERROR);} utilityPanel.setStatus("Unable to launch browser"); } catch (final IOException e) { if (isSystemTraySupported()) {trayIcon.displayMessage(null, "Failed to open URL", TrayIcon.MessageType.ERROR);} utilityPanel.setStatus("Unable to launch browser"); } } protected void eXide(Desktop desktop) { utilityPanel.setStatus("Opening dashboard in browser ..."); final int port = jetty.isPresent() ? jetty.get().getPrimaryPort() : 8080; try { final URI url = new URI("http://localhost:" + port + "/exist/apps/eXide/"); desktop.browse(url); } catch (final URISyntaxException e) { if (isSystemTraySupported()) {trayIcon.displayMessage(null, "Failed to open URL", TrayIcon.MessageType.ERROR);} utilityPanel.setStatus("Unable to launch browser"); } catch (final IOException e) { if (isSystemTraySupported()) {trayIcon.displayMessage(null, "Failed to open URL", TrayIcon.MessageType.ERROR);} utilityPanel.setStatus("Unable to launch browser"); } } protected void monex(Desktop desktop) { utilityPanel.setStatus("Opening Monitoring and Profiling in browser ..."); final int port = jetty.isPresent() ? jetty.get().getPrimaryPort() : 8080; try { final URI url = new URI("http://localhost:" + port + "/exist/apps/monex/"); desktop.browse(url); } catch (final URISyntaxException e) { if (isSystemTraySupported()) {trayIcon.displayMessage(null, "Failed to open URL", TrayIcon.MessageType.ERROR);} utilityPanel.setStatus("Unable to launch browser"); } catch (final IOException e) { if (isSystemTraySupported()) {trayIcon.displayMessage(null, "Failed to open URL", TrayIcon.MessageType.ERROR);} utilityPanel.setStatus("Unable to launch browser"); } } protected void client() { final LauncherWrapper wrapper = new LauncherWrapper("client"); wrapper.launch(); } protected void signalStarted() { if (isSystemTraySupported()) { final int port = jetty.isPresent() ? jetty.get().getPrimaryPort() : 8080; trayIcon.setToolTip("eXist-db server running on port " + port); startItem.setEnabled(false); stopItem.setEnabled(true); checkInstalledApps(); registerObserver(); } if (!inServiceInstall && !runningAsService.isPresent() && SystemUtils.IS_OS_WINDOWS) { inServiceInstall = true; SwingUtilities.invokeLater(() -> { if (JOptionPane.showConfirmDialog(splash, "It is recommended to run eXist as a service on " + "Windows.\nNot doing so may lead to data loss if you shut down the computer before " + "eXist.\n\nWould you like to install the service?", "Install as Service?", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE) == JOptionPane.YES_OPTION) { installAsService(); } }); } } protected void signalShutdown() { if (isSystemTraySupported()) { trayIcon.setToolTip("eXist-db server stopped"); if (!inServiceInstall) { startItem.setEnabled(true); stopItem.setEnabled(false); } } } private void checkInstalledApps() { try { final BrokerPool pool = BrokerPool.getInstance(); try (final DBBroker broker = pool.get(Optional.of(pool.getSecurityManager().getSystemSubject()))) { final XQuery xquery = pool.getXQueryService(); final Sequence pkgs = xquery.execute(broker, "repo:list()", null); for (final SequenceIterator i = pkgs.iterate(); i.hasNext(); ) { final ExistRepository.Notification notification = new ExistRepository.Notification(ExistRepository.Action.INSTALL, i.nextItem().getStringValue()); Optional<ExistRepository> expathRepo = pool.getExpathRepo(); if (expathRepo.isPresent()) { update(expathRepo.get(), notification); utilityPanel.update(expathRepo.get(), notification); } expathRepo.orElseThrow(() -> new XPathException("expath repository is not available.")); } } } catch (final EXistException e) { System.err.println("Failed to check installed packages: " + e.getMessage()); e.printStackTrace(); } catch (final XPathException e) { System.err.println("Failed to check installed packages: " + e.getMessage()); e.printStackTrace(); } catch (final PermissionDeniedException e) { System.err.println("Failed to check installed packages: " + e.getMessage()); e.printStackTrace(); } } private void registerObserver() { try { final BrokerPool pool = BrokerPool.getInstance(); Optional<ExistRepository> repo = pool.getExpathRepo(); if (repo.isPresent()) { repo.get().addObserver(this); repo.get().addObserver(utilityPanel); } else { System.err.println("expath repository is not available."); } } catch (final EXistException e) { System.err.println("Failed to register as observer for package manager events"); e.printStackTrace(); } } @Override public void update(Observable observable, Object o) { final ExistRepository.Notification notification = (ExistRepository.Notification) o; if (notification.getPackageURI().equals(PACKAGE_DASHBOARD) && dashboardItem != null) { dashboardItem.setEnabled(notification.getAction() == ExistRepository.Action.INSTALL); } else if (notification.getPackageURI().equals(PACKAGE_EXIDE) && eXideItem != null) { eXideItem.setEnabled(notification.getAction() == ExistRepository.Action.INSTALL); } else if (notification.getPackageURI().equals(PACKAGE_MONEX) && monexItem != null) { monexItem.setEnabled(notification.getAction() == ExistRepository.Action.INSTALL); } } private Path getJettyConfig() { final String jettyProperty = Optional.ofNullable(System.getProperty("jetty.home")) .orElseGet(() -> { final Optional<Path> home = ConfigurationHelper.getExistHome(); final Path jettyHome = FileUtils.resolve(home, "tools").resolve("jetty"); final String jettyPath = jettyHome.toAbsolutePath().toString(); System.setProperty("jetty.home", jettyPath); return jettyPath; }); return Paths.get(jettyProperty) .normalize() .resolve("etc") .resolve(Main.STANDARD_ENABLED_JETTY_CONFIGS); } protected void showMessageAndExit(String title, String message, boolean logs) { final JPanel panel = new JPanel(); panel.setLayout(new BorderLayout()); final JLabel label = new JLabel(message); label.setHorizontalAlignment(SwingConstants.CENTER); panel.add(label, BorderLayout.CENTER); if (logs) { final JButton displayLogs = new JButton("View Log"); displayLogs.addActionListener(new LogActionListener()); label.setHorizontalAlignment(SwingConstants.CENTER); panel.add(displayLogs, BorderLayout.SOUTH); } utilityPanel.showMessages(); utilityPanel.toFront(); utilityPanel.setVisible(true); JOptionPane.showMessageDialog(splash, panel, title, JOptionPane.WARNING_MESSAGE); //System.exit(SystemExitCodes.CATCH_ALL_GENERAL_ERROR_EXIT_CODE); } protected void showTrayMessage(String message, TrayIcon.MessageType type) { if (isSystemTraySupported()) { trayIcon.displayMessage(message, message, type); } } private void installedAsService() { final WrappedService service = new WrappedService(); service.setLocalConfiguration(new MapConfiguration(wrapperProperties)); service.init(); if (service.isInstalled()) { runningAsService = Optional.of(service); } else { runningAsService = Optional.empty(); } } /** * Ensure that stdout and stderr messages are also printed * to the logs. */ private void captureConsole() { System.setOut(createLoggingProxy(System.out)); System.setErr(createLoggingProxy(System.err)); } public PrintStream createLoggingProxy(final PrintStream realStream) { final OutputStream out = new OutputStream() { @Override public void write(int i) throws IOException { realStream.write(i); String s = String.valueOf((char) i); Launcher.this.setChanged(); Launcher.this.notifyObservers(s); } @Override public void write(byte[] bytes) throws IOException { realStream.write(bytes); String s = new String(bytes); Launcher.this.setChanged(); Launcher.this.notifyObservers(s); } @Override public void write(byte[] bytes, int offset, int len) throws IOException { realStream.write(bytes, offset, len); String s = new String(bytes, offset, len); Launcher.this.setChanged(); Launcher.this.notifyObservers(s); } }; return new PrintStream(out); } private class LogActionListener implements ActionListener { @Override public void actionPerformed(ActionEvent actionEvent) { if (!Desktop.isDesktopSupported()) {return;} final Desktop desktop = Desktop.getDesktop(); final Optional<Path> home = ConfigurationHelper.getExistHome(); final Path logFile = FileUtils.resolve(home, "webapp/WEB-INF/logs/exist.log"); if (!Files.isReadable(logFile)) { trayIcon.displayMessage(null, "Log file not found", TrayIcon.MessageType.ERROR); } else { try { desktop.open(logFile.toFile()); } catch (final IOException e) { trayIcon.displayMessage(null, "Failed to open log file", TrayIcon.MessageType.ERROR); } } } } }