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