• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

evolvedbinary / elemental / 982

29 Apr 2025 08:34PM UTC coverage: 56.409% (+0.007%) from 56.402%
982

push

circleci

adamretter
[feature] Improve README.md badges

28451 of 55847 branches covered (50.94%)

Branch coverage included in aggregate %.

77468 of 131924 relevant lines covered (58.72%)

0.59 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/exist-core/src/main/java/org/exist/launcher/Launcher.java
1
/*
2
 * Elemental
3
 * Copyright (C) 2024, Evolved Binary Ltd
4
 *
5
 * admin@evolvedbinary.com
6
 * https://www.evolvedbinary.com | https://www.elemental.xyz
7
 *
8
 * Use of this software is governed by the Business Source License 1.1
9
 * included in the LICENSE file and at www.mariadb.com/bsl11.
10
 *
11
 * Change Date: 2028-04-27
12
 *
13
 * On the date above, in accordance with the Business Source License, use
14
 * of this software will be governed by the Apache License, Version 2.0.
15
 *
16
 * Additional Use Grant: Production use of the Licensed Work for a permitted
17
 * purpose. A Permitted Purpose is any purpose other than a Competing Use.
18
 * A Competing Use means making the Software available to others in a commercial
19
 * product or service that: substitutes for the Software; substitutes for any
20
 * other product or service we offer using the Software that exists as of the
21
 * date we make the Software available; or offers the same or substantially
22
 * similar functionality as the Software.
23
 *
24
 * NOTE: Parts of this file contain code from 'The eXist-db Authors'.
25
 *       The original license header is included below.
26
 *
27
 * =====================================================================
28
 *
29
 * eXist-db Open Source Native XML Database
30
 * Copyright (C) 2001 The eXist-db Authors
31
 *
32
 * info@exist-db.org
33
 * http://www.exist-db.org
34
 *
35
 * This library is free software; you can redistribute it and/or
36
 * modify it under the terms of the GNU Lesser General Public
37
 * License as published by the Free Software Foundation; either
38
 * version 2.1 of the License, or (at your option) any later version.
39
 *
40
 * This library is distributed in the hope that it will be useful,
41
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
42
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
43
 * Lesser General Public License for more details.
44
 *
45
 * You should have received a copy of the GNU Lesser General Public
46
 * License along with this library; if not, write to the Free Software
47
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
48
 */
49
package org.exist.launcher;
50

51
import org.exist.EXistException;
52
import org.exist.jetty.JettyStart;
53
import org.exist.repo.ExistRepository;
54
import org.exist.security.PermissionDeniedException;
55
import org.exist.start.CompatibleJavaVersionCheck;
56
import org.exist.start.Main;
57
import org.exist.start.StartException;
58
import org.exist.storage.BrokerPool;
59
import org.exist.storage.DBBroker;
60
import org.exist.util.ConfigurationHelper;
61
import org.exist.util.FileUtils;
62
import org.exist.util.SystemExitCodes;
63
import org.exist.xquery.XPathException;
64
import org.exist.xquery.XQuery;
65
import org.exist.xquery.value.Sequence;
66
import org.exist.xquery.value.SequenceIterator;
67

68
import javax.annotation.Nullable;
69
import javax.imageio.ImageIO;
70
import javax.swing.*;
71
import javax.swing.Timer;
72
import java.awt.*;
73
import java.awt.event.*;
74
import java.awt.image.BufferedImage;
75
import java.io.*;
76
import java.net.URI;
77
import java.net.URISyntaxException;
78
import java.nio.file.Files;
79
import java.nio.file.Path;
80
import java.nio.file.Paths;
81
import java.util.*;
82
import java.util.concurrent.locks.ReentrantLock;
83

84
import static java.nio.charset.StandardCharsets.UTF_8;
85
import static org.exist.launcher.ConfigurationUtility.LAUNCHER_PROPERTY_NEVER_INSTALL_SERVICE;
86
import static org.exist.util.OSUtil.IS_LINUX;
87
import static org.exist.util.OSUtil.IS_WINDOWS;
88
import static org.exist.util.ThreadUtils.newGlobalThread;
89

90
/**
91
 * A launcher for the Elemental server integrated with the desktop.
92
 * Shows a splash screen during startup and registers a tray icon
93
 * in the system bar.
94
 *
95
 * @author <a href="mailto:adam@evolvedbinary.com">Adam Retter</a>
96
 * @author Wolfgang Meier
97
 */
