/*
* (C) Copyright 2011-2016 Nuxeo SA (http://nuxeo.com/) and others.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Contributors:
* Julien Carsique
*
*/
package org.nuxeo.launcher.gui;
import java.awt.Color;
import java.awt.Component;
import java.awt.Desktop;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.HeadlessException;
import java.awt.Image;
import java.awt.Insets;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import javax.imageio.ImageIO;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import javax.swing.JTabbedPane;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingConstants;
import javax.swing.WindowConstants;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.log4j.LogManager;
import org.joda.time.DateTime;
import org.nuxeo.common.Environment;
import org.nuxeo.launcher.config.ConfigurationGenerator;
import org.nuxeo.log4j.Log4JHelper;
import org.nuxeo.shell.Shell;
import org.nuxeo.shell.cmds.Interactive;
import org.nuxeo.shell.cmds.InteractiveShellHandler;
import org.nuxeo.shell.swing.ConsolePanel;
/**
* Launcher view for graphical user interface
*
* @author jcarsique
* @since 5.4.2
* @see NuxeoLauncherGUI
*/
public class NuxeoFrame extends JFrame {
/**
* @since 5.5
*/
protected class LogsPanelListener extends ComponentAdapter {
private String logFile;
public LogsPanelListener(String logFile) {
this.logFile = logFile;
}
@Override
public void componentHidden(ComponentEvent e) {
controller.notifyLogsObserver(logFile, false);
}
@Override
public void componentShown(ComponentEvent e) {
controller.notifyLogsObserver(logFile, true);
}
}
protected final class ImagePanel extends JPanel {
private static final long serialVersionUID = 1L;
private Image backgroundImage;
public ImagePanel(Icon image, ImageIcon backgroundImage) {
this.backgroundImage = backgroundImage.getImage();
setOpaque(false);
add(new JLabel(image));
}
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(backgroundImage, 0, 0, this);
}
}
/**
* @since 5.5
*/
protected Action startAction = new AbstractAction() {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(ActionEvent e) {
mainButton.setEnabled(false);
controller.start();
}
};
protected boolean stopping = false;
/**
* @since 5.5
*/
protected Action stopAction = new AbstractAction() {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(ActionEvent e) {
mainButton.setEnabled(false);
controller.stop();
}
};
protected Action launchBrowserAction = new AbstractAction() {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(ActionEvent event) {
try {
Desktop.getDesktop().browse(java.net.URI.create(controller.getLauncher().getURL()));
} catch (Exception e) {
setError("An error occurred while launching browser", e);
}
}
};
/**
* Log error and display its message in {@link #errorMessageLabel}
*
* @since 5.5
* @param message Message to log
* @param e Caught exception
*/
public void setError(String message, Exception e) {
log.error(message, e);
errorMessageLabel.setText(NuxeoLauncherGUI.getMessage("error.occurred") + " <<" + e.getMessage() + ">>.");
}
/**
* Log error and display its message in {@link #errorMessageLabel}
*
* @since 5.5
* @param e Caught exception
*/
public void setError(Exception e) {
log.error(e);
errorMessageLabel.setText(NuxeoLauncherGUI.getMessage("error.occurred") + " <<" + e.getMessage() + ">>.");
}
protected static final Log log = LogFactory.getLog(NuxeoFrame.class);
private static final long serialVersionUID = 1L;
protected static final int LOG_MAX_SIZE = 200000;
protected final ImageIcon startIcon = getImageIcon("icons/start.png");
protected final ImageIcon stopIcon = getImageIcon("icons/stop.png");
protected final ImageIcon appIcon = getImageIcon("icons/control_panel_icon_32.png");
protected JButton mainButton = null;
protected NuxeoLauncherGUI controller;
protected boolean logsShown = false;
protected JButton logsButton;
protected GridBagConstraints constraints;
protected NuxeoFrame contentPane;
protected Component filler;
protected JTabbedPane tabbedPanel;
protected ConsolePanel consolePanel;
protected JLabel summaryStatus;
protected JLabel summaryURL;
protected JButton launchBrowserButton;
protected JLabel errorMessageLabel;
private JTabbedPane logsTab;
/**
* @return JLabel for error display
* @since 5.5
*/
public JLabel getErrorMessageLabel() {
return errorMessageLabel;
}
public NuxeoFrame(NuxeoLauncherGUI controller) throws HeadlessException {
super("NuxeoCtl");
setController(controller);
// Main frame
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setIconImage(appIcon.getImage());
getContentPane().setBackground(new Color(35, 37, 59));
getContentPane().setLayout(new GridBagLayout());
constraints = new GridBagConstraints();
// Header (with main button inside)
constraints.fill = GridBagConstraints.HORIZONTAL;
constraints.gridx = 0;
constraints.anchor = GridBagConstraints.PAGE_START;
JComponent header = buildHeader();
header.setPreferredSize(new Dimension(480, 110));
getContentPane().add(header, constraints);
// Tabs
constraints.fill = GridBagConstraints.BOTH;
constraints.ipady = 100;
constraints.weightx = 1.0;
constraints.weighty = 1.0;
getContentPane().add(buildTabbedPanel(), constraints);
// Footer
constraints.fill = GridBagConstraints.NONE;
constraints.anchor = GridBagConstraints.PAGE_END;
constraints.ipady = 0;
constraints.weightx = 0;
constraints.weighty = 0;
constraints.insets = new Insets(10, 0, 0, 0);
getContentPane().add(buildFooter(), constraints);
// debug((JComponent) this.getContentPane());
}
protected Component buildConsolePanel() {
try {
consolePanel = new ConsolePanel();
} catch (Exception e) {
log.error(e);
}
Interactive.setConsoleReaderFactory(consolePanel.getConsole());
Interactive.setHandler(new InteractiveShellHandler() {
@Override
public void enterInteractiveMode() {
Interactive.reset();
}
@Override
public boolean exitInteractiveMode(int code) {
if (code == 1) {
Interactive.reset();
Shell.reset();
return true;
} else {
consolePanel.getConsole().reset();
return false;
}
}
});
new Thread() {
@Override
public void run() {
try {
Shell.get().main(new String[] { controller.launcher.getURL() + "site/automation" });
} catch (Exception e) {
setError(e);
}
}
}.start();
return consolePanel;
}
protected JComponent buildFooter() {
JLabel label = new JLabel(NuxeoLauncherGUI.getMessage("footer.label", new DateTime().toString("Y")));
label.setForeground(Color.WHITE);
label.setPreferredSize(new Dimension(470, 16));
label.setFont(new Font(label.getFont().getName(), label.getFont().getStyle(), 9));
label.setHorizontalAlignment(SwingConstants.CENTER);
return label;
}
protected JComponent buildHeader() {
ImagePanel headerLogo = new ImagePanel(getImageIcon("img/nuxeo_control_panel_logo.png"),
getImageIcon("img/nuxeo_control_panel_bg.png"));
headerLogo.setLayout(new GridBagLayout());
// Main button (start/stop) (added to header)
GridBagConstraints headerConstraints = new GridBagConstraints();
headerConstraints.gridx = 0;
headerLogo.add(buildMainButton(), headerConstraints);
headerLogo.add(buildLaunchBrowserButton(), headerConstraints);
return headerLogo;
}
protected JComponent buildLaunchBrowserButton() {
launchBrowserButton = createButton(null);
launchBrowserButton.setAction(launchBrowserAction);
launchBrowserButton.setText(NuxeoLauncherGUI.getMessage("browser.button.text"));
updateLaunchBrowserButton();
return launchBrowserButton;
}
protected JTabbedPane buildLogsTab() {
JTabbedPane logsTabbedPane = new JTabbedPane(SwingConstants.TOP);
// Get Launcher log file(s)
ArrayList<String> logFiles = Log4JHelper.getFileAppendersFiles(LogManager.getLoggerRepository());
// Add nuxeoctl log file
File nuxeoctlLog = new File(controller.getConfigurationGenerator().getLogDir(), "nuxeoctl.log");
if (nuxeoctlLog.exists()) {
logFiles.add(nuxeoctlLog.getAbsolutePath());
}
// Get server log file(s)
logFiles.addAll(controller.getConfigurationGenerator().getLogFiles());
for (String logFile : logFiles) {
addFileToLogsTab(logsTabbedPane, logFile);
}
return logsTabbedPane;
}
protected void addFileToLogsTab(JTabbedPane logsTabbedPane, String logFile) {
if (!hideLogTab(logFile) && !controller.getLogsMap().containsKey(logFile)) {
logsTabbedPane.addTab(new File(logFile).getName(), buildLogPanel(logFile));
}
}
/**
* Called by buildLogsTab to know if a log file should be display. Can be overridden. Return false by default.
*
* @param logFile
* @return false
*/
protected boolean hideLogTab(String logFile) {
return false;
}
/**
* @param logFile
*/
protected JComponent buildLogPanel(String logFile) {
ColoredTextPane textArea = new ColoredTextPane();
textArea.setEditable(false);
textArea.setAutoscrolls(true);
textArea.setBackground(new Color(54, 55, 67));
textArea.setMaxSize(LOG_MAX_SIZE);
JScrollPane logsScroller = new JScrollPane(textArea);
logsScroller.setVisible(true);
logsScroller.setBorder(BorderFactory.createLineBorder(Color.BLACK));
logsScroller.setAutoscrolls(true);
logsScroller.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
logsScroller.setWheelScrollingEnabled(true);
logsScroller.setPreferredSize(new Dimension(450, 160));
controller.initLogsManagement(logFile, textArea);
logsScroller.addComponentListener(new LogsPanelListener(logFile));
return logsScroller;
}
protected JComponent buildMainButton() {
mainButton = createButton(null);
updateMainButton();
return mainButton;
}
protected Component buildSummaryPanel() {
JPanel summaryPanel = new JPanel();
summaryPanel.setLayout(new BoxLayout(summaryPanel, BoxLayout.PAGE_AXIS));
summaryPanel.setBackground(new Color(35, 37, 59));
summaryPanel.setForeground(Color.WHITE);
summaryPanel.add(new JLabel("<html><font color=#ffffdd>" + NuxeoLauncherGUI.getMessage("summary.status.label")));
summaryStatus = new JLabel(controller.launcher.status());
summaryStatus.setForeground(Color.WHITE);
summaryPanel.add(summaryStatus);
summaryPanel.add(new JLabel("<html><font color=#ffffdd>" + NuxeoLauncherGUI.getMessage("summary.url.label")));
summaryURL = new JLabel(controller.launcher.getURL());
summaryURL.setForeground(Color.WHITE);
summaryPanel.add(summaryURL);
errorMessageLabel = new JLabel();
errorMessageLabel.setForeground(Color.RED);
summaryPanel.add(errorMessageLabel);
summaryPanel.add(new JSeparator());
ConfigurationGenerator config = controller.launcher.getConfigurationGenerator();
summaryPanel.add(new JLabel("<html><font color=#ffffdd>" + NuxeoLauncherGUI.getMessage("summary.homedir.label")));
summaryPanel.add(new JLabel("<html><font color=white>" + config.getNuxeoHome().getPath()));
summaryPanel.add(new JLabel("<html><font color=#ffffdd>"
+ NuxeoLauncherGUI.getMessage("summary.nuxeoconf.label")));
summaryPanel.add(new JLabel("<html><font color=white>" + config.getNuxeoConf().getPath()));
summaryPanel.add(new JLabel("<html><font color=#ffffdd>" + NuxeoLauncherGUI.getMessage("summary.datadir.label")));
summaryPanel.add(new JLabel("<html><font color=white>" + config.getDataDir().getPath()));
return summaryPanel;
}
protected JComponent buildTabbedPanel() {
tabbedPanel = new JTabbedPane(SwingConstants.TOP);
tabbedPanel.addTab(NuxeoLauncherGUI.getMessage("tab.summary.title"), buildSummaryPanel());
logsTab = buildLogsTab();
tabbedPanel.addTab(NuxeoLauncherGUI.getMessage("tab.logs.title"), logsTab);
tabbedPanel.addTab(NuxeoLauncherGUI.getMessage("tab.shell.title"), buildConsolePanel());
tabbedPanel.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
JTabbedPane pane = (JTabbedPane) e.getSource();
if (pane.getSelectedIndex() == 2) {
consolePanel.getConsole().requestFocusInWindow();
}
}
});
return tabbedPanel;
}
protected JButton createButton(ImageIcon icon) {
JButton button = new JButton();
button.setIcon(icon);
return button;
}
public void debug(JComponent parent) {
for (Component comp : parent.getComponents()) {
if (comp instanceof JComponent) {
((JComponent) comp).setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(Color.red), ((JComponent) comp).getBorder()));
log.info(comp.getClass() + " size: " + ((JComponent) comp).getSize());
}
}
}
protected ImageIcon getImageIcon(String resourcePath) {
BufferedImage image = null;
try {
ImageIO.setCacheDirectory(Environment.getDefault().getTemp());
image = ImageIO.read(getClass().getClassLoader().getResource(resourcePath));
} catch (IOException e) {
log.error(e);
}
return new ImageIcon(image);
}
protected void updateMainButton() {
if (controller.launcher.isStarted()) {
mainButton.setAction(stopAction);
mainButton.setText(NuxeoLauncherGUI.getMessage("mainbutton.stop.text"));
mainButton.setToolTipText(NuxeoLauncherGUI.getMessage("mainbutton.stop.tooltip"));
mainButton.setIcon(stopIcon);
} else if (controller.launcher.isRunning()) {
if (stopping) {
mainButton.setAction(stopAction);
mainButton.setText(NuxeoLauncherGUI.getMessage("mainbutton.stop.inprogress"));
} else {
mainButton.setAction(stopAction);
mainButton.setText(NuxeoLauncherGUI.getMessage("mainbutton.start.inprogress"));
}
mainButton.setToolTipText(NuxeoLauncherGUI.getMessage("mainbutton.stop.tooltip"));
mainButton.setIcon(stopIcon);
} else {
mainButton.setAction(startAction);
mainButton.setText(NuxeoLauncherGUI.getMessage("mainbutton.start.text"));
mainButton.setToolTipText(NuxeoLauncherGUI.getMessage("mainbutton.start.tooltip"));
mainButton.setIcon(startIcon);
}
mainButton.setEnabled(true);
mainButton.validate();
}
/**
* @since 5.5
*/
protected void updateLaunchBrowserButton() {
launchBrowserButton.setEnabled(controller.launcher.isStarted());
}
/**
* Update information displayed in summary tab
*/
public void updateSummary() {
String errorMessageLabelStr = "";
Color summaryStatusFgColor = Color.WHITE;
if (!controller.getConfigurationGenerator().isWizardRequired() && controller.launcher.isStarted()) {
String startupSummary = controller.launcher.getStartupSummary();
if (!controller.launcher.wasStartupFine()) {
String[] lines = startupSummary.split("\n");
// extract line with summary informations
for (String line : lines) {
if (line.contains("Component Loading Status")) {
startupSummary = line;
break;
}
}
errorMessageLabelStr = "An error was detected during startup " + startupSummary + ".";
summaryStatusFgColor = Color.RED;
}
}
errorMessageLabel.setText(errorMessageLabelStr);
summaryStatus.setForeground(summaryStatusFgColor);
summaryStatus.setText(controller.launcher.status());
summaryURL.setText(controller.launcher.getURL());
}
/**
* Add Windows rotated console log
*
* @since 5.6
*/
public void updateLogsTab(String consoleLogId) {
if (consoleLogId != null) {
addFileToLogsTab(logsTab, new File(controller.getConfigurationGenerator().getLogDir(), "console"
+ consoleLogId + ".log").getPath());
}
}
/**
* @since 5.5
* @return GUI controller
*/
public NuxeoLauncherGUI getController() {
return controller;
}
public void setController(NuxeoLauncherGUI controller) {
this.controller = controller;
}
/**
* @since 5.6
*/
public void close() {
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(new WindowEvent(this, WindowEvent.WINDOW_CLOSING));
}
}