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