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