You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by aa...@apache.org on 2016/09/29 09:13:13 UTC

[2/2] cayenne git commit: Refactoring DbImport classes

Refactoring DbImport classes

* (Hopefully) more consistent action naming
* general cleanup


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

Branch: refs/heads/master
Commit: 70062304838f569c56ba2c6195b4aa8e743b238e
Parents: 2a20597
Author: Andrus Adamchik <an...@objectstyle.com>
Authored: Thu Sep 29 11:28:08 2016 +0300
Committer: Andrus Adamchik <an...@objectstyle.com>
Committed: Thu Sep 29 12:06:30 2016 +0300

----------------------------------------------------------------------
 .../tools/dbimport/DbImportActionDefault.java   | 278 ---------------
 .../dbimport/DbImportDbLoaderDelegate.java      |  12 +-
 .../cayenne/tools/dbimport/DbImportModule.java  |   2 +-
 .../tools/dbimport/DefaultDbImportAction.java   | 271 +++++++++++++++
 .../tools/dbimport/DbImportActionTest.java      | 348 -------------------
 .../dbimport/DefaultDbImportActionTest.java     | 348 +++++++++++++++++++
 .../dialog/db/DbImportActionModeler.java        | 118 -------
 .../modeler/dialog/db/DbLoaderHelper.java       |   2 +-
 .../dialog/db/ModelerDbImportAction.java        | 107 ++++++
 9 files changed, 736 insertions(+), 750 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/70062304/cayenne-tools/src/main/java/org/apache/cayenne/tools/dbimport/DbImportActionDefault.java