98
public class Launcher extends Observable implements Observer {
99

100
    private MenuItem stopItem;
101
    private MenuItem startItem;
102
    private MenuItem dashboardItem;
103
    private MenuItem eXideItem;
104
    private MenuItem monexItem;
105
    private MenuItem installServiceItem;
106
    private MenuItem uninstallServiceItem;
107
    private MenuItem quitItem;
108

109
    static final String PACKAGE_DASHBOARD = "http://exist-db.org/apps/dashboard";
110
    static final String PACKAGE_EXIDE = "http://exist-db.org/apps/eXide";
111
    static final String PACKAGE_MONEX = "http://exist-db.org/apps/monex";
112

113
    public static void main(final String[] args) {
114
        try {
115
            CompatibleJavaVersionCheck.checkForCompatibleJavaVersion();
×
116
        } catch (final StartException e) {
×
117
            if (e.getMessage() != null && !e.getMessage().isEmpty()) {
×
118
                System.err.println(e.getMessage());
×
119
            }
120
            System.exit(e.getErrorCode());
×
121
        }
122

123
        final String os = System.getProperty("os.name", "");
×
124
        // Switch to native look and feel except for Linux (ugly)
125
        if (!"Linux".equals(os)) {
×
126
            final String nativeLF = UIManager.getSystemLookAndFeelClassName();
×
127
            try {
128
                UIManager.setLookAndFeel(nativeLF);
×
129
            } catch (final Exception e) {
×
130
                // can be safely ignored
131
            }
132
        }
133
        /* Turn off metal's use of bold fonts */
134
        //UIManager.put("swing.boldMetal", Boolean.FALSE);
135

136
        //Schedule a job for the event-dispatching thread:
137
        SwingUtilities.invokeLater(() -> new Launcher(args));
×
138
    }
×
139

140
    private final ReentrantLock serviceLock = new ReentrantLock();
×
141

142
    /**
143
     * ServiceManager will be null if there is no service
144
     * manager for the current platform
145
     */
146
    @Nullable private final ServiceManager serviceManager;
147
    @Nullable private final SystemTray tray;
148
    private TrayIcon trayIcon = null;
×
149
    private SplashScreen splash;
150
    private final Path jettyConfig;
151
    private Optional<JettyStart> jetty = Optional.empty();
×
152
    private UtilityPanel utilityPanel;
153
    private ConfigurationDialog configDialog;
154
    private boolean isInstallingService = false;
×
155

156
    Launcher(final String[] args) {
×
157
        if (SystemTray.isSupported()) {
×
158
            this.tray = SystemTray.getSystemTray();
×
159
        } else {
×
160
            this.tray = null;
×
161
        }
162

163
        captureConsole();
×
164

165
        // try and figure out exist home dir
166
        final Optional<Path> existHomeDir = getFromSysPropOrEnv(Main.PROP_EXIST_HOME, Main.ENV_EXIST_HOME).map(Paths::get);
×
167

168
        this.jettyConfig = getJettyConfig(existHomeDir);
×
169

170
        this.serviceManager = ServiceManagerFactory.getServiceManager();
×
171

172
        final boolean initSystemTray = tray != null && initSystemTray();
×
173

174
        if (serviceManager != null) {
×
175
            updateGuiServiceState();
×
176
        }
177

178
        this.configDialog = new ConfigurationDialog(this::shutdown);
×
179

180
        this.splash = new SplashScreen(this);
×
181
        splash.addWindowListener(new WindowAdapter() {
×
182
            @Override
183
            public void windowOpened(WindowEvent windowEvent) {
184
                serviceLock.lock();
×
185
                try {
186
                    if (serviceManager != null && serviceManager.isInstalled()) {
×
187
                        splash.setStatus("Elemental is already installed as service! Attaching to it ...");
×
188
                        final Timer timer = new Timer(3000, (event) -> splash.setVisible(false));
×
189
                        timer.setRepeats(false);
×
190
                        timer.start();
×
191
                    } else {
×
192
                        if (ConfigurationUtility.isFirstStart()) {
×
193
                            splash.setVisible(false);
×
194
                            configDialog.open(true);
×
195
                            configDialog.requestFocus();
×
196
                        } else {
×
197
                            startJetty();
×
198
                        }
199
                    }
200
                } finally {
×
201
                    serviceLock.unlock();
×
202
                }
203
            }
×
204
        });
205

206
        final boolean systemTrayReady = initSystemTray && tray.getTrayIcons().length > 0;
×
207

208
        SwingUtilities.invokeLater(() -> utilityPanel = new UtilityPanel(Launcher.this, tray != null, systemTrayReady));
×
209
    }
×
210

211
    private void startJetty() {
212
        final Runnable runnable = () -> {
×
213
            serviceLock.lock();
×
214
            try {
215
                if (!jetty.isPresent()) {
×
216
                    jetty = Optional.of(new JettyStart());
×
217

218
                    final String[] args;
219
                    final Optional<Path> explicitExistConfigPath = ConfigurationHelper.getFromSystemProperty();
×
220
                    if (explicitExistConfigPath.isPresent()) {
×
221
                        args = new String[]{
×
222
                                jettyConfig.toAbsolutePath().toString(),
×
223
                                explicitExistConfigPath.get().toAbsolutePath().toString()};
×
224
                    } else {
×
225
                        args = new String[]{jettyConfig.toAbsolutePath().toString()};
×
226
                    }
227

228
                    jetty.get().run(args, splash);
×
229
                }
230
            } catch (final Exception e) {
×
231
                showMessageAndExit("Error Occurred", "An error occurred during Elemental startup. Please check console output and logs.", true);
×
232
                System.exit(SystemExitCodes.CATCH_ALL_GENERAL_ERROR_EXIT_CODE);
×
233
            } finally {
234
                serviceLock.unlock();
×
235
            }
236
        };
×
237
        newGlobalThread("launcher.startJetty", runnable).start();
×
238
    }
×
239

240
    private boolean initSystemTray() {
241
        if (tray == null) {
×
242
            return false;
×
243
        }
244

245
        final Dimension iconDim = tray.getTrayIconSize();
×
246
        BufferedImage image = null;
×
247
        try {
248
            image = ImageIO.read(getClass().getResource("icon32.png"));
×
249
            trayIcon = new TrayIcon(image.getScaledInstance(iconDim.width, iconDim.height, Image.SCALE_SMOOTH), "Elemental Launcher");
×
250
        } catch (final IOException e) {
×
251
            showMessageAndExit("Launcher failed", "Failed to read system tray icon.", false);
×
252
        }
253

254
        final JDialog hiddenFrame = new JDialog();
×
255
        hiddenFrame.setUndecorated(true);
×
256
        hiddenFrame.setIconImage(image);
×
257

258
        final PopupMenu popup = createMenu();
×
259
        trayIcon.setPopupMenu(popup);
×
260
        trayIcon.addActionListener(actionEvent -> showTrayInfoMessage("Right click for menu"));
×
261

262
        // add listener for left click on system tray icon. doesn't work well on linux though.
263
        if (!IS_LINUX) {
×
264
            trayIcon.addMouseListener(new MouseAdapter() {
×
265
                @Override
266
                public void mouseClicked(MouseEvent mouseEvent) {
267
                    if (mouseEvent.getButton() == MouseEvent.BUTTON1) {
×
268
                        hiddenFrame.add(popup);
×
269
                        popup.show(hiddenFrame, mouseEvent.getXOnScreen(), mouseEvent.getYOnScreen());
×
270
                    }
271
                }
×
272
            });
273
        }
274

275
        try {
276
            hiddenFrame.setResizable(false);
×
277
            hiddenFrame.pack();
×
278
            hiddenFrame.setVisible(true);
×
279
            tray.add(trayIcon);
×
280
        } catch (final AWTException e) {
×
281
            return false;
×
282
        }
283

284
        return true;
×
285
    }
286

287
    private PopupMenu createMenu() {
288
        final PopupMenu popup = new PopupMenu();
×
289
        serviceLock.lock();
×
290
        try {
291
            startItem = new MenuItem("Start server");
×
292
            popup.add(startItem);
×
293
            startItem.addActionListener(actionEvent -> {
×
294
                serviceLock.lock();
×
295
                try {
296
                    if (serviceManager != null && serviceManager.isInstalled()) {
×
297
                        showTrayInfoMessage("Starting the Elemental service. Please wait...");
×
298
                        try {
299
                            serviceManager.start();
×
300
                            updateGuiServiceState();
×
301
                            showTrayInfoMessage("Elemental service started");
×
302
                        } catch (final ServiceManagerException e) {
×
303
                            showTrayErrorMessage("Starting Elemental; service failed" + e.getMessage());
×
304
                            JOptionPane.showMessageDialog(null, "Failed to start service: " + e.getMessage(), "Starting Service Failed", JOptionPane.ERROR_MESSAGE);
×
305
                        }
306
                    } else if (jetty.isPresent()) {
×
307
                        jetty.ifPresent(server -> {
×
308
                            if (server.isStarted()) {
×
309
                                showTrayWarningMessage("Server already started");
×
310
                            } else {
×
311
                                server.run(new String[]{jettyConfig.toAbsolutePath().toString()}, null);
×
312
                                if (server.isStarted()) {
×
313
                                    showTrayInfoMessage("Elemental server running on port " + server.getPrimaryPort());
×
314
                                }
315
                            }
316
                            updateGuiServiceState();
×
317
                        });
×
318
                    } else {
×
319
                        startJetty();
×
320
                    }
321
                } finally {
×
322
                    serviceLock.unlock();
×
323
                }
324
            });
×
325

326
            stopItem = new MenuItem("Stop server");
×
327
            popup.add(stopItem);
×
328
            stopItem.addActionListener(actionEvent -> {
×
329
                serviceLock.lock();
×
330
                try {
331
                    if (jetty.isPresent()) {
×
332
                        jetty.get().shutdown();
×
333
                        updateGuiServiceState();
×
334
                        showTrayInfoMessage("Elemental stopped");
×
335
                    } else if (serviceManager != null && serviceManager.isRunning()) {
×
336
                        try {
337
                            serviceManager.stop();
×
338
                            updateGuiServiceState();
×
339
                            showTrayInfoMessage("Elemental service stopped");
×
340
                        } catch (final ServiceManagerException e) {
×
341
                            showTrayErrorMessage("Stopping Elemental service failed: " + e.getMessage());
×
342
                            JOptionPane.showMessageDialog(null, "Failed to stop service: " + e.getMessage(), "Stopping Service Failed", JOptionPane.ERROR_MESSAGE);
×
343
                        }
344
                    }
345
                } finally {
×
346
                    serviceLock.unlock();
×
347
                }
348
            });
×
349

350
            popup.addSeparator();
×
351
            final MenuItem configItem = new MenuItem("System Configuration");
×
352
            popup.add(configItem);
×
353
            configItem.addActionListener(e -> EventQueue.invokeLater(() -> {
×
354
                configDialog.open(false);
×
355
                configDialog.toFront();
×
356
                configDialog.repaint();
×
357
                configDialog.requestFocus();
×
358
            }));
×
359

360
            final String requiresRootMsg;
361
            if (serviceManager != null) {
×
362
                requiresRootMsg = "";
×
363
            } else {
×
364
                requiresRootMsg = " (requires root)";
×
365
            }
366

367
            installServiceItem = new MenuItem("Install as service" + requiresRootMsg);
×
368

369
            popup.add(installServiceItem);
×
370
            installServiceItem.setEnabled(serviceManager != null);
×
371
            installServiceItem.addActionListener(e -> SwingUtilities.invokeLater(this::installService));
×
372

373
            uninstallServiceItem = new MenuItem("Uninstall service" + requiresRootMsg);
×
374
            popup.add(uninstallServiceItem);
×
375
            uninstallServiceItem.setEnabled(serviceManager != null);
×
376
            uninstallServiceItem.addActionListener(e -> SwingUtilities.invokeLater(this::uninstallService));
×
377

378
            if (IS_WINDOWS) {
×
379
                final MenuItem showServices = new MenuItem("Show services console");
×
380
                popup.add(showServices);
×
381
                showServices.addActionListener(e -> SwingUtilities.invokeLater(this::showNativeServiceManagementConsole));
×
382
            }
383
            popup.addSeparator();
×
384

385
            final MenuItem toolbar = new MenuItem("Show tool window");
×
386
            popup.add(toolbar);
×
387
            toolbar.addActionListener(actionEvent -> EventQueue.invokeLater(() -> {
×
388
                utilityPanel.toFront();
×
389
                utilityPanel.setVisible(true);
×
390
            }));
×
391

392
            MenuItem item;
393

394
            if (Desktop.isDesktopSupported()) {
×
395
                popup.addSeparator();
×
396
                final Desktop desktop = Desktop.getDesktop();
×
397
                if (desktop.isSupported(Desktop.Action.BROWSE)) {
×
398
                    dashboardItem = new MenuItem("Open Dashboard");
×
399
                    popup.add(dashboardItem);
×
400
                    dashboardItem.addActionListener(actionEvent -> dashboard(desktop));
×
401
                    eXideItem = new MenuItem("Open eXide");
×
402
                    popup.add(eXideItem);
×
403
                    eXideItem.addActionListener(actionEvent -> eXide(desktop));
×
404
                    item = new MenuItem("Open Java Admin Client");
×
405
                    popup.add(item);
×
406
                    item.addActionListener(actionEvent -> client());
×
407
                    monexItem = new MenuItem("Open Monitoring and Profiling");
×
408
                    popup.add(monexItem);
×
409
                    monexItem.addActionListener(actionEvent -> monex(desktop));
×
410
                }
411
                if (desktop.isSupported(Desktop.Action.OPEN)) {
×
412
                    popup.addSeparator();
×
413
                    item = new MenuItem("Open elemental.log");
×
414
                    popup.add(item);
×
415
                    item.addActionListener(new LogActionListener());
×
416
                }
417

418
                popup.addSeparator();
×
419
                quitItem = new MenuItem("Quit");
×
420
                popup.add(quitItem);
×
421
                quitItem.addActionListener(actionEvent -> {
×
422
                    if (serviceManager != null && serviceManager.isInstalled()) {
×
423
                        if (tray != null) {
×
424
                            tray.remove(trayIcon);
×
425
                        }
426
                        System.exit(SystemExitCodes.OK_EXIT_CODE);
×
427
                    } else {
×
428
                        shutdown(false);
×
429
                    }
430
                });
×
431
            }
432
        } finally {
×
433
            serviceLock.unlock();
×
434
        }
435
        return popup;
×
436
    }
437

438
    private void installService() {
439
        serviceLock.lock();
×
440
        try {
441
            jetty.ifPresent(server -> {
×
442
                if (server.isStarted()) {
×
443
                    showTrayInfoMessage("Stopping Elemental...");
×
444
                    server.shutdown();
×
445
                }
446
            });
×
447
            jetty = Optional.empty();
×
448

449
            if (serviceManager == null) {
×
450
                showTrayWarningMessage("It is not possible to use Service installation on this platform");
×
451
            } else {
×
452
                showTrayInfoMessage("Installing service and starting Elemental...");
×
453

454
                try {
455
                    serviceManager.install();
×
456
                    serviceManager.start();
×
457
                    updateGuiServiceState();
×
458
                    showTrayInfoMessage("Service installed and started");
×
459
                } catch (final ServiceManagerException e) {
×
460
                    showTrayErrorMessage("Failed to install service.", "Failed to install service: " + e.getMessage());
×
461
                    JOptionPane.showMessageDialog(null, "Failed to install service: " + e.getMessage(), "Install Service Failed", JOptionPane.ERROR_MESSAGE);
×
462
                }
463
            }
464

465
            isInstallingService = false;
×
466
        } finally {
×
467
            serviceLock.unlock();
×
468
        }
469
    }
×
470

471
    private void uninstallService() {
472
        serviceLock.lock();
×
473
        try {
474
            if (serviceManager == null) {
×
475
                showTrayWarningMessage("It is not possible to use Service uninstallation on this platform");
×
476
            } else {
×
477
                showTrayInfoMessage("Uninstalling service...");
×
478

479
                try {
480
                    serviceManager.stop();
×
481
                    serviceManager.uninstall();
×
482
                    updateGuiServiceState();
×
483
                    showTrayInfoMessage("Service stopped and uninstalled");
×
484
                } catch (final ServiceManagerException e) {
×
485
                    showTrayErrorMessage("Failed to uninstall service.", "Failed to uninstall service: " + e.getMessage());
×
486
                    JOptionPane.showMessageDialog(null, "Failed to uninstall service: " + e.getMessage(), "Uninstalling Service Failed", JOptionPane.ERROR_MESSAGE);
×
487
                }
488
            }
489
        } finally {
×
490
            serviceLock.unlock();
×
491
        }
492
    }
×
493

494
    private void showNativeServiceManagementConsole() {
495
        if (serviceManager == null) {
×
496
            showTrayWarningMessage("It is not possible to use Service Management on this platform");
×
497
        } else {
×
498
            try {
499
                serviceManager.showNativeServiceManagementConsole();
×
500
            } catch (final UnsupportedOperationException | ServiceManagerException e) {
×
501
                showTrayErrorMessage("Failed to open Service Management Console", "Failed to open Service Management Console: " + e.getMessage());
×
502
            }
503
        }
504
    }
×
505

506
    private void updateGuiServiceState() {
507
        serviceLock.lock();
×
508
        try {
509
            final boolean serverRunning;
510
            if (serviceManager != null) {
×
511
                if (serviceManager.isInstalled()) {
×
512
                    installServiceItem.setEnabled(false);
×
513
                    uninstallServiceItem.setEnabled(true);
×
514
                } else {
×
515
                    installServiceItem.setEnabled(true);
×
516
                    uninstallServiceItem.setEnabled(false);
×
517
                }
518
                serverRunning = serviceManager.isRunning();
×
519
            } else {
×
520
                serverRunning = jetty.isPresent() && jetty.get().isStarted();
×
521
            }
522

523
            quitItem.setLabel(serverRunning ? "Quit (and stop server)" : "Quit");
×
524

525
            stopItem.setEnabled(serverRunning);
×
526
            startItem.setEnabled(!serverRunning);
×
527
            if (dashboardItem != null) {
×
528
                dashboardItem.setEnabled(serverRunning);
×
529
                monexItem.setEnabled(serverRunning);
×
530
                eXideItem.setEnabled(serverRunning);
×
531
            }
532

533
        } finally {
×
534
            serviceLock.unlock();
×
535
        }
536
    }
×
537

538
    void shutdown(final boolean restart) {
539
        utilityPanel.setStatus("Shutting down ...");
×
540
        SwingUtilities.invokeLater(() -> {
×
541
            serviceLock.lock();
×
542
            try {
543
                if (serviceManager != null && serviceManager.isRunning()) {
×
544

545
                    try {
546
                        serviceManager.stop();
×
547
                        showTrayInfoMessage("Database stopped");
×
548

549
                        if (restart) {
×
550
                            try {
551
                                serviceManager.start();
×
552
                                showTrayInfoMessage("Database started");
×
553
                            } catch (final ServiceManagerException e) {
×
554
                                showTrayErrorMessage("Failed to start. Please start service manually: " + e.getMessage());
×
555
                                JOptionPane.showMessageDialog(null, "Failed to start service. ", "Starting Service Failed", JOptionPane.ERROR_MESSAGE);
×
556
                            }
557
                        }
558

559
                        updateGuiServiceState();
×
560

561
                    } catch (final ServiceManagerException e) {
×
562
                        showTrayErrorMessage("Failed to stop. Please stop service manually: " + e.getMessage());
×
563
                        JOptionPane.showMessageDialog(null, "Failed to stop service: " + e.getMessage(), "Stopping Service Failed", JOptionPane.ERROR_MESSAGE);
×
564
                    }
565
                } else {
×
566
                    if (tray != null) {
×
567
                        tray.remove(trayIcon);
×
568
                    }
569
                    if (jetty.isPresent()) {
×
570
                        jetty.get().shutdown();
×
571
                        if (restart) {
×
572
                            final LauncherWrapper wrapper = new LauncherWrapper(Launcher.class.getName());
×
573
                            wrapper.launch();
×
574
                        }
575
                    }
576
                    System.exit(SystemExitCodes.OK_EXIT_CODE);
×
577
                }
578
            } finally {
×
579
                serviceLock.unlock();
×
580
            }
581
        });
×
582
    }
×
583

584
    void dashboard(final Desktop desktop) {
585
        utilityPanel.setStatus("Opening dashboard in browser ...");
×
586
        serviceLock.lock();
×
587
        try {
588
            final URI uri = getAppUri("dashboard");
×
589
            if (uri != null) {
×
590
                openAppInBrowser(desktop, uri);
×
591
            }
592
        } finally {
×
593
            serviceLock.unlock();
×
594
        }
595
    }
×
596

597
    void eXide(final Desktop desktop) {
598
        utilityPanel.setStatus("Opening eXide in browser ...");
×
599
        serviceLock.lock();
×
600
        try {
601
            final URI uri = getAppUri("eXide");
×
602
            if (uri != null) {
×
603
                openAppInBrowser(desktop, uri);
×
604
            }
605
        } finally {
×
606
            serviceLock.unlock();
×
607
        }
608
    }
×
609

610
    private void monex(final Desktop desktop) {
611
        utilityPanel.setStatus("Opening Monitoring and Profiling in browser ...");
×
612
        serviceLock.lock();
×
613
        try {
614
            final URI uri = getAppUri("monex");
×
615
            if (uri != null) {
×
616
                openAppInBrowser(desktop, uri);
×
617
            }
618
        } finally {
×
619
            serviceLock.unlock();
×
620
        }
621
    }
×
622

623
    private void openAppInBrowser(final Desktop desktop, final URI uri) {
624
        try {
625
            desktop.browse(uri);
×
626
        } catch (final IOException e) {
×
627
            showTrayWarningMessage("Failed to open URL: " + uri.toString(), e.getMessage());
×
628
            utilityPanel.setStatus("Unable to launch browser");
×
629
        }
630
    }
×
631

632
    private @Nullable URI getAppUri(final String appTarget) {
633
        final int port = jetty.map(JettyStart::getPrimaryPort).orElse(8080);
×
634
        final String url = "http://localhost:" + port + "/exist/apps/" + appTarget + "/";
×
635
        try {
636
            return new URI(url);
×
637
        } catch (final URISyntaxException e) {
×
638
            showTrayWarningMessage("Failed to open URL: " + url, e.getMessage());
×
639
            utilityPanel.setStatus("Unable to launch browser");
×
640
            return null;
×
641
        }
642
    }
643

644
    void client() {
645
        final LauncherWrapper wrapper = new LauncherWrapper("client");
×
646
        wrapper.launch();
×
647
    }
×
648

649
    void signalStarted() {
650
        if (tray != null) {
×
651
            startItem.setEnabled(false);
×
652
            stopItem.setEnabled(true);
×
653
            checkInstalledApps();
×
654
            registerObserver();
×
655
        }
656

657
        final Properties properties = ConfigurationUtility.loadProperties();
×
658
        final boolean neverInstallService = Boolean.parseBoolean(properties.getProperty(LAUNCHER_PROPERTY_NEVER_INSTALL_SERVICE, "false"));
×
659

660
        if (!neverInstallService) {
×
661
            if (IS_WINDOWS && !isInstallingService && serviceManager != null && !serviceManager.isInstalled()) {
×
662
                isInstallingService = true;
×
663
                SwingUtilities.invokeLater(() -> {
×
664
                    final int installServiceResult = JOptionPane.showOptionDialog(
×
665
                            splash,
×
666
                            "It is recommended to run Elemental as a service on " +
×
667
                                    "Windows.\nNot doing so may lead to data loss if you shutdown the computer before " +
668
                                    "Elemental.\n\nWould you like to install the service?",
669
                            "Install as Service?",
×
670
                            JOptionPane.YES_NO_CANCEL_OPTION,
×
671
                            JOptionPane.QUESTION_MESSAGE,
×
672
                            null,
×
673
                            new String[]{"Yes", "No", "Never"},
×
674
                            "Yes"
×
675
                    );
676

677
                    if (installServiceResult == JOptionPane.YES_OPTION) {
×
678
                        // install the service
679
                        SwingUtilities.invokeLater(this::installService);
×
680

681
                    } else if (installServiceResult == JOptionPane.CANCEL_OPTION) {
×
682
                        // never - record that we should not be prompted again
683
                        final Properties srvProps = new Properties();
×
684
                        srvProps.setProperty(LAUNCHER_PROPERTY_NEVER_INSTALL_SERVICE, "true");
×
685
                        try {
686
                            ConfigurationUtility.saveProperties(srvProps);
×
687
                        } catch (final IOException e) {
×
688
                            JOptionPane.showMessageDialog(null, "Failed to update service settings in " + ConfigurationUtility.LAUNCHER_PROPERTIES_FILE_NAME + ": " + e.getMessage(), "Failed to save Launcher settings", JOptionPane.ERROR_MESSAGE);
×
689
                        }
690
                    }
691
                });
×
692
            }
693
        }
694
    }
×
695

696
    void signalShutdown() {
697
        if (tray != null) {
×
698
            trayIcon.setToolTip("Elemental server stopped");
×
699
            if (!isInstallingService) {
×
700
                startItem.setEnabled(true);
×
701
                stopItem.setEnabled(false);
×
702
            }
703
        }
704
    }
×
705

706
    private void checkInstalledApps() {
707
        try {
708
            final BrokerPool pool = BrokerPool.getInstance();
×
709
            try (final DBBroker broker = pool.get(Optional.of(pool.getSecurityManager().getSystemSubject()))) {
×
710

711
                final XQuery xquery = pool.getXQueryService();
×
712
                final Sequence pkgs = xquery.execute(broker, "repo:list()", null);
×
713
                for (final SequenceIterator i = pkgs.iterate(); i.hasNext(); ) {
×
714
                    final ExistRepository.Notification notification = new ExistRepository.Notification(ExistRepository.Action.INSTALL, i.nextItem().getStringValue());
×
715
                    final Optional<ExistRepository> expathRepo = pool.getExpathRepo();
×
716
                    if (expathRepo.isPresent()) {
×
717
                        update(expathRepo.get(), notification);
×
718
                        utilityPanel.update(expathRepo.get(), notification);
×
719
                    }
720
                    expathRepo.orElseThrow(() -> new EXistException("EXPath repository is not available."));
×
721
                }
722
            }
723
        } catch (final EXistException | XPathException | PermissionDeniedException e) {
×
724
            System.err.println("Failed to check installed packages: " + e.getMessage());
×
725
            e.printStackTrace();
×
726
        }
727
    }
×
728

729
    private void registerObserver() {
730
        try {
731
            final BrokerPool pool = BrokerPool.getInstance();
×
732
            final Optional<ExistRepository> repo = pool.getExpathRepo();
×
733
            if (repo.isPresent()) {
×
734
                repo.get().addObserver(this);
×
735
                repo.get().addObserver(utilityPanel);
×
736
            } else {
×
737
                System.err.println("EXPath repository is not available.");
×
738
            }
739
        } catch (final EXistException e) {
×
740
            System.err.println("Failed to register as observer for package manager events");
×
741
            e.printStackTrace();
×
742
        }
743
    }
×
744

745
    @Override
746
    public void update(final Observable observable, final Object o) {
747
        final ExistRepository.Notification notification = (ExistRepository.Notification) o;
×
748

749
        if (notification.getPackageURI().equals(PACKAGE_DASHBOARD) && dashboardItem != null) {
×
750
            dashboardItem.setEnabled(notification.getAction() == ExistRepository.Action.INSTALL);
×
751

752
        } else if (notification.getPackageURI().equals(PACKAGE_EXIDE) && eXideItem != null) {
×
753
            eXideItem.setEnabled(notification.getAction() == ExistRepository.Action.INSTALL);
×
754

755
        } else if (notification.getPackageURI().equals(PACKAGE_MONEX) && monexItem != null) {
×
756
            monexItem.setEnabled(notification.getAction() == ExistRepository.Action.INSTALL);
×
757
        }
758
    }
×
759

760
    private Path getJettyConfig(final Optional<Path> existHomeDir) {
761

762
        Optional<Path> existJettyConfigFile = getFromSysPropOrEnv(Main.PROP_EXIST_JETTY_CONFIG, Main.ENV_EXIST_JETTY_CONFIG).map(Paths::get);
×
763
        if (!existJettyConfigFile.isPresent()) {
×
764
            final Optional<Path> jettyHomeDir = getFromSysPropOrEnv(Main.PROP_JETTY_HOME, Main.ENV_JETTY_HOME).map(Paths::get);
×
765

766
            if (jettyHomeDir.isPresent() && Files.exists(jettyHomeDir.get().resolve(Main.CONFIG_DIR_NAME))) {
×
767
                existJettyConfigFile = jettyHomeDir.map(f -> f.resolve(Main.CONFIG_DIR_NAME).resolve(Main.STANDARD_ENABLED_JETTY_CONFIGS));
×
768
            }
769

770
            if (existHomeDir.isPresent() && Files.exists(existHomeDir.get().resolve(Main.CONFIG_DIR_NAME))) {
×
771
                existJettyConfigFile = existHomeDir.map(f -> f.resolve(Main.CONFIG_DIR_NAME).resolve(Main.STANDARD_ENABLED_JETTY_CONFIGS));
×
772
            }
773

774
            if (!existJettyConfigFile.isPresent()) {
×
775
                showMessageAndExit("Error Occurred", "ERROR: jetty config file could not be found! Make sure to set exist.jetty.config or EXIST_JETTY_CONFIG.", true);
×
776
                System.exit(SystemExitCodes.CATCH_ALL_GENERAL_ERROR_EXIT_CODE);
×
777
            }
778
        }
779

780
        return existJettyConfigFile.get();
×
781
    }
782

783
    private Optional<String> getFromSysPropOrEnv(final String sysPropName, final String envVarName) {
784
        Optional<String> value = Optional.ofNullable(System.getProperty(sysPropName));
×
785
        if (!value.isPresent()) {
×
786
            value = Optional.ofNullable(System.getenv().get(envVarName));
×
787
            // if we managed to detect from environment, store it in a system property
788
            value.ifPresent(s -> System.setProperty(sysPropName, s));
×
789
        }
790
        return value;
×
791
    }
792

793
    void showMessageAndExit(final String title, final String message, final boolean logs) {
794
        final JPanel panel = new JPanel();
×
795
        panel.setLayout(new BorderLayout());
×
796

797
        final JLabel label = new JLabel(message);
×
798
        label.setHorizontalAlignment(SwingConstants.CENTER);
×
799
        panel.add(label, BorderLayout.CENTER);
×
800
        if (logs) {
×
801
            final JButton displayLogs = new JButton("View Log");
×
802
            displayLogs.addActionListener(new LogActionListener());
×
803
            label.setHorizontalAlignment(SwingConstants.CENTER);
×
804
            panel.add(displayLogs, BorderLayout.SOUTH);
×
805
        }
806

807
        utilityPanel.showMessages();
×
808
        utilityPanel.toFront();
×
809
        utilityPanel.setVisible(true);
×
810

811
        JOptionPane.showMessageDialog(splash, panel, title, JOptionPane.WARNING_MESSAGE);
×
812
        //System.exit(SystemExitCodes.CATCH_ALL_GENERAL_ERROR_EXIT_CODE);
813
    }
×
814

815
    private void showTrayInfoMessage(final String message) {
816
        showTrayInfoMessage(message, message);
×
817
    }
×
818

819
    private void showTrayInfoMessage(final String caption, final String message) {
820
        if (tray != null) {
×
821
            trayIcon.displayMessage(caption, message, TrayIcon.MessageType.INFO);
×
822
        }
823
    }
×
824

825
    private void showTrayWarningMessage(final String message) {
826
        showTrayInfoMessage(message, message);
×
827
    }
×
828

829
    private void showTrayWarningMessage(final String caption, final String message) {
830
        if (tray != null) {
×
831
            trayIcon.displayMessage(caption, message, TrayIcon.MessageType.WARNING);
×
832
        }
833
    }
×
834

835
    private void showTrayErrorMessage(final String message) {
836
        showTrayErrorMessage(message, message);
×
837
    }
×
838

839
    private void showTrayErrorMessage(final String caption, final String message) {
840
        if (tray != null) {
×
841
            trayIcon.displayMessage(caption, message, TrayIcon.MessageType.ERROR);
×
842
        }
843
    }
×
844

845
    /**
846
     * Ensure that stdout and stderr messages are also printed
847
     * to the logs.
848
     */
849
    private void captureConsole() {
850
        System.setOut(createLoggingProxy(System.out));
×
851
        System.setErr(createLoggingProxy(System.err));
×
852
    }
×
853

854
    private PrintStream createLoggingProxy(final PrintStream realStream) {
855
        final OutputStream out = new OutputStream() {
×
856
            @Override
857
            public void write(final int i) {
858
                realStream.write(i);
×
859
                final String s = Character.toString((char)i);
×
860
                Launcher.this.setChanged();
×
861
                Launcher.this.notifyObservers(s);
×
862
            }
×
863

864
            @Override
865
            public void write(final byte[] bytes) throws IOException {
866
                realStream.write(bytes);
×
867
                final String s = new String(bytes, UTF_8);
×
868
                Launcher.this.setChanged();
×
869
                Launcher.this.notifyObservers(s);
×
870
            }
×
871

872
            @Override
873
            public void write(final byte[] bytes, final int offset, final int len) {
874
                realStream.write(bytes, offset, len);
×
875
                final String s = new String(bytes, offset, len, UTF_8);
×
876
                Launcher.this.setChanged();
×
877
                Launcher.this.notifyObservers(s);
×
878
            }
×
879
        };
880
        return new PrintStream(out);
×
881
    }
882

883
    private class LogActionListener implements ActionListener {
×
884

885
        @Override
886
        public void actionPerformed(final ActionEvent actionEvent) {
887
            if (!Desktop.isDesktopSupported()) {
×
888
                return;
×
889
            }
890
            final Desktop desktop = Desktop.getDesktop();
×
891
            final Optional<Path> home = ConfigurationHelper.getExistHome();
×
892

893
            final Path logFile = FileUtils.resolve(home, "logs/elemental.log");
×
894

895
            if (!Files.isReadable(logFile)) {
×
896
                showTrayErrorMessage("Log file not found: " + logFile.toAbsolutePath().normalize().toString());
×
897
            } else {
×
898
                try {
899
                    desktop.open(logFile.toFile());
×
900
                } catch (final IOException e) {
×
901
                    showTrayErrorMessage("Failed to open log file: " + logFile.toAbsolutePath().normalize().toString() + ". " + e.getMessage());
×
902
                }
903
            }
904
        }
×
905
    }
906
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc