package com.onaro.sanscreen.client.error; import java.awt.Dimension; import java.awt.event.ActionEvent; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import javax.swing.AbstractAction; import javax.swing.JDialog; import javax.swing.JFrame; import javax.swing.JOptionPane; import javax.swing.JScrollPane; import javax.swing.JTextArea; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import com.onaro.commons.lang.ClassLoaderUtils; import com.onaro.commons.prefs.PreferencesWrapper; import com.onaro.sanscreen.client.Messages; import com.onaro.sanscreen.client.prefs.ClientPrefs; /** * A default handler that in addition to send the errors to the log, prompts the user with a configurable message and a * set of options he can choose from. The optionns are specifyed by using an array of SelectedOption * enumeration values. An option can provide a behaviour when it is selected. */ public class MessageWindowHandler implements ErrorHandler { static abstract class Action extends AbstractAction { private static final long serialVersionUID = 1L; public final void actionPerformed(ActionEvent e) { //do nothing } /** * * @param dialog The message window dialog. * @return Close the window. */ protected abstract boolean doAction(String shortDescription, Throwable exception); } static class ExitAction extends Action { private static final long serialVersionUID = 1L; public ExitAction() { putValue(Action.NAME, Messages.INSTANCE.getExitActionText()); } @Override public boolean doAction(String shortDescription, Throwable exception) { System.exit(-1); return true; } }; static class IgnoreAction extends Action { private static final long serialVersionUID = 1L; public IgnoreAction() { putValue(Action.NAME, Messages.INSTANCE.getIgnoreActionText()); } @Override public boolean doAction(String shortDescription, Throwable exception) { return true; } }; static class ShowExceptionAction extends Action { private static final long serialVersionUID = 1L; public ShowExceptionAction() { putValue(Action.NAME, Messages.INSTANCE.getShowExceptionText()); } @Override public boolean doAction(String shortDescription, Throwable exception) { showException(shortDescription, exception); return false; } /** * Displays a dialog with the Exception stack trace in an JTextArea where it can be copied and edited. */ private void showException(String shortDescription, Throwable exception) { if (exception == null) return; // Formerly used ExceptionUtils.getFullStackTrace(), but this was removed from Apache Commons Lang 3. String fullStackTrace = ExceptionUtils.getStackTrace(exception); JTextArea textArea = new JTextArea(fullStackTrace); JScrollPane pane = new JScrollPane(textArea); pane.setPreferredSize(new Dimension(640, 400)); JOptionPane jOptionPane = new JOptionPane(pane); JDialog dialog = jOptionPane.createDialog(null, Messages.INSTANCE.getExceptionStackTraceTitle()); dialog.setResizable(true); dialog.setLocationRelativeTo(null); // blocks until closed dialog.setVisible(true); dialog.dispose(); } }; /** * The logger used for logging the errors. */ static final Logger logger = LogManager.getLogger(MessageWindowHandler.class); /** * The window's title. */ private String title; private Object message; private final int optionPaneOptionType; private final Map actionsByNameMap; /** * Initialized this message handler setting the default options to "exit" only. * * @param title * the window's title * @param message * the mesasge * @param optionPaneOptionType * @see JOptionPane */ public MessageWindowHandler(String title, Object message, int optionPaneOptionType) { this.title = title; this.message = message; this.optionPaneOptionType = optionPaneOptionType; actionsByNameMap = new LinkedHashMap(); } /** * Handle any error it gets by simply logging its details and displaying a message box with the preconfigured * message and options. When an option is selected, activate its behaviour. *

* Note that the window is always popped using the swing's thread. * * @param shortDescription * an optional short description of the error, may be null * @param exception * the error itself * @return true */ public boolean handle(final String shortDescription, final Throwable exception) { if (isLogOnly()) { // all events are logged by the central error handler return false; } Runnable runnable = new Runnable() { public void run() { showDialog(shortDescription, exception); } }; ClassLoaderUtils.execute(runnable, getClass().getClassLoader()); return true; } /** * Should this message be logged only (i.e. GUI). */ protected boolean isLogOnly() { return getLogOnlyFromPrefs(); } /** * Get's the "log.only" property from the preferences (also set's the default) * * @return true if errors should only be logged (no dialog will popup) */ private boolean getLogOnlyFromPrefs() { PreferencesWrapper prefs = ClientPrefs.getClient(); boolean logOnly = prefs.getBoolean("log.only", true); //$NON-NLS-1$ return logOnly; } void showDialog(String shortDescription, Throwable exception) { try { showDialogImpl(shortDescription, exception); } catch (Exception e) { logger.warn(e.getMessage(), e); } } void showDialogImpl(String shortDescription, Throwable exception) { List actionNames = new ArrayList(); for (String actionName : actionsByNameMap.keySet()) { actionNames.add(actionName); } if (actionNames.isEmpty()) return; JOptionPane optionPane = new JOptionPane(message, optionPaneOptionType, JOptionPane.DEFAULT_OPTION, null, actionNames.toArray()); JDialog dialog = optionPane.createDialog(null, title); dialog.setModal(true); dialog.setResizable(true); dialog.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); dialog.setLocationRelativeTo(null); do { dialog.setVisible(true); Object selectedValue = optionPane.getValue(); if (selectedValue == null) return; Action action = actionsByNameMap.get(selectedValue); try { boolean disposeDialog = action.doAction(title, exception); if (disposeDialog) { dialog.dispose(); } } catch (Exception e) { logger.warn(e.getMessage(), e); } } while (dialog.isDisplayable()); } /** * Sets the message that will be displayed. * * @param message * the message */ public final void setMessage(Object message) { this.message = message; } /** * Sets the window's title. * * @param title * the title */ public final void setTitle(String title) { this.title = title; } /** * Add the ignore button to the message window. * */ protected final void addActionIgnore() { addAction(new IgnoreAction()); } /** * Add the exit button to the message window. * */ protected final void addActionExit() { addAction(new ExitAction()); } /** * Add the show exception button to the message window. * */ protected final void addActionShowException() { addAction(new ShowExceptionAction()); } /** * Add a custom action. * * @return true if the action was added successfully. */ protected final boolean addAction(Action action) { boolean added = false; try { if (action == null) throw new IllegalArgumentException("action"); //$NON-NLS-1$ String actionName = (String) action.getValue(Action.NAME); if (StringUtils.isBlank(actionName)) throw new IllegalArgumentException("Action name not set"); //$NON-NLS-1$ actionsByNameMap.put(actionName, action); added = true; } catch (Exception e) { String msg = "Failed to add action"; //$NON-NLS-1$ logger.warn(msg + " - " + e.getMessage(), e); //$NON-NLS-1$ } return added; } }