001/* 002 * (C) Copyright 2011-2018 Nuxeo (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * Julien Carsique 018 * 019 */ 020 021package org.nuxeo.launcher.gui; 022 023import java.awt.*; 024import java.awt.event.ActionEvent; 025import java.awt.event.ComponentAdapter; 026import java.awt.event.ComponentEvent; 027import java.awt.event.WindowEvent; 028import java.awt.image.BufferedImage; 029import java.io.File; 030import java.io.IOException; 031import java.util.List; 032 033import javax.imageio.ImageIO; 034import javax.swing.*; 035 036import org.apache.commons.logging.Log; 037import org.apache.commons.logging.LogFactory; 038import org.apache.logging.log4j.core.LoggerContext; 039import org.joda.time.DateTime; 040import org.nuxeo.common.Environment; 041import org.nuxeo.launcher.config.ConfigurationGenerator; 042import org.nuxeo.log4j.Log4JHelper; 043import org.nuxeo.shell.Shell; 044import org.nuxeo.shell.cmds.Interactive; 045import org.nuxeo.shell.cmds.InteractiveShellHandler; 046import org.nuxeo.shell.swing.ConsolePanel; 047 048/** 049 * Launcher view for graphical user interface 050 * 051 * @author jcarsique 052 * @since 5.4.2 053 * @see NuxeoLauncherGUI 054 */ 055public class NuxeoFrame extends JFrame { 056 057 /** 058 * @since 5.5 059 */ 060 protected class LogsPanelListener extends ComponentAdapter { 061 private String logFile; 062 063 public LogsPanelListener(String logFile) { 064 this.logFile = logFile; 065 } 066 067 @Override 068 public void componentHidden(ComponentEvent e) { 069 controller.notifyLogsObserver(logFile, false); 070 } 071 072 @Override 073 public void componentShown(ComponentEvent e) { 074 controller.notifyLogsObserver(logFile, true); 075 } 076 077 } 078 079 protected final class ImagePanel extends JPanel { 080 private static final long serialVersionUID = 1L; 081 082 private Image backgroundImage; 083 084 public ImagePanel(Icon image, ImageIcon backgroundImage) { 085 if (backgroundImage != null) { 086 this.backgroundImage = backgroundImage.getImage(); 087 } 088 setOpaque(false); 089 add(new JLabel(image)); 090 } 091 092 @Override 093 public void paintComponent(Graphics g) { 094 super.paintComponent(g); 095 g.drawImage(backgroundImage, 0, 0, this); 096 } 097 } 098 099 /** 100 * @since 5.5 101 */ 102 protected Action startAction = new AbstractAction() { 103 private static final long serialVersionUID = 1L; 104 105 @Override 106 public void actionPerformed(ActionEvent e) { 107 mainButton.setEnabled(false); 108 controller.start(); 109 } 110 }; 111 112 protected boolean stopping = false; 113 114 /** 115 * @since 5.5 116 */ 117 protected Action stopAction = new AbstractAction() { 118 private static final long serialVersionUID = 1L; 119 120 @Override 121 public void actionPerformed(ActionEvent e) { 122 mainButton.setEnabled(false); 123 controller.stop(); 124 } 125 }; 126 127 protected Action launchBrowserAction = new AbstractAction() { 128 private static final long serialVersionUID = 1L; 129 130 @Override 131 public void actionPerformed(ActionEvent event) { 132 try { 133 Desktop.getDesktop().browse(java.net.URI.create(controller.getLauncher().getURL())); 134 } catch (Exception e) { 135 setError("An error occurred while launching browser", e); 136 } 137 } 138 139 }; 140 141 /** 142 * Log error and display its message in {@link #errorMessageLabel} 143 * 144 * @since 5.5 145 * @param message Message to log 146 * @param e Caught exception 147 */ 148 public void setError(String message, Exception e) { 149 log.error(message, e); 150 errorMessageLabel.setText(NuxeoLauncherGUI.getMessage("error.occurred") + " <<" + e.getMessage() + ">>."); 151 } 152 153 /** 154 * Log error and display its message in {@link #errorMessageLabel} 155 * 156 * @since 5.5 157 * @param e Caught exception 158 */ 159 public void setError(Exception e) { 160 log.error(e); 161 errorMessageLabel.setText(NuxeoLauncherGUI.getMessage("error.occurred") + " <<" + e.getMessage() + ">>."); 162 } 163 164 protected static final Log log = LogFactory.getLog(NuxeoFrame.class); 165 166 private static final long serialVersionUID = 1L; 167 168 protected static final int LOG_MAX_SIZE = 200000; 169 170 protected final ImageIcon startIcon = getImageIcon("icons/start.png"); 171 172 protected final ImageIcon stopIcon = getImageIcon("icons/stop.png"); 173 174 protected final ImageIcon appIcon = getImageIcon("icons/control_panel_icon_32.png"); 175 176 protected JButton mainButton = null; 177 178 protected NuxeoLauncherGUI controller; 179 180 protected boolean logsShown = false; 181 182 protected JButton logsButton; 183 184 protected GridBagConstraints constraints; 185 186 protected NuxeoFrame contentPane; 187 188 protected Component filler; 189 190 protected JTabbedPane tabbedPanel; 191 192 protected ConsolePanel consolePanel; 193 194 protected JLabel summaryStatus; 195 196 protected JLabel summaryURL; 197 198 protected JButton launchBrowserButton; 199 200 protected JLabel errorMessageLabel; 201 202 private JTabbedPane logsTab; 203 204 /** 205 * @return JLabel for error display 206 * @since 5.5 207 */ 208 public JLabel getErrorMessageLabel() { 209 return errorMessageLabel; 210 } 211 212 public NuxeoFrame(NuxeoLauncherGUI controller) throws HeadlessException { 213 super("NuxeoCtl"); 214 setController(controller); 215 UIManager.getDefaults().put("Button.disabledText", Color.BLACK); 216 217 // Main frame 218 setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); 219 setIconImage(appIcon.getImage()); 220 getContentPane().setBackground(Color.BLACK); 221 getContentPane().setLayout(new GridBagLayout()); 222 constraints = new GridBagConstraints(); 223 224 // Header (with main button inside) 225 constraints.fill = GridBagConstraints.HORIZONTAL; 226 constraints.gridx = 0; 227 constraints.anchor = GridBagConstraints.PAGE_START; 228 JComponent header = buildHeader(); 229 header.setPreferredSize(new Dimension(480, 170)); 230 getContentPane().add(header, constraints); 231 232 // Tabs 233 constraints.fill = GridBagConstraints.BOTH; 234 constraints.ipady = 100; 235 constraints.weightx = 1.0; 236 constraints.weighty = 1.0; 237 getContentPane().add(buildTabbedPanel(), constraints); 238 239 // Footer 240 constraints.fill = GridBagConstraints.NONE; 241 constraints.anchor = GridBagConstraints.PAGE_END; 242 constraints.ipady = 0; 243 constraints.weightx = 0; 244 constraints.weighty = 0; 245 constraints.insets = new Insets(10, 0, 0, 0); 246 getContentPane().add(buildFooter(), constraints); 247 } 248 249 protected Component buildConsolePanel() { 250 try { 251 consolePanel = new ConsolePanel(); 252 } catch (Exception e) { 253 log.error(e); 254 } 255 Interactive.setConsoleReaderFactory(consolePanel.getConsole()); 256 Interactive.setHandler(new InteractiveShellHandler() { 257 @Override 258 public void enterInteractiveMode() { 259 Interactive.reset(); 260 } 261 262 @Override 263 public boolean exitInteractiveMode(int code) { 264 if (code == 1) { 265 Interactive.reset(); 266 Shell.reset(); 267 return true; 268 } else { 269 consolePanel.getConsole().reset(); 270 return false; 271 } 272 } 273 }); 274 new Thread(() -> { 275 try { 276 Shell.get().main(new String[] { controller.launcher.getURL() + "site/automation" }); 277 } catch (Exception e) { 278 setError(e); 279 } 280 }).start(); 281 return consolePanel; 282 } 283 284 protected JComponent buildFooter() { 285 JLabel label = new JLabel(NuxeoLauncherGUI.getMessage("footer.label", new DateTime().toString("Y"))); 286 label.setForeground(Color.WHITE); 287 label.setPreferredSize(new Dimension(470, 16)); 288 label.setFont(new Font(label.getFont().getName(), label.getFont().getStyle(), 9)); 289 label.setHorizontalAlignment(SwingConstants.CENTER); 290 return label; 291 } 292 293 protected JComponent buildHeader() { 294 ImagePanel headerLogo = new ImagePanel(getImageIcon("img/nuxeo_control_panel_logo.png"), null); 295 headerLogo.setLayout(new GridBagLayout()); 296 // Main button (start/stop) (added to header) 297 298 GridBagConstraints headerConstraints = new GridBagConstraints(); 299 headerConstraints.gridx = 0; 300 headerLogo.add(buildMainButton(), headerConstraints); 301 headerLogo.add(buildLaunchBrowserButton(), headerConstraints); 302 return headerLogo; 303 } 304 305 protected JComponent buildLaunchBrowserButton() { 306 launchBrowserButton = createButton(null); 307 launchBrowserButton.setAction(launchBrowserAction); 308 launchBrowserButton.setText(NuxeoLauncherGUI.getMessage("browser.button.text")); 309 updateLaunchBrowserButton(); 310 return launchBrowserButton; 311 } 312 313 protected JTabbedPane buildLogsTab() { 314 JTabbedPane logsTabbedPane = new JTabbedPane(SwingConstants.TOP); 315 // Get Launcher log file(s) 316 List<String> logFiles = Log4JHelper.getFileAppendersFileNames( 317 LoggerContext.getContext(false).getConfiguration()); 318 // Add nuxeoctl log file 319 File nuxeoctlLog = new File(controller.getConfigurationGenerator().getLogDir(), "nuxeoctl.log"); 320 if (nuxeoctlLog.exists()) { 321 logFiles.add(nuxeoctlLog.getAbsolutePath()); 322 } 323 // Get server log file(s) 324 logFiles.addAll(controller.getConfigurationGenerator().getLogFiles()); 325 for (String logFile : logFiles) { 326 addFileToLogsTab(logsTabbedPane, logFile); 327 } 328 return logsTabbedPane; 329 } 330 331 protected void addFileToLogsTab(JTabbedPane logsTabbedPane, String logFile) { 332 if (!hideLogTab(logFile) && !controller.getLogsMap().containsKey(logFile)) { 333 logsTabbedPane.addTab(new File(logFile).getName(), buildLogPanel(logFile)); 334 } 335 } 336 337 /** 338 * Called by buildLogsTab to know if a log file should be display. Can be overridden. Return false by default. 339 * 340 * @return false 341 */ 342 protected boolean hideLogTab(String logFile) { 343 return false; 344 } 345 346 protected JComponent buildLogPanel(String logFile) { 347 ColoredTextPane textArea = new ColoredTextPane(); 348 textArea.setEditable(false); 349 textArea.setAutoscrolls(true); 350 textArea.setBackground(Color.BLACK); 351 textArea.setMaxSize(LOG_MAX_SIZE); 352 353 JScrollPane logsScroller = new JScrollPane(textArea); 354 logsScroller.setVisible(true); 355 logsScroller.setBorder(BorderFactory.createLineBorder(Color.BLACK)); 356 logsScroller.setAutoscrolls(true); 357 logsScroller.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); 358 logsScroller.setWheelScrollingEnabled(true); 359 logsScroller.setPreferredSize(new Dimension(450, 160)); 360 361 controller.initLogsManagement(logFile, textArea); 362 logsScroller.addComponentListener(new LogsPanelListener(logFile)); 363 return logsScroller; 364 } 365 366 protected JComponent buildMainButton() { 367 mainButton = createButton(null); 368 updateMainButton(); 369 return mainButton; 370 } 371 372 protected Component buildSummaryPanel() { 373 JPanel summaryPanel = new JPanel(); 374 summaryPanel.setLayout(new BoxLayout(summaryPanel, BoxLayout.PAGE_AXIS)); 375 summaryPanel.setBackground(Color.BLACK); 376 summaryPanel.setForeground(Color.WHITE); 377 378 summaryPanel.add( 379 new JLabel("<html><font color=#ffffdd>" + NuxeoLauncherGUI.getMessage("summary.status.label"))); 380 summaryStatus = new JLabel(controller.launcher.status()); 381 summaryStatus.setForeground(Color.WHITE); 382 summaryPanel.add(summaryStatus); 383 384 summaryPanel.add(new JLabel("<html><font color=#ffffdd>" + NuxeoLauncherGUI.getMessage("summary.url.label"))); 385 summaryURL = new JLabel(controller.launcher.getURL()); 386 summaryURL.setForeground(Color.WHITE); 387 summaryPanel.add(summaryURL); 388 389 errorMessageLabel = new JLabel(); 390 errorMessageLabel.setForeground(Color.RED); 391 summaryPanel.add(errorMessageLabel); 392 393 summaryPanel.add(new JSeparator()); 394 ConfigurationGenerator config = controller.launcher.getConfigurationGenerator(); 395 summaryPanel.add( 396 new JLabel("<html><font color=#ffffdd>" + NuxeoLauncherGUI.getMessage("summary.homedir.label"))); 397 summaryPanel.add(new JLabel("<html><font color=white>" + config.getNuxeoHome().getPath())); 398 summaryPanel.add( 399 new JLabel("<html><font color=#ffffdd>" + NuxeoLauncherGUI.getMessage("summary.nuxeoconf.label"))); 400 summaryPanel.add(new JLabel("<html><font color=white>" + config.getNuxeoConf().getPath())); 401 summaryPanel.add( 402 new JLabel("<html><font color=#ffffdd>" + NuxeoLauncherGUI.getMessage("summary.datadir.label"))); 403 summaryPanel.add(new JLabel("<html><font color=white>" + config.getDataDir().getPath())); 404 return summaryPanel; 405 } 406 407 protected JComponent buildTabbedPanel() { 408 tabbedPanel = new JTabbedPane(SwingConstants.TOP); 409 tabbedPanel.addTab(NuxeoLauncherGUI.getMessage("tab.summary.title"), buildSummaryPanel()); 410 logsTab = buildLogsTab(); 411 tabbedPanel.addTab(NuxeoLauncherGUI.getMessage("tab.logs.title"), logsTab); 412 tabbedPanel.addTab(NuxeoLauncherGUI.getMessage("tab.shell.title"), buildConsolePanel()); 413 tabbedPanel.addChangeListener(e -> { 414 JTabbedPane pane = (JTabbedPane) e.getSource(); 415 if (pane.getSelectedIndex() == 2) { 416 consolePanel.getConsole().requestFocusInWindow(); 417 } 418 }); 419 return tabbedPanel; 420 } 421 422 protected JButton createButton(ImageIcon icon) { 423 JButton button = new JButton(); 424 button.setIcon(icon); 425 return button; 426 } 427 428 public void debug(JComponent parent) { 429 for (Component comp : parent.getComponents()) { 430 if (comp instanceof JComponent) { 431 ((JComponent) comp).setBorder(BorderFactory.createCompoundBorder( 432 BorderFactory.createLineBorder(Color.red), ((JComponent) comp).getBorder())); 433 log.info(comp.getClass() + " size: " + comp.getSize()); 434 } 435 } 436 } 437 438 protected ImageIcon getImageIcon(String resourcePath) { 439 BufferedImage image; 440 try { 441 ImageIO.setCacheDirectory(Environment.getDefault().getTemp()); 442 image = ImageIO.read(getClass().getClassLoader().getResource(resourcePath)); 443 } catch (IOException e) { 444 log.error(e); 445 throw new RuntimeException(e); 446 } 447 return new ImageIcon(image); 448 } 449 450 protected void updateMainButton() { 451 if (controller.launcher.isStarted()) { 452 mainButton.setAction(stopAction); 453 mainButton.setText(NuxeoLauncherGUI.getMessage("mainbutton.stop.text")); 454 mainButton.setToolTipText(NuxeoLauncherGUI.getMessage("mainbutton.stop.tooltip")); 455 mainButton.setIcon(stopIcon); 456 } else if (controller.launcher.isRunning()) { 457 if (stopping) { 458 mainButton.setAction(stopAction); 459 mainButton.setText(NuxeoLauncherGUI.getMessage("mainbutton.stop.inprogress")); 460 } else { 461 mainButton.setAction(stopAction); 462 mainButton.setText(NuxeoLauncherGUI.getMessage("mainbutton.start.inprogress")); 463 } 464 mainButton.setToolTipText(NuxeoLauncherGUI.getMessage("mainbutton.stop.tooltip")); 465 mainButton.setIcon(stopIcon); 466 } else { 467 mainButton.setAction(startAction); 468 mainButton.setText(NuxeoLauncherGUI.getMessage("mainbutton.start.text")); 469 mainButton.setToolTipText(NuxeoLauncherGUI.getMessage("mainbutton.start.tooltip")); 470 mainButton.setIcon(startIcon); 471 } 472 mainButton.setEnabled(true); 473 mainButton.validate(); 474 } 475 476 /** 477 * @since 5.5 478 */ 479 protected void updateLaunchBrowserButton() { 480 launchBrowserButton.setEnabled(controller.launcher.isStarted()); 481 } 482 483 /** 484 * Update information displayed in summary tab 485 */ 486 public void updateSummary() { 487 String errorMessageLabelStr = ""; 488 Color summaryStatusFgColor = Color.WHITE; 489 if (!controller.getConfigurationGenerator().isWizardRequired() && controller.launcher.isStarted()) { 490 String startupSummary = controller.launcher.getStartupSummary(); 491 if (!controller.launcher.wasStartupFine()) { 492 String[] lines = startupSummary.split("\n"); 493 // extract line with summary informations 494 for (String line : lines) { 495 if (line.contains("Component Loading Status")) { 496 startupSummary = line; 497 break; 498 } 499 } 500 errorMessageLabelStr = "An error was detected during startup " + startupSummary + "."; 501 summaryStatusFgColor = Color.RED; 502 } 503 } 504 errorMessageLabel.setText(errorMessageLabelStr); 505 summaryStatus.setForeground(summaryStatusFgColor); 506 summaryStatus.setText(controller.launcher.status()); 507 summaryURL.setText(controller.launcher.getURL()); 508 } 509 510 /** 511 * Add Windows rotated console log 512 * 513 * @since 5.6 514 */ 515 public void updateLogsTab(String consoleLogId) { 516 if (consoleLogId != null) { 517 addFileToLogsTab(logsTab, new File(controller.getConfigurationGenerator().getLogDir(), 518 "console" + consoleLogId + ".log").getPath()); 519 } 520 } 521 522 /** 523 * @since 5.5 524 * @return GUI controller 525 */ 526 public NuxeoLauncherGUI getController() { 527 return controller; 528 } 529 530 public void setController(NuxeoLauncherGUI controller) { 531 this.controller = controller; 532 } 533 534 /** 535 * @since 5.6 536 */ 537 public void close() { 538 setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); 539 Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(new WindowEvent(this, WindowEvent.WINDOW_CLOSING)); 540 } 541 542}