----------------------------------------------------------------------
diff --git a/cayenne-tools/src/main/java/org/apache/cayenne/tools/dbimport/DbImportActionDefault.java b/cayenne-tools/src/main/java/org/apache/cayenne/tools/dbimport/DbImportActionDefault.java
deleted file mode 100644
index 92648d1..0000000
--- a/cayenne-tools/src/main/java/org/apache/cayenne/tools/dbimport/DbImportActionDefault.java
+++ /dev/null
@@ -1,278 +0,0 @@
-/*
- * 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.tools.dbimport;
-
-import org.apache.cayenne.access.DbLoader;
-import org.apache.cayenne.configuration.ConfigurationTree;
-import org.apache.cayenne.configuration.DataNodeDescriptor;
-import org.apache.cayenne.configuration.server.DataSourceFactory;
-import org.apache.cayenne.configuration.server.DbAdapterFactory;
-import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.di.Inject;
-import org.apache.cayenne.map.DataMap;
-import org.apache.cayenne.map.EntityResolver;
-import org.apache.cayenne.map.MapLoader;
-import org.apache.cayenne.map.ObjEntity;
-import org.apache.cayenne.map.ObjRelationship;
-import org.apache.cayenne.merge.AbstractToModelToken;
-import org.apache.cayenne.merge.AddRelationshipToDb;
-import org.apache.cayenne.merge.DbMerger;
-import org.apache.cayenne.merge.MergerContext;
-import org.apache.cayenne.merge.MergerFactory;
-import org.apache.cayenne.merge.MergerToken;
-import org.apache.cayenne.merge.ModelMergeDelegate;
-import org.apache.cayenne.merge.ProxyModelMergeDelegate;
-import org.apache.cayenne.project.Project;
-import org.apache.cayenne.project.ProjectSaver;
-import org.apache.cayenne.resource.URLResource;
-import org.apache.cayenne.validation.SimpleValidationFailure;
-import org.apache.cayenne.validation.ValidationFailure;
-import org.apache.cayenne.validation.ValidationResult;
-import org.apache.commons.logging.Log;
-import org.xml.sax.InputSource;
-
-import javax.sql.DataSource;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.sql.Connection;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.LinkedList;
-import java.util.List;
-
-import static org.apache.commons.lang.StringUtils.isBlank;
-
-/**
- * A thin wrapper around {@link DbLoader} that encapsulates DB import logic for
- * the benefit of Ant and Maven db importers.
- *
- * @since 4.0
- */
-public class DbImportActionDefault implements DbImportAction {
-
-    private final ProjectSaver projectSaver;
-    private final Log logger;
-    private final DataSourceFactory dataSourceFactory;
-    private final DbAdapterFactory adapterFactory;
-    private final MapLoader mapLoader;
-
-    public DbImportActionDefault(@Inject Log logger,
-                                 @Inject ProjectSaver projectSaver,
-                                 @Inject DataSourceFactory dataSourceFactory,
-                                 @Inject DbAdapterFactory adapterFactory,
-                                 @Inject MapLoader mapLoader) {
-        this.logger = logger;
-        this.projectSaver = projectSaver;
-        this.dataSourceFactory = dataSourceFactory;
-        this.adapterFactory = adapterFactory;
-        this.mapLoader = mapLoader;
-    }
-
-    public void execute(DbImportConfiguration config) throws Exception {
-
-        if (logger.isDebugEnabled()) {
-            logger.debug("DB connection: " + config.getDataSourceInfo());
-        }
-
-        if (logger.isDebugEnabled()) {
-            logger.debug(config);
-        }
-
-        DataNodeDescriptor dataNodeDescriptor = config.createDataNodeDescriptor();
-        DataSource dataSource = dataSourceFactory.getDataSource(dataNodeDescriptor);
-        DbAdapter adapter = adapterFactory.createAdapter(dataNodeDescriptor, dataSource);
-
-        DataMap loadedFomDb = load(config, adapter, dataSource.getConnection());
-        if (loadedFomDb == null) {
-            logger.info("Nothing was loaded from db.");
-            return;
-        }
-
-        DataMap existing = loadExistingDataMap(config.getDataMapFile());
-        if (existing == null) {
-            logger.info("");
-            File file = config.getDataMapFile();
-            logger.info("Map file does not exist. Loaded db model will be saved into '"
-                    + (file == null ? "null" : file.getAbsolutePath() + "'"));
-
-            saveLoaded(config.initializeDataMap(loadedFomDb));
-        } else {
-            MergerFactory mergerFactory = adapter.mergerFactory();
-
-            List<MergerToken> mergeTokens = new DbMerger(mergerFactory)
-                    .createMergeTokens(existing, loadedFomDb, config.getDbLoaderConfig());
-            if (mergeTokens.isEmpty()) {
-                logger.info("");
-                logger.info("Detected changes: No changes to import.");
-                return;
-            }
-
-            if (!isBlank(config.getDefaultPackage())) {
-                existing.setDefaultPackage(config.getDefaultPackage());
-            }
-
-            final Collection<ObjEntity> loadedObjEntities = new LinkedList<>();
-            DataMap executed = execute(new ProxyModelMergeDelegate(config.createMergeDelegate()) {
-                @Override
-                public void objEntityAdded(ObjEntity ent) {
-                    loadedObjEntities.add(ent);
-
-                    super.objEntityAdded(ent);
-                }
-
-            }, existing, log(sort(reverse(mergerFactory, mergeTokens))));
-
-            DbLoader.flattenManyToManyRelationships(executed, loadedObjEntities, config.getNameGenerator());
-
-            relationshipsSanity(executed);
-
-
-            saveLoaded(executed);
-        }
-    }
-
-    private void relationshipsSanity(DataMap executed) {
-        for (ObjEntity objEntity : executed.getObjEntities()) {
-            List<ObjRelationship> rels = new LinkedList<>(objEntity.getRelationships());
-            for (ObjRelationship rel : rels) {
-                if (rel.getSourceEntity() == null || rel.getTargetEntity() == null) {
-                    logger.error("Incorrect obj relationship source or target entity is null: " + rel);
-
-                    objEntity.removeRelationship(rel.getName());
-                }
-            }
-        }
-    }
-
-    protected static List<MergerToken> sort(List<MergerToken> reverse) {
-        Collections.sort(reverse, new Comparator<MergerToken>() {
-            @Override
-            public int compare(MergerToken o1, MergerToken o2) {
-                if (o1 instanceof AddRelationshipToDb && o2 instanceof AddRelationshipToDb) {
-                    return 0;
-                }
-
-                if (!(o1 instanceof AddRelationshipToDb || o2 instanceof AddRelationshipToDb)) {
-                    return o1.getClass().getSimpleName().compareTo(o2.getClass().getSimpleName());
-                }
-
-                return o1 instanceof AddRelationshipToDb ? 1 : -1;
-            }
-        });
-
-        return reverse;
-    }
-
-    private Collection<MergerToken> log(List<MergerToken> tokens) {
-        logger.info("");
-        if (tokens.isEmpty()) {
-            logger.info("Detected changes: No changes to import.");
-            return tokens;
-        }
-
-        logger.info("Detected changes: ");
-        for (MergerToken token : tokens) {
-            logger.info(String.format("    %-20s %s", token.getTokenName(), token.getTokenValue()));
-        }
-        logger.info("");
-
-        return tokens;
-    }
-
-    protected DataMap loadExistingDataMap(File dataMapFile) throws IOException {
-        if (dataMapFile != null && dataMapFile.exists() && dataMapFile.canRead()) {
-            DataMap dataMap = mapLoader.loadDataMap(new InputSource(dataMapFile.getCanonicalPath()));
-            dataMap.setNamespace(new EntityResolver(Collections.singleton(dataMap)));
-            dataMap.setConfigurationSource(new URLResource(dataMapFile.toURI().toURL()));
-
-            return dataMap;
-        }
-
-        return null;
-    }
-
-    private List<MergerToken> reverse(
-            MergerFactory mergerFactory, 
-            Iterable<MergerToken> mergeTokens) throws IOException {
-        List<MergerToken> tokens = new LinkedList<>();
-        for (MergerToken token : mergeTokens) {
-            if (token instanceof AbstractToModelToken) {
-                continue;
-            }
-            tokens.add(token.createReverse(mergerFactory));
-        }
-        return tokens;
-    }
-
-    /**
-     * Performs configured schema operations via DbGenerator.
-     */
-    private DataMap execute(ModelMergeDelegate mergeDelegate, DataMap dataMap, Collection<MergerToken> tokens) {
-
-        MergerContext mergerContext = MergerContext.builder(dataMap).delegate(mergeDelegate).build();
-
-        for (MergerToken tok : tokens) {
-            try {
-                tok.execute(mergerContext);
-            } catch (Throwable th) {
-                String message = "Migration Error. Can't apply changes from token: " + tok.getTokenName()
-                        + " (" + tok.getTokenValue() + ")";
-
-                logger.error(message, th);
-                mergerContext.getValidationResult().addFailure(new SimpleValidationFailure(th, message));
-            }
-        }
-
-        ValidationResult failures = mergerContext.getValidationResult();
-        if (failures.hasFailures()) {
-            logger.info("Migration Complete.");
-            logger.warn("Migration finished. The following problem(s) were encountered and ignored.");
-            for (ValidationFailure failure : failures.getFailures()) {
-                logger.warn(failure.toString());
-            }
-        } else {
-            logger.info("Migration Complete Successfully.");
-        }
-
-        return dataMap;
-    }
-
-    protected void saveLoaded(DataMap dataMap) throws FileNotFoundException {
-        ConfigurationTree<DataMap> projectRoot = new ConfigurationTree<>(dataMap);
-        Project project = new Project(projectRoot);
-        projectSaver.save(project);
-    }
-
-    protected DataMap load(DbImportConfiguration config, DbAdapter adapter, Connection connection) throws Exception {
-        DataMap dataMap = config.createDataMap();
-
-        try {
-            DbLoader loader = config.createLoader(adapter, connection, config.createLoaderDelegate());
-            loader.load(dataMap, config.getDbLoaderConfig());
-        } finally {
-            if (connection != null) {
-                connection.close();
-            }
-        }
-
-        return dataMap;
-    }
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/70062304/cayenne-tools/src/main/java/org/apache/cayenne/tools/dbimport/DbImportDbLoaderDelegate.java
----------------------------------------------------------------------
diff --git a/cayenne-tools/src/main/java/org/apache/cayenne/tools/dbimport/DbImportDbLoaderDelegate.java b/cayenne-tools/src/main/java/org/apache/cayenne/tools/dbimport/DbImportDbLoaderDelegate.java
index e3fbc01..7772b73 100644
--- a/cayenne-tools/src/main/java/org/apache/cayenne/tools/dbimport/DbImportDbLoaderDelegate.java
+++ b/cayenne-tools/src/main/java/org/apache/cayenne/tools/dbimport/DbImportDbLoaderDelegate.java
@@ -38,27 +38,31 @@ class DbImportDbLoaderDelegate extends DefaultDbLoaderDelegate {
     private final List<ObjEntity> removedObjEntities;
 
     DbImportDbLoaderDelegate() {
-        addedDbEntities = new ArrayList<DbEntity>();
-        removedDbEntities = new ArrayList<DbEntity>();
-        addedObjEntities = new ArrayList<ObjEntity>();
-        removedObjEntities = new ArrayList<ObjEntity>();
+        addedDbEntities = new ArrayList<>();
+        removedDbEntities = new ArrayList<>();
+        addedObjEntities = new ArrayList<>();
+        removedObjEntities = new ArrayList<>();
     }
 
+    @Override
     public void dbEntityAdded(DbEntity ent) {
         ent.getDataMap().addDbEntity(ent);
         addedDbEntities.add(ent);
     }
 
+    @Override
     public void dbEntityRemoved(DbEntity ent) {
         ent.getDataMap().removeDbEntity(ent.getName());
         removedDbEntities.add(ent);
     }
 
+    @Override
     public void objEntityAdded(ObjEntity ent) {
         ent.getDataMap().addObjEntity(ent);
         addedObjEntities.add(ent);
     }
 
+    @Override
     public void objEntityRemoved(ObjEntity ent) {
         ent.getDataMap().removeObjEntity(ent.getName());
         removedObjEntities.add(ent);

http://git-wip-us.apache.org/repos/asf/cayenne/blob/70062304/cayenne-tools/src/main/java/org/apache/cayenne/tools/dbimport/DbImportModule.java
----------------------------------------------------------------------
diff --git a/cayenne-tools/src/main/java/org/apache/cayenne/tools/dbimport/DbImportModule.java b/cayenne-tools/src/main/java/org/apache/cayenne/tools/dbimport/DbImportModule.java
index 1b4505a..18cc197 100644
--- a/cayenne-tools/src/main/java/org/apache/cayenne/tools/dbimport/DbImportModule.java
+++ b/cayenne-tools/src/main/java/org/apache/cayenne/tools/dbimport/DbImportModule.java
@@ -36,7 +36,7 @@ import org.apache.cayenne.tools.configuration.ToolsModule;
 public class DbImportModule implements Module {
 
     public void configure(Binder binder) {
-        binder.bind(DbImportAction.class).to(DbImportActionDefault.class);
+        binder.bind(DbImportAction.class).to(DefaultDbImportAction.class);
         binder.bind(ProjectSaver.class).to(FileProjectSaver.class);
         binder.bind(ConfigurationNameMapper.class).to(DefaultConfigurationNameMapper.class);
         binder.bind(MapLoader.class).to(MapLoader.class);

http://git-wip-us.apache.org/repos/asf/cayenne/blob/70062304/cayenne-tools/src/main/java/org/apache/cayenne/tools/dbimport/DefaultDbImportAction.java
----------------------------------------------------------------------
diff --git a/cayenne-tools/src/main/java/org/apache/cayenne/tools/dbimport/DefaultDbImportAction.java b/cayenne-tools/src/main/java/org/apache/cayenne/tools/dbimport/DefaultDbImportAction.java
new file mode 100644
index 0000000..0be2223
--- /dev/null
+++ b/cayenne-tools/src/main/java/org/apache/cayenne/tools/dbimport/DefaultDbImportAction.java
@@ -0,0 +1,271 @@
+/*
+ * 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.tools.dbimport;
+
+import org.apache.cayenne.access.DbLoader;
+import org.apache.cayenne.configuration.ConfigurationTree;
+import org.apache.cayenne.configuration.DataNodeDescriptor;
+import org.apache.cayenne.configuration.server.DataSourceFactory;
+import org.apache.cayenne.configuration.server.DbAdapterFactory;
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.di.Inject;
+import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.map.EntityResolver;
+import org.apache.cayenne.map.MapLoader;
+import org.apache.cayenne.map.ObjEntity;
+import org.apache.cayenne.map.ObjRelationship;
+import org.apache.cayenne.merge.AbstractToModelToken;
+import org.apache.cayenne.merge.AddRelationshipToDb;
+import org.apache.cayenne.merge.DbMerger;
+import org.apache.cayenne.merge.MergerContext;
+import org.apache.cayenne.merge.MergerFactory;
+import org.apache.cayenne.merge.MergerToken;
+import org.apache.cayenne.merge.ModelMergeDelegate;
+import org.apache.cayenne.merge.ProxyModelMergeDelegate;
+import org.apache.cayenne.project.Project;
+import org.apache.cayenne.project.ProjectSaver;
+import org.apache.cayenne.resource.URLResource;
+import org.apache.cayenne.validation.SimpleValidationFailure;
+import org.apache.cayenne.validation.ValidationFailure;
+import org.apache.cayenne.validation.ValidationResult;
+import org.apache.commons.logging.Log;
+import org.xml.sax.InputSource;
+
+import javax.sql.DataSource;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.sql.Connection;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedList;
+import java.util.List;
+
+import static org.apache.commons.lang.StringUtils.isBlank;
+
+/**
+ * A thin wrapper around {@link DbLoader} that encapsulates DB import logic for
+ * the benefit of Ant and Maven db importers.
+ *
+ * @since 4.0
+ */
+public class DefaultDbImportAction implements DbImportAction {
+
+    private final ProjectSaver projectSaver;
+    private final Log logger;
+    private final DataSourceFactory dataSourceFactory;
+    private final DbAdapterFactory adapterFactory;
+    private final MapLoader mapLoader;
+
+    public DefaultDbImportAction(@Inject Log logger,
+                                 @Inject ProjectSaver projectSaver,
+                                 @Inject DataSourceFactory dataSourceFactory,
+                                 @Inject DbAdapterFactory adapterFactory,
+                                 @Inject MapLoader mapLoader) {
+        this.logger = logger;
+        this.projectSaver = projectSaver;
+        this.dataSourceFactory = dataSourceFactory;
+        this.adapterFactory = adapterFactory;
+        this.mapLoader = mapLoader;
+    }
+
+    protected static List<MergerToken> sort(List<MergerToken> reverse) {
+        Collections.sort(reverse, new Comparator<MergerToken>() {
+            @Override
+            public int compare(MergerToken o1, MergerToken o2) {
+                if (o1 instanceof AddRelationshipToDb && o2 instanceof AddRelationshipToDb) {
+                    return 0;
+                }
+
+                if (!(o1 instanceof AddRelationshipToDb || o2 instanceof AddRelationshipToDb)) {
+                    return o1.getClass().getSimpleName().compareTo(o2.getClass().getSimpleName());
+                }
+
+                return o1 instanceof AddRelationshipToDb ? 1 : -1;
+            }
+        });
+
+        return reverse;
+    }
+
+    public void execute(DbImportConfiguration config) throws Exception {
+
+        if (logger.isDebugEnabled()) {
+            logger.debug("DB connection: " + config.getDataSourceInfo());
+            logger.debug(config);
+        }
+
+        DataNodeDescriptor dataNodeDescriptor = config.createDataNodeDescriptor();
+        DataSource dataSource = dataSourceFactory.getDataSource(dataNodeDescriptor);
+        DbAdapter adapter = adapterFactory.createAdapter(dataNodeDescriptor, dataSource);
+
+        DataMap loadedFomDb;
+        try(Connection connection = dataSource.getConnection()) {
+            loadedFomDb = load(config, adapter, connection);
+        }
+
+        if (loadedFomDb == null) {
+            logger.info("Nothing was loaded from db.");
+            return;
+        }
+
+        DataMap existing = loadExistingDataMap(config.getDataMapFile());
+        if (existing == null) {
+            logger.info("");
+            File file = config.getDataMapFile();
+            logger.info("Map file does not exist. Loaded db model will be saved into '"
+                    + (file == null ? "null" : file.getAbsolutePath() + "'"));
+
+            saveLoaded(config.initializeDataMap(loadedFomDb));
+        } else {
+            MergerFactory mergerFactory = adapter.mergerFactory();
+
+            List<MergerToken> mergeTokens = new DbMerger(mergerFactory)
+                    .createMergeTokens(existing, loadedFomDb, config.getDbLoaderConfig());
+            if (mergeTokens.isEmpty()) {
+                logger.info("");
+                logger.info("Detected changes: No changes to import.");
+                return;
+            }
+
+            if (!isBlank(config.getDefaultPackage())) {
+                existing.setDefaultPackage(config.getDefaultPackage());
+            }
+
+            final Collection<ObjEntity> loadedObjEntities = new LinkedList<>();
+            DataMap executed = execute(new ProxyModelMergeDelegate(config.createMergeDelegate()) {
+                @Override
+                public void objEntityAdded(ObjEntity ent) {
+                    loadedObjEntities.add(ent);
+
+                    super.objEntityAdded(ent);
+                }
+
+            }, existing, log(sort(reverse(mergerFactory, mergeTokens))));
+
+            DbLoader.flattenManyToManyRelationships(executed, loadedObjEntities, config.getNameGenerator());
+
+            relationshipsSanity(executed);
+
+
+            saveLoaded(executed);
+        }
+    }
+
+    private void relationshipsSanity(DataMap executed) {
+        for (ObjEntity objEntity : executed.getObjEntities()) {
+            List<ObjRelationship> rels = new LinkedList<>(objEntity.getRelationships());
+            for (ObjRelationship rel : rels) {
+                if (rel.getSourceEntity() == null || rel.getTargetEntity() == null) {
+                    logger.error("Incorrect obj relationship source or target entity is null: " + rel);
+
+                    objEntity.removeRelationship(rel.getName());
+                }
+            }
+        }
+    }
+
+    private Collection<MergerToken> log(List<MergerToken> tokens) {
+        logger.info("");
+        if (tokens.isEmpty()) {
+            logger.info("Detected changes: No changes to import.");
+            return tokens;
+        }
+
+        logger.info("Detected changes: ");
+        for (MergerToken token : tokens) {
+            logger.info(String.format("    %-20s %s", token.getTokenName(), token.getTokenValue()));
+        }
+        logger.info("");
+
+        return tokens;
+    }
+
+    protected DataMap loadExistingDataMap(File dataMapFile) throws IOException {
+        if (dataMapFile != null && dataMapFile.exists() && dataMapFile.canRead()) {
+            DataMap dataMap = mapLoader.loadDataMap(new InputSource(dataMapFile.getCanonicalPath()));
+            dataMap.setNamespace(new EntityResolver(Collections.singleton(dataMap)));
+            dataMap.setConfigurationSource(new URLResource(dataMapFile.toURI().toURL()));
+
+            return dataMap;
+        }
+
+        return null;
+    }
+
+    private List<MergerToken> reverse(
+            MergerFactory mergerFactory,
+            Iterable<MergerToken> mergeTokens) throws IOException {
+        List<MergerToken> tokens = new LinkedList<>();
+        for (MergerToken token : mergeTokens) {
+            if (token instanceof AbstractToModelToken) {
+                continue;
+            }
+            tokens.add(token.createReverse(mergerFactory));
+        }
+        return tokens;
+    }
+
+    /**
+     * Performs configured schema operations via DbGenerator.
+     */
+    private DataMap execute(ModelMergeDelegate mergeDelegate, DataMap dataMap, Collection<MergerToken> tokens) {
+
+        MergerContext mergerContext = MergerContext.builder(dataMap).delegate(mergeDelegate).build();
+
+        for (MergerToken tok : tokens) {
+            try {
+                tok.execute(mergerContext);
+            } catch (Throwable th) {
+                String message = "Migration Error. Can't apply changes from token: " + tok.getTokenName()
+                        + " (" + tok.getTokenValue() + ")";
+
+                logger.error(message, th);
+                mergerContext.getValidationResult().addFailure(new SimpleValidationFailure(th, message));
+            }
+        }
+
+        ValidationResult failures = mergerContext.getValidationResult();
+        if (failures.hasFailures()) {
+            logger.info("Migration Complete.");
+            logger.warn("Migration finished. The following problem(s) were encountered and ignored.");
+            for (ValidationFailure failure : failures.getFailures()) {
+                logger.warn(failure.toString());
+            }
+        } else {
+            logger.info("Migration Complete Successfully.");
+        }
+
+        return dataMap;
+    }
+
+    protected void saveLoaded(DataMap dataMap) throws FileNotFoundException {
+        ConfigurationTree<DataMap> projectRoot = new ConfigurationTree<>(dataMap);
+        Project project = new Project(projectRoot);
+        projectSaver.save(project);
+    }
+
+    protected DataMap load(DbImportConfiguration config, DbAdapter adapter, Connection connection) throws Exception {
+        DataMap dataMap = config.createDataMap();
+        DbLoader loader = config.createLoader(adapter, connection, config.createLoaderDelegate());
+        loader.load(dataMap, config.getDbLoaderConfig());
+        return dataMap;
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/70062304/cayenne-tools/src/test/java/org/apache/cayenne/tools/dbimport/DbImportActionTest.java
----------------------------------------------------------------------
diff --git a/cayenne-tools/src/test/java/org/apache/cayenne/tools/dbimport/DbImportActionTest.java b/cayenne-tools/src/test/java/org/apache/cayenne/tools/dbimport/DbImportActionTest.java
deleted file mode 100644
index 7b90bd3..0000000
--- a/cayenne-tools/src/test/java/org/apache/cayenne/tools/dbimport/DbImportActionTest.java
+++ /dev/null
@@ -1,348 +0,0 @@
-/*
- * 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.tools.dbimport;
-
-import org.apache.cayenne.CayenneRuntimeException;
-import org.apache.cayenne.access.DbLoader;
-import org.apache.cayenne.access.DbLoaderDelegate;
-import org.apache.cayenne.access.loader.DbLoaderConfiguration;
-import org.apache.cayenne.configuration.DataNodeDescriptor;
-import org.apache.cayenne.configuration.server.DataSourceFactory;
-import org.apache.cayenne.configuration.server.DbAdapterFactory;
-import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.di.DIBootstrap;
-import org.apache.cayenne.di.Injector;
-import org.apache.cayenne.map.DataMap;
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.map.MapLoader;
-import org.apache.cayenne.merge.AddColumnToDb;
-import org.apache.cayenne.merge.AddRelationshipToDb;
-import org.apache.cayenne.merge.CreateTableToDb;
-import org.apache.cayenne.merge.CreateTableToModel;
-import org.apache.cayenne.merge.DefaultModelMergeDelegate;
-import org.apache.cayenne.merge.MergerFactory;
-import org.apache.cayenne.merge.MergerToken;
-import org.apache.cayenne.merge.builders.DataMapBuilder;
-import org.apache.cayenne.project.FileProjectSaver;
-import org.apache.cayenne.project.Project;
-import org.apache.cayenne.resource.URLResource;
-import org.apache.cayenne.tools.configuration.ToolsModule;
-import org.apache.cayenne.util.Util;
-import org.apache.commons.logging.Log;
-import org.junit.Test;
-import org.xml.sax.InputSource;
-
-import javax.sql.DataSource;
-import java.io.File;
-import java.net.URL;
-import java.sql.Connection;
-import java.sql.SQLException;
-import java.util.LinkedList;
-import java.util.List;
-
-import static java.util.Arrays.asList;
-import static org.apache.cayenne.merge.builders.ObjectMother.dbAttr;
-import static org.apache.cayenne.merge.builders.ObjectMother.dbEntity;
-import static org.apache.cayenne.merge.builders.ObjectMother.objAttr;
-import static org.apache.cayenne.merge.builders.ObjectMother.objEntity;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.stub;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-public class DbImportActionTest {
-
-    public static final File FILE_STUB = new File("") {
-        @Override
-        public boolean exists() {
-            return true;
-        }
-
-		@Override
-		public boolean canRead() {
-			return true;
-		}
-	};
-
-	@Test
-	public void testNewDataMapImport() throws Exception {
-
-		DbLoader dbLoader = new DbLoader(null, null, null) {
-			@Override
-			public void load(DataMap dataMap, DbLoaderConfiguration config) throws SQLException {
-				new DataMapBuilder(dataMap).withDbEntities(2).build();
-			}
-
-			@Override
-			public String[] getDefaultTableTypes() {
-				return null;
-			}
-		};
-
-        DbImportConfiguration params = mock(DbImportConfiguration.class);
-        when(params.createLoader(any(DbAdapter.class), any(Connection.class), any(DbLoaderDelegate.class)))
-                .thenReturn(dbLoader);
-
-		when(params.createDataMap()).thenReturn(new DataMap("testImport"));
-		when(params.createMergeDelegate()).thenReturn(new DefaultModelMergeDelegate());
-		when(params.getDbLoaderConfig()).thenReturn(new DbLoaderConfiguration());
-
-		final DataMap DATA_MAP = new DataMap();
-		when(params.initializeDataMap(any(DataMap.class))).thenReturn(DATA_MAP);
-
-        final boolean[] haveWeTriedToSave = {false};
-        DbImportActionDefault action = buildDbImportAction(new FileProjectSaver() {
-            @Override
-            public void save(Project project) {
-                haveWeTriedToSave[0] = true;
-
-				// Validation phase
-				assertEquals(DATA_MAP, project.getRootNode());
-			}
-		}, null);
-
-		action.execute(params);
-
-		assertTrue("We should try to save.", haveWeTriedToSave[0]);
-	}
-
-    @Test
-    public void testImportWithFieldChanged() throws Exception {
-        DbLoader dbLoader = new DbLoader(null, null, null) {
-            @Override
-            public void load(DataMap dataMap, DbLoaderConfiguration config) throws SQLException {
-                new DataMapBuilder(dataMap).with(
-                        dbEntity("ARTGROUP").attributes(
-                                dbAttr("GROUP_ID").typeInt().primaryKey(),
-                                dbAttr("NAME").typeVarchar(100).mandatory(),
-                                dbAttr("NAME_01").typeVarchar(100).mandatory(),
-                                dbAttr("PARENT_GROUP_ID").typeInt()
-                        )).with(
-                        objEntity("org.apache.cayenne.testdo.testmap", "ArtGroup", "ARTGROUP").attributes(
-                                objAttr("name").type(String.class).dbPath("NAME")
-                        ));
-            }
-
-			@Override
-			public String[] getDefaultTableTypes() {
-				return null;
-			}
-		};
-
-        DbImportConfiguration params = mock(DbImportConfiguration.class);
-        when(params.createLoader(any(DbAdapter.class), any(Connection.class), any(DbLoaderDelegate.class)))
-                .thenReturn(dbLoader);
-
-		when(params.createDataMap()).thenReturn(new DataMap("testImport"));
-		when(params.getDataMapFile()).thenReturn(FILE_STUB);
-		when(params.createMergeDelegate()).thenReturn(new DefaultModelMergeDelegate());
-		when(params.getDbLoaderConfig()).thenReturn(new DbLoaderConfiguration());
-
-        final boolean[] haveWeTriedToSave = {false};
-        DbImportActionDefault action = buildDbImportAction(new FileProjectSaver() {
-            @Override
-            public void save(Project project) {
-                haveWeTriedToSave[0] = true;
-
-				// Validation phase
-				DataMap rootNode = (DataMap) project.getRootNode();
-				assertEquals(1, rootNode.getObjEntities().size());
-				assertEquals(1, rootNode.getDbEntityMap().size());
-
-				DbEntity entity = rootNode.getDbEntity("ARTGROUP");
-				assertNotNull(entity);
-				assertEquals(4, entity.getAttributes().size());
-				assertNotNull(entity.getAttribute("NAME_01"));
-			}
-		}, new MapLoader() {
-
-            @Override
-            public synchronized DataMap loadDataMap(InputSource src) throws CayenneRuntimeException {
-                return new DataMapBuilder().with(
-                        dbEntity("ARTGROUP").attributes(
-                                dbAttr("GROUP_ID").typeInt().primaryKey(),
-                                dbAttr("NAME").typeVarchar(100).mandatory(),
-                                dbAttr("PARENT_GROUP_ID").typeInt()
-                        )).with(
-                        objEntity("org.apache.cayenne.testdo.testmap", "ArtGroup", "ARTGROUP").attributes(
-                                objAttr("name").type(String.class).dbPath("NAME")
-                        )).build();
-            }
-        });
-
-		action.execute(params);
-
-		assertTrue("We should try to save.", haveWeTriedToSave[0]);
-	}
-
-    @Test
-    public void testImportWithoutChanges() throws Exception {
-        DbLoader dbLoader = new DbLoader(null, null, null) {
-            @Override
-            public void load(DataMap dataMap, DbLoaderConfiguration config) throws SQLException {
-                new DataMapBuilder(dataMap).with(
-                        dbEntity("ARTGROUP").attributes(
-                                dbAttr("NAME").typeVarchar(100).mandatory()
-                        ));
-            }
-
-			@Override
-			public String[] getDefaultTableTypes() {
-				return null;
-			}
-		};
-
-        DbImportConfiguration params = mock(DbImportConfiguration.class);
-        when(params.createLoader(any(DbAdapter.class), any(Connection.class), any(DbLoaderDelegate.class)))
-                .thenReturn(dbLoader);
-
-		when(params.createDataMap()).thenReturn(new DataMap("testImport"));
-		when(params.getDataMapFile()).thenReturn(FILE_STUB);
-		when(params.createMergeDelegate()).thenReturn(new DefaultModelMergeDelegate());
-		when(params.getDbLoaderConfig()).thenReturn(new DbLoaderConfiguration());
-
-		Log log = mock(Log.class);
-		when(log.isDebugEnabled()).thenReturn(false);
-		when(log.isInfoEnabled()).thenReturn(false);
-
-		FileProjectSaver projectSaver = mock(FileProjectSaver.class);
-		doNothing().when(projectSaver).save(any(Project.class));
-
-        MapLoader mapLoader = mock(MapLoader.class);
-        stub(mapLoader.loadDataMap(any(InputSource.class))).toReturn(new DataMapBuilder().with(
-                dbEntity("ARTGROUP").attributes(
-                        dbAttr("NAME").typeVarchar(100).mandatory()
-                )).build());
-
-        DbImportActionDefault action = buildDbImportAction(log, projectSaver, mapLoader);
-
-		action.execute(params);
-
-		verify(projectSaver, never()).save(any(Project.class));
-		verify(mapLoader, times(1)).loadDataMap(any(InputSource.class));
-	}
-
-	@Test
-	public void testImportWithDbError() throws Exception {
-		DbLoader dbLoader = mock(DbLoader.class);
-		when(dbLoader.getDefaultTableTypes()).thenReturn(null);
-		doThrow(new SQLException()).when(dbLoader).load(any(DataMap.class), any(DbLoaderConfiguration.class));
-
-        DbImportConfiguration params = mock(DbImportConfiguration.class);
-        when(params.createLoader(any(DbAdapter.class), any(Connection.class), any(DbLoaderDelegate.class)))
-                .thenReturn(dbLoader);
-
-		FileProjectSaver projectSaver = mock(FileProjectSaver.class);
-		doNothing().when(projectSaver).save(any(Project.class));
-
-		MapLoader mapLoader = mock(MapLoader.class);
-		when(mapLoader.loadDataMap(any(InputSource.class))).thenReturn(null);
-
-        DbImportActionDefault action = buildDbImportAction(projectSaver, mapLoader);
-
-		try {
-			action.execute(params);
-			fail();
-		} catch (SQLException e) {
-			// expected
-		}
-
-		verify(projectSaver, never()).save(any(Project.class));
-		verify(mapLoader, never()).loadDataMap(any(InputSource.class));
-	}
-
-    private DbImportActionDefault buildDbImportAction(FileProjectSaver projectSaver, MapLoader mapLoader) throws Exception {
-        Log log = mock(Log.class);
-        when(log.isDebugEnabled()).thenReturn(true);
-        when(log.isInfoEnabled()).thenReturn(true);
-
-		return buildDbImportAction(log, projectSaver, mapLoader);
-	}
-
-    private DbImportActionDefault buildDbImportAction(Log log, FileProjectSaver projectSaver, MapLoader mapLoader) throws Exception {
-        DbAdapter dbAdapter = mock(DbAdapter.class);
-        when(dbAdapter.mergerFactory()).thenReturn(new MergerFactory());
-
-		DbAdapterFactory adapterFactory = mock(DbAdapterFactory.class);
-		when(adapterFactory.createAdapter(any(DataNodeDescriptor.class), any(DataSource.class))).thenReturn(dbAdapter);
-
-		DataSourceFactory dataSourceFactory = mock(DataSourceFactory.class);
-		DataSource mock = mock(DataSource.class);
-		when(dataSourceFactory.getDataSource(any(DataNodeDescriptor.class))).thenReturn(mock);
-
-        return new DbImportActionDefault(log, projectSaver, dataSourceFactory, adapterFactory, mapLoader);
-    }
-
-	@Test
-	public void testSaveLoaded() throws Exception {
-		Log log = mock(Log.class);
-		Injector i = DIBootstrap.createInjector(new ToolsModule(log), new DbImportModule());
-
-        DbImportActionDefault action = (DbImportActionDefault) i.getInstance(DbImportAction.class);
-
-		String packagePath = getClass().getPackage().getName().replace('.', '/');
-		URL packageUrl = getClass().getClassLoader().getResource(packagePath);
-		assertNotNull(packageUrl);
-		URL outUrl = new URL(packageUrl, "dbimport/testSaveLoaded1.map.xml");
-
-		File out = new File(outUrl.toURI());
-		out.delete();
-		assertFalse(out.isFile());
-
-		DataMap map = new DataMap("testSaveLoaded1");
-		map.setConfigurationSource(new URLResource(outUrl));
-
-		action.saveLoaded(map);
-
-		assertTrue(out.isFile());
-
-		String contents = Util.stringFromFile(out);
-		assertTrue("Has no project version saved", contents.contains("project-version=\""));
-	}
-
-	@Test
-	public void testMergeTokensSorting() {
-		LinkedList<MergerToken> tokens = new LinkedList<MergerToken>();
-		tokens.add(new AddColumnToDb(null, null));
-		tokens.add(new AddRelationshipToDb(null, null));
-		tokens.add(new CreateTableToDb(null));
-		tokens.add(new CreateTableToModel(null));
-
-        assertEquals(asList("AddColumnToDb", "CreateTableToDb", "CreateTableToModel", "AddRelationshipToDb"),
-                toClasses(DbImportActionDefault.sort(tokens)));
-    }
-
-	private List<String> toClasses(List<MergerToken> sort) {
-		LinkedList<String> res = new LinkedList<String>();
-		for (MergerToken mergerToken : sort) {
-			res.add(mergerToken.getClass().getSimpleName());
-		}
-		return res;
-	}
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/70062304/cayenne-tools/src/test/java/org/apache/cayenne/tools/dbimport/DefaultDbImportActionTest.java
----------------------------------------------------------------------
diff --git a/cayenne-tools/src/test/java/org/apache/cayenne/tools/dbimport/DefaultDbImportActionTest.java b/cayenne-tools/src/test/java/org/apache/cayenne/tools/dbimport/DefaultDbImportActionTest.java
new file mode 100644
index 0000000..474b827
--- /dev/null
+++ b/cayenne-tools/src/test/java/org/apache/cayenne/tools/dbimport/DefaultDbImportActionTest.java
@@ -0,0 +1,348 @@
+/*
+ * 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.tools.dbimport;
+
+import org.apache.cayenne.CayenneRuntimeException;
+import org.apache.cayenne.access.DbLoader;
+import org.apache.cayenne.access.DbLoaderDelegate;
+import org.apache.cayenne.access.loader.DbLoaderConfiguration;
+import org.apache.cayenne.configuration.DataNodeDescriptor;
+import org.apache.cayenne.configuration.server.DataSourceFactory;
+import org.apache.cayenne.configuration.server.DbAdapterFactory;
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.di.DIBootstrap;
+import org.apache.cayenne.di.Injector;
+import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.MapLoader;
+import org.apache.cayenne.merge.AddColumnToDb;
+import org.apache.cayenne.merge.AddRelationshipToDb;
+import org.apache.cayenne.merge.CreateTableToDb;
+import org.apache.cayenne.merge.CreateTableToModel;
+import org.apache.cayenne.merge.DefaultModelMergeDelegate;
+import org.apache.cayenne.merge.MergerFactory;
+import org.apache.cayenne.merge.MergerToken;
+import org.apache.cayenne.merge.builders.DataMapBuilder;
+import org.apache.cayenne.project.FileProjectSaver;
+import org.apache.cayenne.project.Project;
+import org.apache.cayenne.resource.URLResource;
+import org.apache.cayenne.tools.configuration.ToolsModule;
+import org.apache.cayenne.util.Util;
+import org.apache.commons.logging.Log;
+import org.junit.Test;
+import org.xml.sax.InputSource;
+
+import javax.sql.DataSource;
+import java.io.File;
+import java.net.URL;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.LinkedList;
+import java.util.List;
+
+import static java.util.Arrays.asList;
+import static org.apache.cayenne.merge.builders.ObjectMother.dbAttr;
+import static org.apache.cayenne.merge.builders.ObjectMother.dbEntity;
+import static org.apache.cayenne.merge.builders.ObjectMother.objAttr;
+import static org.apache.cayenne.merge.builders.ObjectMother.objEntity;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.stub;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class DefaultDbImportActionTest {
+
+    public static final File FILE_STUB = new File("") {
+        @Override
+        public boolean exists() {
+            return true;
+        }
+
+		@Override
+		public boolean canRead() {
+			return true;
+		}
+	};
+
+	@Test
+	public void testNewDataMapImport() throws Exception {
+
+		DbLoader dbLoader = new DbLoader(null, null, null) {
+			@Override
+			public void load(DataMap dataMap, DbLoaderConfiguration config) throws SQLException {
+				new DataMapBuilder(dataMap).withDbEntities(2).build();
+			}
+
+			@Override
+			public String[] getDefaultTableTypes() {
+				return null;
+			}
+		};
+
+        DbImportConfiguration params = mock(DbImportConfiguration.class);
+        when(params.createLoader(any(DbAdapter.class), any(Connection.class), any(DbLoaderDelegate.class)))
+                .thenReturn(dbLoader);
+
+		when(params.createDataMap()).thenReturn(new DataMap("testImport"));
+		when(params.createMergeDelegate()).thenReturn(new DefaultModelMergeDelegate());
+		when(params.getDbLoaderConfig()).thenReturn(new DbLoaderConfiguration());
+
+		final DataMap DATA_MAP = new DataMap();
+		when(params.initializeDataMap(any(DataMap.class))).thenReturn(DATA_MAP);
+
+        final boolean[] haveWeTriedToSave = {false};
+        DefaultDbImportAction action = buildDbImportAction(new FileProjectSaver() {
+            @Override
+            public void save(Project project) {
+                haveWeTriedToSave[0] = true;
+
+				// Validation phase
+				assertEquals(DATA_MAP, project.getRootNode());
+			}
+		}, null);
+
+		action.execute(params);
+
+		assertTrue("We should try to save.", haveWeTriedToSave[0]);
+	}
+
+    @Test
+    public void testImportWithFieldChanged() throws Exception {
+        DbLoader dbLoader = new DbLoader(null, null, null) {
+            @Override
+            public void load(DataMap dataMap, DbLoaderConfiguration config) throws SQLException {
+                new DataMapBuilder(dataMap).with(
+                        dbEntity("ARTGROUP").attributes(
+                                dbAttr("GROUP_ID").typeInt().primaryKey(),
+                                dbAttr("NAME").typeVarchar(100).mandatory(),
+                                dbAttr("NAME_01").typeVarchar(100).mandatory(),
+                                dbAttr("PARENT_GROUP_ID").typeInt()
+                        )).with(
+                        objEntity("org.apache.cayenne.testdo.testmap", "ArtGroup", "ARTGROUP").attributes(
+                                objAttr("name").type(String.class).dbPath("NAME")
+                        ));
+            }
+
+			@Override
+			public String[] getDefaultTableTypes() {
+				return null;
+			}
+		};
+
+        DbImportConfiguration params = mock(DbImportConfiguration.class);
+        when(params.createLoader(any(DbAdapter.class), any(Connection.class), any(DbLoaderDelegate.class)))
+                .thenReturn(dbLoader);
+
+		when(params.createDataMap()).thenReturn(new DataMap("testImport"));
+		when(params.getDataMapFile()).thenReturn(FILE_STUB);
+		when(params.createMergeDelegate()).thenReturn(new DefaultModelMergeDelegate());
+		when(params.getDbLoaderConfig()).thenReturn(new DbLoaderConfiguration());
+
+        final boolean[] haveWeTriedToSave = {false};
+        DefaultDbImportAction action = buildDbImportAction(new FileProjectSaver() {
+            @Override
+            public void save(Project project) {
+                haveWeTriedToSave[0] = true;
+
+				// Validation phase
+				DataMap rootNode = (DataMap) project.getRootNode();
+				assertEquals(1, rootNode.getObjEntities().size());
+				assertEquals(1, rootNode.getDbEntityMap().size());
+
+				DbEntity entity = rootNode.getDbEntity("ARTGROUP");
+				assertNotNull(entity);
+				assertEquals(4, entity.getAttributes().size());
+				assertNotNull(entity.getAttribute("NAME_01"));
+			}
+		}, new MapLoader() {
+
+            @Override
+            public synchronized DataMap loadDataMap(InputSource src) throws CayenneRuntimeException {
+                return new DataMapBuilder().with(
+                        dbEntity("ARTGROUP").attributes(
+                                dbAttr("GROUP_ID").typeInt().primaryKey(),
+                                dbAttr("NAME").typeVarchar(100).mandatory(),
+                                dbAttr("PARENT_GROUP_ID").typeInt()
+                        )).with(
+                        objEntity("org.apache.cayenne.testdo.testmap", "ArtGroup", "ARTGROUP").attributes(
+                                objAttr("name").type(String.class).dbPath("NAME")
+                        )).build();
+            }
+        });
+
+		action.execute(params);
+
+		assertTrue("We should try to save.", haveWeTriedToSave[0]);
+	}
+
+    @Test
+    public void testImportWithoutChanges() throws Exception {
+        DbLoader dbLoader = new DbLoader(null, null, null) {
+            @Override
+            public void load(DataMap dataMap, DbLoaderConfiguration config) throws SQLException {
+                new DataMapBuilder(dataMap).with(
+                        dbEntity("ARTGROUP").attributes(
+                                dbAttr("NAME").typeVarchar(100).mandatory()
+                        ));
+            }
+
+			@Override
+			public String[] getDefaultTableTypes() {
+				return null;
+			}
+		};
+
+        DbImportConfiguration params = mock(DbImportConfiguration.class);
+        when(params.createLoader(any(DbAdapter.class), any(Connection.class), any(DbLoaderDelegate.class)))
+                .thenReturn(dbLoader);
+
+		when(params.createDataMap()).thenReturn(new DataMap("testImport"));
+		when(params.getDataMapFile()).thenReturn(FILE_STUB);
+		when(params.createMergeDelegate()).thenReturn(new DefaultModelMergeDelegate());
+		when(params.getDbLoaderConfig()).thenReturn(new DbLoaderConfiguration());
+
+		Log log = mock(Log.class);
+		when(log.isDebugEnabled()).thenReturn(false);
+		when(log.isInfoEnabled()).thenReturn(false);
+
+		FileProjectSaver projectSaver = mock(FileProjectSaver.class);
+		doNothing().when(projectSaver).save(any(Project.class));
+
+        MapLoader mapLoader = mock(MapLoader.class);
+        stub(mapLoader.loadDataMap(any(InputSource.class))).toReturn(new DataMapBuilder().with(
+                dbEntity("ARTGROUP").attributes(
+                        dbAttr("NAME").typeVarchar(100).mandatory()
+                )).build());
+
+        DefaultDbImportAction action = buildDbImportAction(log, projectSaver, mapLoader);
+
+		action.execute(params);
+
+		verify(projectSaver, never()).save(any(Project.class));
+		verify(mapLoader, times(1)).loadDataMap(any(InputSource.class));
+	}
+
+	@Test
+	public void testImportWithDbError() throws Exception {
+		DbLoader dbLoader = mock(DbLoader.class);
+		when(dbLoader.getDefaultTableTypes()).thenReturn(null);
+		doThrow(new SQLException()).when(dbLoader).load(any(DataMap.class), any(DbLoaderConfiguration.class));
+
+        DbImportConfiguration params = mock(DbImportConfiguration.class);
+        when(params.createLoader(any(DbAdapter.class), any(Connection.class), any(DbLoaderDelegate.class)))
+                .thenReturn(dbLoader);
+
+		FileProjectSaver projectSaver = mock(FileProjectSaver.class);
+		doNothing().when(projectSaver).save(any(Project.class));
+
+		MapLoader mapLoader = mock(MapLoader.class);
+		when(mapLoader.loadDataMap(any(InputSource.class))).thenReturn(null);
+
+        DefaultDbImportAction action = buildDbImportAction(projectSaver, mapLoader);
+
+		try {
+			action.execute(params);
+			fail();
+		} catch (SQLException e) {
+			// expected
+		}
+
+		verify(projectSaver, never()).save(any(Project.class));
+		verify(mapLoader, never()).loadDataMap(any(InputSource.class));
+	}
+
+    private DefaultDbImportAction buildDbImportAction(FileProjectSaver projectSaver, MapLoader mapLoader) throws Exception {
+        Log log = mock(Log.class);
+        when(log.isDebugEnabled()).thenReturn(true);
+        when(log.isInfoEnabled()).thenReturn(true);
+
+		return buildDbImportAction(log, projectSaver, mapLoader);
+	}
+
+    private DefaultDbImportAction buildDbImportAction(Log log, FileProjectSaver projectSaver, MapLoader mapLoader) throws Exception {
+        DbAdapter dbAdapter = mock(DbAdapter.class);
+        when(dbAdapter.mergerFactory()).thenReturn(new MergerFactory());
+
+		DbAdapterFactory adapterFactory = mock(DbAdapterFactory.class);
+		when(adapterFactory.createAdapter(any(DataNodeDescriptor.class), any(DataSource.class))).thenReturn(dbAdapter);
+
+		DataSourceFactory dataSourceFactory = mock(DataSourceFactory.class);
+		DataSource mock = mock(DataSource.class);
+		when(dataSourceFactory.getDataSource(any(DataNodeDescriptor.class))).thenReturn(mock);
+
+        return new DefaultDbImportAction(log, projectSaver, dataSourceFactory, adapterFactory, mapLoader);
+    }
+
+	@Test
+	public void testSaveLoaded() throws Exception {
+		Log log = mock(Log.class);
+		Injector i = DIBootstrap.createInjector(new ToolsModule(log), new DbImportModule());
+
+        DefaultDbImportAction action = (DefaultDbImportAction) i.getInstance(DbImportAction.class);
+
+		String packagePath = getClass().getPackage().getName().replace('.', '/');
+		URL packageUrl = getClass().getClassLoader().getResource(packagePath);
+		assertNotNull(packageUrl);
+		URL outUrl = new URL(packageUrl, "dbimport/testSaveLoaded1.map.xml");
+
+		File out = new File(outUrl.toURI());
+		out.delete();
+		assertFalse(out.isFile());
+
+		DataMap map = new DataMap("testSaveLoaded1");
+		map.setConfigurationSource(new URLResource(outUrl));
+
+		action.saveLoaded(map);
+
+		assertTrue(out.isFile());
+
+		String contents = Util.stringFromFile(out);
+		assertTrue("Has no project version saved", contents.contains("project-version=\""));
+	}
+
+	@Test
+	public void testMergeTokensSorting() {
+		LinkedList<MergerToken> tokens = new LinkedList<MergerToken>();
+		tokens.add(new AddColumnToDb(null, null));
+		tokens.add(new AddRelationshipToDb(null, null));
+		tokens.add(new CreateTableToDb(null));
+		tokens.add(new CreateTableToModel(null));
+
+        assertEquals(asList("AddColumnToDb", "CreateTableToDb", "CreateTableToModel", "AddRelationshipToDb"),
+                toClasses(DefaultDbImportAction.sort(tokens)));
+    }
+
+	private List<String> toClasses(List<MergerToken> sort) {
+		LinkedList<String> res = new LinkedList<String>();
+		for (MergerToken mergerToken : sort) {
+			res.add(mergerToken.getClass().getSimpleName());
+		}
+		return res;
+	}
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/70062304/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/DbImportActionModeler.java
----------------------------------------------------------------------
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/DbImportActionModeler.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/DbImportActionModeler.java
deleted file mode 100644
index 150444b..0000000
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/DbImportActionModeler.java
+++ /dev/null
@@ -1,118 +0,0 @@
-package org.apache.cayenne.modeler.dialog.db; /*****************************************************************
- *   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.
- ****************************************************************/
-
-
-import org.apache.cayenne.configuration.DataChannelDescriptor;
-import org.apache.cayenne.configuration.event.DataMapEvent;
-import org.apache.cayenne.configuration.server.DataSourceFactory;
-import org.apache.cayenne.configuration.server.DbAdapterFactory;
-import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.di.Inject;
-import org.apache.cayenne.map.DataMap;
-import org.apache.cayenne.map.MapLoader;
-import org.apache.cayenne.map.event.MapEvent;
-import org.apache.cayenne.modeler.Application;
-import org.apache.cayenne.modeler.ProjectController;
-import org.apache.cayenne.project.ProjectSaver;
-import org.apache.cayenne.resource.Resource;
-import org.apache.cayenne.tools.dbimport.DbImportAction;
-import org.apache.cayenne.tools.dbimport.DbImportActionDefault;
-import org.apache.cayenne.tools.dbimport.DbImportConfiguration;
-import org.apache.commons.logging.Log;
-
-import java.io.File;
-import java.sql.Connection;
-
-public class DbImportActionModeler implements DbImportAction {
-
-    private final Log logger;
-
-    private final DbLoaderHelper dbLoaderHelper;
-
-    @Inject
-    private ProjectSaver projectSaver;
-
-    @Inject
-    private DataSourceFactory dataSourceFactory;
-
-    @Inject
-    private DbAdapterFactory adapterFactory;
-
-    @Inject
-    private MapLoader mapLoader;
-
-    public DbImportActionModeler(Log logger, DbLoaderHelper dbLoaderHelper) {
-        this.logger = logger;
-        this.dbLoaderHelper = dbLoaderHelper;
-    }
-
-    @Override
-    public void execute(DbImportConfiguration config) throws Exception {
-        if (dbLoaderHelper == null) {
-            throw new IllegalStateException("Before using execute method you must set dbLoaderHelper");
-        }
-
-        new DbImportActionDefault(logger, projectSaver, dataSourceFactory, adapterFactory, mapLoader) {
-
-            @Override
-            protected DataMap loadExistingDataMap(File dataMapFile) {
-                return dbLoaderHelper.getDataMap();
-            }
-
-            @Override
-            protected void saveLoaded(DataMap dataMap) {
-                dbLoaderHelper.cleanup();
-
-                ProjectController mediator = dbLoaderHelper.getMediator();
-
-                if (mediator.getCurrentDataMap() != null) {
-                    mediator.fireDataMapEvent(new DataMapEvent(Application.getFrame(), dataMap, MapEvent.REMOVE));
-                    mediator.fireDataMapEvent(new DataMapEvent(Application.getFrame(), dataMap, MapEvent.ADD));
-                } else {
-                    DataChannelDescriptor currentDomain = (DataChannelDescriptor) mediator.getProject().getRootNode();
-                    Resource baseResource = currentDomain.getConfigurationSource();
-
-                    // this will be new data map so need to set configuration source
-                    // for it
-                    if (baseResource != null) {
-                        Resource dataMapResource = baseResource.getRelativeResource(dataMap.getName());
-                        dataMap.setConfigurationSource(dataMapResource);
-                    }
-                    mediator.addDataMap(Application.getFrame(), dataMap);
-                }
-            }
-
-            @Override
-            protected DataMap load(DbImportConfiguration config, 
-                                   DbAdapter adapter, Connection connection) throws Exception {
-                DataMap dataMap;
-
-                try {
-                    dataMap = dbLoaderHelper.getLoader().load(config.getDbLoaderConfig());
-                } finally {
-                    if (connection != null) {
-                        connection.close();
-                    }
-                }
-
-                return dataMap;
-            }
-        }.execute(config);
-    }
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/70062304/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/DbLoaderHelper.java
----------------------------------------------------------------------
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/DbLoaderHelper.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/DbLoaderHelper.java
index 721bf76..743d410 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/DbLoaderHelper.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/DbLoaderHelper.java
@@ -374,7 +374,7 @@ public class DbLoaderHelper {
             config.getDbLoaderConfig().setFiltersConfig(filtersConfigBuilder.filtersConfig());
 
 
-            DbImportActionModeler importAction = new DbImportActionModeler(logObj, DbLoaderHelper.this);
+            ModelerDbImportAction importAction = new ModelerDbImportAction(logObj, DbLoaderHelper.this);
             Injector injector = DIBootstrap.createInjector(new ToolsModule(logObj), new DbImportModule());
             injector.injectMembers(importAction);
             try {

http://git-wip-us.apache.org/repos/asf/cayenne/blob/70062304/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/ModelerDbImportAction.java
----------------------------------------------------------------------
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/ModelerDbImportAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/ModelerDbImportAction.java
new file mode 100644
index 0000000..3be2ba3
--- /dev/null
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/ModelerDbImportAction.java
@@ -0,0 +1,107 @@
+package org.apache.cayenne.modeler.dialog.db; /*****************************************************************
+ *   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.
+ ****************************************************************/
+
+
+import org.apache.cayenne.configuration.DataChannelDescriptor;
+import org.apache.cayenne.configuration.event.DataMapEvent;
+import org.apache.cayenne.configuration.server.DataSourceFactory;
+import org.apache.cayenne.configuration.server.DbAdapterFactory;
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.di.Inject;
+import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.map.MapLoader;
+import org.apache.cayenne.map.event.MapEvent;
+import org.apache.cayenne.modeler.Application;
+import org.apache.cayenne.modeler.ProjectController;
+import org.apache.cayenne.project.ProjectSaver;
+import org.apache.cayenne.resource.Resource;
+import org.apache.cayenne.tools.dbimport.DbImportAction;
+import org.apache.cayenne.tools.dbimport.DefaultDbImportAction;
+import org.apache.cayenne.tools.dbimport.DbImportConfiguration;
+import org.apache.commons.logging.Log;
+
+import java.io.File;
+import java.sql.Connection;
+
+public class ModelerDbImportAction implements DbImportAction {
+
+    private final Log logger;
+
+    private final DbLoaderHelper dbLoaderHelper;
+
+    @Inject
+    private ProjectSaver projectSaver;
+
+    @Inject
+    private DataSourceFactory dataSourceFactory;
+
+    @Inject
+    private DbAdapterFactory adapterFactory;
+
+    @Inject
+    private MapLoader mapLoader;
+
+    public ModelerDbImportAction(Log logger, DbLoaderHelper dbLoaderHelper) {
+        this.logger = logger;
+        this.dbLoaderHelper = dbLoaderHelper;
+    }
+
+    @Override
+    public void execute(DbImportConfiguration config) throws Exception {
+        if (dbLoaderHelper == null) {
+            throw new IllegalStateException("Before using execute method you must set dbLoaderHelper");
+        }
+
+        new DefaultDbImportAction(logger, projectSaver, dataSourceFactory, adapterFactory, mapLoader) {
+
+            @Override
+            protected DataMap loadExistingDataMap(File dataMapFile) {
+                return dbLoaderHelper.getDataMap();
+            }
+
+            @Override
+            protected void saveLoaded(DataMap dataMap) {
+                dbLoaderHelper.cleanup();
+
+                ProjectController mediator = dbLoaderHelper.getMediator();
+
+                if (mediator.getCurrentDataMap() != null) {
+                    mediator.fireDataMapEvent(new DataMapEvent(Application.getFrame(), dataMap, MapEvent.REMOVE));
+                    mediator.fireDataMapEvent(new DataMapEvent(Application.getFrame(), dataMap, MapEvent.ADD));
+                } else {
+                    DataChannelDescriptor currentDomain = (DataChannelDescriptor) mediator.getProject().getRootNode();
+                    Resource baseResource = currentDomain.getConfigurationSource();
+
+                    // this will be new data map so need to set configuration source
+                    // for it
+                    if (baseResource != null) {
+                        Resource dataMapResource = baseResource.getRelativeResource(dataMap.getName());
+                        dataMap.setConfigurationSource(dataMapResource);
+                    }
+                    mediator.addDataMap(Application.getFrame(), dataMap);
+                }
+            }
+
+            @Override
+            protected DataMap load(DbImportConfiguration config, DbAdapter adapter, Connection connection) throws Exception {
+               return dbLoaderHelper.getLoader().load(config.getDbLoaderConfig());
+            }
+        }.execute(config);
+    }
+}
\ No newline at end of file