cayenne-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ntimof...@apache.org
Subject [1/4] cayenne git commit: CAY-2407
Date Mon, 05 Mar 2018 07:47:32 GMT
Repository: cayenne
Updated Branches:
  refs/heads/master fd0d06ec9 -> 7758dbf42


CAY-2407


Project: http://git-wip-us.apache.org/repos/asf/cayenne/repo
Commit: http://git-wip-us.apache.org/repos/asf/cayenne/commit/8b48c7c6
Tree: http://git-wip-us.apache.org/repos/asf/cayenne/tree/8b48c7c6
Diff: http://git-wip-us.apache.org/repos/asf/cayenne/diff/8b48c7c6

Branch: refs/heads/master
Commit: 8b48c7c6f327e22877a8ddf9b547dea6179fba8f
Parents: 1525bc7
Author: Arseni Bulatski <ancarseni@gmail.com>
Authored: Mon Feb 19 10:48:28 2018 +0300
Committer: Arseni Bulatski <ancarseni@gmail.com>
Committed: Mon Feb 19 10:48:28 2018 +0300

----------------------------------------------------------------------
 .../cayenne/map/SQLTemplateDescriptor.java      |  41 +++-
 .../modeler/editor/SQLTemplateMainTab.java      | 116 ++++++++-
 .../modeler/editor/SQLTemplateOrderingTab.java  | 237 +++++++++++++++++++
 .../modeler/editor/SQLTemplatePrefetchTab.java  | 236 ++++++++++++++++++
 .../modeler/editor/SQLTemplateTabbedView.java   |   7 +
 .../AddPrefetchUndoableEditForSqlTemplate.java  |  35 +++
 6 files changed, 666 insertions(+), 6 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/8b48c7c6/cayenne-server/src/main/java/org/apache/cayenne/map/SQLTemplateDescriptor.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/map/SQLTemplateDescriptor.java
b/cayenne-server/src/main/java/org/apache/cayenne/map/SQLTemplateDescriptor.java
index e1c3afe..7e449ca 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/map/SQLTemplateDescriptor.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/map/SQLTemplateDescriptor.java
@@ -22,9 +22,7 @@ import org.apache.cayenne.configuration.ConfigurationNodeVisitor;
 import org.apache.cayenne.query.SQLTemplate;
 import org.apache.cayenne.util.XMLEncoder;
 
