blob: ffbca1a61ed243f18855ef0b8630c8648c545028 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
package org.apache.log4j.chainsaw.logui;
import org.apache.commons.configuration2.AbstractConfiguration;
import org.apache.log4j.chainsaw.*;
import org.apache.log4j.chainsaw.components.elements.SmallButton;
import org.apache.log4j.chainsaw.components.logpanel.LogPanel;
import org.apache.log4j.chainsaw.components.tabbedpane.ChainsawTabbedPane;
import org.apache.log4j.chainsaw.components.tutorial.TutorialFrame;
import org.apache.log4j.chainsaw.components.welcome.WelcomePanel;
import org.apache.log4j.chainsaw.dnd.FileDnDTarget;
import org.apache.log4j.chainsaw.help.HelpManager;
import org.apache.log4j.chainsaw.icons.ChainsawIcons;
import org.apache.log4j.chainsaw.logevents.ChainsawLoggingEvent;
import org.apache.log4j.chainsaw.osx.OSXIntegration;
import org.apache.log4j.chainsaw.prefs.SettingsManager;
import org.apache.log4j.chainsaw.receiver.ChainsawReceiver;
import org.apache.log4j.chainsaw.receiver.ChainsawReceiverFactory;
import org.apache.log4j.chainsaw.zeroconf.ZeroConfPlugin;
import org.apache.log4j.rule.ExpressionRule;
import org.apache.log4j.rule.Rule;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import javax.swing.*;
import javax.swing.event.EventListenerList;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.Set;
/**
* The main entry point for Chainsaw, this class represents the first frame
* that is used to display a Welcome panel, and any other panels that are
* generated because Logging Events are streamed via a Receiver, or other
* mechanism.
* <p>
* NOTE: Some of Chainsaw's application initialization should be performed prior
* to activating receivers and the logging framework used to perform self-logging.
* <p>
* DELAY as much as possible the logging framework initialization process,
* currently initialized by the creation of a ChainsawAppenderHandler.
*
* @author Scott Deboy &lt;sdeboy@apache.org&gt;
* @author Paul Smith &lt;psmith@apache.org&gt;
*/
public class LogUI extends JFrame {
private static Logger logger = LogManager.getLogger(LogUI.class);
private static final String MAIN_WINDOW_HEIGHT = "main.window.height";
private static final String MAIN_WINDOW_WIDTH = "main.window.width";
private static final String MAIN_WINDOW_Y = "main.window.y";
private static final String MAIN_WINDOW_X = "main.window.x";
/* Panels / Views */
private final JFrame preferencesFrame = new JFrame();
private WelcomePanel welcomePanel;
private ChainsawTabbedPane tabbedPane;
private ChainsawAbout aboutBox;
public TutorialFrame tutorialFrame;
private final List<LogPanel> identifierPanels = new ArrayList<>();
private JToolBar toolbar;
private ChainsawToolBarAndMenus chainsawToolBarAndMenus;
private ChainsawStatusBar statusBar;
private ApplicationPreferenceModelPanel applicationPreferenceModelPanel;
private final Map<String, Component> panelMap = new HashMap<>();
public ChainsawAppender chainsawAppender;
private SettingsManager settingsManager;
private final List<ChainsawReceiver> receivers = new ArrayList<>();
private final List<ReceiverEventListener> receiverListeners = new ArrayList<>();
private final ZeroConfPlugin zeroConf = new ZeroConfPlugin(settingsManager);
/**
* Clients can register a ShutdownListener to be notified when the user has
* requested Chainsaw to exit.
*/
private final EventListenerList shutdownListenerList = new EventListenerList();
private final AbstractConfiguration configuration;
private final ShutdownManager shutdownManager;
private LogUIPanelBuilder logUIPanelBuilder;
/**
* Constructor which builds up all the visual elements of the frame including
* the Menu bar
*/
public LogUI(SettingsManager settingsManager) {
super("Chainsaw");
this.settingsManager = settingsManager;
this.configuration = settingsManager.getGlobalConfiguration();
setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
if (ChainsawIcons.WINDOW_ICON != null) {
setIconImage(new ImageIcon(ChainsawIcons.WINDOW_ICON).getImage());
}
shutdownManager = new ShutdownManager(this, configuration, receivers, shutdownListenerList);
}
/**
* Initialises the menu's and toolbars, but does not actually create any of
* the main panel components.
*/
private void initGUI() {
setupHelpSystem();
statusBar = new ChainsawStatusBar(this, configuration);
boolean showStatusBar = configuration.getBoolean("statusBar", true);
setStatusBarVisible(showStatusBar);
chainsawToolBarAndMenus = new ChainsawToolBarAndMenus(this, configuration);
toolbar = chainsawToolBarAndMenus.getToolbar();
setJMenuBar(chainsawToolBarAndMenus.getMenubar());
tabbedPane = new ChainsawTabbedPane();
createDragAndDrop();
applicationPreferenceModelPanel = new ApplicationPreferenceModelPanel(settingsManager);
applicationPreferenceModelPanel.setOkCancelActionListener(e -> preferencesFrame.setVisible(false));
KeyStroke escape = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, false);
Action closeAction = new AbstractAction() {
public void actionPerformed(ActionEvent e) {
preferencesFrame.setVisible(false);
}
};
preferencesFrame.getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(escape, "ESCAPE");
preferencesFrame.getRootPane().
getActionMap().put("ESCAPE", closeAction);
logUIPanelBuilder = new LogUIPanelBuilder(tabbedPane, identifierPanels, chainsawToolBarAndMenus, panelMap, settingsManager, statusBar, zeroConf);
OSXIntegration.init(this);
}
/**
* This adds Drag & Drop capability to Chainsaw
*/
private void createDragAndDrop() {
FileDnDTarget dnDTarget = new FileDnDTarget(tabbedPane);
dnDTarget.addPropertyChangeListener("fileList", evt -> {
final List fileList = (List) evt.getNewValue();
Thread thread = new Thread(() -> {
logger.debug("Loading files: {}", fileList);
for (Object aFileList : fileList) {
File file = (File) aFileList;
try {
getStatusBar().setMessage("Loading " + file.getAbsolutePath() + "...");
} catch (Exception e) {
String errorMsg = "Failed to import a file";
logger.error(errorMsg, e);
getStatusBar().setMessage(errorMsg);
}
}
});
thread.setPriority(Thread.MIN_PRIORITY);
thread.start();
});
}
/**
* Initialises the Help system and the WelcomePanel
*/
private void setupHelpSystem() {
welcomePanel = new WelcomePanel();
tutorialFrame = new TutorialFrame(receivers, receiverListeners, this, statusBar);
JToolBar tb = welcomePanel.getToolbar();
JButton help = new SmallButton.Builder().iconUrl(ChainsawIcons.HELP)
.action(tutorialFrame::setupTutorial).shortDescription("Tutorial").build();
tb.add(help);
tb.addSeparator();
JButton exampleButton = new SmallButton.Builder().iconUrl(ChainsawIcons.HELP)
.action(
() -> HelpManager.getInstance().setHelpURL(ChainsawConstants.EXAMPLE_CONFIG_URL)
)
.name("View example Receiver configuration")
.shortDescription("Displays an example Log4j configuration file with several Receivers defined.")
.build();
tb.add(exampleButton);
tb.add(Box.createHorizontalGlue());
/*
* Setup a listener on the HelpURL property and automatically change the WelcomePages URL
* to it.
*/
HelpManager.getInstance().addPropertyChangeListener(
"helpURL",
evt -> {
URL newURL = (URL) evt.getNewValue();
if (newURL != null) {
welcomePanel.setURL(newURL);
ensureWelcomePanelVisible();
}
});
}
/**
* ensure that the Welcome Panel is made visible
*/
private void ensureWelcomePanelVisible() {
if (!tabbedPane.containsWelcomePanel()) {
addWelcomePanel();
}
tabbedPane.setSelectedComponent(welcomePanel);
}
/**
* Given the load event, configures the size/location of the main window etc. etc.
*/
private void loadSettings() {
AbstractConfiguration config = settingsManager.getGlobalConfiguration();
setLocation(
config.getInt(LogUI.MAIN_WINDOW_X, 0),
config.getInt(LogUI.MAIN_WINDOW_Y, 0));
int width = config.getInt(LogUI.MAIN_WINDOW_WIDTH, -1);
int height = config.getInt(LogUI.MAIN_WINDOW_HEIGHT, -1);
if (width == -1 && height == -1) {
width = Toolkit.getDefaultToolkit().getScreenSize().width;
height = Toolkit.getDefaultToolkit().getScreenSize().height;
setSize(width, height);
setExtendedState(getExtendedState() | MAXIMIZED_BOTH);
} else {
setSize(width, height);
}
chainsawToolBarAndMenus.stateChange();
}
/**
* Activates itself as a viewer by configuring Size, and location of itself,
* and configures the default Tabbed Pane elements with the correct layout,
* table columns, and sets itself viewable.
*/
public void activateViewer() {
initGUI();
loadSettings();
JPanel panePanel = new JPanel();
panePanel.setLayout(new BorderLayout(2, 2));
getContentPane().setLayout(new BorderLayout());
tabbedPane.addChangeListener(chainsawToolBarAndMenus);
tabbedPane.addChangeListener(e -> {
LogPanel thisLogPanel = getCurrentLogPanel();
if (thisLogPanel != null) {
thisLogPanel.updateStatusBar();
}
});
LogUiKeyStrokeCreator.createKeyStrokeRight(tabbedPane);
LogUiKeyStrokeCreator.createKeyStrokeLeft(tabbedPane);
LogUiKeyStrokeCreator.createKeyStrokeGotoLine(tabbedPane, this);
MouseAdapter mouseAdapter = createMouseAdapter();
tabbedPane.addMouseListener(mouseAdapter);
panePanel.add(tabbedPane);
addWelcomePanel();
getContentPane().add(toolbar, BorderLayout.NORTH);
getContentPane().add(statusBar, BorderLayout.SOUTH);
createReceiversPanel(panePanel);
addWindowListener(
new WindowAdapter() {
public void windowClosing(WindowEvent event) {
exit();
}
});
preferencesFrame.setTitle("'Application-wide Preferences");
preferencesFrame.setIconImage(
((ImageIcon) ChainsawIcons.ICON_PREFERENCES).getImage());
preferencesFrame.getContentPane().add(applicationPreferenceModelPanel);
preferencesFrame.setSize(750, 520);
Dimension screenDimension = Toolkit.getDefaultToolkit().getScreenSize();
preferencesFrame.setLocation(
new Point(
(screenDimension.width / 2) - (preferencesFrame.getSize().width / 2),
(screenDimension.height / 2) - (preferencesFrame.getSize().height / 2)));
pack();
JPopupMenu tabPopup = new JPopupMenu();
Action hideCurrentTabAction = ceateHideCurrentTabAction();
Action hideOtherTabsAction = createHideOtherTabsAction();
Action showHiddenTabsAction = createShowHiddenTabsAction();
tabPopup.add(hideCurrentTabAction);
tabPopup.add(hideOtherTabsAction);
tabPopup.addSeparator();
tabPopup.add(showHiddenTabsAction);
final PopupListener tabPopupListener = new PopupListener(tabPopup);
tabbedPane.addMouseListener(tabPopupListener);
int tooltipDisplayMillis = configuration.getInt("tooltipDisplayMillis", 4000);
ToolTipManager.sharedInstance().setDismissDelay(tooltipDisplayMillis);
boolean showToolbar = configuration.getBoolean("toolbar", true);
toolbar.setVisible(showToolbar);
setVisible(true);
/*
* loads the saved tab settings and if there are hidden tabs,
* hide those tabs out of currently loaded tabs..
*/
if (!configuration.getBoolean("displayWelcomeTab", true)) {
displayPanel(ChainsawTabbedPane.WELCOME_TAB, false);
}
if (!configuration.getBoolean("displayZeroconfTab", true)) {
displayPanel(ChainsawTabbedPane.ZEROCONF, false);
}
chainsawToolBarAndMenus.stateChange();
}
private void createReceiversPanel(JPanel panePanel) {
LogUiReceiversPanel logUiReceiversPanel = new LogUiReceiversPanel(settingsManager, receivers, this, statusBar, panePanel);
getContentPane().add(logUiReceiversPanel.getMainReceiverSplitPane(), BorderLayout.CENTER);
}
public void createCustomExpressionLogPanel(String ident) {
//collect events matching the rule from all of the tabs
try {
List<ChainsawLoggingEvent> list = new ArrayList<>();
Rule rule = ExpressionRule.getRule(ident);
for (LogPanel identifierPanel : identifierPanels) {
for (LoggingEventWrapper e : identifierPanel.getMatchingEvents(rule)) {
list.add(e.getLoggingEvent());
}
}
logUIPanelBuilder.buildLogPanel(true, ident, null);
} catch (IllegalArgumentException iae) {
statusBar.setMessage(
"Unable to add tab using expression: " + ident + ", reason: "
+ iae.getMessage());
}
}
public void buildChainsawLogPanel() {
logUIPanelBuilder.buildLogPanel(false, "Chainsaw", chainsawAppender.getReceiver());
}
public void addWelcomePanel() {
tabbedPane.insertTab(
ChainsawTabbedPane.WELCOME_TAB, new ImageIcon(ChainsawIcons.ABOUT), welcomePanel,
"Welcome/Help", 0);
tabbedPane.setSelectedComponent(welcomePanel);
panelMap.put(ChainsawTabbedPane.WELCOME_TAB, welcomePanel);
}
public void removeWelcomePanel() {
EventQueue.invokeLater(() -> {
if (tabbedPane.containsWelcomePanel()) {
tabbedPane.remove(
tabbedPane.getComponentAt(tabbedPane.indexOfTab(ChainsawTabbedPane.WELCOME_TAB)));
}
});
}
public void showAboutBox() {
if (aboutBox == null) {
aboutBox = new ChainsawAbout(this);
}
aboutBox.setVisible(true);
}
void displayPanel(String panelName, boolean display) {
Component p = panelMap.get(panelName);
int index = tabbedPane.indexOfTab(panelName);
if ((index == -1) && display) {
tabbedPane.addTab(panelName, p);
}
if ((index > -1) && !display) {
tabbedPane.removeTabAt(index);
}
}
public void showApplicationPreferences() {
preferencesFrame.setVisible(true);
}
/**
* Registers a ShutdownListener with this class so that it can be notified
* when the user has requested that Chainsaw exit.
*
* @param listener the listener to add
*/
public void addShutdownListener(ShutdownListener listener) {
shutdownListenerList.add(ShutdownListener.class, listener);
}
/**
* Exits the application, ensuring Settings are saved.
*/
public boolean exit() {
for (ChainsawReceiver rx : receivers) {
settingsManager.saveSettingsForReceiver(rx);
}
settingsManager.saveAllSettings();
return shutdownManager.shutdown();
}
public void loadReceiver() {
Runnable r = () -> {
JFileChooser jfc = new JFileChooser(SettingsManager.getSettingsDirectory());
int returnVal = jfc.showOpenDialog(this);
if (returnVal != JFileChooser.APPROVE_OPTION) {
return;
}
logger.debug("Load file {}", jfc.getSelectedFile());
// Create the receiver
String fileToLoad = jfc.getSelectedFile().getName();
String receiverName = fileToLoad.split("-")[0];
AbstractConfiguration config = settingsManager.getSettingsForReceiverTab(receiverName);
String typeToLoad = config.getString("receiver.type");
ServiceLoader<ChainsawReceiverFactory> sl = ServiceLoader.load(ChainsawReceiverFactory.class);
for (ChainsawReceiverFactory crFactory : sl) {
if (crFactory.getReceiverName().equals(typeToLoad)) {
ChainsawReceiver rx = crFactory.create();
rx.setName(receiverName);
settingsManager.loadSettingsForReceiver(rx);
addReceiver(rx);
rx.start();
}
}
};
SwingUtilities.invokeLater(r);
}
Map<String, Boolean> getPanels() {
Map<String, Boolean> result = new HashMap<>();
Set<Map.Entry<String, Component>> panelSet = panelMap.entrySet();
for (Map.Entry<String, Component> panel : panelSet) {
Component component = panel.getValue();
boolean value = !(component instanceof LogPanel) || ((DockablePanel) panel.getValue()).isDocked();
result.put(panel.getKey(), value);
}
return result;
}
/**
* Returns the currently selected LogPanel, if there is one, otherwise null
*
* @return current log panel
*/
public LogPanel getCurrentLogPanel() {
Component selectedTab = tabbedPane.getSelectedComponent();
if (selectedTab instanceof LogPanel) {
return (LogPanel) selectedTab;
}
return null;
}
/**
* @param visible
*/
private void setStatusBarVisible(final boolean visible) {
logger.debug("Setting StatusBar to {}", visible);
SwingUtilities.invokeLater(
() -> statusBar.setVisible(visible));
}
public boolean isStatusBarVisible() {
return statusBar.isVisible();
}
/**
* DOCUMENT ME!
*
* @return DOCUMENT ME!
*/
public String getActiveTabName() {
int index = tabbedPane.getSelectedIndex();
if (index == -1) {
return null;
} else {
return tabbedPane.getTitleAt(index);
}
}
/**
* DOCUMENT ME!
*
* @return log tree panel visible flag
*/
public boolean isLogTreePanelVisible() {
return getCurrentLogPanel() != null && getCurrentLogPanel().isLogTreeVisible();
}
public void addReceiver(ChainsawReceiver rx) {
receivers.add(rx);
logUIPanelBuilder.buildLogPanel(false, rx.getName(), rx);
for (ReceiverEventListener listen : receiverListeners) {
listen.receiverAdded(rx);
}
}
public void addReceiverEventListener(ReceiverEventListener listener) {
receiverListeners.add(listener);
}
public List<ChainsawReceiver> getAllReceivers() {
return receivers;
}
/** @deprecated */
public ChainsawTabbedPane getTabbedPane() {
return tabbedPane;
}
public ChainsawStatusBar getStatusBar() {
return statusBar;
}
/**
* We listen for double clicks, and auto-undock currently selected Tab if
* the mouse event location matches the currently selected tab
*/
private MouseAdapter createMouseAdapter() {
return new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
super.mouseClicked(e);
if (
(e.getClickCount() > 1)
&& ((e.getModifiers() & InputEvent.BUTTON1_MASK) > 0)) {
int tabIndex = tabbedPane.getSelectedIndex();
if (
(tabIndex != -1)
&& (tabIndex == tabbedPane.getSelectedIndex())) {
LogPanel logPanel = getCurrentLogPanel();
if (logPanel != null) {
logPanel.undock();
}
}
}
}
};
}
private Action ceateHideCurrentTabAction() {
return new AbstractAction("Hide") {
public void actionPerformed(ActionEvent e) {
Component selectedComp = tabbedPane.getSelectedComponent();
if (selectedComp instanceof LogPanel) {
displayPanel(getCurrentLogPanel().getIdentifier(), false);
chainsawToolBarAndMenus.stateChange();
} else {
tabbedPane.remove(selectedComp);
}
}
};
}
private Action createHideOtherTabsAction() {
return new AbstractAction("Hide Others") {
public void actionPerformed(ActionEvent e) {
Component selectedComp = tabbedPane.getSelectedComponent();
String currentName;
if (selectedComp instanceof LogPanel) {
currentName = getCurrentLogPanel().getIdentifier();
} else if (selectedComp instanceof WelcomePanel) {
currentName = ChainsawTabbedPane.WELCOME_TAB;
} else {
currentName = ChainsawTabbedPane.ZEROCONF;
}
int count = tabbedPane.getTabCount();
int index = 0;
for (int i = 0; i < count; i++) {
String name = tabbedPane.getTitleAt(index);
if (
panelMap.containsKey(name) && !name.equals(currentName)) {
displayPanel(name, false);
chainsawToolBarAndMenus.stateChange();
} else {
index++;
}
}
}
};
}
private Action createShowHiddenTabsAction() {
return new AbstractAction("Show All Hidden") {
public void actionPerformed(ActionEvent e) {
for (Map.Entry<String, Boolean> entry : getPanels().entrySet()) {
Boolean docked = entry.getValue();
if (Boolean.TRUE.equals(docked)) {
String identifier = entry.getKey();
int count = tabbedPane.getTabCount();
boolean found = false;
for (int i = 0; i < count; i++) {
String name = tabbedPane.getTitleAt(i);
if (name.equals(identifier)) {
found = true;
break;
}
}
if (!found) {
displayPanel(identifier, true);
chainsawToolBarAndMenus.stateChange();
}
}
}
}
};
}
}