You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by sk...@apache.org on 2016/12/19 11:46:18 UTC

[02/10] cayenne git commit: CAY-2172 Split DB actions to different packages Clean up ReverseEngineeringAction Clean up DataSource select dialogs

http://git-wip-us.apache.org/repos/asf/cayenne/blob/7da0e897/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/gen/TableSelectorController.java
----------------------------------------------------------------------
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/gen/TableSelectorController.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/gen/TableSelectorController.java
new file mode 100644
index 0000000..9304e52
--- /dev/null
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/gen/TableSelectorController.java
@@ -0,0 +1,251 @@
+/*****************************************************************
+ *   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.dialog.db.gen;
+
+import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.DbRelationship;
+import org.apache.cayenne.modeler.ProjectController;
+import org.apache.cayenne.modeler.util.CayenneController;
+import org.apache.cayenne.project.Project;
+import org.apache.cayenne.project.validation.ProjectValidator;
+import org.apache.cayenne.swing.BindingBuilder;
+import org.apache.cayenne.swing.ObjectBinding;
+import org.apache.cayenne.swing.TableBindingBuilder;
+import org.apache.cayenne.validation.ValidationFailure;
+import org.apache.cayenne.validation.ValidationResult;
+
+import java.awt.Component;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ */
+public class TableSelectorController extends CayenneController {
+
+    protected TableSelectorView view;
+    protected ObjectBinding tableBinding;
+
+    protected DbEntity table;
+    protected List<DbEntity> tables;
+    protected int permanentlyExcludedCount;
+    protected Map<String, DbEntity> excludedTables;
+    protected List<DbEntity> selectableTablesList;
+
+    protected Map<String, String> validationMessages;
+
+    public TableSelectorController(ProjectController parent) {
+        super(parent);
+        this.view = new TableSelectorView();
+        this.excludedTables = new HashMap<>();
+        this.selectableTablesList = new ArrayList<>();
+        this.validationMessages = new HashMap<>();
+        initController();
+    }
+
+    public Component getView() {
+        return view;
+    }
+
+    /**
+     * Called by table binding script to set current table.
+     */
+    public void setTable(DbEntity table) {
+        this.table = table;
+    }
+
+    /**
+     * Returns DbEntities that are excluded from DB generation.
+     */
+    public Collection<DbEntity> getExcludedTables() {
+        return excludedTables.values();
+    }
+
+    public List<DbEntity> getTables() {
+        return tables;
+    }
+
+    public boolean isIncluded() {
+        if (table == null) {
+            return false;
+        }
+
+        return !excludedTables.containsKey(table.getName());
+    }
+
+    public void setIncluded(boolean b) {
+        if (table == null) {
+            return;
+        }
+
+        if (b) {
+            excludedTables.remove(table.getName());
+        }
+        else {
+            excludedTables.put(table.getName(), table);
+        }
+
+        tableSelectedAction();
+    }
+
+    /**
+     * A callback action that updates the state of Select All checkbox.
+     */
+    public void tableSelectedAction() {
+        int unselectedCount = excludedTables.size() - permanentlyExcludedCount;
+
+        if (unselectedCount == selectableTablesList.size()) {
+            view.getCheckAll().setSelected(false);
+        }
+        else if (unselectedCount == 0) {
+            view.getCheckAll().setSelected(true);
+        }
+    }
+
+    public Object getProblem() {
+        return (table != null) ? validationMessages.get(table.getName()) : null;
+    }
+
+    // ------ other stuff ------
+
+    protected void initController() {
+
+        BindingBuilder builder = new BindingBuilder(
+                getApplication().getBindingFactory(),
+                this);
+
+        builder.bindToAction(view.getCheckAll(), "checkAllAction()");
+
+        TableBindingBuilder tableBuilder = new TableBindingBuilder(builder);
+
+        tableBuilder.addColumn(
+                "",
+                "setTable(#item), included",
+                Boolean.class,
+                true,
+                Boolean.TRUE);
+        tableBuilder.addColumn(
+                "Table",
+                "#item.name",
+                String.class,
+                false,
+                "XXXXXXXXXXXXXXXX");
+        tableBuilder.addColumn(
+                "Problems",
+                "setTable(#item), problem",
+                String.class,
+                false,
+                "XXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
+
+        this.tableBinding = tableBuilder.bindToTable(view.getTables(), "tables");
+    }
+
+    /**
+     * Performs validation of DbEntities in the current DataMap. Returns a collection of
+     * ValidationInfo objects describing the problems.
+     */
+    public void updateTables(Collection<DataMap> dataMaps) {
+        this.tables = new ArrayList<DbEntity>();
+
+        for (DataMap dataMap : dataMaps) {
+            this.tables.addAll(dataMap.getDbEntities());
+        }
+
+        excludedTables.clear();
+        validationMessages.clear();
+
+        // if there were errors, filter out those related to
+        // non-derived DbEntities...
+
+        // TODO: this is inefficient.. we need targeted validation
+        // instead of doing it on the whole project
+
+        Project project = getApplication().getProject();
+
+        ProjectValidator projectValidator = getApplication().getInjector().getInstance(
+                ProjectValidator.class);
+        ValidationResult validationResult = projectValidator.validate(project
+                .getRootNode());
+
+        if (validationResult.getFailures().size() > 0) {
+
+            for (ValidationFailure nextProblem : validationResult.getFailures()) {
+                DbEntity failedEntity = null;
+
+                if (nextProblem.getSource() instanceof DbAttribute) {
+                    DbAttribute failedAttribute = (DbAttribute) nextProblem.getSource();
+                    failedEntity = (DbEntity) failedAttribute.getEntity();
+                }
+                else if (nextProblem.getSource() instanceof DbRelationship) {
+                    DbRelationship failedRelationship = (DbRelationship) nextProblem
+                            .getSource();
+                    failedEntity = (DbEntity) failedRelationship.getSourceEntity();
+                }
+                else if (nextProblem.getSource() instanceof DbEntity) {
+                    failedEntity = (DbEntity) nextProblem.getSource();
+                }
+
+                if (failedEntity == null) {
+                    continue;
+                }
+
+                excludedTables.put(failedEntity.getName(), failedEntity);
+                validationMessages.put(failedEntity.getName(), nextProblem
+                        .getDescription());
+            }
+        }
+
+        // Find selectable tables
+        permanentlyExcludedCount = excludedTables.size();
+        selectableTablesList.clear();
+        for (DbEntity table : tables) {
+            if (false == excludedTables.containsKey(table.getName())) {
+                selectableTablesList.add(table);
+            }
+        }
+
+        tableBinding.updateView();
+        tableSelectedAction();
+    }
+
+    public void checkAllAction() {
+
+        boolean isCheckAllSelected = view.getCheckAll().isSelected();
+
+        if (isCheckAllSelected) {
+            selectableTablesList.clear();
+            selectableTablesList.addAll(tables);
+            excludedTables.clear();
+        }
+        else {
+            excludedTables.clear();
+            for (DbEntity table : tables) {
+                excludedTables.put(table.getName(), table);
+            }
+            selectableTablesList.clear();
+        }
+
+        tableBinding.updateView();
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/7da0e897/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/gen/TableSelectorView.java
----------------------------------------------------------------------
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/gen/TableSelectorView.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/gen/TableSelectorView.java
new file mode 100644
index 0000000..f618e65
--- /dev/null
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/gen/TableSelectorView.java
@@ -0,0 +1,94 @@
+/*****************************************************************
+ *   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.dialog.db.gen;
+
+import java.awt.BorderLayout;
+import java.awt.FlowLayout;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+
+import javax.swing.JCheckBox;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+
+import com.jgoodies.forms.builder.PanelBuilder;
+import com.jgoodies.forms.layout.CellConstraints;
+import com.jgoodies.forms.layout.FormLayout;
+
+/**
+ */
+public class TableSelectorView extends JPanel {
+
+    protected JTable tables;
+    protected JCheckBox checkAll;
+    protected JLabel checkAllLabel;
+
+    public TableSelectorView() {
+
+        this.checkAll = new JCheckBox();
+        this.checkAllLabel = new JLabel("Check All Tables");
+
+        checkAll.addItemListener(new ItemListener() {
+
+            public void itemStateChanged(ItemEvent event) {
+                if (checkAll.isSelected()) {
+                    checkAllLabel.setText("Uncheck All Tables");
+                }
+                else {
+                    checkAllLabel.setText("Check All Tables");
+                }
+            }
+        });
+
+        // assemble
+        JPanel topPanel = new JPanel(new FlowLayout(FlowLayout.LEADING));
+        topPanel.add(checkAll);
+        topPanel.add(checkAllLabel);
+
+        tables = new JTable();
+        tables.setRowHeight(25);
+        tables.setRowMargin(3);
+
+        CellConstraints cc = new CellConstraints();
+        PanelBuilder builder = new PanelBuilder(new FormLayout(
+                "fill:min(50dlu;pref):grow",
+                "p, 3dlu, fill:40dlu:grow"));
+        builder.setDefaultDialogBorder();
+        builder.addSeparator("Select Tables", cc.xy(1, 1));
+        builder.add(new JScrollPane(
+                tables,
+                JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
+                JScrollPane.HORIZONTAL_SCROLLBAR_NEVER), cc.xy(1, 3));
+
+        setLayout(new BorderLayout());
+        add(topPanel, BorderLayout.NORTH);
+        add(builder.getPanel(), BorderLayout.CENTER);
+    }
+
+    public JTable getTables() {
+        return tables;
+    }
+
+    public JCheckBox getCheckAll() {
+        return checkAll;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/7da0e897/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/load/DbImportProjectSaver.java
----------------------------------------------------------------------
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/load/DbImportProjectSaver.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/load/DbImportProjectSaver.java
new file mode 100644
index 0000000..2a58363
--- /dev/null
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/load/DbImportProjectSaver.java
@@ -0,0 +1,74 @@
+/*****************************************************************
+ *   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.dialog.db.load;
+
+import org.apache.cayenne.configuration.ConfigurationNameMapper;
+import org.apache.cayenne.configuration.DataChannelDescriptor;
+import org.apache.cayenne.configuration.event.DataMapEvent;
+import org.apache.cayenne.di.Inject;
+import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.map.event.MapEvent;
+import org.apache.cayenne.modeler.Application;
+import org.apache.cayenne.modeler.ProjectController;
+import org.apache.cayenne.project.Project;
+import org.apache.cayenne.project.ProjectSaver;
+import org.apache.cayenne.resource.Resource;
+
+public class DbImportProjectSaver implements ProjectSaver {
+
+    private ConfigurationNameMapper nameMapper;
+
+    private ProjectController projectController;
+
+    public DbImportProjectSaver(@Inject ProjectController projectController, @Inject ConfigurationNameMapper nameMapper) {
+        this.projectController = projectController;
+        this.nameMapper = nameMapper;
+    }
+
+    @Override
+    public String getSupportedVersion() {
+        // not important in the context of non-saving saver
+        return "-1";
+    }
+
+    @Override
+    public void save(Project project) {
+
+        DataMap dataMap = (DataMap) project.getRootNode();
+
+        if (projectController.getCurrentDataMap() != null) {
+            projectController.fireDataMapEvent(new DataMapEvent(Application.getFrame(), dataMap, MapEvent.REMOVE));
+            projectController.fireDataMapEvent(new DataMapEvent(Application.getFrame(), dataMap, MapEvent.ADD));
+        } else {
+            DataChannelDescriptor currentDomain = (DataChannelDescriptor) projectController.getProject().getRootNode();
+            Resource baseResource = currentDomain.getConfigurationSource();
+            // a new DataMap, so need to set configuration source for it
+            if (baseResource != null) {
+                Resource dataMapResource = baseResource.getRelativeResource(nameMapper.configurationLocation(dataMap));
+                dataMap.setConfigurationSource(dataMapResource);
+            }
+            projectController.addDataMap(Application.getFrame(), dataMap);
+        }
+    }
+
+    @Override
+    public void saveAs(Project project, Resource baseDirectory) {
+        save(project);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/7da0e897/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/load/DbLoaderContext.java
----------------------------------------------------------------------
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/load/DbLoaderContext.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/load/DbLoaderContext.java
new file mode 100644
index 0000000..6a550a6
--- /dev/null
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/load/DbLoaderContext.java
@@ -0,0 +1,176 @@
+/*****************************************************************
+ *   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.dialog.db.load;
+
+import java.io.File;
+import java.sql.Connection;
+import javax.swing.JOptionPane;
+import javax.swing.SwingUtilities;
+
+import org.apache.cayenne.configuration.ConfigurationNode;
+import org.apache.cayenne.dbimport.Catalog;
+import org.apache.cayenne.dbimport.ExcludeTable;
+import org.apache.cayenne.dbimport.IncludeProcedure;
+import org.apache.cayenne.dbimport.IncludeTable;
+import org.apache.cayenne.dbimport.ReverseEngineering;
+import org.apache.cayenne.dbimport.Schema;
+import org.apache.cayenne.dbsync.naming.NameBuilder;
+import org.apache.cayenne.dbsync.reverse.db.DbLoaderDelegate;
+import org.apache.cayenne.dbsync.reverse.filters.FiltersConfigBuilder;
+import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.modeler.Application;
+import org.apache.cayenne.modeler.ProjectController;
+import org.apache.cayenne.modeler.dialog.db.DataSourceWizard;
+import org.apache.cayenne.modeler.pref.DBConnectionInfo;
+import org.apache.cayenne.tools.dbimport.DbImportConfiguration;
+import org.apache.cayenne.util.Util;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+public class DbLoaderContext {
+
+    private static Log LOGGER = LogFactory.getLog(DbLoaderContext.class);
+
+    private DbImportConfiguration config;
+    private Connection connection;
+    private ProjectController projectController;
+    private boolean existingMap;
+    private DataMap dataMap;
+    private boolean stopping;
+    private String loadStatusNote;
+
+    public DbLoaderContext() {
+    }
+
+    DataMap getDataMap() {
+        return dataMap;
+    }
+
+    boolean isExistingDataMap() {
+        return existingMap;
+    }
+
+    public void setProjectController(ProjectController projectController) {
+        this.projectController = projectController;
+    }
+
+    ProjectController getProjectController() {
+        return projectController;
+    }
+
+    void setConfig(DbImportConfiguration config) {
+        this.config = config;
+    }
+
+    DbImportConfiguration getConfig() {
+        return config;
+    }
+
+    public void setConnection(Connection connection) {
+        this.connection = connection;
+    }
+
+    public Connection getConnection() {
+        return connection;
+    }
+
+    public boolean isStopping() {
+        return stopping;
+    }
+
+    void setStopping(boolean stopping) {
+        this.stopping = stopping;
+    }
+
+    String getStatusNote() {
+        return loadStatusNote;
+    }
+
+    void setStatusNote(String loadStatusNote) {
+        this.loadStatusNote = loadStatusNote;
+    }
+
+    public boolean buildConfig(DataSourceWizard connectionWizard, DbLoaderOptionsDialog dialog) {
+        if (dialog == null || connectionWizard == null) {
+            return false;
+        }
+
+        // Build filters
+        ReverseEngineering reverseEngineering = new ReverseEngineering();
+        reverseEngineering.addCatalog(new Catalog(dialog.getSelectedCatalog()));
+        reverseEngineering.addSchema(new Schema(dialog.getSelectedSchema()));
+        reverseEngineering.addIncludeTable(new IncludeTable(dialog.getTableNamePattern()));
+        reverseEngineering.addExcludeTable(new ExcludeTable("auto_pk_support|AUTO_PK_SUPPORT"));
+        reverseEngineering.addIncludeProcedure(new IncludeProcedure(dialog.getProcedureNamePattern()));
+        FiltersConfigBuilder filtersConfigBuilder = new FiltersConfigBuilder(reverseEngineering);
+
+        DbImportConfiguration config = new DbImportConfiguration() {
+            @Override
+            public DbLoaderDelegate createLoaderDelegate() {
+                return new LoaderDelegate(DbLoaderContext.this);
+            }
+        };
+
+        // Build config
+        DBConnectionInfo connectionInfo = connectionWizard.getConnectionInfo();
+        config.setAdapter(connectionWizard.getAdapter().getClass().getName());
+        config.setUsername(connectionInfo.getUserName());
+        config.setPassword(connectionInfo.getPassword());
+        config.setDriver(connectionInfo.getJdbcDriver());
+        config.setUrl(connectionInfo.getUrl());
+        config.getDbLoaderConfig().setFiltersConfig(filtersConfigBuilder.build());
+        config.setMeaningfulPkTables(dialog.getMeaningfulPk());
+        config.setNamingStrategy(dialog.getNamingStrategy());
+        setConfig(config);
+
+        prepareDataMap();
+
+        return true;
+    }
+
+    private void prepareDataMap() {
+        dataMap = getProjectController().getCurrentDataMap();
+        existingMap = dataMap != null;
+
+        if (!existingMap) {
+            ConfigurationNode root = getProjectController().getProject().getRootNode();
+            dataMap = new DataMap();
+            dataMap.setName(NameBuilder.builder(dataMap, root).name());
+        }
+        if (dataMap.getConfigurationSource() != null) {
+            getConfig().setTargetDataMap(new File(dataMap.getConfigurationSource().getURL().getPath()));
+        }
+    }
+
+    public void processWarn(final Throwable th, final String message) {
+        LOGGER.warn(message, Util.unwindException(th));
+    }
+
+    public void processException(final Throwable th, final String message) {
+        LOGGER.info("Exception on reverse engineering", Util.unwindException(th));
+        SwingUtilities.invokeLater(new Runnable() {
+
+            public void run() {
+                JOptionPane.showMessageDialog(Application.getFrame(), th.getMessage(), message,
+                        JOptionPane.ERROR_MESSAGE);
+            }
+        });
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/7da0e897/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/load/DbLoaderOptionsDialog.java
----------------------------------------------------------------------
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/load/DbLoaderOptionsDialog.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/load/DbLoaderOptionsDialog.java
new file mode 100644
index 0000000..1ccc7aa
--- /dev/null
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/load/DbLoaderOptionsDialog.java
@@ -0,0 +1,247 @@
+/*****************************************************************
+ *   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.dialog.db.load;
+
+import com.jgoodies.forms.builder.DefaultFormBuilder;
+import com.jgoodies.forms.layout.FormLayout;
+import org.apache.cayenne.modeler.Application;
+import org.apache.cayenne.modeler.util.CayenneDialog;
+import org.apache.cayenne.modeler.util.NameGeneratorPreferences;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.Collection;
+import java.util.Vector;
+
+/**
+ * Dialog for selecting database reverse-engineering parameters.
+ */
+public class DbLoaderOptionsDialog extends CayenneDialog {
+
+    public static final String WILDCARD_PATTERN = ".*";
+
+    public static final int CANCEL = 0;
+    public static final int SELECT = 1;
+
+    protected JLabel catalogLabel;
+    protected JComboBox<String> catalogSelector;
+    protected JLabel schemaLabel;
+    protected JComboBox<String> schemaSelector;
+    protected JTextField tableNamePatternField;
+    protected JTextField meaningfulPk;
+    protected JTextField procNamePatternField;
+    protected JLabel procedureLabel;
+    protected JButton selectButton;
+    protected JButton cancelButton;
+
+
+    protected JComboBox<String> strategyCombo;
+    protected String strategy;
+    protected int choice;
+
+    /**
+     * Creates and initializes new ChooseSchemaDialog.
+     */
+    public DbLoaderOptionsDialog(Collection<String> schemas, Collection<String> catalogs, String currentSchema,
+                                 String dbCatalog) {
+        super(Application.getFrame(), "Reengineer DB Schema: Select Options");
+
+        init();
+        initController();
+        initFromModel(schemas, catalogs, currentSchema, dbCatalog);
+
+        pack();
+        setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
+        setModal(true);
+        centerWindow();
+    }
+
+    /** Sets up the graphical components. */
+    protected void init() {
+
+        // create widgets...
+        selectButton = new JButton("Continue");
+        cancelButton = new JButton("Cancel");
+        catalogSelector = new JComboBox<>();
+        schemaSelector = new JComboBox<>();
+        tableNamePatternField = new JTextField();
+        tableNamePatternField.setToolTipText("<html>Regular expression to filter table names.<br>" +
+                "Default expression <b>.*</b> includes all tables.</html>");
+        procNamePatternField = new JTextField();
+        procNamePatternField.setToolTipText("<html>Regular expression to filter stored procedures names.<br>" +
+                "Default expression .* includes all stored procedures.</html>");
+        meaningfulPk = new JTextField();
+        meaningfulPk.setToolTipText("<html>Regular expression to filter tables with meaningful primary keys.<br>" +
+                "Multiple expressions divided by comma can be used.<br>" +
+                "Example: <b>^table1,^table2,^prefix.*</b></html>");
+        strategyCombo = new JComboBox<>();
+        strategyCombo.setEditable(true);
+
+        // assemble
+        FormLayout layout = new FormLayout(
+                "right:pref, 3dlu, fill:max(170dlu;pref):grow",
+                "");
+        DefaultFormBuilder builder = new DefaultFormBuilder(layout);
+        builder.setDefaultDialogBorder();
+
+        catalogLabel = builder.append("Select Catalog:", catalogSelector);
+        schemaLabel = builder.append("Select Schema:", schemaSelector);
+        builder.append("Table Name Pattern:", tableNamePatternField);
+        procedureLabel = builder.append("Procedure Name Pattern:", procNamePatternField);
+        builder.append("Naming Strategy:", strategyCombo);
+        builder.append("Tables with Meaningful PK Pattern:", meaningfulPk);
+
+        JPanel buttons = new JPanel(new FlowLayout(FlowLayout.RIGHT));
+        buttons.add(cancelButton);
+        buttons.add(selectButton);
+
+        getContentPane().setLayout(new BorderLayout());
+        getContentPane().add(builder.getPanel(), BorderLayout.CENTER);
+        getContentPane().add(buttons, BorderLayout.SOUTH);
+    }
+
+    protected void initController() {
+        selectButton.addActionListener(new ActionListener() {
+
+            public void actionPerformed(ActionEvent e) {
+                processSelect();
+            }
+        });
+        cancelButton.addActionListener(new ActionListener() {
+
+            public void actionPerformed(ActionEvent e) {
+                processCancel();
+            }
+        });
+    }
+
+    protected void initFromModel(
+            Collection<String> schemas,
+            Collection<String> catalogs,
+            String currentSchema,
+            String currentCatalog) {
+
+        this.choice = CANCEL;
+        this.tableNamePatternField.setText(WILDCARD_PATTERN);
+        this.procNamePatternField.setText(WILDCARD_PATTERN);
+
+        Vector<String> arr = NameGeneratorPreferences
+                .getInstance()
+                .getLastUsedStrategies();
+        strategyCombo.setModel(new DefaultComboBoxModel<>(arr));
+
+        boolean showSchemaSelector = schemas != null && !schemas.isEmpty();
+        schemaSelector.setVisible(showSchemaSelector);
+        schemaLabel.setVisible(showSchemaSelector);
+
+        if (showSchemaSelector) {
+
+            schemaSelector.setModel(new DefaultComboBoxModel<>(schemas.toArray(new String[0])));
+
+            if (currentSchema != null) {
+                for (String schema : schemas) {
+                    if (currentSchema.equalsIgnoreCase(schema)) {
+                        schemaSelector.setSelectedItem(schema);
+                        break;
+                    }
+                }
+            }
+        }
+
+        boolean showCatalogSelector = catalogs != null && !catalogs.isEmpty();
+        catalogSelector.setVisible(showCatalogSelector);
+        catalogLabel.setVisible(showCatalogSelector);
+
+        if (showCatalogSelector) {
+            catalogSelector.setModel(new DefaultComboBoxModel<>(catalogs.toArray(new String[0])));
+
+            if (currentCatalog != null && !currentCatalog.isEmpty()) {
+                for (String catalog : catalogs) {
+                    if (currentCatalog.equalsIgnoreCase(catalog)) {
+                        catalogSelector.setSelectedItem(catalog);
+                        break;
+                    }
+                }
+            }
+        }
+    }
+
+    public int getChoice() {
+        return choice;
+    }
+
+    private void processSelect() {
+        strategy = (String) strategyCombo.getSelectedItem();
+
+        choice = SELECT;
+        setVisible(false);
+    }
+
+    private void processCancel() {
+        choice = CANCEL;
+        setVisible(false);
+    }
+
+    /**
+     * Returns selected catalog.
+     */
+    public String getSelectedCatalog() {
+        String catalog = (String) catalogSelector.getSelectedItem();
+        return "".equals(catalog) ? null : catalog;
+    }
+
+    /**
+     * Returns selected schema.
+     */
+    public String getSelectedSchema() {
+        String schema = (String) schemaSelector.getSelectedItem();
+        return "".equals(schema) ? null : schema;
+    }
+
+    /**
+     * Returns the tableNamePattern.
+     */
+    public String getTableNamePattern() {
+        return "".equals(tableNamePatternField.getText()) ? null : tableNamePatternField
+                .getText();
+    }
+
+    public String getMeaningfulPk() {
+        return "".equals(meaningfulPk.getText()) ? null : meaningfulPk
+                .getText();
+    }
+
+    /**
+     * Returns the procedure name pattern.
+     */
+    public String getProcedureNamePattern() {
+        return "".equals(procNamePatternField.getText()) ? null : procNamePatternField
+                .getText();
+    }
+
+    /**
+     * Returns configured naming strategy
+     */
+    public String getNamingStrategy() {
+        return strategy;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/7da0e897/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/load/LoadDataMapTask.java
----------------------------------------------------------------------
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/load/LoadDataMapTask.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/load/LoadDataMapTask.java
new file mode 100644
index 0000000..ceb0d44
--- /dev/null
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/load/LoadDataMapTask.java
@@ -0,0 +1,94 @@
+/*****************************************************************
+ *   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.dialog.db.load;
+
+import javax.swing.JFrame;
+
+import org.apache.cayenne.dbsync.DbSyncModule;
+import org.apache.cayenne.di.DIBootstrap;
+import org.apache.cayenne.di.Injector;
+import org.apache.cayenne.modeler.util.LongRunningTask;
+import org.apache.cayenne.modeler.util.ProjectUtil;
+import org.apache.cayenne.tools.configuration.ToolsModule;
+import org.apache.cayenne.tools.dbimport.DbImportAction;
+import org.apache.cayenne.tools.dbimport.DbImportModule;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+final public class LoadDataMapTask extends LongRunningTask {
+
+    private static Log LOGGER = LogFactory.getLog(DbLoaderContext.class);
+
+    private DbLoaderContext context;
+
+    public LoadDataMapTask(JFrame frame, String title, DbLoaderContext context) {
+        super(frame, title);
+        setMinValue(0);
+        setMaxValue(10);
+        this.context = context;
+    }
+
+    @Override
+    protected void execute() {
+        context.setStatusNote("Preparing...");
+        try {
+            createAction().execute(context.getConfig());
+        } catch (Exception e) {
+            context.processException(e, "Error importing database schema.");
+        }
+        ProjectUtil.cleanObjMappings(context.getDataMap());
+    }
+
+    private DbImportAction createAction() {
+        Injector injector = DIBootstrap.createInjector(new DbSyncModule(),
+                new ToolsModule(LOGGER),
+                new DbImportModule(),
+                new ModelerSyncModule(context));
+        return injector.getInstance(DbImportAction.class);
+    }
+
+    @Override
+    protected String getCurrentNote() {
+        return context.getStatusNote();
+    }
+
+    @Override
+    protected int getCurrentValue() {
+        return getMinValue();
+    }
+
+    @Override
+    protected boolean isIndeterminate() {
+        return true;
+    }
+
+    @Override
+    public boolean isCanceled() {
+        return context.isStopping();
+    }
+
+    @Override
+    public void setCanceled(boolean canceled) {
+        if (canceled) {
+            context.setStatusNote("Canceling..");
+        }
+        context.setStopping(canceled);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/7da0e897/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/load/LoaderDelegate.java
----------------------------------------------------------------------
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/load/LoaderDelegate.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/load/LoaderDelegate.java
new file mode 100644
index 0000000..92043fc
--- /dev/null
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/load/LoaderDelegate.java
@@ -0,0 +1,71 @@
+/*****************************************************************
+ *   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.dialog.db.load;
+
+import org.apache.cayenne.CayenneRuntimeException;
+import org.apache.cayenne.dbsync.reverse.db.DefaultDbLoaderDelegate;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.DbRelationship;
+import org.apache.cayenne.map.event.EntityEvent;
+import org.apache.cayenne.map.event.MapEvent;
+import org.apache.cayenne.modeler.Application;
+
+final class LoaderDelegate extends DefaultDbLoaderDelegate {
+
+    private DbLoaderContext context;
+
+    LoaderDelegate(DbLoaderContext dbLoaderContext) {
+        this.context = dbLoaderContext;
+    }
+
+    @Override
+    public void dbEntityAdded(DbEntity entity) {
+        checkCanceled();
+        context.setStatusNote("Importing table '" + entity.getName() + "'...");
+    }
+
+    @Override
+    public void dbEntityRemoved(DbEntity entity) {
+        checkCanceled();
+        if (context.isExistingDataMap()) {
+            context.getProjectController().fireDbEntityEvent(new EntityEvent(Application.getFrame(), entity, MapEvent.REMOVE));
+        }
+    }
+
+    @Override
+    public boolean dbRelationship(DbEntity entity) {
+        checkCanceled();
+        context.setStatusNote("Load relationships for '" + entity.getName() + "'...");
+        return true;
+    }
+
+    @Override
+    public boolean dbRelationshipLoaded(DbEntity entity, DbRelationship relationship) {
+        checkCanceled();
+        context.setStatusNote("Load relationship: '" + entity.getName() + "'; '" + relationship.getName() + "'...");
+        return true;
+    }
+
+    private void checkCanceled() {
+        if (context.isStopping()) {
+            throw new CayenneRuntimeException("Reengineering was canceled.");
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/7da0e897/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/load/ModelerDbImportAction.java
----------------------------------------------------------------------
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/load/ModelerDbImportAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/load/ModelerDbImportAction.java
new file mode 100644
index 0000000..34ad1b5
--- /dev/null
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/load/ModelerDbImportAction.java
@@ -0,0 +1,53 @@
+/*****************************************************************
+ *   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.dialog.db.load;
+
+import org.apache.cayenne.configuration.server.DataSourceFactory;
+import org.apache.cayenne.configuration.server.DbAdapterFactory;
+import org.apache.cayenne.dbsync.merge.factory.MergerTokenFactoryProvider;
+import org.apache.cayenne.di.Inject;
+import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.map.MapLoader;
+import org.apache.cayenne.project.ProjectSaver;
+import org.apache.cayenne.tools.dbimport.DbImportConfiguration;
+import org.apache.cayenne.tools.dbimport.DefaultDbImportAction;
+import org.apache.commons.logging.Log;
+
+import java.io.IOException;
+
+public class ModelerDbImportAction extends DefaultDbImportAction {
+
+    @Inject
+    private DataMap targetMap;
+
+    public ModelerDbImportAction(@Inject Log logger,
+                                 @Inject ProjectSaver projectSaver,
+                                 @Inject DataSourceFactory dataSourceFactory,
+                                 @Inject DbAdapterFactory adapterFactory,
+                                 @Inject MapLoader mapLoader,
+                                 @Inject MergerTokenFactoryProvider mergerTokenFactoryProvider
+                                 ) {
+        super(logger, projectSaver, dataSourceFactory, adapterFactory, mapLoader, mergerTokenFactoryProvider);
+    }
+
+    @Override
+    protected DataMap existingTargetMap(DbImportConfiguration configuration) throws IOException {
+        return targetMap;
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/7da0e897/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/load/ModelerSyncModule.java
----------------------------------------------------------------------
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/load/ModelerSyncModule.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/load/ModelerSyncModule.java
new file mode 100644
index 0000000..24fdfcf
--- /dev/null
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/load/ModelerSyncModule.java
@@ -0,0 +1,44 @@
+/*****************************************************************
+ *   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.dialog.db.load;
+
+import org.apache.cayenne.di.Binder;
+import org.apache.cayenne.di.Module;
+import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.modeler.ProjectController;
+import org.apache.cayenne.project.ProjectSaver;
+import org.apache.cayenne.tools.dbimport.DbImportAction;
+
+class ModelerSyncModule implements Module {
+
+    private DbLoaderContext dbLoaderContext;
+
+    ModelerSyncModule(DbLoaderContext dbLoaderHelper) {
+        this.dbLoaderContext = dbLoaderHelper;
+    }
+
+    @Override
+    public void configure(Binder binder) {
+        binder.bind(ProjectController.class).toInstance(dbLoaderContext.getProjectController());
+        binder.bind(ProjectSaver.class).to(DbImportProjectSaver.class);
+        binder.bind(DbImportAction.class).to(ModelerDbImportAction.class);
+        binder.bind(DataMap.class).toInstance(dbLoaderContext.getDataMap());
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/7da0e897/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/merge/DbMigrateOptionsDialog.java
----------------------------------------------------------------------
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/merge/DbMigrateOptionsDialog.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/merge/DbMigrateOptionsDialog.java
new file mode 100644
index 0000000..b3fb929
--- /dev/null
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/merge/DbMigrateOptionsDialog.java
@@ -0,0 +1,176 @@
+/*****************************************************************
+ *   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.dialog.db.merge;
+
+import java.awt.BorderLayout;
+import java.awt.FlowLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.Collection;
+
+import javax.swing.DefaultComboBoxModel;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import org.apache.cayenne.modeler.Application;
+import org.apache.cayenne.modeler.util.CayenneDialog;
+
+import com.jgoodies.forms.builder.DefaultFormBuilder;
+import com.jgoodies.forms.layout.FormLayout;
+
+public class DbMigrateOptionsDialog extends CayenneDialog {
+	private static final long serialVersionUID = 1L;
+	public static final int CANCEL = 0;
+    public static final int SELECT = 1;
+
+	protected JLabel schemaLabel;
+	protected JLabel catalogLabel;
+    protected JComboBox<String> catalogSelector;
+    protected JComboBox<String> schemaSelector;
+    protected JButton selectButton;
+    protected JButton cancelButton;
+    protected int choice;
+    
+    public DbMigrateOptionsDialog(Collection<String> catalogs, Collection<String> schemas, String dbUserName) {
+        super(Application.getFrame(), "Migrate DB Schema: Select Catalog and Schema");
+        init();
+        initController();
+        initFromModel(catalogs, schemas, dbUserName);
+
+        pack();
+        setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
+        setModal(true);
+        centerWindow();    	
+    }
+    
+    protected void init() {
+        selectButton = new JButton("Continue");
+        cancelButton = new JButton("Cancel");
+        catalogSelector = new JComboBox<>();
+        schemaSelector = new JComboBox<>();
+        FormLayout layout = new FormLayout(
+                "right:pref, 3dlu, fill:max(170dlu;pref):grow",
+                "");
+        DefaultFormBuilder builder = new DefaultFormBuilder(layout);
+        builder.setDefaultDialogBorder();
+
+        catalogLabel = builder.append("Select Catalog:", catalogSelector, true);
+        schemaLabel = builder.append("Select Schema:", schemaSelector);
+
+        JPanel buttons = new JPanel(new FlowLayout(FlowLayout.RIGHT));
+        buttons.add(selectButton);
+        buttons.add(cancelButton);
+        
+        getContentPane().setLayout(new BorderLayout());
+        getContentPane().add(builder.getPanel(), BorderLayout.CENTER);
+        getContentPane().add(buttons, BorderLayout.SOUTH);        
+    }
+    
+    protected void initController() {
+        selectButton.addActionListener(new ActionListener() {
+
+            public void actionPerformed(ActionEvent e) {
+                processSelect();
+            }
+        });
+
+        cancelButton.addActionListener(new ActionListener() {
+
+            public void actionPerformed(ActionEvent e) {
+                processCancel();
+            }
+        });
+    }
+    
+    private void processSelect() {
+        choice = SELECT;
+        setVisible(false);
+    }
+
+    private void processCancel() {
+    	schemaSelector.setSelectedItem(null);
+        choice = CANCEL;
+        setVisible(false);
+    }
+    
+    protected void initFromModel(Collection<String> catalogs, Collection<String> schemas, String dbUserName) {
+
+        this.choice = CANCEL;
+
+        if(!schemas.isEmpty()) {
+            schemaLabel.setVisible(true);
+            schemaSelector.setModel(new DefaultComboBoxModel<>(schemas.toArray(new String[0])));
+            schemaSelector.setVisible(true);
+        } else {
+            schemaLabel.setVisible(false);
+            schemaSelector.setVisible(false);
+        }
+
+        if(!catalogs.isEmpty()) {
+            catalogLabel.setVisible(true);
+            catalogSelector.setModel(new DefaultComboBoxModel<>(catalogs.toArray(new String[0])));
+            catalogSelector.setVisible(true);
+        } else {
+            catalogLabel.setVisible(false);
+            catalogSelector.setVisible(false);
+        }
+
+        if (dbUserName == null) {
+            return;
+        }
+
+        // select schema belonging to the user
+        for (String schema : schemas) {
+            if (dbUserName.equalsIgnoreCase(schema)) {
+                schemaSelector.setSelectedItem(schema);
+                break;
+            }
+        }
+
+        for(String catalog : catalogs) {
+            if(dbUserName.equalsIgnoreCase(catalog)) {
+                catalogSelector.setSelectedItem(catalog);
+                break;
+            }
+        }
+    }
+    
+    /**
+     * Returns selected schema.
+     */
+    public String getSelectedSchema() {
+    	return (String) schemaSelector.getSelectedItem();
+    }
+
+    public String getSelectedCatalog() {
+        return (String) catalogSelector.getSelectedItem();
+    }
+    
+    public int getChoice() {
+    	return choice;
+    }
+    
+    public void showDialog() {
+    	setVisible(true);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/7da0e897/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/merge/MergerOptions.java
----------------------------------------------------------------------
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/merge/MergerOptions.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/merge/MergerOptions.java
new file mode 100644
index 0000000..a914cf1
--- /dev/null
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/merge/MergerOptions.java
@@ -0,0 +1,395 @@
+/*****************************************************************
+ *   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.dialog.db.merge;
+
+import org.apache.cayenne.CayenneRuntimeException;
+import org.apache.cayenne.configuration.event.DataMapEvent;
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.dbsync.merge.AbstractToDbToken;
+import org.apache.cayenne.dbsync.merge.DbMerger;
+import org.apache.cayenne.dbsync.merge.DefaultModelMergeDelegate;
+import org.apache.cayenne.dbsync.merge.MergeDirection;
+import org.apache.cayenne.dbsync.merge.MergerContext;
+import org.apache.cayenne.dbsync.merge.MergerToken;
+import org.apache.cayenne.dbsync.merge.ModelMergeDelegate;
+import org.apache.cayenne.dbsync.merge.ProxyModelMergeDelegate;
+import org.apache.cayenne.dbsync.merge.factory.MergerTokenFactory;
+import org.apache.cayenne.dbsync.merge.factory.MergerTokenFactoryProvider;
+import org.apache.cayenne.dbsync.naming.DefaultObjectNameGenerator;
+import org.apache.cayenne.dbsync.naming.NoStemStemmer;
+import org.apache.cayenne.dbsync.reverse.db.DbLoader;
+import org.apache.cayenne.dbsync.reverse.db.DbLoaderConfiguration;
+import org.apache.cayenne.dbsync.reverse.db.LoggingDbLoaderDelegate;
+import org.apache.cayenne.dbsync.reverse.filters.FiltersConfig;
+import org.apache.cayenne.dbsync.reverse.filters.PatternFilter;
+import org.apache.cayenne.dbsync.reverse.filters.TableFilter;
+import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.map.ObjEntity;
+import org.apache.cayenne.map.event.MapEvent;
+import org.apache.cayenne.modeler.Application;
+import org.apache.cayenne.modeler.ProjectController;
+import org.apache.cayenne.modeler.dialog.ValidationResultBrowser;
+import org.apache.cayenne.modeler.pref.DBConnectionInfo;
+import org.apache.cayenne.modeler.util.CayenneController;
+import org.apache.cayenne.project.Project;
+import org.apache.cayenne.resource.Resource;
+import org.apache.cayenne.swing.BindingBuilder;
+import org.apache.cayenne.swing.ObjectBinding;
+import org.apache.cayenne.tools.dbimport.DefaultDbImportAction;
+import org.apache.cayenne.validation.ValidationResult;
+import org.apache.commons.logging.LogFactory;
+
+import javax.sql.DataSource;
+import javax.swing.JFileChooser;
+import javax.swing.JOptionPane;
+import javax.swing.WindowConstants;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import java.awt.Component;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+public class MergerOptions extends CayenneController {
+
+    protected MergerOptionsView view;
+    protected ObjectBinding sqlBinding;
+
+    protected DBConnectionInfo connectionInfo;
+    protected DataMap dataMap;
+    protected DbAdapter adapter;
+    protected String textForSQL;
+
+    protected MergerTokenSelectorController tokens;
+    protected String defaultCatalog;
+    protected String defaultSchema;
+    private MergerTokenFactoryProvider mergerTokenFactoryProvider;
+
+    public MergerOptions(ProjectController parent,
+                         String title,
+                         DBConnectionInfo connectionInfo,
+                         DataMap dataMap,
+                         String defaultCatalog,
+                         String defaultSchema,
+                         MergerTokenFactoryProvider mergerTokenFactoryProvider) {
+        super(parent);
+
+        this.mergerTokenFactoryProvider = mergerTokenFactoryProvider;
+        this.dataMap = dataMap;
+        this.tokens = new MergerTokenSelectorController(parent);
+        this.view = new MergerOptionsView(tokens.getView());
+        this.connectionInfo = connectionInfo;
+        this.defaultCatalog = defaultCatalog;
+        this.defaultSchema = defaultSchema;
+        this.view.setTitle(title);
+        initController();
+
+        prepareMigrator();
+        createSQL();
+        refreshView();
+    }
+
+    public Component getView() {
+        return view;
+    }
+
+    public String getTextForSQL() {
+        return textForSQL;
+    }
+
+    protected void initController() {
+
+        BindingBuilder builder = new BindingBuilder(
+                getApplication().getBindingFactory(),
+                this);
+
+        sqlBinding = builder.bindToTextArea(view.getSql(), "textForSQL");
+
+        builder.bindToAction(view.getGenerateButton(), "generateSchemaAction()");
+        builder.bindToAction(view.getSaveSqlButton(), "storeSQLAction()");
+        builder.bindToAction(view.getCancelButton(), "closeAction()");
+
+        // refresh SQL if different tables were selected
+        view.getTabs().addChangeListener(new ChangeListener() {
+
+            public void stateChanged(ChangeEvent e) {
+                if (view.getTabs().getSelectedIndex() == 1) {
+                    // this assumes that some tables where checked/unchecked... not very
+                    // efficient
+                    refreshGeneratorAction();
+                }
+            }
+        });
+    }
+
+    /**
+     * check database and create the {@link List} of {@link MergerToken}s
+     */
+    protected void prepareMigrator() {
+        try {
+            adapter = connectionInfo.makeAdapter(getApplication().getClassLoadingService());
+
+            MergerTokenFactory mergerTokenFactory = mergerTokenFactoryProvider.get(adapter);
+            tokens.setMergerTokenFactory(mergerTokenFactory);
+
+
+            FiltersConfig filters = FiltersConfig.create(defaultCatalog, defaultSchema, TableFilter.everything(),
+                    PatternFilter.INCLUDE_NOTHING);
+
+            DbMerger merger = DbMerger.builder(mergerTokenFactory)
+                    .filters(filters)
+                    .build();
+
+            DbLoaderConfiguration config = new DbLoaderConfiguration();
+            config.setFiltersConfig(filters);
+
+            DataSource dataSource = connectionInfo.makeDataSource(getApplication().getClassLoadingService());
+
+            DataMap dbImport = new DataMap();
+            try (Connection conn = dataSource.getConnection();) {
+                new DbLoader(conn,
+                        adapter,
+                        new LoggingDbLoaderDelegate(LogFactory.getLog(DbLoader.class)),
+                        new DefaultObjectNameGenerator(NoStemStemmer.getInstance()))
+                        .load(dbImport, config);
+
+            } catch (SQLException e) {
+                throw new CayenneRuntimeException("Can't doLoad dataMap from db.", e);
+            }
+
+            tokens.setTokens(merger.createMergeTokens(dataMap, dbImport));
+        } catch (Exception ex) {
+            reportError("Error loading adapter", ex);
+        }
+    }
+
+    /**
+     * Returns SQL statements generated for selected schema generation options.
+     */
+    protected void createSQL() {
+        // convert them to string representation for display
+        StringBuilder buf = new StringBuilder();
+
+        Iterator<MergerToken> it = tokens.getSelectedTokens().iterator();
+        String batchTerminator = adapter.getBatchTerminator();
+
+        String lineEnd = batchTerminator != null ? "\n" + batchTerminator + "\n\n" : "\n\n";
+        while (it.hasNext()) {
+            MergerToken token = it.next();
+
+            if (token instanceof AbstractToDbToken) {
+                AbstractToDbToken tdb = (AbstractToDbToken) token;
+                for (String sql : tdb.createSql(adapter)) {
+                    buf.append(sql);
+                    buf.append(lineEnd);
+                }
+            }
+        }
+
+        textForSQL = buf.toString();
+    }
+
+    protected void refreshView() {
+        sqlBinding.updateView();
+    }
+
+    // ===============
+    // Actions
+    // ===============
+
+    /**
+     * Starts options dialog.
+     */
+    public void startupAction() {
+        view.pack();
+        view.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
+        view.setModal(true);
+        makeCloseableOnEscape();
+        centerView();
+        view.setVisible(true);
+    }
+
+    public void refreshGeneratorAction() {
+        refreshSQLAction();
+    }
+
+    /**
+     * Updates a text area showing generated SQL.
+     */
+    public void refreshSQLAction() {
+        createSQL();
+        sqlBinding.updateView();
+    }
+
+    /**
+     * Performs configured schema operations via DbGenerator.
+     */
+    public void generateSchemaAction() {
+        refreshGeneratorAction();
+
+        // sanity check...
+        List<MergerToken> tokensToMigrate = tokens.getSelectedTokens();
+        if (tokensToMigrate.isEmpty()) {
+            JOptionPane.showMessageDialog(getView(), "Nothing to migrate.");
+            return;
+        }
+
+        DataSource dataSource;
+        try {
+            dataSource = connectionInfo.makeDataSource(getApplication()
+                    .getClassLoadingService());
+        } catch (SQLException ex) {
+            reportError("Migration Error", ex);
+            return;
+        }
+
+        final Collection<ObjEntity> loadedObjEntities = new LinkedList<>();
+
+        MergerContext mergerContext = MergerContext.builder(dataMap)
+                .syntheticDataNode(dataSource, adapter)
+                .delegate(createDelegate(loadedObjEntities))
+                .build();
+
+        boolean modelChanged = applyTokens(tokensToMigrate, mergerContext);
+
+        DefaultDbImportAction.flattenManyToManyRelationships(
+                dataMap,
+                loadedObjEntities,
+                mergerContext.getNameGenerator());
+
+        notifyProjectModified(modelChanged);
+
+        reportFailures(mergerContext);
+    }
+
+    private ModelMergeDelegate createDelegate(final Collection<ObjEntity> loadedObjEntities) {
+        return new ProxyModelMergeDelegate(new DefaultModelMergeDelegate()) {
+            @Override
+            public void objEntityAdded(ObjEntity ent) {
+                loadedObjEntities.add(ent);
+                super.objEntityAdded(ent);
+            }
+        };
+    }
+
+    private boolean applyTokens(List<MergerToken> tokensToMigrate, MergerContext mergerContext) {
+        boolean modelChanged = false;
+
+        try {
+            for (MergerToken tok : tokensToMigrate) {
+                int numOfFailuresBefore = getFailuresCount(mergerContext);
+
+                tok.execute(mergerContext);
+
+                if (!modelChanged && tok.getDirection().equals(MergeDirection.TO_MODEL)) {
+                    modelChanged = true;
+                }
+                if (numOfFailuresBefore == getFailuresCount(mergerContext)) {
+                    // looks like the token executed without failures
+                    tokens.removeToken(tok);
+                }
+            }
+        } catch (Throwable th) {
+            reportError("Migration Error", th);
+        }
+
+        return modelChanged;
+    }
+
+    private int getFailuresCount(MergerContext mergerContext) {
+        return mergerContext.getValidationResult().getFailures().size();
+    }
+
+    private void reportFailures(MergerContext mergerContext) {
+        ValidationResult failures = mergerContext.getValidationResult();
+        if (failures == null || !failures.hasFailures()) {
+            JOptionPane.showMessageDialog(getView(), "Migration Complete.");
+        } else {
+            new ValidationResultBrowser(this).startupAction(
+                    "Migration Complete",
+                    "Migration finished. The following problem(s) were ignored.",
+                    failures);
+        }
+    }
+
+    private void notifyProjectModified(boolean modelChanged) {
+        if(!modelChanged) {
+            return;
+        }
+
+        // mark the model as unsaved
+        Project project = getApplication().getProject();
+        project.setModified(true);
+
+        ProjectController projectController = getProjectController();
+        projectController.setDirty(true);
+
+        projectController.fireDataMapEvent(new DataMapEvent(Application.getFrame(),
+                dataMap, MapEvent.REMOVE));
+        projectController.fireDataMapEvent(new DataMapEvent(Application.getFrame(),
+                dataMap, MapEvent.ADD));
+    }
+
+    /**
+     * Allows user to save generated SQL in a file.
+     */
+    public void storeSQLAction() {
+        JFileChooser fc = new JFileChooser();
+        fc.setDialogType(JFileChooser.SAVE_DIALOG);
+        fc.setDialogTitle("Save SQL Script");
+
+        Resource projectDir = getApplication().getProject().getConfigurationResource();
+
+        if (projectDir != null) {
+            fc.setCurrentDirectory(new File(projectDir.getURL().getPath()));
+        }
+
+        if (fc.showSaveDialog(getView()) == JFileChooser.APPROVE_OPTION) {
+            refreshGeneratorAction();
+
+            try {
+                File file = fc.getSelectedFile();
+                FileWriter fw = new FileWriter(file);
+                PrintWriter pw = new PrintWriter(fw);
+                pw.print(textForSQL);
+                pw.flush();
+                pw.close();
+            } catch (IOException ex) {
+                reportError("Error Saving SQL", ex);
+            }
+        }
+    }
+
+    private ProjectController getProjectController() {
+        return getApplication().getFrameController().getProjectController();
+    }
+
+    public void closeAction() {
+        view.dispose();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/7da0e897/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/merge/MergerOptionsView.java
----------------------------------------------------------------------
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/merge/MergerOptionsView.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/merge/MergerOptionsView.java
new file mode 100644
index 0000000..ba68857
--- /dev/null
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/merge/MergerOptionsView.java
@@ -0,0 +1,134 @@
+/*****************************************************************
+ *   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.dialog.db.merge;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+
+import javax.swing.Box;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTabbedPane;
+import javax.swing.JTextArea;
+import javax.swing.ScrollPaneConstants;
+import javax.swing.SwingConstants;
+
+import org.apache.cayenne.modeler.Application;
+
+import com.jgoodies.forms.builder.PanelBuilder;
+import com.jgoodies.forms.layout.CellConstraints;
+import com.jgoodies.forms.layout.FormLayout;
+
+/**
+ * Wizard for altering the database to match the data map.
+ */
+public class MergerOptionsView extends JDialog {
+
+    protected JTextArea sql;
+    protected JButton generateButton;
+    protected JButton cancelButton;
+    protected JButton saveSqlButton;
+
+    protected Component tables;
+    protected JTabbedPane tabs;
+
+    public MergerOptionsView(Component tables) {
+        super(Application.getFrame());
+        
+        // create widgets
+        this.generateButton = new JButton("Migrate");
+        this.cancelButton = new JButton("Close");
+        this.saveSqlButton = new JButton("Save SQL");
+
+        this.tables = tables;
+        this.tabs = new JTabbedPane(SwingConstants.TOP);
+        this.sql = new JTextArea();
+        sql.setEditable(false);
+        sql.setLineWrap(true);
+        sql.setWrapStyleWord(true);
+
+        JPanel sqlTextPanel = new JPanel(new BorderLayout());
+        sqlTextPanel.add(new JScrollPane(
+                sql,
+                ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
+                ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED), BorderLayout.CENTER);
+
+        CellConstraints cc = new CellConstraints();
+        PanelBuilder builder = new PanelBuilder(new FormLayout(
+                "fill:min(50dlu;pref):grow",
+                "p, 9dlu, p, 3dlu, fill:40dlu:grow"));
+        builder.setDefaultDialogBorder();
+        builder.addSeparator("Generated SQL", cc.xywh(1, 3, 1, 1));
+        builder.add(sqlTextPanel, cc.xy(1, 5));
+
+        tabs.addTab("Operations", new JScrollPane(
+                tables,
+                ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
+                ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED));
+        tabs.addTab("Generated SQL", builder.getPanel());
+
+        // we need the right preferred size so that dialog "pack()" produces decent
+        // default size...
+        tabs.setPreferredSize(new Dimension(600, 350));
+
+        JPanel buttons = new JPanel(new FlowLayout(FlowLayout.RIGHT));
+        buttons.add(saveSqlButton);
+        buttons.add(Box.createHorizontalStrut(20));
+        buttons.add(cancelButton);
+        buttons.add(generateButton);
+
+        Container contentPane = this.getContentPane();
+        contentPane.setLayout(new BorderLayout());
+        contentPane.add(tabs, BorderLayout.CENTER);
+        contentPane.add(buttons, BorderLayout.SOUTH);
+    }
+
+    public JButton getCancelButton() {
+        return cancelButton;
+    }
+
+    public JTabbedPane getTabs() {
+        return tabs;
+    }
+
+    /*
+     * public JCheckBox getCreateFK() { return createFK; } public JCheckBox getCreatePK() {
+     * return createPK; } public JCheckBox getCreateTables() { return createTables; }
+     * public JCheckBox getDropPK() { return dropPK; } public JCheckBox getDropTables() {
+     * return dropTables; }
+     */
+
+    public JButton getGenerateButton() {
+        return generateButton;
+    }
+
+    public JButton getSaveSqlButton() {
+        return saveSqlButton;
+    }
+
+    public JTextArea getSql() {
+        return sql;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/7da0e897/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/merge/MergerTokenSelectorController.java
----------------------------------------------------------------------
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/merge/MergerTokenSelectorController.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/merge/MergerTokenSelectorController.java
new file mode 100644
index 0000000..52d6ce3
--- /dev/null
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/merge/MergerTokenSelectorController.java
@@ -0,0 +1,240 @@
+/*****************************************************************
+ *   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.dialog.db.merge;
+
+import org.apache.cayenne.dbsync.merge.MergeDirection;
+import org.apache.cayenne.dbsync.merge.MergerToken;
+import org.apache.cayenne.dbsync.merge.TokenComparator;
+import org.apache.cayenne.dbsync.merge.factory.MergerTokenFactory;
+import org.apache.cayenne.modeler.Application;
+import org.apache.cayenne.modeler.util.CayenneController;
+import org.apache.cayenne.swing.BindingBuilder;
+import org.apache.cayenne.swing.ObjectBinding;
+
+import javax.swing.*;
+import javax.swing.table.AbstractTableModel;
+import javax.swing.table.TableColumn;
+import javax.swing.table.TableColumnModel;
+import javax.swing.table.TableModel;
+import java.awt.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public class MergerTokenSelectorController extends CayenneController {
+
+    protected MergerTokenSelectorView view;
+    protected ObjectBinding tableBinding;
+
+    protected MergerToken token;
+    protected int permanentlyExcludedCount;
+    protected Set<MergerToken> excludedTokens;
+    protected List<MergerToken> selectableTokensList;
+    protected MergerTokenFactory mergerTokenFactory;
+
+    public MergerTokenSelectorController(CayenneController parent) {
+        super(parent);
+        this.view = new MergerTokenSelectorView();
+        this.excludedTokens = new HashSet<MergerToken>();
+        this.selectableTokensList = new ArrayList<MergerToken>();
+        initController();
+    }
+
+    public void setMergerTokenFactory(MergerTokenFactory mergerTokenFactory) {
+        this.mergerTokenFactory = mergerTokenFactory;
+    }
+
+    public void setTokens(List<MergerToken> tokens) {
+        selectableTokensList.clear();
+        selectableTokensList.addAll(tokens);
+        excludedTokens.addAll(tokens);
+    }
+
+    public List<MergerToken> getSelectedTokens() {
+        List<MergerToken> t = new ArrayList<MergerToken>(selectableTokensList);
+        t.removeAll(excludedTokens);
+        return Collections.unmodifiableList(t);
+    }
+
+    public List<MergerToken> getSelectableTokens() {
+        return Collections.unmodifiableList(selectableTokensList);
+    }
+    
+    public void removeToken(MergerToken token) {
+        selectableTokensList.remove(token);
+        excludedTokens.remove(token);
+
+        AbstractTableModel model = (AbstractTableModel) view.getTokens().getModel();
+        model.fireTableDataChanged();
+    }
+
+    // ----- properties -----
+
+    public Component getView() {
+        return view;
+    }
+
+    /**
+     * Called by table binding script to set current token.
+     */
+    public void setToken(MergerToken token) {
+        this.token = token;
+    }
+
+    /**
+     * Returns {@link MergerToken}s that are excluded from DB generation.
+     */
+    /*
+     * public Collection getExcludedTokens() { return excludedTokens; }
+     */
+
+    public boolean isIncluded() {
+        if (token == null) {
+            return false;
+        }
+
+        return !excludedTokens.contains(token);
+    }
+
+    public void setIncluded(boolean b) {
+        if (token == null) {
+            return;
+        }
+
+        if (b) {
+            excludedTokens.remove(token);
+        }
+        else {
+            excludedTokens.add(token);
+        }
+
+        tableSelectedAction();
+    }
+
+    /**
+     * A callback action that updates the state of Select All checkbox.
+     */
+    public void tableSelectedAction() {
+        int unselectedCount = excludedTokens.size() - permanentlyExcludedCount;
+
+        if (unselectedCount == selectableTokensList.size()) {
+            view.getCheckAll().setSelected(false);
+        }
+        else if (unselectedCount == 0) {
+            view.getCheckAll().setSelected(true);
+        }
+    }
+
+    // ------ other stuff ------
+
+    protected void initController() {
+        BindingBuilder builder = new BindingBuilder(
+                getApplication().getBindingFactory(),
+                this);
+
+        builder.bindToAction(view.getCheckAll(), "checkAllAction()");
+        builder.bindToAction(view.getReverseAll(), "reverseAllAction()");
+
+        TableModel model = new MergerTokenTableModel(this);
+
+        MergeDirection[] dirs = new MergeDirection[] {
+                MergeDirection.TO_DB, MergeDirection.TO_MODEL
+        };
+
+        view.getTokens().setModel(model);
+
+        TableColumnModel columnModel = view.getTokens().getColumnModel();
+        
+        // dropdown for direction column
+        JComboBox directionCombo = Application.getWidgetFactory().createComboBox(dirs, false);
+        directionCombo.setEditable(false);
+        TableColumn directionColumn = columnModel.getColumn(
+                MergerTokenTableModel.COL_DIRECTION);
+        directionColumn.setCellEditor(new DefaultCellEditor(directionCombo));
+
+        columnModel.getColumn(MergerTokenTableModel.COL_SELECT).setPreferredWidth(50);
+        columnModel.getColumn(MergerTokenTableModel.COL_DIRECTION).setPreferredWidth(100);
+        columnModel.getColumn(MergerTokenTableModel.COL_SELECT).setMaxWidth(50);
+        columnModel.getColumn(MergerTokenTableModel.COL_DIRECTION).setMaxWidth(100);
+    }
+
+    public boolean isSelected(MergerToken token) {
+        return (selectableTokensList.contains(token) && !excludedTokens.contains(token));
+    }
+
+    public void select(MergerToken token, boolean select) {
+        if (select) {
+            excludedTokens.remove(token);
+        }
+        else {
+            excludedTokens.add(token);
+        }
+    }
+
+    public void setDirection(MergerToken token, MergeDirection direction) {
+        if (token.getDirection().equals(direction)) {
+            return;
+        }
+        int i = selectableTokensList.indexOf(token);
+        MergerToken reverse = token.createReverse(mergerTokenFactory);
+        selectableTokensList.set(i, reverse);
+        if (excludedTokens.remove(token)) {
+            excludedTokens.add(reverse);
+        }
+        
+        /**
+         * Repaint, so that "Operation" column updates properly
+         */
+        view.getTokens().repaint();
+    }
+
+    public void checkAllAction() {
+
+        boolean isCheckAllSelected = view.getCheckAll().isSelected();
+
+        if (isCheckAllSelected) {
+            excludedTokens.clear();
+        }
+        else {
+            excludedTokens.addAll(selectableTokensList);
+        }
+
+        AbstractTableModel model = (AbstractTableModel) view.getTokens().getModel();
+        model.fireTableDataChanged();
+    }
+
+    public void reverseAllAction() {
+        
+        for (int i = 0; i < selectableTokensList.size(); i++) {
+            MergerToken token = selectableTokensList.get(i);
+            MergerToken reverse = token.createReverse(mergerTokenFactory);
+            selectableTokensList.set(i, reverse);
+            if (excludedTokens.remove(token)) {
+                excludedTokens.add(reverse);
+            }
+        }
+
+        Collections.sort(selectableTokensList, new TokenComparator());
+        AbstractTableModel model = (AbstractTableModel) view.getTokens().getModel();
+        model.fireTableDataChanged();
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/7da0e897/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/merge/MergerTokenSelectorView.java
----------------------------------------------------------------------
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/merge/MergerTokenSelectorView.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/merge/MergerTokenSelectorView.java
new file mode 100644
index 0000000..263fb7c
--- /dev/null
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/merge/MergerTokenSelectorView.java
@@ -0,0 +1,106 @@
+/*****************************************************************
+ *   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.dialog.db.merge;
+
+import java.awt.BorderLayout;
+import java.awt.FlowLayout;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+
+import com.jgoodies.forms.builder.PanelBuilder;
+import com.jgoodies.forms.factories.ButtonBarFactory;
+import com.jgoodies.forms.layout.CellConstraints;
+import com.jgoodies.forms.layout.FormLayout;
+
+/**
+ */
+public class MergerTokenSelectorView extends JPanel {
+
+    protected JTable tokens;
+    protected JCheckBox checkAll;
+    protected JLabel checkAllLabel;
+    protected JButton reverseAll;
+
+    public MergerTokenSelectorView() {
+
+        this.checkAll = new JCheckBox();
+        this.checkAllLabel = new JLabel("Check All Operations");
+        this.reverseAll = new JButton("Reverse All Operations");
+
+        checkAll.addItemListener(new ItemListener() {
+
+            public void itemStateChanged(ItemEvent event) {
+                if (checkAll.isSelected()) {
+                    checkAllLabel.setText("Uncheck All Operations");
+                }
+                else {
+                    checkAllLabel.setText("Check All Operations");
+                }
+            }
+        });
+
+        // assemble
+        JPanel checkAllPanel = new JPanel(new FlowLayout(FlowLayout.LEADING));
+        checkAllPanel.add(checkAll);
+        checkAllPanel.add(checkAllLabel);
+        JPanel topPanel = new JPanel();
+        topPanel.setLayout(new BorderLayout());
+        topPanel.add(checkAllPanel, BorderLayout.WEST);
+        topPanel.add(ButtonBarFactory.buildRightAlignedBar(reverseAll), BorderLayout.EAST);
+
+        tokens = new JTable();
+        tokens.setRowHeight(25);
+        tokens.setRowMargin(3);
+
+        CellConstraints cc = new CellConstraints();
+        PanelBuilder builder = new PanelBuilder(new FormLayout(
+                "fill:min(50dlu;pref):grow",
+                "p, 3dlu, fill:40dlu:grow"));
+        builder.setDefaultDialogBorder();
+        builder.addSeparator("Select Operations", cc.xy(1, 1));
+        builder.add(new JScrollPane(
+                tokens,
+                JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
+                JScrollPane.HORIZONTAL_SCROLLBAR_NEVER), cc.xy(1, 3));
+
+        setLayout(new BorderLayout());
+        add(topPanel, BorderLayout.NORTH);
+        add(builder.getPanel(), BorderLayout.CENTER);
+    }
+
+    public JTable getTokens() {
+        return tokens;
+    }
+
+    public JCheckBox getCheckAll() {
+        return checkAll;
+    }
+    
+    public JButton getReverseAll() {
+        return reverseAll;
+    }
+}