Java tutorial
/* * Copyright (c) 2008, SQL Power Group Inc. * * This file is part of Wabit. * * Wabit is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * Wabit is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package ca.sqlpower.wabit.swingui; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Dimension; import java.awt.Font; import java.awt.Point; import java.awt.Toolkit; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.Transferable; import java.awt.dnd.DnDConstants; import java.awt.dnd.DropTarget; import java.awt.dnd.DropTargetDragEvent; import java.awt.dnd.DropTargetDropEvent; import java.awt.dnd.DropTargetEvent; import java.awt.dnd.DropTargetListener; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; 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.awt.event.WindowListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.lang.reflect.Method; import java.net.URI; import java.net.URISyntaxException; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.ArrayList; import java.util.EnumSet; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import java.util.prefs.BackingStoreException; import java.util.prefs.Preferences; import java.util.regex.Pattern; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.jmdns.JmDNS; import javax.naming.NamingException; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.BorderFactory; import javax.swing.ButtonGroup; import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.InputMap; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JRadioButtonMenuItem; import javax.swing.JScrollPane; import javax.swing.JSpinner; import javax.swing.JSplitPane; import javax.swing.JTextPane; import javax.swing.JToolBar; import javax.swing.JTree; import javax.swing.KeyStroke; import javax.swing.SwingConstants; import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.swing.WindowConstants; import javax.swing.border.EmptyBorder; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; import javax.swing.text.SimpleAttributeSet; import javax.swing.text.StyleConstants; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.TreeModel; import javax.swing.tree.TreePath; import net.miginfocom.swing.MigLayout; import org.apache.log4j.Logger; import org.olap4j.OlapConnection; import org.olap4j.PreparedOlapStatement; import ca.sqlpower.architect.ArchitectUtils; import ca.sqlpower.enterprise.client.Grant; import ca.sqlpower.enterprise.client.Group; import ca.sqlpower.enterprise.client.SPServerInfo; import ca.sqlpower.enterprise.client.User; import ca.sqlpower.object.AbstractPoolingSPListener; import ca.sqlpower.object.SPListener; import ca.sqlpower.object.SPObject; import ca.sqlpower.object.SPVariableHelper; import ca.sqlpower.sql.DataSourceCollection; import ca.sqlpower.sql.JDBCDataSource; import ca.sqlpower.sql.Olap4jDataSource; import ca.sqlpower.sql.PlDotIni; import ca.sqlpower.sql.SPDataSource; import ca.sqlpower.sqlobject.SQLDatabase; import ca.sqlpower.sqlobject.SQLObject; import ca.sqlpower.sqlobject.SQLObjectException; import ca.sqlpower.sqlobject.SQLObjectRuntimeException; import ca.sqlpower.swingui.MemoryMonitor; import ca.sqlpower.swingui.RecentMenu; import ca.sqlpower.swingui.SPSUtils; import ca.sqlpower.swingui.SPSwingWorker; import ca.sqlpower.swingui.Search; import ca.sqlpower.swingui.SearchTextField; import ca.sqlpower.swingui.StackedTabComponent; import ca.sqlpower.swingui.SwingUIUserPrompterFactory; import ca.sqlpower.swingui.SwingWorkerRegistry; import ca.sqlpower.swingui.StackedTabComponent.StackedTab; import ca.sqlpower.swingui.action.ForumAction; import ca.sqlpower.swingui.action.OpenUrlAction; import ca.sqlpower.swingui.event.SessionLifecycleEvent; import ca.sqlpower.swingui.event.SessionLifecycleListener; import ca.sqlpower.util.SQLPowerUtils; import ca.sqlpower.util.UserPrompter; import ca.sqlpower.util.UserPrompterFactory; import ca.sqlpower.util.UserPrompter.UserPromptOptions; import ca.sqlpower.util.UserPrompter.UserPromptResponse; import ca.sqlpower.validation.swingui.StatusComponent; import ca.sqlpower.wabit.ServerListListener; import ca.sqlpower.wabit.WabitDataSource; import ca.sqlpower.wabit.WabitObject; import ca.sqlpower.wabit.WabitSession; import ca.sqlpower.wabit.WabitSessionContext; import ca.sqlpower.wabit.WabitSessionContextImpl; import ca.sqlpower.wabit.WabitVersion; import ca.sqlpower.wabit.WabitWorkspace; import ca.sqlpower.wabit.dao.OpenWorkspaceXMLDAO; import ca.sqlpower.wabit.dao.WorkspaceXMLDAO; import ca.sqlpower.wabit.enterprise.client.ReportTask; import ca.sqlpower.wabit.enterprise.client.WabitClientSession; import ca.sqlpower.wabit.enterprise.client.security.CachingWabitAccessManager; import ca.sqlpower.wabit.enterprise.client.security.WabitAccessManager; import ca.sqlpower.wabit.image.WabitImage; import ca.sqlpower.wabit.report.CellSetRenderer; import ca.sqlpower.wabit.report.ChartRenderer; import ca.sqlpower.wabit.report.ContentBox; import ca.sqlpower.wabit.report.ImageRenderer; import ca.sqlpower.wabit.report.WabitLabel; import ca.sqlpower.wabit.report.Layout; import ca.sqlpower.wabit.report.Report; import ca.sqlpower.wabit.report.ReportContentRenderer; import ca.sqlpower.wabit.report.ResultSetRenderer; import ca.sqlpower.wabit.report.Template; import ca.sqlpower.wabit.report.chart.Chart; import ca.sqlpower.wabit.rs.olap.OlapQuery; import ca.sqlpower.wabit.rs.query.QueryCache; import ca.sqlpower.wabit.swingui.action.AboutAction; import ca.sqlpower.wabit.swingui.action.CheckForUpdateAction; import ca.sqlpower.wabit.swingui.action.CloseWorkspaceAction; import ca.sqlpower.wabit.swingui.action.DeleteWabitServerWorkspaceAction; import ca.sqlpower.wabit.swingui.action.ImportWorkspaceAction; import ca.sqlpower.wabit.swingui.action.NewChartAction; import ca.sqlpower.wabit.swingui.action.NewGroupAction; import ca.sqlpower.wabit.swingui.action.NewImageAction; import ca.sqlpower.wabit.swingui.action.NewOLAPQueryAction; import ca.sqlpower.wabit.swingui.action.NewQueryAction; import ca.sqlpower.wabit.swingui.action.NewReportAction; import ca.sqlpower.wabit.swingui.action.NewReportTaskAction; import ca.sqlpower.wabit.swingui.action.NewServerWorkspaceAction; import ca.sqlpower.wabit.swingui.action.NewTemplateAction; import ca.sqlpower.wabit.swingui.action.NewUserAction; import ca.sqlpower.wabit.swingui.action.NewWorkspaceAction; import ca.sqlpower.wabit.swingui.action.OpenWorkspaceAction; import ca.sqlpower.wabit.swingui.action.RenameWabitServerWorkspaceAction; import ca.sqlpower.wabit.swingui.action.SaveWorkspaceAction; import ca.sqlpower.wabit.swingui.action.SaveWorkspaceAsAction; import ca.sqlpower.wabit.swingui.action.ShowWabitApplicationPreferencesAction; import ca.sqlpower.wabit.swingui.chart.ChartPanel; import ca.sqlpower.wabit.swingui.enterprise.GroupPanel; import ca.sqlpower.wabit.swingui.enterprise.ReportTaskPanel; import ca.sqlpower.wabit.swingui.enterprise.UserPanel; import ca.sqlpower.wabit.swingui.enterprise.action.RefreshWorkspaceAction; import ca.sqlpower.wabit.swingui.olap.OlapQueryPanel; import ca.sqlpower.wabit.swingui.report.LayoutPanel; import ca.sqlpower.wabit.swingui.report.ReportPanel; import ca.sqlpower.wabit.swingui.tree.SmartTreeTransferable; import ca.sqlpower.wabit.swingui.tree.WorkspaceTreeCellRenderer; import ca.sqlpower.wabit.swingui.tree.WorkspaceTreeModel; import com.jgoodies.forms.builder.DefaultFormBuilder; import com.jgoodies.forms.layout.FormLayout; import edu.umd.cs.findbugs.annotations.OverrideMustInvoke; /** * This is the swing version of the WabitSessionContext. Swing specific operations for * the context will be done in this implementation */ public class WabitSwingSessionContextImpl implements WabitSwingSessionContext { private WabitAccessManager accessManager = null; private User currentUser = null; private static final Logger logger = Logger.getLogger(WabitSwingSessionContextImpl.class); private static final String plDotIniPath() throws SQLObjectException { Preferences prefs = Preferences.userNodeForPackage(WabitSessionContextImpl.class); String plIniPath = ArchitectUtils .checkForValidPlDotIni(prefs.get(WabitSessionContextImpl.PREFS_PL_INI_PATH, null), "Wabit"); prefs.put(WabitSessionContextImpl.PREFS_PL_INI_PATH, plIniPath); return plIniPath; } private static final DataSourceCollection<SPDataSource> loadPlDotIni() throws SQLObjectException { String path = plDotIniPath(); DataSourceCollection<SPDataSource> dataSources = new PlDotIni(); String iniToLoad = "ca/sqlpower/sql/default_database_types.ini"; try { logger.debug("Reading PL.INI defaults"); dataSources.read(WabitSwingSessionContextImpl.class.getClassLoader().getResourceAsStream(iniToLoad)); iniToLoad = "/ca/sqlpower/demodata/example_database.ini"; dataSources.read(WabitSessionContextImpl.class.getResourceAsStream(iniToLoad)); } catch (IOException e) { throw new ca.sqlpower.sqlobject.SQLObjectRuntimeException( new SQLObjectException("Failed to read system resource " + iniToLoad, e)); } try { if (dataSources != null) { logger.debug("Reading new PL.INI instance"); dataSources.read(new File(path)); } } catch (IOException e) { throw new SQLObjectRuntimeException( new SQLObjectException("Failed to read pl.ini at \"" + path + "\"", e)); } return dataSources; } /** * Action map key for the "select search tab" (accel-F) action. */ private static final String SEARCH_TAB = "Select Search Tab"; public static final String EXAMPLE_WORKSPACE_URL = "/ca/sqlpower/wabit/example_workspace.wabit"; /** * The regex to split all of the workspaces on when they are saved to prefs */ private static final String WORKSPACE_PREFS_REGEX = ";"; /** * This icon is at the top left of every frame. */ public static final ImageIcon FRAME_ICON = new ImageIcon( WabitSwingSessionImpl.class.getResource("/icons/wabit-16.png")); /** * The icon for the "Open Demonstration Workspace" button. */ private static final Icon OPEN_DEMO_ICON = new ImageIcon( WabitSwingSessionContextImpl.class.getClassLoader().getResource("icons/workspace-demo-16.png")); public static final Icon OPEN_WABIT_ICON = new ImageIcon( WabitSwingSessionContextImpl.class.getClassLoader().getResource("icons/workspace-16.png")); /** * Default width of the session tree (left hand side of the main divider). */ private static final int DEFAULT_TREE_WIDTH = 220; private static final String MAIN_DIVIDER_LOCATION = "WabitSwingSessionContext.mainDividerLocation"; /** * Prefs key for source list style. Value is the name of one of the * {@link SourceListStyle} enum constants. */ private static final String SOURCE_LIST_STYLE = "WabitSwingSessionContext.sourceListStyle"; /** * Prefs key for the location of the source list split pane divider. */ private static final String SOURCE_LIST_DIVIDER_LOCATON = "WabitSwingSessionContext.sourceList.dividerLoc2"; private static final Double SOURCE_LIST_DEFAULT_DIVIDER_LOCATON = 0.75d; /** * Prefs key for source list dialog's x coordinate. */ private static final String SOURCE_LIST_DIALOG_X = "WabitSwingSessionContext.sourceListDialog.x"; /** * Prefs key for source list dialog's y coordinate. */ private static final String SOURCE_LIST_DIALOG_Y = "WabitSwingSessionContext.sourceListDialog.y"; /** * Prefs key for source list dialog's width. */ private static final String SOURCE_LIST_DIALOG_WIDTH = "WabitSwingSessionContext.sourceListDialog.width"; /** * Prefs key for source list dialog's height. */ private static final String SOURCE_LIST_DIALOG_HEIGHT = "WabitSwingSessionContext.sourceListDialog.height"; private final class MaximizeEditorAction extends AbstractAction implements PropertyChangeListener { private boolean maximized = false; private int originalDividerLocation; private MaximizeEditorAction() { putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_PERIOD, InputEvent.CTRL_DOWN_MASK)); updateName(); } private void updateName() { if (maximized) { putValue(NAME, "Reveal Tree"); } else { putValue(NAME, "Maximize Editor"); } } public void actionPerformed(ActionEvent e) { if (maximized) { wabitPane.setDividerLocation(originalDividerLocation); maximized = false; } else { originalDividerLocation = wabitPane.getDividerLocation(); wabitPane.setDividerLocation(0); maximized = true; wabitPane.addPropertyChangeListener("dividerLocation", this); } updateName(); } public void propertyChange(PropertyChangeEvent evt) { SQLPowerUtils.logPropertyChange(logger, "Split Pane Moved", evt); int newLoc = ((Number) evt.getNewValue()).intValue(); /* the split pane may readjust itself when we try to move it all the * way left. We'll ignore any movements that leave it smaller than it * was initially. */ if (newLoc >= originalDividerLocation) { maximized = false; wabitPane.removePropertyChangeListener("dividerLocation", this); updateName(); } } public boolean isEditorMaximized() { return maximized; } public int getOriginalDividerLocation() { return originalDividerLocation; } } /** * All the ways the source list can be present in the GUI. */ public enum SourceListStyle { /** * Source list appears in a split pane to the right of the editor. */ DOCKED("Docked") { @Override void apply(final WabitSwingSessionContextImpl context, WabitPanel wabitPanel) { super.apply(context, wabitPanel); context.sourceListDialog.setVisible(false); JComponent sourceComponent = wabitPanel.getSourceComponent(); JComponent panel = wabitPanel.getPanel(); if (sourceComponent != null) { final JSplitPane sp = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); sp.setBorder(BorderFactory.createEmptyBorder()); sp.setLeftComponent(panel); sp.setRightComponent(sourceComponent); SwingUtilities.invokeLater(new Runnable() { public void run() { sp.setDividerLocation(WabitSwingSessionContextImpl.prefs.getDouble( WabitSwingSessionContextImpl.SOURCE_LIST_DIVIDER_LOCATON, WabitSwingSessionContextImpl.SOURCE_LIST_DEFAULT_DIVIDER_LOCATON)); sp.addPropertyChangeListener("dividerLocation", new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent e) { Number value = (Number) e.getNewValue(); WabitSwingSessionContextImpl.prefs.putDouble( WabitSwingSessionContextImpl.SOURCE_LIST_DIVIDER_LOCATON, value.doubleValue() / sp.getBounds().width); } }); } }); safelySetRightComponent(context.wabitPane, wabitPanel.getToolbar(), sp); } else { safelySetRightComponent(context.wabitPane, wabitPanel.getToolbar(), panel); } } }, /** * Source list appears in its own dialog. */ FLOATING("Floating") { @Override void apply(WabitSwingSessionContextImpl context, WabitPanel wabitPanel) { super.apply(context, wabitPanel); JComponent sourceComponent = wabitPanel.getSourceComponent(); if (sourceComponent != null) { context.sourceListDialog.setContentPane(sourceComponent); context.sourceListDialog.setTitle("Source List - " + wabitPanel.getTitle()); context.sourceListDialog.setVisible(true); } else { context.sourceListDialog.setVisible(false); } safelySetRightComponent(context.wabitPane, wabitPanel.getToolbar(), wabitPanel.getPanel()); } }, /** * Source list is not visible at all. */ HIDDEN("Hidden") { @Override void apply(WabitSwingSessionContextImpl context, WabitPanel wabitPanel) { super.apply(context, wabitPanel); context.sourceListDialog.setVisible(false); safelySetRightComponent(context.wabitPane, wabitPanel.getToolbar(), wabitPanel.getPanel()); } }; private final String localizedName; private SourceListStyle(String localizedName) { this.localizedName = localizedName; } @OverrideMustInvoke void apply(WabitSwingSessionContextImpl context, WabitPanel wabitPanel) { context.sourceListStyle = this; prefs.put(SOURCE_LIST_STYLE, name()); } /** * Creates a JMenuItem for this source list style. When the menu item is * activated, it applies the appropriate style to the given context. * * @param context * The session context the menu item belongs to (and operates * upon). * @param sourceListStyleGroup * The button group to add the menu item to (for mutual * exclusion). * @return */ public JMenuItem makeMenuItem(final WabitSwingSessionContextImpl context, ButtonGroup sourceListStyleGroup) { JRadioButtonMenuItem mi = new JRadioButtonMenuItem(getLocalizedName() + " Source List"); sourceListStyleGroup.add(mi); if (context.sourceListStyle == this) { // doing this before adding the action listener seems wise mi.setSelected(true); } mi.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { apply(context, context.currentEditorPanel); } }); return mi; } /** * Returns the localized text associated with this source list style. */ public String getLocalizedName() { return localizedName; } /** * Changes the right-hand component of the given scroll panel to * <code>c</code> without moving the divider location. * * @param sp * The split pane whose right component to replace with c. * Null not permitted. * @param toolBar * The toolbar that should appear at the top of the * right-hand component. Null means not to show a toolbar. * @param c * The component to add to sp. Null not permitted. */ protected void safelySetRightComponent(@Nonnull JSplitPane sp, @Nullable JToolBar toolBar, @Nonnull JComponent c) { int oldDividerLoc = sp.getDividerLocation(); if (toolBar != null) { JPanel p = new JPanel(new BorderLayout()); p.add(toolBar, BorderLayout.NORTH); p.add(c, BorderLayout.CENTER); sp.setRightComponent(p); } else { sp.setRightComponent(c); } sp.setDividerLocation(oldDividerLoc); } } /** * The current source list preference. Never null, but doesn't apply when * the current editor panel is null or the current editor panel has a null * source list component. */ private SourceListStyle sourceListStyle; /** * When the source list is floating, it appears in this dialog. This dialog * is never null (unless the session is headless), but it will be hidden * when there is no floating source list to display. It gets disposed when * this session context is closed. */ private final JDialog sourceListDialog; /** * This is a simple {@link SwingWorkerRegistry} implementation for the * context to track workers involved with loading files. It would be useful * if the {@link OpenWorkspaceXMLDAO} created the session(s) to load into * before creating the thread to do the loading. Then the loading thread * could register with the session and we could remove this implementation. */ private static class LoadingSwingWorkerRegistry implements SwingWorkerRegistry { private final List<SPSwingWorker> activeWorkers = new ArrayList<SPSwingWorker>(); public void registerSwingWorker(SPSwingWorker worker) { activeWorkers.add(worker); } public void removeSwingWorker(SPSwingWorker worker) { activeWorkers.remove(worker); } public void close() { for (SPSwingWorker worker : activeWorkers) { worker.kill(); } } } /** * This is the {@link SwingWorkerRegistry} responsible for tracking * the threads used in loading sessions that don't have a session yet. */ private final LoadingSwingWorkerRegistry loadingRegistry = new LoadingSwingWorkerRegistry(); /** * This listener is attached to the context's frame to call close * when the frame is going away. This way we can prompt to save * changes and cleanup. */ private final WindowListener windowClosingListener = new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { close(); }; }; /** * The core session context that this swing session context delegates its * "core" operations to. */ private final WabitSessionContext delegateContext; private final UserPrompterFactory upf; /** * Maps each currently-registered session to its tab in the stacked tab pane * so that tabs can be removed reliably from the tabbed pane when their * corresponding sessions are closed. */ private final Map<WabitSwingSession, StackedTab> sessionTabs = new HashMap<WabitSwingSession, StackedTab>(); /** * This is a preference that stores the absolute file location of each file * that should be started when Wabit starts up. Each file name is separated * by the {@link #WORKSPACE_PREFS_REGEX}. To get the files that need to be * opened when Wabit starts get the string stored in prefs with this key and * split the string by the regex. Each file stored in the prefs should be * loaded and if an exception occurs stop loading. If no files are listed * the welcome screen should appear. */ private static final String PREFS_OPEN_WORKSPACES = "OPEN_WORKSPACES"; public static final ForumAction FORUM_ACTION = new ForumAction( new ImageIcon(StatusComponent.class.getClassLoader().getResource("icons/wabit-24px.png")), "Go to Wabit support forum"); /** * This is the prefs for the entire context. */ private final static Preferences prefs = Preferences.userNodeForPackage(WabitSwingSessionContextImpl.class); /** * This is the main frame of the context. */ private final JFrame frame; /** * This is the main split pane that shows the tree on the left of the split * and the current editor on the right of the split. */ private final JSplitPane wabitPane; /** * This action will display an about dialog that is parented to the {@link #frame}. */ private AbstractAction aboutAction; /** * This action displays a dialog for modifying application preferences */ private final Action prefsAction; /** * This is the status label at the bottom of one of the windows. */ private final JLabel statusLabel; /** * This is the current WabitPanel that provides the editor component and the * source list. Must be set via {@link #createEditorPanel(WabitObject)}. */ private WabitPanel currentEditorPanel; /** * This tabbed pane contains all of the trees for each swing session in the context. */ private final StackedTabComponent stackedTabPane; /** * This is the limit of all result sets in Wabit. Changing this spinner * will cause cached result sets to be flushed. */ private final JSpinner rowLimitSpinner; /** * This object will fire property changes for the context when values change. */ private final PropertyChangeSupport pcs = new PropertyChangeSupport(this); /** * This welcome screen's panel will be displayed when there is no active session * available. */ private final WabitWelcomeScreen welcomeScreen = new WabitWelcomeScreen(this); /** * This action will close all of the open sessions and, if successful, close the app. */ private final Action exitAction = new AbstractAction("Exit") { public void actionPerformed(ActionEvent e) { close(); } }; public static final Icon NEW_ICON = new ImageIcon( WabitSwingSessionContextImpl.class.getClassLoader().getResource("icons/32x32/new.png")); public static final Icon OPEN_ICON = new ImageIcon( WabitSwingSessionContextImpl.class.getClassLoader().getResource("icons/32x32/open.png")); /** * An action that creates a popup with an open and import option */ private final Action openAction = new AbstractAction("Open", OPEN_ICON) { public void actionPerformed(ActionEvent e) { if (e.getSource() instanceof JButton) { JButton source = (JButton) e.getSource(); JPopupMenu popupMenu = new JPopupMenu(); popupMenu.add(new OpenWorkspaceAction(WabitSwingSessionContextImpl.this)); WabitSwingSession activeSession = getActiveSwingSession(); if (activeSession != null) { popupMenu.add(new ImportWorkspaceAction(WabitSwingSessionContextImpl.this)); } popupMenu.show(source, 0, source.getHeight()); } } }; public static final Icon SAVE_ICON = new ImageIcon( WabitSwingSessionContextImpl.class.getClassLoader().getResource("icons/32x32/save.png")); /** * An action that saves the current active workspace. */ private final Action saveAction = new AbstractAction("Save", SAVE_ICON) { public void actionPerformed(ActionEvent e) { SaveWorkspaceAction.saveAllSessions(WabitSwingSessionContextImpl.this); } }; private MaximizeEditorAction maximizeEditorAction = new MaximizeEditorAction(); /** * This is the model of the search JTree, it is just a default tree model * that we add {@link DefaultMutableTreeNode}s to. */ private DefaultTreeModel searchTreeModel; /** * This is the root of our searchTree, it is never visible and it is just a * {@link DefaultMutableTreeNode} with null in it. */ private DefaultMutableTreeNode searchTreeRoot; /** * This is the text area that a user types into to search. */ private final SearchTextField searchTextArea = new SearchTextField(new SearchWabitTree(), 0); /** * This is the tree that displays a user's search results. */ private final JTree searchTree = new JTree(); /** * This action selects the search tab which will let users search all of * the workspaces at the same time. */ private final Action selectSearchTabAction = new AbstractAction("Search") { public void actionPerformed(ActionEvent arg0) { // Choose the search tab. This, of course, assumes that the // Search Tab is always at index 0. stackedTabPane.setSelectedIndex(0); // This seems to improve the chances of requestFocusInWindow succeeding... SwingUtilities.invokeLater(new Runnable() { public void run() { searchTextArea.getTextField().requestFocusInWindow(); } }); } }; /** * This is the cell renderer in the search tree, it basically just gets * the object out of the {@link DefaultMutableTreeNode} class that is in * the tree and passes that object (which will be something that the * {@link WorkspaceTreeCellRenderer} knows how to deal with) to the * {@link WorkspaceTreeCellRenderer} so the search tree looks exactly * like the Workspace tree. */ private class SearchTreeCellRenderer extends WorkspaceTreeCellRenderer { @Override public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) { Object objectToRender = ((DefaultMutableTreeNode) value).getUserObject(); if (objectToRender == null) { //this means its the root node, it doesn't matter if we pass the cell //renderer something that it can't really render because the root node //is hidden objectToRender = value; } return super.getTreeCellRendererComponent(tree, objectToRender, sel, expanded, leaf, row, hasFocus); } } private JTextPane explainText = new JTextPane(); private JScrollPane searchScrollPane = new JScrollPane(); /** * If true then the frame is not going to be shown and the context should act * as though there is no UI. */ private final boolean headless; /** * @param delegateContext * The context which this swing session delegates all its core * {@link WabitSessionContext} operations to. This core session * will also have joint ownership of all sessions registered with * this swing context. As such, the delegate context must not * contain any registered child sessions of its own when passed * in. If it does, an IllegalStateException will be thrown by * this constructor. * @param headless * Set to true to not create any GUI objects when the context * starts. This stops the welcome screen from being created. */ public WabitSwingSessionContextImpl(WabitSessionContext delegate, boolean headless) throws IOException, SQLObjectException { this(delegate, headless, new SwingUIUserPrompterFactory(null)); } /** * @param delegateContext * The context which this swing session delegates all its core * {@link WabitSessionContext} operations to. This core session * will also have joint ownership of all sessions registered with * this swing context. As such, the delegate context must not * contain any registered child sessions of its own when passed * in. If it does, an IllegalStateException will be thrown by * this constructor. * @param terminateWhenLastSessionCloses * Set to true if the context should stop the app when the last * session is closed. If false the app will have to be closed in * a way other than closing all of the sessions. * @param headless * Set to true to not create any GUI objects when the context * starts. This stops the welcome screen from being created. * @param upf * A user prompter factory that will be used to display * appropriate prompts to the user. This can be set if a * non-default prompt is desired for places like saving. This * cannot be null. */ public WabitSwingSessionContextImpl(WabitSessionContext delegateContext, boolean headless, UserPrompterFactory upf) throws IOException, SQLObjectException { this.upf = upf; this.delegateContext = delegateContext; if (delegateContext.getSessions().size() > 0) { throw new IllegalStateException("Can't establish a new SwingSessionContext around a delegate " + "which already contains existing child sessions of its own " + "(in other words, the delegate must be empty but it isn't)"); } this.headless = headless; Thread.setDefaultUncaughtExceptionHandler(new ExceptionHandler()); frame = new JFrame("SQL Power Wabit " + WabitVersion.VERSION + " - " + getName()); try { sourceListStyle = SourceListStyle.valueOf(prefs.get(SOURCE_LIST_STYLE, SourceListStyle.DOCKED.name())); } catch (IllegalArgumentException e) { logger.warn("Bad value for " + SOURCE_LIST_STYLE + " preference. Defaulting."); sourceListStyle = SourceListStyle.DOCKED; } wabitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); statusLabel = new JLabel(); stackedTabPane = new StackedTabComponent(); stackedTabPane.setDropTarget(new DropTarget(stackedTabPane, treeTabDropTargetListener)); InputMap inputMap = stackedTabPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_F, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()), SEARCH_TAB); stackedTabPane.getActionMap().put(SEARCH_TAB, selectSearchTabAction); searchTreeRoot = new DefaultMutableTreeNode(); searchTreeModel = new DefaultTreeModel(searchTreeRoot); searchTree.setModel(searchTreeModel); searchTree.setCellRenderer(new SearchTreeCellRenderer()); searchTree.setRootVisible(false); searchTree.setShowsRootHandles(true); JPanel searchPanel = new JPanel(new BorderLayout()); //all this is to have a centered text field SimpleAttributeSet attribs = new SimpleAttributeSet(); StyleConstants.setAlignment(attribs, StyleConstants.ALIGN_CENTER); explainText.setParagraphAttributes(attribs, true); searchPanel.add(searchTextArea.getPanel(), BorderLayout.NORTH); explainText.setText( "For performance reasons, OLAP " + "connections and unvisited relational database objects (schemas," + "tables, columns) will not be included in searches."); explainText.setEditable(false); explainText.setMinimumSize(new Dimension(10, 10)); explainText.setBackground(Color.WHITE); Font defaultFont = UIManager.getLookAndFeel().getDefaults().getFont("Label.font"); explainText.setFont(defaultFont); searchScrollPane.setViewportView(explainText); searchPanel.add(searchScrollPane, BorderLayout.CENTER); stackedTabPane.addTab("Search", searchPanel, false); TreeSelectionListener searchTreeSelectionListener = new TreeSelectionListener() { public void valueChanged(TreeSelectionEvent e) { TreePath pathToSelection = e.getNewLeadSelectionPath(); if (pathToSelection == null) return; DefaultMutableTreeNode node = (DefaultMutableTreeNode) pathToSelection.getLastPathComponent(); Object userObject = node.getUserObject(); if (userObject instanceof SPObject) { SPObject wo = (SPObject) userObject; while (wo.getParent() != null) { wo = wo.getParent(); } WabitWorkspace workspace = (WabitWorkspace) wo; setActiveSession(workspace.getSession()); JTree tree = ((WabitSwingSessionImpl) workspace.getSession()).getTree(); TreePath path = ((WorkspaceTreeModel) tree.getModel()) .createTreePathForObject((WabitObject) userObject); if (path == null) { // the item probably got deleted since the search started return; } tree.expandPath(path); tree.setSelectionPath(path); workspace.setEditorPanelModel((WabitObject) userObject); setEditorPanel(); } } }; searchTree.addTreeSelectionListener(searchTreeSelectionListener); rowLimitSpinner = new JSpinner(); final JSpinner.NumberEditor rowLimitEditor = new JSpinner.NumberEditor(getRowLimitSpinner()); getRowLimitSpinner().setEditor(rowLimitEditor); getRowLimitSpinner().setValue(1000); rowLimitSpinner.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent e) { //TODO the general row limit will move to the preferences panel //and each query can have an individual row limit seperate from //this row limit. for (WabitSession session : getSessions()) { for (QueryCache query : session.getWorkspace().getQueries()) { query.setRowLimit((Integer) rowLimitSpinner.getValue()); } } } }); if (!headless) { prefsAction = new ShowWabitApplicationPreferencesAction(frame, getPrefs()); sourceListDialog = new JDialog(frame); buildUI(); // doing this after buildUI() so we can base defaults on frame's bounds sourceListDialog.setBounds( prefs.getInt(SOURCE_LIST_DIALOG_X, frame.getX() + frame.getWidth() - DEFAULT_TREE_WIDTH), prefs.getInt(SOURCE_LIST_DIALOG_Y, frame.getY()), prefs.getInt(SOURCE_LIST_DIALOG_WIDTH, DEFAULT_TREE_WIDTH), prefs.getInt(SOURCE_LIST_DIALOG_HEIGHT, frame.getHeight() / 2)); sourceListDialog.addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { sourceListDialog.setVisible(false); SourceListStyle.DOCKED.apply(WabitSwingSessionContextImpl.this, currentEditorPanel); } }); macOSXRegistration(); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { CheckForUpdateAction.checkForUpdate(frame); } }); } else { sourceListDialog = null; prefsAction = null; } } /** * This is the class that actually does the search on the search tree. * It implements 'Search' which is something used in the library to have * a generic search text box that looks nice and has regular expressions. */ private class SearchWabitTree implements Search { public void doSearch(Pattern p, boolean matchExactly) { String searchString = searchTextArea.getText().trim(); //clear the tree and start over searchTreeRoot = new DefaultMutableTreeNode(); searchTreeModel = new DefaultTreeModel(searchTreeRoot); searchTree.setModel(searchTreeModel); //this just makes sure that we don't see an all workspaces tree when there is //no text showing we are not sure if we actually want this if (searchString.equals("")) { searchScrollPane.setViewportView(explainText); } else { searchScrollPane.setViewportView(searchTree); } //get all the tree models we can search List<TreeModel> searchableModels = new ArrayList<TreeModel>(); for (WabitSession session : getSessions()) { if (!(session instanceof WabitSwingSession)) { throw new IllegalStateException("Found non swing session in swing session context!"); } JTree tree = ((WabitSwingSession) session).getTree(); searchableModels.add(tree.getModel()); } for (TreeModel originalModel : searchableModels) { WorkspaceTreeModel model = (WorkspaceTreeModel) originalModel; //search the tree ArrayList<Object> rootTreePath = new ArrayList<Object>(); rootTreePath.add(model.getRoot()); List<List<Object>> matchedTreePaths = searchTree(rootTreePath, model, p, matchExactly); //add everything into the tree if it's not already there for (List<Object> treePath : matchedTreePaths) { DefaultMutableTreeNode lastObject = (DefaultMutableTreeNode) searchTreeModel.getRoot(); for (Object object : treePath) { int indexOfChild = -1; for (int i = 0; i < lastObject.getChildCount(); i++) { DefaultMutableTreeNode childNode = (DefaultMutableTreeNode) lastObject.getChildAt(i); if (childNode.getUserObject().equals(object)) { indexOfChild = i; break; } } if (indexOfChild == -1) { DefaultMutableTreeNode currentNode = new DefaultMutableTreeNode(object); searchTreeModel.insertNodeInto(currentNode, lastObject, lastObject.getChildCount()); lastObject = currentNode; } else { if (treePath.indexOf(object) != (treePath.size() - 1)) { lastObject = (DefaultMutableTreeNode) searchTreeModel.getChild(lastObject, indexOfChild); } } } } //Show everything in the tree, we would do this as we go but it doesn't wan't to work. searchTree.expandPath(new TreePath(searchTreeModel.getRoot())); for (int i = 0; i < searchTree.getRowCount(); i++) { searchTree.expandRow(i); } } } } private boolean isSafeForSearchChild(Object currentObject) { if (currentObject instanceof SQLObject) { //only search populated tables if (!((SQLObject) currentObject).isPopulated()) { return false; } } else if (currentObject instanceof WabitDataSource) { SPDataSource ds = ((WabitDataSource) currentObject).getSPDataSource(); if (ds instanceof Olap4jDataSource) { //TODO there is no way of knowing whether an OLAP query is cached... //We should eventually make the search run in the background return false; } } return true; } /** * Recursive function which searches all of the objects' names in a tree * model for a given string. * * @param searchString * The string to search all the names of objects in the treemodel for * @param currentTreePath * The current path in the tree (since the function is recursive) * @param model * The tree model being searched * @return * Returns a WabitSwingSessionContextImpl.this.createConnelist of paths to all the tree objects in a model which have * a name which contains the searchString */ private List<List<Object>> searchTree(List<Object> currentTreePath, WorkspaceTreeModel model, Pattern p, boolean matchExactly) { Object currentObject = currentTreePath.get(currentTreePath.size() - 1); ArrayList<List<Object>> returnList = new ArrayList<List<Object>>(); if (isSafeForSearchChild(currentObject)) { for (int i = 0; i < model.getChildCount(currentObject); i++) { List<Object> childPath = new ArrayList<Object>(currentTreePath); childPath.add(model.getChild(currentObject, i)); returnList.addAll(searchTree(childPath, model, p, matchExactly)); } } if (currentObject instanceof WabitObject) { //It could be a FolderNode... WabitObject currentWO = (WabitObject) currentObject; String name = currentWO.getName(); if (currentWO instanceof ContentBox) { ReportContentRenderer content = ((ContentBox) currentWO).getContentRenderer(); if (content instanceof CellSetRenderer) { name = ((CellSetRenderer) content).getContent().getName(); } else if (content instanceof ResultSetRenderer) { name = ((ResultSetRenderer) content).getContent().getName(); } else if (content instanceof WabitLabel) { name = ((WabitLabel) content).getText(); } else if (content instanceof ChartRenderer) { name = ((ChartRenderer) content).getContent().getName(); } else if (content instanceof ImageRenderer) { name = ((ImageRenderer) content).getImage().getName(); } } if (matchExactly && p.matcher(name).matches()) { returnList.add(currentTreePath); } else if (!matchExactly && p.matcher(name).find()) { returnList.add(currentTreePath); } } else if (currentObject instanceof SQLObject) { String name = ((SQLObject) currentObject).getName(); if (matchExactly && p.matcher(name).matches()) { returnList.add(currentTreePath); } else if (!matchExactly && p.matcher(name).find()) { returnList.add(currentTreePath); } } return returnList; } public WabitSwingSession createSession() { final WabitSwingSessionImpl session = new WabitSwingSessionImpl(this, delegateContext.createSession()); return session; } private TreeTabDropTargetListener treeTabDropTargetListener = new TreeTabDropTargetListener(); /** * This is the droplistener on the tabbed pane which controls importing * and exporting between workspaces */ public class TreeTabDropTargetListener implements DropTargetListener { boolean isFileList = false; public void dragEnter(DropTargetDragEvent dtde) { //don't care } public void dragExit(DropTargetEvent dte) { //don't care } public void dragOver(DropTargetDragEvent dtde) { if (canImport(dtde.getCurrentDataFlavors())) { dtde.acceptDrag(dtde.getDropAction()); } else { dtde.rejectDrag(); } } public boolean canImport(DataFlavor[] transferFlavors) { for (DataFlavor dataFlavor : transferFlavors) { if (dataFlavor.equals(SmartTreeTransferable.WABIT_OBJECT_FLAVOUR_TO_EXPORT)) { isFileList = false; return true; } else if (dataFlavor.equals(DataFlavor.javaFileListFlavor)) { isFileList = true; return true; } } return false; } @SuppressWarnings("unchecked") public void drop(DropTargetDropEvent dtde) { Point mouseLocation = dtde.getLocation(); int tabIndex = stackedTabPane.indexAtLocation(mouseLocation.x, mouseLocation.y); if (tabIndex == -1 || tabIndex == 0) return; //The search tab should always have an index of 0 stackedTabPane.setSelectedIndex(tabIndex); Transferable transferable = dtde.getTransferable(); if (isFileList == true) { dtde.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE); DataFlavor dataFlavor = DataFlavor.javaFileListFlavor; List<File> transferData; try { transferData = (List<File>) transferable.getTransferData(dataFlavor); } catch (Exception e1) { throw new RuntimeException(e1); } for (File file : transferData) { FileInputStream input; try { input = new FileInputStream(file); } catch (FileNotFoundException e) { throw new RuntimeException(e); } OpenWorkspaceXMLDAO open = new OpenWorkspaceXMLDAO(WabitSwingSessionContextImpl.this, input, file.length(), getActiveSession().getDataSources()); open.importWorkspaces(getActiveSession()); } } else { DataFlavor dataFlavor = SmartTreeTransferable.WABIT_OBJECT_FLAVOUR_TO_EXPORT; Object[] transferData; try { transferData = (Object[]) transferable.getTransferData(dataFlavor); } catch (Exception e1) { throw new RuntimeException(e1); } List<WabitObject> wabitObjects = new ArrayList<WabitObject>(); for (int i = 0; i < transferData.length; i++) { wabitObjects.add((WabitObject) transferData[i]); } boolean importSuccessful = importIntoActiveSession(wabitObjects); dtde.dropComplete(importSuccessful); } } public void dropActionChanged(DropTargetDragEvent dtde) { //don't care } } /** * Helper method for dropping wabit objects onto the tree in Wabit. * The given Wabit objects will be added to the active session. If * there are data sources in the source the user will be prompted * that security information will be transferred to the other workspace. * <p> * This is package private for testing * * @param transferData An array of WabitObjects to add to the active workspace. * @return True if the drop was successful, false otherwise. */ boolean importIntoActiveSession(List<WabitObject> transferData) { ByteArrayOutputStream byteOut; List<WabitObject> wabitObjectsToExport = new ArrayList<WabitObject>(); Set<String> wabitDataSourcesBeingExported = new TreeSet<String>(); for (int i = 0; i < transferData.size(); i++) { WabitObject wo = (WabitObject) transferData.get(i); wabitObjectsToExport.add(wo); if (transferData.get(i) instanceof WabitDataSource) { wabitDataSourcesBeingExported.add(wo.getName()); } else { wabitDataSourcesBeingExported.add(getDatasourceDependencies(wo)); } } boolean shouldContinue = false; if (wabitDataSourcesBeingExported.size() > 0) { StringBuilder promptMessage = new StringBuilder( "WARNING: By performing the following export you are exposing your database\n" + " credentials to all users who have access to the workspace being dragged into. This \n" + "is safe if you are merely transferring the data to another local workspace but please use\n" + " caution before transferring these over to a server. The following connections are being transferred: \n"); for (String dataSource : wabitDataSourcesBeingExported) { promptMessage.append(dataSource).append("\n"); } UserPrompter up = upf.createUserPrompter(promptMessage.toString(), UserPromptType.BOOLEAN, UserPromptOptions.OK_CANCEL, UserPromptResponse.OK, true, "Continue", "Cancel"); UserPromptResponse response = up.promptUser(); shouldContinue = response.equals(UserPromptResponse.OK); } else { shouldContinue = true; } if (!shouldContinue) return false; byteOut = new ByteArrayOutputStream(); WorkspaceXMLDAO dao = new WorkspaceXMLDAO(byteOut, delegateContext); dao.save(wabitObjectsToExport); try { byteOut.flush(); } catch (IOException e) { throw new RuntimeException(e); } if (logger.isDebugEnabled()) { logger.debug("Importing " + byteOut.toString() + " to " + getActiveSession()); } byte[] outByteArray = byteOut.toByteArray(); ByteArrayInputStream input = new ByteArrayInputStream(outByteArray); OpenWorkspaceXMLDAO open = new OpenWorkspaceXMLDAO(this, input, outByteArray.length, getActiveSession().getDataSources()); open.importWorkspaces(getActiveSession()); return true; } /** * This is a recursive function which returns a \n delimited string of all of the * {@link WabitDataSource}s that the given {@link SPObject} is dependant on. */ private String getDatasourceDependencies(SPObject wo) { String wabitDatasources = ""; for (SPObject dependency : wo.getDependencies()) { if (dependency instanceof WabitDataSource) { wabitDatasources += (((WabitDataSource) dependency).getName() + "\n"); } else { wabitDatasources += getDatasourceDependencies(dependency); } } return wabitDatasources; } public WabitSwingSession createServerSession(SPServerInfo serverInfo) { final WabitSwingSessionImpl session = new WabitSwingSessionImpl(this, delegateContext.createServerSession(serverInfo)); return session; } public RecentMenu createRecentMenu() { RecentMenu menu = new RecentMenu(this.getClass()) { @Override public void loadFile(String fileName) throws IOException { File file = new File(fileName); OpenWorkspaceAction.loadFiles(WabitSwingSessionContextImpl.this, file.toURI()); } }; menu.setText("Open Recent Workspace"); return menu; } /** * Handles the cleanup required when a child session has closed. */ private SessionLifecycleListener<WabitSession> childSessionLifecycleListener = new SessionLifecycleListener<WabitSession>() { public void sessionClosing(SessionLifecycleEvent<WabitSession> e) { WabitSession child = e.getSource(); StackedTabComponent.StackedTab tab = sessionTabs.get(child); if (tab == null) { throw new IllegalArgumentException("Session " + child + " is not in this context"); } int tabIndex = stackedTabPane.indexOfTab(tab); if (tabIndex == -1) { throw new IllegalArgumentException("Session " + child + " doesn't appear in the tabbed pane!"); } stackedTabPane.removeTabAt(tabIndex); sessionTabs.remove(child); child.getWorkspace().removeSPListener(nameChangeListener); } public void sessionOpening(SessionLifecycleEvent<WabitSession> e) { } }; /** * Registers this application in Mac OS X if we're running on that platform. * * <p>This code came from Apple's "OS X Java Adapter" example. */ @SuppressWarnings("unchecked") private void macOSXRegistration() { Action exitAction = new AbstractAction() { public void actionPerformed(ActionEvent e) { close(); } }; // Whether or not this is OS X, the three actions we're referencing must have been initialized by now. if (exitAction == null) throw new IllegalStateException("Exit action has not been initialized"); //$NON-NLS-1$ if (prefsAction == null) throw new IllegalStateException("Prefs action has not been initialized"); //$NON-NLS-1$ if (aboutAction == null) throw new IllegalStateException("About action has not been initialized"); //$NON-NLS-1$ if (isMacOSX()) { try { Class osxAdapter = ClassLoader.getSystemClassLoader() .loadClass("ca.sqlpower.architect.swingui.OSXAdapter"); //$NON-NLS-1$ // The main registration method. Takes quitAction, prefsAction, aboutAction. Class[] defArgs = { Action.class, Action.class, Action.class, Action.class }; Method registerMethod = osxAdapter.getDeclaredMethod("registerMacOSXApplication", defArgs); //$NON-NLS-1$ Object[] args = { exitAction, prefsAction, aboutAction, new OpenWorkspaceAction(this) }; registerMethod.invoke(osxAdapter, args); // The enable prefs method. Takes a boolean. defArgs = new Class[] { boolean.class }; Method prefsEnableMethod = osxAdapter.getDeclaredMethod("enablePrefs", defArgs); //$NON-NLS-1$ args = new Object[] { Boolean.TRUE }; prefsEnableMethod.invoke(osxAdapter, args); } catch (NoClassDefFoundError e) { // This will be thrown first if the OSXAdapter is loaded on a system without the EAWT // because OSXAdapter extends ApplicationAdapter in its def System.err.println("This version of Mac OS X does not support the Apple EAWT. " + "Application Menu handling has been disabled (" + e + ")"); //$NON-NLS-1$ //$NON-NLS-2$ } catch (ClassNotFoundException e) { // This shouldn't be reached; if there's a problem with the OSXAdapter we should get the // above NoClassDefFoundError first. System.err.println("This version of Mac OS X does not support the Apple EAWT. " + "Application Menu handling has been disabled (" + e + ")"); //$NON-NLS-1$ //$NON-NLS-2$ } catch (Exception e) { System.err.println("Exception while loading the OSXAdapter:"); //$NON-NLS-1$ e.printStackTrace(); } } } /** * Builds the GUI * @throws SQLObjectException */ private void buildUI() throws SQLObjectException { frame.setIconImage(FRAME_ICON.getImage()); frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); frame.addWindowListener(windowClosingListener); aboutAction = new AboutAction(frame); // this will be the frame's content pane JPanel cp = new JPanel(new BorderLayout()); JToolBar toolBar = new JToolBar(JToolBar.HORIZONTAL); toolBar.setFloatable(false); /** * Creates a popup menu with all the possible 'New <insert Wabit object * here>' options */ Action newAction = new AbstractAction("New", NEW_ICON) { public void actionPerformed(ActionEvent e) { if (e.getSource() instanceof JButton) { WabitSwingSession activeSession = getActiveSwingSession(); JButton source = (JButton) e.getSource(); JPopupMenu popupMenu = new JPopupMenu(); popupMenu.add(new NewWorkspaceAction(WabitSwingSessionContextImpl.this)); JMenu newWorkspaceServerSubMenu = createServerListMenu(frame, "New Server Workspace", new ServerListMenuItemFactory() { public JMenuItem createMenuEntry(SPServerInfo serviceInfo, Component dialogOwner) { return new JMenuItem(new NewServerWorkspaceAction(dialogOwner, WabitSwingSessionContextImpl.this, serviceInfo)); } }); if (activeSession != null) { objectsMenu(popupMenu, WabitWorkspace.class.getSimpleName(), null, newWorkspaceServerSubMenu, WabitAccessManager.Permission.CREATE); if (activeSession.getWorkspace().isSystemWorkspace()) { objectsMenu(popupMenu, User.class.getSimpleName(), null, new JMenuItem(new NewUserAction(activeSession)), WabitAccessManager.Permission.CREATE); objectsMenu(popupMenu, Group.class.getSimpleName(), null, new JMenuItem(new NewGroupAction(activeSession)), WabitAccessManager.Permission.CREATE); } else { objectsMenu(popupMenu, QueryCache.class.getSimpleName(), null, new JMenuItem(new NewQueryAction(activeSession)), WabitAccessManager.Permission.CREATE); objectsMenu(popupMenu, OlapQuery.class.getSimpleName(), null, new JMenuItem(new NewOLAPQueryAction(activeSession)), WabitAccessManager.Permission.CREATE); objectsMenu(popupMenu, Chart.class.getSimpleName(), null, new JMenuItem(new NewChartAction(activeSession)), WabitAccessManager.Permission.CREATE); objectsMenu(popupMenu, WabitImage.class.getSimpleName(), null, new JMenuItem(new NewImageAction(activeSession)), WabitAccessManager.Permission.CREATE); objectsMenu(popupMenu, Report.class.getSimpleName(), null, new JMenuItem(new NewReportAction(activeSession)), WabitAccessManager.Permission.CREATE); objectsMenu(popupMenu, Template.class.getSimpleName(), null, new JMenuItem(new NewTemplateAction(activeSession)), WabitAccessManager.Permission.CREATE); if (activeSession.getWorkspace().isServerWorkspace()) { objectsMenu(popupMenu, ReportTask.class.getSimpleName(), null, new JMenuItem(new NewReportTaskAction(activeSession)), WabitAccessManager.Permission.CREATE); } } } popupMenu.show(source, 0, source.getHeight()); } } }; JButton newButton = new JButton(newAction); newButton.setVerticalTextPosition(SwingConstants.BOTTOM); newButton.setHorizontalTextPosition(SwingConstants.CENTER); JButton openButton = new JButton(openAction); openButton.setVerticalTextPosition(SwingConstants.BOTTOM); openButton.setHorizontalTextPosition(SwingConstants.CENTER); JButton saveButton = new JButton(saveAction); saveButton.setVerticalTextPosition(SwingConstants.BOTTOM); saveButton.setHorizontalTextPosition(SwingConstants.CENTER); JButton refreshButton = new JButton(new RefreshWorkspaceAction(this)); refreshButton.setVerticalTextPosition(SwingConstants.BOTTOM); refreshButton.setHorizontalTextPosition(SwingConstants.CENTER); // OS X specific client properties to modify the button appearance. // This only seems to affect OS X 10.5 Leopard's buttons. newButton.putClientProperty("JButton.buttonType", "toolbar"); openButton.putClientProperty("JButton.buttonType", "toolbar"); saveButton.putClientProperty("JButton.buttonType", "toolbar"); refreshButton.putClientProperty("JButton.buttonType", "toolbar"); toolBar.add(newButton); toolBar.add(openButton); toolBar.add(saveButton); toolBar.add(refreshButton); JPanel leftPanel = new JPanel(new BorderLayout()); // this is checked in the constructor assert getSessions().isEmpty(); final ChangeListener tabChangeListener = new ChangeListener() { public void stateChanged(ChangeEvent e) { final int selectedIndex = stackedTabPane.getSelectedIndex(); if (stackedTabPane.indexOfTab("Search") == selectedIndex) return; if (selectedIndex >= 0) { setActiveSession((WabitSwingSession) getSessions().get(selectedIndex - 1)); setEditorPanel(); } } }; stackedTabPane.addChangeListener(tabChangeListener); JPanel logoPanel = new JPanel(new MigLayout("fill, ins 0, gap 0 0")); JLabel sqlPowerLogoLabel = SPSUtils.getSQLPowerLogoLabel(); sqlPowerLogoLabel.setOpaque(false); logoPanel.add(sqlPowerLogoLabel, "grow 0 0, push 0 0"); logoPanel.add(new JLabel(), "grow 100 0, push 100 0"); leftPanel.add(toolBar, BorderLayout.NORTH); leftPanel.add(stackedTabPane, BorderLayout.CENTER); leftPanel.add(logoPanel, BorderLayout.SOUTH); wabitPane.setLeftComponent(leftPanel); wabitPane.setDividerLocation(prefs.getInt(MAIN_DIVIDER_LOCATION, DEFAULT_TREE_WIDTH)); DefaultFormBuilder statusBarBuilder = new DefaultFormBuilder( new FormLayout("pref:grow, 4dlu, pref, 2dlu, max(50dlu; pref), 4dlu, pref")); statusBarBuilder.append(statusLabel); statusBarBuilder.append("Row Limit", getRowLimitSpinner()); MemoryMonitor memoryMonitor = new MemoryMonitor(); memoryMonitor.start(); JLabel memoryLabel = memoryMonitor.getLabel(); memoryLabel.setBorder(new EmptyBorder(0, 20, 0, 20)); statusBarBuilder.append(memoryLabel); cp.add(wabitPane, BorderLayout.CENTER); cp.add(statusBarBuilder.getPanel(), BorderLayout.SOUTH); JMenuBar menuBar = new JMenuBar(); JMenu fileMenu = new JMenu("File"); fileMenu.setMnemonic('f'); menuBar.add(fileMenu); fileMenu.add(new NewWorkspaceAction(this)); JMenu newWorkspaceServerSubMenu = createServerListMenu(frame, "New Server Workspace", new ServerListMenuItemFactory() { public JMenuItem createMenuEntry(SPServerInfo serviceInfo, Component dialogOwner) { return new JMenuItem(new NewServerWorkspaceAction(dialogOwner, WabitSwingSessionContextImpl.this, serviceInfo)); } }); fileMenu.add(newWorkspaceServerSubMenu); fileMenu.add(new OpenWorkspaceAction(this)); fileMenu.add(createRecentMenu()); fileMenu.addSeparator(); fileMenu.add(createServerListMenu(frame, "Log In to Wabit Server", new ServerListMenuItemFactory() { public JMenuItem createMenuEntry(SPServerInfo serviceInfo, Component dialogOwner) { return new JMenuItem( new LogInToServerAction(dialogOwner, serviceInfo, WabitSwingSessionContextImpl.this)); } })); JMenuItem openDemoMenuItem = new JMenuItem(new AbstractAction() { public void actionPerformed(ActionEvent e) { try { final URI resource = WabitSwingSessionContextImpl.class.getResource(EXAMPLE_WORKSPACE_URL) .toURI(); OpenWorkspaceAction.loadFiles(WabitSwingSessionContextImpl.this, resource); } catch (URISyntaxException ex) { throw new RuntimeException(ex); } } }); fileMenu.addSeparator(); openDemoMenuItem.setText("Open Demo Workspace"); openDemoMenuItem.setIcon(OPEN_DEMO_ICON); fileMenu.add(openDemoMenuItem); fileMenu.addSeparator(); fileMenu.add(new ImportWorkspaceAction(this)); fileMenu.addSeparator(); fileMenu.add(new SaveWorkspaceAction(this)); fileMenu.add(new SaveWorkspaceAsAction(this)); Icon saveAllIcon = new ImageIcon( WabitSwingSessionContextImpl.class.getClassLoader().getResource("icons/saveAll-16.png")); fileMenu.add(new AbstractAction("Save All", saveAllIcon) { public void actionPerformed(ActionEvent e) { SaveWorkspaceAction.saveAllSessions(WabitSwingSessionContextImpl.this); } }); fileMenu.addSeparator(); JMenuItem closeMenuItem = new JMenuItem(new CloseWorkspaceAction(this)); closeMenuItem.setAccelerator( KeyStroke.getKeyStroke(KeyEvent.VK_W, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); fileMenu.add(closeMenuItem); fileMenu.addSeparator(); JMenuItem databaseConnectionManager = new JMenuItem(new AbstractAction("Database Connection Manager...") { public void actionPerformed(ActionEvent e) { WabitSwingSession activeSwingSession = getActiveSwingSession(); if (activeSwingSession != null) { getActiveSwingSession().getDbConnectionManager().showDialog(getFrame()); } else { // XXX: Temporary fix until setActiveSession() fires // property change events so that we can disable this action // instead. JOptionPane.showMessageDialog(frame, "Please open a workspace first in order to see\n" + "which database connections are available"); } } }); fileMenu.add(databaseConnectionManager); if (!isMacOSX()) { fileMenu.addSeparator(); fileMenu.add(prefsAction); fileMenu.addSeparator(); fileMenu.add(exitAction); } JMenu editMenu = new JMenu("Edit"); editMenu.setMnemonic('e'); menuBar.add(editMenu); final JMenuItem searchMenuItem = new JMenuItem(selectSearchTabAction); searchMenuItem.setAccelerator( KeyStroke.getKeyStroke(KeyEvent.VK_F, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); editMenu.add(searchMenuItem); JMenu viewMenu = new JMenu("View"); viewMenu.setMnemonic('v'); menuBar.add(viewMenu); viewMenu.add(new JMenuItem(maximizeEditorAction)); viewMenu.addSeparator(); ButtonGroup sourceListStyleGroup = new ButtonGroup(); for (SourceListStyle sls : SourceListStyle.values()) { viewMenu.add(sls.makeMenuItem(this, sourceListStyleGroup)); } JMenu helpMenu = new JMenu("Help"); helpMenu.setMnemonic('h'); menuBar.add(helpMenu); if (!isMacOSX()) { helpMenu.add(aboutAction); helpMenu.addSeparator(); } helpMenu.add(new OpenUrlAction(SPSUtils.WABIT_GS_URL, "Getting Started")); helpMenu.add(new OpenUrlAction(SPSUtils.WABIT_DEMO_URL, "Tutorials")); helpMenu.add(new OpenUrlAction(SPSUtils.WABIT_FAQ_URL, "Frequently Asked Questions")); helpMenu.add(SPSUtils.forumAction); helpMenu.addSeparator(); helpMenu.add(new OpenUrlAction(SPSUtils.WABIT_UPGRADE_URL, "Upgrade to Enterprise Edition")); helpMenu.add(new OpenUrlAction(SPSUtils.WABIT_PS_URL, "Premium Support")); helpMenu.add(new OpenUrlAction(SPSUtils.WABIT_UG_URL, "User Guide")); helpMenu.addSeparator(); helpMenu.add(new CheckForUpdateAction("Check for Updates...", frame)); frame.setJMenuBar(menuBar); frame.setContentPane(cp); //prefs if (prefs.get("frameBounds", null) != null) { String[] frameBounds = prefs.get("frameBounds", null).split(","); if (frameBounds.length == 4) { logger.debug("Frame bounds are " + Integer.parseInt(frameBounds[0]) + ", " + Integer.parseInt(frameBounds[1]) + ", " + Integer.parseInt(frameBounds[2]) + ", " + Integer.parseInt(frameBounds[3])); frame.setBounds(Integer.parseInt(frameBounds[0]), Integer.parseInt(frameBounds[1]), Integer.parseInt(frameBounds[2]), Integer.parseInt(frameBounds[3])); } } else { frame.setSize(1050, 750); frame.setLocation(200, 100); } frame.setVisible(true); logger.debug("UI is built."); } public boolean setEditorPanel() { if (headless) return true; if (isLoading()) return false; logger.debug("setEditorPanel() starting"); if (!removeEditorPanel()) { return false; } int dividerLoc; if (currentEditorPanel != null) { dividerLoc = wabitPane.getDividerLocation(); } else { dividerLoc = prefs.getInt(MAIN_DIVIDER_LOCATION, DEFAULT_TREE_WIDTH); } // JSplitPane.setRightComponent() will remove the right component magically SPObject entryPanelModel = null; if (getActiveSession() != null) { entryPanelModel = getActiveSession().getWorkspace().getEditorPanelModel(); } currentEditorPanel = createEditorPanel(entryPanelModel); sourceListStyle.apply(this, currentEditorPanel); wabitPane.setDividerLocation(dividerLoc); frame.setTitle(currentEditorPanel.getTitle()); // TODO Select the proper panel in the wabit tree return true; } /** * This is a helper method for {@link #setEditorPanel()} that will create * the panel to edit the model object given. */ private WabitPanel createEditorPanel(SPObject entryPanelModel) { logger.debug("createEditorPanel() starting"); // final JDialog dialog; // if (!isLoading()) { // // TODO show a loading throbber... // final JPanel panel = new JPanel(new BorderLayout()); // final JLabel throbberLabel = new JLabel(WabitIcons.EXPORT_ICON_32); // final JLabel loadingLabel = new JLabel("Loading..."); // panel.add(throbberLabel, BorderLayout.WEST); // panel.add(loadingLabel, BorderLayout.EAST); // dialog = new JDialog(frame, "", false); // dialog.add(panel); // dialog.pack(); // dialog.setVisible(true); // } else { // dialog = null; // } try { if (getActiveSession() == null) { currentEditorPanel = welcomeScreen.getPanel(); } else if (entryPanelModel instanceof QueryCache) { currentEditorPanel = new QueryPanel(getActiveSwingSession(), (QueryCache) entryPanelModel); } else if (entryPanelModel instanceof OlapQuery) { currentEditorPanel = new OlapQueryPanel(getActiveSwingSession(), wabitPane, (OlapQuery) entryPanelModel); } else if (entryPanelModel instanceof Chart) { currentEditorPanel = new ChartPanel(getActiveSwingSession(), (Chart) entryPanelModel); } else if (entryPanelModel instanceof WabitImage) { currentEditorPanel = new WabitImagePanel((WabitImage) entryPanelModel, this); } else if (entryPanelModel instanceof Report) { currentEditorPanel = new ReportPanel(getActiveSwingSession(), (Report) entryPanelModel); } else if (entryPanelModel instanceof Layout) { currentEditorPanel = new LayoutPanel(getActiveSwingSession(), (Layout) entryPanelModel); } else if (entryPanelModel instanceof ReportTask) { currentEditorPanel = new ReportTaskPanel((ReportTask) entryPanelModel); } else if (entryPanelModel instanceof User) { currentEditorPanel = new UserPanel((User) entryPanelModel); } else if (entryPanelModel instanceof Group) { currentEditorPanel = new GroupPanel((Group) entryPanelModel); } else if (entryPanelModel instanceof WabitWorkspace) { currentEditorPanel = new WorkspacePanel(getActiveSwingSession()); } else if (entryPanelModel instanceof SQLDatabase) { currentEditorPanel = new WorkspacePanel(getActiveSwingSession()); } else { if (entryPanelModel.getParent() != null) { currentEditorPanel = createEditorPanel(entryPanelModel.getParent()); } else { throw new IllegalStateException("Unknown model for the defined types of entry panels. " + "The type is " + entryPanelModel.getClass()); } } sourceListStyle.apply(this, currentEditorPanel); return currentEditorPanel; } finally { // if (dialog != null) { // dialog.setVisible(false); // dialog.dispose(); // } } } /** * This will close the editor panel the user is currently modifying if * the user has no changes or discards their changes. This will return true * if the panel was properly closed or false if it was not closed (ie: due to * unsaved changes). */ private boolean removeEditorPanel() { logger.debug("removeEditorPanel() starting"); if (currentEditorPanel != null && currentEditorPanel.hasUnsavedChanges()) { int retval = JOptionPane.showConfirmDialog(frame, "There are unsaved changes. Discard?", "Discard changes", JOptionPane.YES_NO_OPTION); if (retval == JOptionPane.NO_OPTION) { return false; } } if (currentEditorPanel != null) { // TODO This stuff doesn't work. // prefs.putInt(SOURCE_LIST_DIVIDER_LOCATON, wabitPane.getDividerLocation()); if (sourceListDialog != null) { prefs.putInt(SOURCE_LIST_DIALOG_X, sourceListDialog.getX()); prefs.putInt(SOURCE_LIST_DIALOG_Y, sourceListDialog.getY()); prefs.putInt(SOURCE_LIST_DIALOG_WIDTH, sourceListDialog.getWidth()); prefs.putInt(SOURCE_LIST_DIALOG_HEIGHT, sourceListDialog.getHeight()); } logger.debug("removeEditorPanel() calling discardChanges()"); currentEditorPanel.discardChanges(); } return true; } public JFrame getFrame() { return frame; } public List<WabitSession> getSessions() { return delegateContext.getSessions(); } public void putRecentFileName(String fileName) { createRecentMenu().putRecentFileName(fileName); } public void close() { if (!removeEditorPanel()) { return; } try { if (maximizeEditorAction.isEditorMaximized()) { prefs.putInt(MAIN_DIVIDER_LOCATION, maximizeEditorAction.getOriginalDividerLocation()); } else { prefs.putInt(MAIN_DIVIDER_LOCATION, wabitPane.getDividerLocation()); } prefs.put("frameBounds", String.format("%d,%d,%d,%d", frame.getX(), frame.getY(), frame.getWidth(), frame.getHeight())); prefs.flush(); } catch (BackingStoreException ex) { logger.warn("Failed to flush preferences", ex); } if (hasUnsavedChanges()) { int response = JOptionPane.showOptionDialog(frame, "You have unsaved changes. Do you want to save?", //$NON-NLS-1$ "Unsaved Changes", //$NON-NLS-1$ JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null, new Object[] { "Don't Save", "Cancel", "Save" }, "Save"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ if (response == 0) { //we are closing } else if (response == JOptionPane.CLOSED_OPTION || response == 1) { setEditorPanel(); return; } else { boolean isClosing = true; if (!SaveWorkspaceAction.saveAllSessions(WabitSwingSessionContextImpl.this)) { isClosing = false; } if (!isClosing) return; } } frame.dispose(); getPrefs().remove(PREFS_OPEN_WORKSPACES); for (int i = 0; i < getSessionCount(); i++) { File currentFile = ((WabitSwingSession) getSessions().get(i)).getCurrentURIAsFile(); if (currentFile == null) continue; String currentWorkspaces; String workspaces = getPrefs().get(PREFS_OPEN_WORKSPACES, null); if (workspaces == null) { currentWorkspaces = ""; } else { currentWorkspaces = workspaces + WORKSPACE_PREFS_REGEX; } String saveOutString = currentWorkspaces + currentFile.getAbsolutePath(); getPrefs().put(PREFS_OPEN_WORKSPACES, saveOutString); } loadingRegistry.close(); delegateContext.close(); System.exit(0); } public JSpinner getRowLimitSpinner() { return rowLimitSpinner; } public int getRowLimit() { //XXX This limit should be saved in the delegate context. return (Integer) rowLimitSpinner.getValue(); } /** * sets the StatusMessage */ public void setStatusMessage(String msg) { statusLabel.setText(msg); } /** * Returns true if any of this context's sessions have unsaved changes. */ private boolean hasUnsavedChanges() { for (WabitSession session : getSessions()) { if (session instanceof WabitSwingSession) { WabitSwingSession swingSession = (WabitSwingSession) session; if (swingSession.hasUnsavedChanges()) { return true; } } } return false; } public DataSourceCollection<SPDataSource> getDataSources() { return delegateContext.getDataSources(); } public List<SPServerInfo> getEnterpriseServers(boolean includeDiscoveredServers) { return delegateContext.getEnterpriseServers(includeDiscoveredServers); } public void addServer(SPServerInfo serverInfo) { delegateContext.addServer(serverInfo); } public void removeServer(SPServerInfo si) { delegateContext.removeServer(si); } public JmDNS getJmDNS() { return delegateContext.getJmDNS(); } public int getSessionCount() { return delegateContext.getSessionCount(); } public boolean isMacOSX() { return delegateContext.isMacOSX(); } /** * Listens for changes to the workspace name and updates the name on the * stacked tab accordingly. */ private final SPListener nameChangeListener = new AbstractPoolingSPListener() { public void propertyChangeImpl(PropertyChangeEvent evt) { if (evt.getPropertyName().equals("name")) { WabitSession sourceSession = ((WabitWorkspace) evt.getSource()).getSession(); int index = getSessions().indexOf(sourceSession) + 1; stackedTabPane.setTitleAt(index, (String) evt.getNewValue()); } } }; /** * Registers the given child session with this context, marking the child * session as not having any unsaved changes. * * @param child * The child session to register. Must be non-null. If it is not * a WabitSwingSession, it will be wrapped in one before it's * registered. */ public void registerChildSession(WabitSession child) { WabitSwingSession swingSession; if (!(child instanceof WabitSwingSession)) { swingSession = new WabitSwingSessionImpl(this, child); } else { swingSession = (WabitSwingSession) child; } swingSession.addSessionLifecycleListener(childSessionLifecycleListener); delegateContext.registerChildSession(swingSession); // mark the session clean (this is the correct way, according to interface docs) swingSession.setCurrentURI(swingSession.getCurrentURI()); swingSession.getWorkspace().addSPListener(nameChangeListener); final StackedTab tab = stackedTabPane.addTab(swingSession.getWorkspace().getName(), new JScrollPane(swingSession.getTree()), true); if (child instanceof WabitClientSession) { final JPopupMenu tabMenu = new JPopupMenu(); tabMenu.add(new DeleteWabitServerWorkspaceAction(WabitSwingSessionContextImpl.this)); tabMenu.add(new RenameWabitServerWorkspaceAction((WabitClientSession) child, getFrame())); tab.getTabComponent().addMouseListener(new MouseAdapter() { public void mouseClicked(MouseEvent e) { maybeShowPopup(e); } public void mousePressed(MouseEvent e) { boolean showedPopup = maybeShowPopup(e); if (!showedPopup && tab.isCloseable() && tab.closeButtonContains(e.getX(), e.getY())) { CloseWorkspaceAction.closeActiveWorkspace(WabitSwingSessionContextImpl.this); } } public void mouseReleased(MouseEvent e) { maybeShowPopup(e); } private boolean maybeShowPopup(MouseEvent e) { if (e.isPopupTrigger() && stackedTabPane.getSelectedTab() == tab) { tabMenu.show(tab.getTabComponent(), e.getX(), e.getY()); return true; } else { return false; } } }); } else { tab.getTabComponent().addMouseListener(new MouseAdapter() { public void mousePressed(MouseEvent e) { if (tab.isCloseable() && tab.closeButtonContains(e.getX(), e.getY()) && CloseWorkspaceAction.checkUnsavedChanges(WabitSwingSessionContextImpl.this)) { CloseWorkspaceAction.closeActiveWorkspace(WabitSwingSessionContextImpl.this); } } }); } stackedTabPane.setSelectedIndex(stackedTabPane.indexOfTab(tab)); sessionTabs.put(swingSession, tab); } public Preferences getPrefs() { return delegateContext.getPrefs(); } public JMenu createServerListMenu(Component dialogOwner, String name, ServerListMenuItemFactory itemFactory) { return new ServerListMenu(this, name, dialogOwner, itemFactory); } public String getName() { return delegateContext.getName(); } public UserPrompter createDatabaseUserPrompter(String question, List<Class<? extends SPDataSource>> dsTypes, UserPromptOptions optionType, UserPromptResponse defaultResponseType, Object defaultResponse, DataSourceCollection<SPDataSource> dsCollection, String... buttonNames) { return upf.createDatabaseUserPrompter(question, dsTypes, optionType, defaultResponseType, defaultResponse, dsCollection, buttonNames); } public WabitSession getActiveSession() { return delegateContext.getActiveSession(); } public WabitSwingSession getActiveSwingSession() { return (WabitSwingSession) delegateContext.getActiveSession(); } public void setActiveSession(WabitSession activeSession) { WabitSession oldSession = delegateContext.getActiveSession(); delegateContext.setActiveSession(activeSession); stackedTabPane.setSelectedIndex(getSessions().indexOf(activeSession) + 1); if (oldSession != activeSession) { setEditorPanel(); } //XXX: Fire a property changed event. Mainly so actions that don't work on null active sessions can disable themselves. } public void addPropertyChangeListener(PropertyChangeListener l) { delegateContext.addPropertyChangeListener(l); pcs.addPropertyChangeListener(l); } public void removePropertyChangeListener(PropertyChangeListener l) { delegateContext.removePropertyChangeListener(l); pcs.removePropertyChangeListener(l); } public Connection borrowConnection(JDBCDataSource dataSource) throws SQLObjectException { return delegateContext.borrowConnection(dataSource); } public boolean isLoading() { return delegateContext.isLoading(); } public void startLoading() { delegateContext.startLoading(); } public void endLoading() { delegateContext.endLoading(); } public SQLDatabase getDatabase(JDBCDataSource ds) { return delegateContext.getDatabase(ds); } public OlapConnection createConnection(Olap4jDataSource dataSource) throws SQLException, ClassNotFoundException, NamingException { return delegateContext.createConnection(dataSource); } public PreparedOlapStatement createPreparedStatement(Olap4jDataSource dataSource, String mdx, SPVariableHelper helper) { return delegateContext.createPreparedStatement(dataSource, mdx, helper); } public Connection createConnection(JDBCDataSource dataSource) throws SQLObjectException { return delegateContext.createConnection(dataSource); } public PreparedStatement createPreparedStatement(JDBCDataSource dataSource, String sql, SPVariableHelper helper) throws SQLObjectException { return delegateContext.createPreparedStatement(dataSource, sql, helper); } /** * Launches the Wabit application by loading the configuration and * displaying the GUI. * * @throws Exception if startup fails */ public static void main(final String[] args) throws Exception { SPSUtils.setMasterIcon(FRAME_ICON); try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (Exception e) { logger.error("Unable to set native look and feel. Continuing with default.", e); } SwingUtilities.invokeLater(new Runnable() { public void run() { try { System.setProperty("com.apple.mrj.application.apple.menu.about.name", "Wabit"); System.setProperty("apple.laf.useScreenMenuBar", "true"); DataSourceCollection<SPDataSource> dsCollection = loadPlDotIni(); String path = plDotIniPath(); WabitSessionContextImpl coreContext = new WabitSessionContextImpl(false, true, dsCollection, path, true); WabitSwingSessionContext context = new WabitSwingSessionContextImpl(coreContext, false); context.setEditorPanel(); final List<File> importFile = new ArrayList<File>(); if (args.length > 0) { importFile.add(new File(args[0])); } else { String workspacesToLoad = context.getPrefs().get(PREFS_OPEN_WORKSPACES, null); if (workspacesToLoad != null) { for (String workspaceLocation : workspacesToLoad.split(WORKSPACE_PREFS_REGEX)) { File newFile = new File(workspaceLocation); importFile.add(newFile); } } } List<URI> startupURIs = new ArrayList<URI>(); for (File file : importFile) { if (file != null) { startupURIs.add(file.toURI()); } } OpenWorkspaceAction.loadFiles(context, startupURIs.toArray(new URI[startupURIs.size()])); } catch (Exception ex) { ex.printStackTrace(); // We wish we had a parent component to direct the dialog but this is being invoked, so // everything else blew up. SPSUtils.showExceptionDialogNoReport(null, "An unexpected error occured while launching Wabit", ex); } } }); } /** * Will decide if we must insert or not in the menu a given menu item * based on access rights and object simple class name. * If we are not in a server workspace, we insert all options. * @param menu The menu into which we insert if needed * @param simpleName The simple class name of the WabitObject concerned * by the menu option * @param subjectUuid the particular object's UUID we want to check permissions for. * @param menuItem The menu item we might add to the menu. * @param permissions Permissions to lookup */ private void objectsMenu(JPopupMenu menu, String simpleName, @Nullable String subjectUuid, JMenuItem menuItem, WabitAccessManager.Permission permission) { this.objectsMenu(menu, simpleName, subjectUuid, menuItem, false, permission); } /** * Will decide if we must insert or not in the menu a given menu item * based on access rights and object simple class name. * If we are not in a server workspace, we insert all options. * @param menu The menu into which we insert if needed * @param simpleName The simple class name of the WabitObject concerned * by the menu option * @param subjectUuid the particular object's UUID we want to check permissions for. * @param menuItem The menu item we might add to the menu. * @param appendSeparator Wether to append a separator after the menu item or not. * @param permissions Permissions to lookup */ private void objectsMenu(JPopupMenu menu, String simpleName, @Nullable String subjectUuid, JMenuItem menuItem, boolean appendSeparator, WabitAccessManager.Permission permission) { // If we are not in a server session, we display everything. if (this.delegateContext.getActiveSession() == null || !this.delegateContext.getActiveSession().isEnterpriseServerSession()) { menu.add(menuItem); return; } if (this.currentUser == null) { this.currentUser = this.getCurrentUser(); if (this.currentUser == null) { // No way to resolve this. We add all menus menu.add(menuItem); return; } } // Init the access manager since we will need it if (accessManager == null) { accessManager = new CachingWabitAccessManager(); accessManager.init(this.currentUser, this.delegateContext.getActiveSession(), this.delegateContext.getActiveSession().getSystemWorkspace().getSession()); } if (subjectUuid == null) { if (WabitAccessManager.Permission.GRANT.equals(permission) && accessManager .isGrantGranted(new Grant(subjectUuid, simpleName, false, false, false, false, false))) { menu.add(menuItem); if (appendSeparator) { menu.addSeparator(); } } else if (accessManager.isGranted(simpleName, EnumSet.of(permission))) { menu.add(menuItem); if (appendSeparator) { menu.addSeparator(); } } } else { if (accessManager.isGranted(subjectUuid, simpleName, EnumSet.of(permission))) { menu.add(menuItem); if (appendSeparator) { menu.addSeparator(); } } } } /** * Returns the current user object, null if it cannot be found * or we are not on a server workspace. */ private User getCurrentUser() { if (!this.delegateContext.getActiveSession().isEnterpriseServerSession()) { return null; } WabitWorkspace systemWorkspace = this.delegateContext.getActiveSession().getSystemWorkspace(); if (systemWorkspace == null) { return null; } String username = ((WabitSwingSessionImpl) this.delegateContext.getActiveSession()) .getEnterpriseServerInfos().getUsername(); User currentUser = null; for (User user : systemWorkspace.getUsers()) { if (user.getUsername().equals(username)) { currentUser = user; break; } } return currentUser; } public UserPrompter createUserPrompter(String question, UserPromptType responseType, UserPromptOptions optionType, UserPromptResponse defaultResponseType, Object defaultResponse, String... buttonNames) { return upf.createUserPrompter(question, responseType, optionType, defaultResponseType, defaultResponse, buttonNames); } public void addServerListListener(ServerListListener l) { delegateContext.addServerListListener(l); } public void removeServerListListener(ServerListListener l) { delegateContext.removeServerListListener(l); } public SwingWorkerRegistry getLoadingRegistry() { return loadingRegistry; } @Override public <T> UserPrompter createListUserPrompter(String question, List<T> responses, T defaultResponse) { return upf.createListUserPrompter(question, responses, defaultResponse); } }