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