-import java.util.HashMap;
-import java.util.Map;
-import java.util.TreeSet;
+import java.util.*;
 
 /**
  * @since 4.0
@@ -32,6 +30,7 @@ import java.util.TreeSet;
 public class SQLTemplateDescriptor extends QueryDescriptor {
 
     protected String sql;
+    protected List<String> prefetches = new ArrayList<>();
 
     protected Map<String, String> adapterSql = new HashMap<>();
 
@@ -67,6 +66,34 @@ public class SQLTemplateDescriptor extends QueryDescriptor {
         this.adapterSql = adapterSql;
     }
 
+    /**
+     * Returns list of prefetch paths for this query.
+     */
+    public List<String> getPrefetches() {
+        return prefetches;
+    }
+
+    /**
+     * Sets list of prefetch paths for this query.
+     */
+    public void setPrefetches(List<String> prefetches) {
+        this.prefetches = prefetches;
+    }
+
+    /**
+     * Adds single prefetch path to this query.
+     */
+    public void addPrefetch(String prefetchPath) {
+        this.prefetches.add(prefetchPath);
+    }
+
+    /**
+     * Removes single prefetch path from this query.
+     */
+    public void removePrefetch(String prefetchPath) {
+        this.prefetches.remove(prefetchPath);
+    }
+
     @Override
     public SQLTemplate buildQuery() {
         SQLTemplate template = new SQLTemplate();
@@ -77,6 +104,14 @@ public class SQLTemplateDescriptor extends QueryDescriptor {
 
         template.initWithProperties(this.getProperties());
 
+        List<String> prefetches = this.getPrefetches();
+
+        if (prefetches != null && !prefetches.isEmpty()) {
+            for (String prefetch : prefetches) {
+                template.addPrefetch(prefetch);
+            }
+        }
+
         // init SQL
         template.setDefaultTemplate(this.getSql());
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/8b48c7c6/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/SQLTemplateMainTab.java
----------------------------------------------------------------------
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/SQLTemplateMainTab.java
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/SQLTemplateMainTab.java
index 387391c..78ef8d0 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/SQLTemplateMainTab.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/SQLTemplateMainTab.java
@@ -25,22 +25,35 @@ import com.jgoodies.forms.layout.FormLayout;
 import com.jgoodies.forms.layout.RowSpec;
 import org.apache.cayenne.configuration.event.QueryEvent;
 import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.map.Entity;
 import org.apache.cayenne.map.ObjEntity;
 import org.apache.cayenne.map.QueryDescriptor;
 import org.apache.cayenne.modeler.Application;
 import org.apache.cayenne.modeler.ProjectController;
+import org.apache.cayenne.modeler.util.CellRenderers;
+import org.apache.cayenne.modeler.util.Comparators;
 import org.apache.cayenne.modeler.util.ProjectUtil;
 import org.apache.cayenne.modeler.util.TextAdapter;
+import org.apache.cayenne.modeler.util.combo.AutoCompletion;
 import org.apache.cayenne.project.extension.info.ObjectInfo;
 import org.apache.cayenne.query.CapsStrategy;
 import org.apache.cayenne.query.SQLTemplate;
 import org.apache.cayenne.util.Util;
 import org.apache.cayenne.validation.ValidationException;
 
-import javax.swing.*;
-import java.awt.*;
+import javax.swing.DefaultComboBoxModel;
+import javax.swing.DefaultListCellRenderer;
+import javax.swing.JComboBox;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+import java.awt.BorderLayout;
+import java.awt.Component;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
+import java.awt.event.FocusEvent;
+import java.awt.event.FocusListener;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -69,12 +82,14 @@ public class SQLTemplateMainTab extends JPanel {
     protected ProjectController mediator;
     protected TextAdapter name;
     protected TextAdapter comment;
+    protected JComboBox<ObjEntity> queryRoot;
     protected SelectPropertiesPanel properties;
 
     public SQLTemplateMainTab(ProjectController mediator) {
         this.mediator = mediator;
 
         initView();
+        initController();
     }
 
     private void initView() {
@@ -93,13 +108,17 @@ public class SQLTemplateMainTab extends JPanel {
             }
         };
 
+        queryRoot = Application.getWidgetFactory().createComboBox();
+        AutoCompletion.enable(queryRoot);
+        queryRoot.setRenderer(CellRenderers.listRendererWithIcons());
+
         properties = new SQLTemplateQueryPropertiesPanel(mediator);
 
         // assemble
         CellConstraints cc = new CellConstraints();
         FormLayout layout = new FormLayout(
                 "right:max(80dlu;pref), 3dlu, fill:max(200dlu;pref)",
-                "p, 3dlu, p, 3dlu, p");
+                "p, 3dlu, p, 3dlu, p, 3dlu, p");
         PanelBuilder builder = new PanelBuilder(layout);
         builder.setDefaultDialogBorder();
 
@@ -108,12 +127,22 @@ public class SQLTemplateMainTab extends JPanel {
         builder.add(name.getComponent(), cc.xy(3, 3));
         builder.addLabel("Comment:", cc.xy(1, 5));
         builder.add(comment.getComponent(), cc.xy(3, 5));
+        builder.addLabel("Query Root:", cc.xy(1, 7));
+        builder.add(queryRoot, cc.xy(3, 7));
 
         this.setLayout(new BorderLayout());
         this.add(builder.getPanel(), BorderLayout.NORTH);
         this.add(properties, BorderLayout.CENTER);
     }
 
+    private void initController() {
+        RootSelectionHandler rootHandler = new RootSelectionHandler();
+
+        queryRoot.addActionListener(rootHandler);
+        queryRoot.addFocusListener(rootHandler);
+        queryRoot.getEditor().getEditorComponent().addFocusListener(rootHandler);
+    }
+
     /**
      * Updates the view from the current model state. Invoked when a currently displayed
      * query is changed.
@@ -130,6 +159,17 @@ public class SQLTemplateMainTab extends JPanel {
         properties.initFromModel(query);
         comment.setText(getQueryComment(query));
 
+        DataMap map = mediator.getCurrentDataMap();
+        ObjEntity[] roots = map.getObjEntities().toArray(new ObjEntity[0]);
+
+        if (roots.length > 1) {
+            Arrays.sort(roots, Comparators.getDataMapChildrenComparator());
+        }
+
+        DefaultComboBoxModel<ObjEntity> model = new DefaultComboBoxModel<>(roots);
+        model.setSelectedItem(query.getRoot());
+        queryRoot.setModel(model);
+
         setVisible(true);
     }
 
@@ -177,6 +217,76 @@ public class SQLTemplateMainTab extends JPanel {
     }
 
     /**
+     * Handler to user's actions with root selection combobox
+     */
+    class RootSelectionHandler implements FocusListener, ActionListener {
+        String newName = null;
+        boolean needChangeName;
+
+        public void actionPerformed(ActionEvent ae) {
+            QueryDescriptor query = getQuery();
+            if (query != null) {
+                Entity root = (Entity) queryRoot.getModel().getSelectedItem();
+
+                if (root != null) {
+                    query.setRoot(root);
+
+                    if (needChangeName) { //not changed by user
+                        /*
+                         * Doing auto name change, following CAY-888 #2
+                         */
+                        String newPrefix = root.getName() + "Query";
+                        newName = newPrefix;
+
+                        DataMap map = mediator.getCurrentDataMap();
+                        long postfix = 1;
+
+                        while (map.getQueryDescriptor(newName) != null) {
+                            newName = newPrefix + (postfix++);
+                        }
+
+                        name.setText(newName);
+                    }
+                }
+            }
+        }
+
+        public void focusGained(FocusEvent e) {
+            //reset new name tracking
+            newName = null;
+
+            QueryDescriptor query = getQuery();
+            if (query != null) {
+                needChangeName = hasDefaultName(query);
+            } else {
+                needChangeName = false;
+            }
+        }
+
+        public void focusLost(FocusEvent e) {
+            if (newName != null) {
+                setQueryName(newName);
+            }
+
+            newName = null;
+            needChangeName = false;
+        }
+
+        /**
+         * @return whether specified's query name is 'default' i.e. Cayenne generated
+         * A query's name is 'default' if it starts with 'UntitledQuery' or with root name.
+         *
+         * We cannot follow user input because tab might be opened many times
+         */
+        boolean hasDefaultName(QueryDescriptor query) {
+            String prefix = query.getRoot() == null ? "UntitledQuery" :
+                    CellRenderers.asString(query.getRoot()) + "Query";
+
+            return name.getComponent().getText().startsWith(prefix);
+        }
+    }
+
+    /**
      * Returns an entity that maps to a procedure query result class.
      */
     ObjEntity getEntity(QueryDescriptor query) {

http://git-wip-us.apache.org/repos/asf/cayenne/blob/8b48c7c6/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/SQLTemplateOrderingTab.java
----------------------------------------------------------------------
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/SQLTemplateOrderingTab.java
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/SQLTemplateOrderingTab.java
new file mode 100644
index 0000000..c21841d
--- /dev/null
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/SQLTemplateOrderingTab.java
@@ -0,0 +1,237 @@
+/*****************************************************************
+ *   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.cayenne.modeler.editor;
+
+import org.apache.cayenne.configuration.event.QueryEvent;
+import org.apache.cayenne.map.Entity;
+import org.apache.cayenne.map.QueryDescriptor;
+import org.apache.cayenne.map.SQLTemplateDescriptor;
+import org.apache.cayenne.map.SelectQueryDescriptor;
+import org.apache.cayenne.modeler.Application;
+import org.apache.cayenne.modeler.ProjectController;
+import org.apache.cayenne.modeler.util.EntityTreeModel;
+import org.apache.cayenne.modeler.util.MultiColumnBrowser;
+import org.apache.cayenne.modeler.util.UIUtil;
+import org.apache.cayenne.query.Ordering;
+import org.apache.cayenne.query.SQLTemplate;
+import org.apache.cayenne.query.SortOrder;
+import org.apache.cayenne.swing.components.image.FilteredIconFactory;
+import org.apache.cayenne.util.CayenneMapEntry;
+
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JSplitPane;
+import javax.swing.JTable;
+import javax.swing.ListSelectionModel;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.table.AbstractTableModel;
+import javax.swing.table.TableModel;
+import javax.swing.tree.TreeModel;
+import java.awt.BorderLayout;
+import java.awt.CardLayout;
+import java.awt.Dimension;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.prefs.Preferences;
+
+/**
+ * A panel for picking SQLTemplate orderings.
+ *
+ */
+public class SQLTemplateOrderingTab extends JPanel implements PropertyChangeListener {
+    // property for split pane divider size
+    private static final String SPLIT_DIVIDER_LOCATION_PROPERTY = "query.orderings.divider.location";
+
+    static final Dimension BROWSER_CELL_DIM = new Dimension(150, 100);
+    static final Dimension TABLE_DIM = new Dimension(460, 60);
+
+    static final String PATH_HEADER = "Path";
+    static final String ASCENDING_HEADER = "Ascending";
+    static final String IGNORE_CASE_HEADER = "Ignore Case";
+
+    static final String REAL_PANEL = "real";
+    static final String PLACEHOLDER_PANEL = "placeholder";
+
+    protected ProjectController mediator;
+    protected SQLTemplateDescriptor sqlTemplate;
+
+    protected MultiColumnBrowser browser;
+    protected JTable table;
+
+    protected CardLayout cardLayout;
+    protected JPanel messagePanel;
+
+    public SQLTemplateOrderingTab(ProjectController mediator) {
+        this.mediator = mediator;
+
+        initView();
+        initController();
+    }
+
+    protected void initView() {
+
+        messagePanel = new JPanel(new BorderLayout());
+        cardLayout = new CardLayout();
+
+        Preferences detail = Application.getInstance().getPreferencesNode(this.getClass(),
"");
+
+        int defLocation = Application.getFrame().getHeight() / 2;
+        int location = detail != null ? detail.getInt(
+                getDividerLocationProperty(),
+                defLocation) : defLocation;
+
+        //As of CAY-888 #3 main pane is now a JSplitPane. Top component is a bit larger.
+        JSplitPane mainPanel = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
+        mainPanel.addPropertyChangeListener(JSplitPane.DIVIDER_LOCATION_PROPERTY, this);
+        mainPanel.setDividerLocation(location);
+
+        mainPanel.setTopComponent(createEditorPanel());
+        mainPanel.setBottomComponent(createSelectorPanel());
+
+        setLayout(cardLayout);
+        add(mainPanel, REAL_PANEL);
+        add(messagePanel, PLACEHOLDER_PANEL);
+    }
+
+    protected void initController() {
+
+        // scroll to selected row whenever a selection even occurs
+        table.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
+
+            public void valueChanged(ListSelectionEvent e) {
+                if (!e.getValueIsAdjusting()) {
+                    UIUtil.scrollToSelectedRow(table);
+                }
+            }
+        });
+    }
+
+    protected void initFromModel() {
+        QueryDescriptor query = mediator.getCurrentQuery();
+
+        if (query == null || !QueryDescriptor.SQL_TEMPLATE.equals(query.getType())) {
+            processInvalidModel("Unknown query.");
+            return;
+        }
+
+        if (!(query.getRoot() instanceof Entity)) {
+            processInvalidModel("SQLTemplate has no root set.");
+            return;
+        }
+
+        this.sqlTemplate = (SQLTemplateDescriptor) query;
+        browser.setModel(createBrowserModel((Entity) sqlTemplate.getRoot()));
+
+        // init column sizes
+       // table.getColumnModel().getColumn(0).setPreferredWidth(250);
+
+        cardLayout.show(this, REAL_PANEL);
+    }
+
+    protected void processInvalidModel(String message) {
+        JLabel messageLabel = new JLabel(message, JLabel.CENTER);
+
+        messagePanel.removeAll();
+        messagePanel.add(messageLabel, BorderLayout.CENTER);
+        cardLayout.show(this, PLACEHOLDER_PANEL);
+    }
+
+    protected JPanel createEditorPanel() {
+
+        table = new JTable();
+        table.setRowHeight(25);
+        table.setRowMargin(3);
+        table.setPreferredScrollableViewportSize(TABLE_DIM);
+        table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+
+        JPanel panel = new JPanel(new BorderLayout());
+        panel.add(new JScrollPane(table), BorderLayout.CENTER);
+
+        return panel;
+    }
+
+    protected JPanel createSelectorPanel() {
+        browser = new MultiColumnBrowser();
+        browser.setPreferredColumnSize(BROWSER_CELL_DIM);
+        browser.setDefaultRenderer();
+
+        JPanel panel = new JPanel(new BorderLayout());
+        panel.add(new JScrollPane(
+                browser,
+                JScrollPane.VERTICAL_SCROLLBAR_NEVER,
+                JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED), BorderLayout.CENTER);
+
+        // setting minimal size, otherwise scrolling looks awful, because of
+        // VERTICAL_SCROLLBAR_NEVER strategy
+        panel.setMinimumSize(panel.getPreferredSize());
+
+        return panel;
+    }
+
+    protected TreeModel createBrowserModel(Entity entity) {
+        return new EntityTreeModel(entity);
+    }
+
+    protected String getSelectedPath() {
+        Object[] path = browser.getSelectionPath().getPath();
+
+        // first item in the path is Entity, so we must have
+        // at least two elements to constitute a valid ordering path
+        if (path.length < 2) {
+            return null;
+        }
+
+        StringBuilder buffer = new StringBuilder();
+
+        // attribute or relationships
+        CayenneMapEntry first = (CayenneMapEntry) path[1];
+        buffer.append(first.getName());
+
+        for (int i = 2; i < path.length; i++) {
+            CayenneMapEntry pathEntry = (CayenneMapEntry) path[i];
+            buffer.append(".").append(pathEntry.getName());
+        }
+
+        return buffer.toString();
+    }
+
+    /**
+     * Updates split pane divider location in properties
+     */
+    public void propertyChange(PropertyChangeEvent evt) {
+        if (JSplitPane.DIVIDER_LOCATION_PROPERTY.equals(evt.getPropertyName())) {
+            int value = (Integer) evt.getNewValue();
+
+            Preferences detail = Application.getInstance().getPreferencesNode(this.getClass(),
"");
+            detail.putInt(getDividerLocationProperty(), value);
+        }
+    }
+
+    /**
+     * Returns name of a property for divider location.
+     */
+    protected String getDividerLocationProperty() {
+        return SPLIT_DIVIDER_LOCATION_PROPERTY;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/8b48c7c6/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/SQLTemplatePrefetchTab.java
----------------------------------------------------------------------
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/SQLTemplatePrefetchTab.java
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/SQLTemplatePrefetchTab.java
new file mode 100644
index 0000000..b9e7147
--- /dev/null
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/SQLTemplatePrefetchTab.java
@@ -0,0 +1,236 @@
+/*****************************************************************
+ *   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.cayenne.modeler.editor;
+
+import org.apache.cayenne.configuration.event.QueryEvent;
+import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.ExpressionException;
+import org.apache.cayenne.exp.ExpressionFactory;
+import org.apache.cayenne.map.Attribute;
+import org.apache.cayenne.map.Entity;
+import org.apache.cayenne.map.Relationship;
+import org.apache.cayenne.modeler.Application;
+import org.apache.cayenne.modeler.ProjectController;
+import org.apache.cayenne.modeler.undo.AddPrefetchUndoableEdit;
+import org.apache.cayenne.modeler.undo.AddPrefetchUndoableEditForSqlTemplate;
+import org.apache.cayenne.modeler.util.CayenneAction;
+import org.apache.cayenne.modeler.util.EntityTreeFilter;
+import org.apache.cayenne.modeler.util.EntityTreeModel;
+import org.apache.cayenne.modeler.util.ModelerUtil;
+import org.apache.cayenne.swing.components.image.FilteredIconFactory;
+
+import javax.swing.BorderFactory;
+import javax.swing.Icon;
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JToolBar;
+import javax.swing.table.AbstractTableModel;
+import javax.swing.table.TableModel;
+import javax.swing.tree.TreeModel;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+public class SQLTemplatePrefetchTab extends SQLTemplateOrderingTab{
+
+    public SQLTemplatePrefetchTab(ProjectController mediator) {
+        super(mediator);
+    }
+
+    protected JComponent createToolbar() {
+
+        JButton add = new CayenneAction.CayenneToolbarButton(null, 1);
+        add.setText("Add Prefetch");
+        Icon addIcon = ModelerUtil.buildIcon("icon-plus.png");
+        add.setIcon(addIcon);
+        add.setDisabledIcon(FilteredIconFactory.createDisabledIcon(addIcon));
+
+        add.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                String prefetch = getSelectedPath();
+
+                if (prefetch == null) {
+                    return;
+                }
+
+                addPrefetch(prefetch);
+
+                Application.getInstance().getUndoManager().addEdit(new AddPrefetchUndoableEditForSqlTemplate(prefetch,
SQLTemplatePrefetchTab.this));
+            }
+
+        });
+
+        JButton remove = new CayenneAction.CayenneToolbarButton(null, 3);
+        remove.setText("Remove Prefetch");
+        Icon removeIcon = ModelerUtil.buildIcon("icon-trash.png");
+        remove.setIcon(removeIcon);
+        remove.setDisabledIcon(FilteredIconFactory.createDisabledIcon(removeIcon));
+
+        remove.addActionListener(new ActionListener() {
+
+            public void actionPerformed(ActionEvent e) {
+                int selection = table.getSelectedRow();
+
+                if (selection < 0) {
+                    return;
+                }
+
+                String prefetch = (String) table.getModel().getValueAt(selection, 0);
+
+                removePrefetch(prefetch);
+            }
+
+        });
+
+        JToolBar toolBar = new JToolBar();
+        toolBar.setBorder(BorderFactory.createEmptyBorder());
+        toolBar.setFloatable(false);
+        toolBar.add(add);
+        toolBar.add(remove);
+        return toolBar;
+    }
+
+    protected TreeModel createBrowserModel(Entity entity) {
+        EntityTreeModel treeModel = new EntityTreeModel(entity);
+        treeModel.setFilter(
+                new EntityTreeFilter() {
+                    public boolean attributeMatch(Object node, Attribute attr) {
+                        return false;
+                    }
+
+                    public boolean relationshipMatch(Object node, Relationship rel) {
+                        return true;
+                    }
+                });
+        return treeModel;
+    }
+
+    protected TableModel createTableModel() {
+        return new PrefetchModel();
+    }
+
+    public void addPrefetch(String prefetch) {
+
+        // check if such prefetch already exists
+        if (!sqlTemplate.getPrefetches().isEmpty() && sqlTemplate.getPrefetches().contains(prefetch))
{
+            return;
+        }
+
+        sqlTemplate.addPrefetch(prefetch);
+
+        // reset the model, since it is immutable
+        table.setModel(createTableModel());
+
+        mediator.fireQueryEvent(new QueryEvent(this, sqlTemplate));
+    }
+
+    public void removePrefetch(String prefetch) {
+        sqlTemplate.removePrefetch(prefetch);
+
+        // reset the model, since it is immutable
+        table.setModel(createTableModel());
+        mediator.fireQueryEvent(new QueryEvent(this, sqlTemplate));
+    }
+
+    boolean isToMany(String prefetch) {
+        if (sqlTemplate == null) {
+            return false;
+        }
+
+        Object root = sqlTemplate.getRoot();
+
+        // totally invalid path would result in ExpressionException
+        try {
+            Expression exp = ExpressionFactory.exp(prefetch);
+            Object object = exp.evaluate(root);
+            if (object instanceof Relationship) {
+                return ((Relationship) object).isToMany();
+            }
+            else {
+                return false;
+            }
+        }
+        catch (ExpressionException e) {
+            return false;
+        }
+    }
+
+    /**
+     * A table model for the Ordering editing table.
+     */
+    final class PrefetchModel extends AbstractTableModel {
+
+        String[] prefetches;
+
+        PrefetchModel() {
+            if (sqlTemplate != null) {
+                prefetches = new String[sqlTemplate.getPrefetches().size()];
+
+                for (int i = 0; i < prefetches.length; i++) {
+                    prefetches[i] = sqlTemplate.getPrefetches().get(i);
+                }
+            }
+        }
+
+        public int getColumnCount() {
+            return 2;
+        }
+
+        public int getRowCount() {
+            return (prefetches != null) ? prefetches.length : 0;
+        }
+
+        public Object getValueAt(int row, int column) {
+            switch (column) {
+                case 0:
+                    return prefetches[row];
+                case 1:
+                    return isToMany(prefetches[row]) ? Boolean.TRUE : Boolean.FALSE;
+                default:
+                    throw new IndexOutOfBoundsException("Invalid column: " + column);
+            }
+        }
+
+        public Class getColumnClass(int column) {
+            switch (column) {
+                case 0:
+                    return String.class;
+                case 1:
+                    return Boolean.class;
+                default:
+                    throw new IndexOutOfBoundsException("Invalid column: " + column);
+            }
+        }
+
+        public String getColumnName(int column) {
+            switch (column) {
+                case 0:
+                    return "Prefetch Path";
+                case 1:
+                    return "To Many";
+                default:
+                    throw new IndexOutOfBoundsException("Invalid column: " + column);
+            }
+        }
+
+        public boolean isCellEditable(int row, int column) {
+            return false;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/8b48c7c6/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/SQLTemplateTabbedView.java
----------------------------------------------------------------------
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/SQLTemplateTabbedView.java
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/SQLTemplateTabbedView.java
index 48c7bc7..2862c05 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/SQLTemplateTabbedView.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/SQLTemplateTabbedView.java
@@ -37,6 +37,7 @@ public class SQLTemplateTabbedView extends JTabbedPane {
     protected ProjectController mediator;
     protected SQLTemplateMainTab mainTab;
     protected SQLTemplateScriptsTab scriptsTab;
+    protected SQLTemplatePrefetchTab prefetchTab;
     protected int lastSelectionIndex;
 
     public SQLTemplateTabbedView(ProjectController mediator) {
@@ -54,6 +55,9 @@ public class SQLTemplateTabbedView extends JTabbedPane {
 
         this.scriptsTab = new SQLTemplateScriptsTab(mediator);
         addTab("SQL Scripts", scriptsTab);
+
+        this.prefetchTab = new SQLTemplatePrefetchTab(mediator);
+        addTab("Prefetches", prefetchTab);
     }
 
     private void initController() {
@@ -104,6 +108,9 @@ public class SQLTemplateTabbedView extends JTabbedPane {
             case 1:
                 scriptsTab.initFromModel();
                 break;
+            case 2:
+                prefetchTab.initFromModel();
+                break;
         }
     }
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/8b48c7c6/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/undo/AddPrefetchUndoableEditForSqlTemplate.java
----------------------------------------------------------------------
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/undo/AddPrefetchUndoableEditForSqlTemplate.java
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/undo/AddPrefetchUndoableEditForSqlTemplate.java
new file mode 100644
index 0000000..4ab68ac
--- /dev/null
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/undo/AddPrefetchUndoableEditForSqlTemplate.java
@@ -0,0 +1,35 @@
+package org.apache.cayenne.modeler.undo;
+
+import org.apache.cayenne.modeler.editor.SQLTemplatePrefetchTab;
+import org.apache.cayenne.modeler.editor.SelectQueryPrefetchTab;
+
+import javax.swing.undo.AbstractUndoableEdit;
+import javax.swing.undo.CannotRedoException;
+import javax.swing.undo.CannotUndoException;
+
+public class AddPrefetchUndoableEditForSqlTemplate extends AbstractUndoableEdit {
+
+    private String prefetch;
+    private SQLTemplatePrefetchTab tab;
+
+    public AddPrefetchUndoableEditForSqlTemplate(String prefetch, SQLTemplatePrefetchTab
tab) {
+        super();
+        this.prefetch = prefetch;
+        this.tab = tab;
+    }
+
+    @Override
+    public String getPresentationName() {
+        return "Add Prefetch";
+    }
+
+    @Override
+    public void redo() throws CannotRedoException {
+        tab.addPrefetch(prefetch);
+    }
+
+    @Override
+    public void undo() throws CannotUndoException {
+        tab.removePrefetch(prefetch);
+    }
+}


Mime
View raw message