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 2014/10/29 19:40:42 UTC

[7/7] git commit: CAY-1946 CDbimport improvements

CAY-1946 CDbimport improvements

    * pull request #15 by AlexKolonitsky
    * andrus: merging against extracted POM changes on master
    * andrus: standardizing/adding missing license headers
    * andrus: squashing a few random changes


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

Branch: refs/heads/CAY-1946
Commit: fde7761f80ed8db65df3e157ab4acd5f47d75004
Parents: 5be9a2e
Author: aadamchik <aa...@apache.org>
Authored: Wed Oct 29 20:56:05 2014 +0300
Committer: aadamchik <aa...@apache.org>
Committed: Wed Oct 29 21:23:41 2014 +0300

----------------------------------------------------------------------
 .../org/apache/cayenne/access/DbLoader.java     | 785 +++++++++----------
 .../cayenne/access/DbLoaderConfiguration.java   |  48 --
 .../cayenne/access/DefaultDbLoaderDelegate.java |  54 --
 .../access/ManyToManyCandidateEntity.java       | 126 ---
 .../access/loader/BooleanNameFilter.java        |  35 +
 .../access/loader/DbLoaderConfiguration.java    | 115 +++
 .../access/loader/DefaultDbLoaderDelegate.java  |  55 ++
 .../loader/ManyToManyCandidateEntity.java       | 126 +++
 .../cayenne/access/loader/NameFilter.java       |  27 +
 .../access/loader/NamePatternMatcher.java       | 225 ++++++
 .../cayenne/access/loader/filters/DbPath.java   | 154 ++++
 .../access/loader/filters/EntityFilters.java    | 399 ++++++++++
 .../access/loader/filters/ExcludeFilter.java    |  42 +
 .../cayenne/access/loader/filters/Filter.java   |  31 +
 .../access/loader/filters/FilterFactory.java    |  94 +++
 .../access/loader/filters/FiltersConfig.java    | 180 +++++
 .../access/loader/filters/IncludeFilter.java    |  76 ++
 .../access/loader/filters/ListFilter.java       | 123 +++
 .../cayenne/access/loader/mapper/DbType.java    | 195 +++++
 .../mapper/DefaultJdbc2JavaTypeMapper.java      | 282 +++++++
 .../loader/mapper/Jdbc2JavaTypeMapper.java      |  33 +
 .../DefaultConfigurationNameMapper.java         |  66 +-
 .../org/apache/cayenne/dba/JdbcAdapter.java     |  93 ++-
 .../java/org/apache/cayenne/map/DataMap.java    |  35 +
 .../org/apache/cayenne/map/DbRelationship.java  |  21 +-
 .../java/org/apache/cayenne/map/ObjEntity.java  |  13 +-
 .../map/naming/DefaultUniqueNameGenerator.java  |   7 +-
 .../apache/cayenne/map/naming/NameCheckers.java |   6 +-
 .../org/apache/cayenne/merge/AddColumnToDb.java |  72 +-
 .../cayenne/merge/CreateTableToModel.java       |  20 +-
 .../java/org/apache/cayenne/merge/DbMerger.java |  91 +--
 .../apache/cayenne/merge/SetColumnTypeToDb.java |  68 +-
 .../apache/cayenne/util/EntityMergeSupport.java | 170 ++--
 .../cayenne/access/DbLoaderPartialTest.java     |   2 +-
 .../org/apache/cayenne/access/DbLoaderTest.java | 220 +++++-
 .../access/ManyToManyCandidateEntityTest.java   | 106 ---
 .../loader/ManyToManyCandidateEntityTest.java   | 106 +++
 .../access/loader/filters/DbPathTest.java       |  75 ++
 .../loader/filters/EntityFiltersTest.java       |  62 ++
 .../loader/filters/FiltersConfigTest.java       | 198 +++++
 .../access/loader/filters/FiltersFactory.java   |  44 ++
 .../access/loader/mapper/DbTypeTest.java        |  87 ++
 .../org/apache/cayenne/merge/MergeCase.java     |   5 +-
 .../cayenne-relationship-optimisation.xml       |   4 -
 .../cayenne-relationship-optimisation.xml       |   4 +
 .../loader/relationship-optimisation.map.xml    |  43 +
 .../access/relationship-optimisation.map.xml    |  43 -
 cayenne-tools/pom.xml                           |  26 +
 .../cayenne/gen/ClassGenerationAction.java      |   2 +-
 .../cayenne/gen/ClientDataMapArtifact.java      |  17 +-
 .../org/apache/cayenne/gen/DataMapArtifact.java |  13 +-
 .../cayenne/tools/AntDataPortDelegate.java      |   7 +-
 .../CayenneGeneratorEntityFilterAction.java     |  20 +-
 .../cayenne/tools/CayenneGeneratorTask.java     |   6 +-
 .../apache/cayenne/tools/DbImporterTask.java    | 182 +++--
 .../cayenne/tools/NamePatternMatcher.java       | 294 -------
 .../cayenne/tools/dbimport/DbImportAction.java  |  65 +-
 .../tools/dbimport/DbImportConfiguration.java   | 322 +++-----
 .../tools/dbimport/config/AntNestedElement.java |  38 +
 .../cayenne/tools/dbimport/config/Catalog.java  |  82 ++
 .../config/DefaultReverseEngineeringLoader.java | 211 +++++
 .../config/DefaultTypeMapperBuilder.java        |  82 ++
 .../tools/dbimport/config/ExcludeColumn.java    |  31 +
 .../tools/dbimport/config/ExcludeProcedure.java |  31 +
 .../tools/dbimport/config/ExcludeTable.java     |  31 +
 .../tools/dbimport/config/FilterContainer.java  | 109 +++
 .../dbimport/config/FiltersConfigBuilder.java   | 169 ++++
 .../tools/dbimport/config/IncludeColumn.java    |  31 +
 .../tools/dbimport/config/IncludeProcedure.java |  31 +
 .../tools/dbimport/config/IncludeTable.java     |  77 ++
 .../tools/dbimport/config/PatternParam.java     |  74 ++
 .../dbimport/config/ReverseEngineering.java     |  60 ++
 .../config/ReverseEngineeringLoader.java        |  31 +
 .../cayenne/tools/dbimport/config/Schema.java   |  58 ++
 .../cayenne/tools/dbimport/config/Type.java     | 136 ++++
 .../tools/dbimport/config/TypeMapper.java       |  95 +++
 .../cayenne/tools/DbImporterTaskTest.java       | 168 ++++
 .../cayenne/tools/NamePatternMatcherTest.java   |  49 +-
 .../tools/dbimport/DbImportActionTest.java      |  65 +-
 .../dbimport/DbImportConfigurationTest.java     | 193 -----
 .../DefaultReverseEngineeringLoaderTest.java    | 203 +++++
 .../config/FiltersConfigBuilderTest.java        | 153 ++++
 .../cayenne/tools/build-catalog-and-schema.xml  |  78 ++
 .../org/apache/cayenne/tools/build-catalog.xml  |  83 ++
 .../org/apache/cayenne/tools/build-flat.xml     |  75 ++
 .../org/apache/cayenne/tools/build-mapping.xml  |  44 ++
 ...ild-reverse-engineering-in-external-file.xml |  33 +
 .../org/apache/cayenne/tools/build-schema.xml   |  83 ++
 .../dbimport/build-include-table.map.xml-result |  37 +
 .../tools/dbimport/build-include-table.xml      |  35 +
 .../tools/dbimport/build-include-table.xml.sql  |  20 +
 .../config/reverseEngineering-ant-mapping.xml   |  31 +
 .../reverseEngineering-catalog-and-schema.xml   |  65 ++
 .../config/reverseEngineering-catalog.xml       |  68 ++
 .../dbimport/config/reverseEngineering-flat.xml |  61 ++
 .../config/reverseEngineering-maven-mapping.xml |  55 ++
 .../config/reverseEngineering-mixed-mapping.xml |  43 +
 .../config/reverseEngineering-schema.xml        |  69 ++
 .../modeler/action/CreateObjEntityAction.java   | 209 +++--
 .../cayenne/modeler/action/MigrateAction.java   |   2 -
 .../dialog/datamap/PackageUpdateController.java |  20 +-
 .../modeler/dialog/db/DbLoaderHelper.java       | 129 +--
 .../modeler/dialog/db/MergerOptions.java        |  61 +-
 .../dialog/objentity/ClassNameUpdater.java      |  31 +-
 .../dialog/objentity/EntitySyncController.java  |  32 +-
 .../modeler/util/NameGeneratorPreferences.java  |  14 +-
 plugins/maven-cayenne-plugin/pom.xml            |  28 +-
 .../cayenne/tools/CayenneGeneratorMojo.java     |   4 +-
 .../apache/cayenne/tools/DbImporterMojo.java    | 272 ++++---
 .../tools/DbImporterMojoConfigurationTest.java  |  56 ++
 .../cayenne/tools/DbImporterMojoTest.java       |  99 ++-
 .../org/apache/cayenne/tools/config/pom-01.xml  |  96 +++
 .../tools/config/pom-catalog-and-schema.xml     |  82 ++
 .../apache/cayenne/tools/config/pom-catalog.xml |  85 ++
 .../apache/cayenne/tools/config/pom-flat.xml    |  76 ++
 .../apache/cayenne/tools/config/pom-mapping.xml |  67 ++
 .../apache/cayenne/tools/config/pom-schema.xml  |  85 ++
 .../dbimport/testFilteringWithSchema-pom.xml    |  41 +
 .../testFilteringWithSchema.map.xml-result      |  51 ++
 .../tools/dbimport/testFilteringWithSchema.sql  |  48 ++
 .../testOldParamsSchemasAndTableExclude-pom.xml |  43 +
 ...dParamsSchemasAndTableExclude.map.xml-result |  37 +
 .../testOldParamsSchemasAndTableExclude.sql     |  48 ++
 .../dbimport/testSchemasAndTableExclude-pom.xml |  45 ++
 .../testSchemasAndTableExclude.map.xml-result   |  37 +
 .../dbimport/testSchemasAndTableExclude.sql     |  48 ++
 .../tools/dbimport/testSimpleFiltering-pom.xml  |  41 +
 .../dbimport/testSimpleFiltering.map.xml-result |  37 +
 .../tools/dbimport/testSimpleFiltering.sql      |  20 +
 plugins/pom.xml                                 |  16 +
 130 files changed, 8404 insertions(+), 2464 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/fde7761f/cayenne-server/src/main/java/org/apache/cayenne/access/DbLoader.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/DbLoader.java b/cayenne-server/src/main/java/org/apache/cayenne/access/DbLoader.java
index 4f8fdee..a994ce2 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/DbLoader.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/DbLoader.java
@@ -16,7 +16,6 @@
  *  specific language governing permissions and limitations
  *  under the License.
  ****************************************************************/
-
 package org.apache.cayenne.access;
 
 import java.sql.Connection;
@@ -28,12 +27,18 @@ import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.Iterator;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
 import org.apache.cayenne.CayenneException;
+import org.apache.cayenne.access.loader.DbLoaderConfiguration;
+import org.apache.cayenne.access.loader.ManyToManyCandidateEntity;
+import org.apache.cayenne.access.loader.filters.EntityFilters;
+import org.apache.cayenne.access.loader.filters.Filter;
+import org.apache.cayenne.access.loader.filters.FiltersConfig;
+import org.apache.cayenne.access.loader.filters.DbPath;
 import org.apache.cayenne.dba.DbAdapter;
 import org.apache.cayenne.dba.TypesMapping;
 import org.apache.cayenne.map.DataMap;
@@ -47,66 +52,46 @@ import org.apache.cayenne.map.Entity;
 import org.apache.cayenne.map.ObjEntity;
 import org.apache.cayenne.map.Procedure;
 import org.apache.cayenne.map.ProcedureParameter;
-import org.apache.cayenne.map.naming.LegacyNameGenerator;
+import org.apache.cayenne.map.naming.DefaultUniqueNameGenerator;
 import org.apache.cayenne.map.naming.ExportedKey;
+import org.apache.cayenne.map.naming.LegacyNameGenerator;
+import org.apache.cayenne.map.naming.NameCheckers;
 import org.apache.cayenne.map.naming.ObjectNameGenerator;
 import org.apache.cayenne.util.EntityMergeSupport;
-import org.apache.cayenne.util.Util;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
+import static org.apache.cayenne.access.loader.filters.FilterFactory.*;
+
 /**
  * Utility class that does reverse engineering of the database. It can create
  * DataMaps using database meta data obtained via JDBC driver.
  */
 public class DbLoader {
 
-    private static final Log logger = LogFactory.getLog(DbLoader.class);
-
-    // TODO: remove this hardcoded stuff once delegate starts to support
-    // procedure
-    // loading...
-    private static final Collection<String> EXCLUDED_PROCEDURES = Arrays.asList("auto_pk_for_table",
-            "auto_pk_for_table;1" /*
-                                   * the last name is some Mac OS X Sybase
-                                   * artifact
-                                   */
-    );
+    private static final Log LOGGER = LogFactory.getLog(DbLoader.class);
 
     public static final String WILDCARD = "%";
 
-    /** List of db entities to process. */
-    private List<DbEntity> dbEntityList = new ArrayList<DbEntity>();
-
     /**
-     * CAY-479 - need to track which entities are skipped in the loader so that
-     * relationships to non-skipped entities can be loaded
+     * CAY-479 - need to track which entities which are skipped during loading from db since it it already present in
+     * dataMap and haven't marked for overriding so that relationships to non-skipped entities can be loaded
      */
     private Set<DbEntity> skippedEntities = new HashSet<DbEntity>();
 
-    /** Creates a unique name for loaded relationship on the given entity. */
-    private static String uniqueRelName(Entity entity, String preferredName) {
-        int currentSuffix = 1;
-        String relName = preferredName;
+    private final Connection connection;
+    private final DbAdapter adapter;
+    private final DbLoaderDelegate delegate;
 
-        while (entity.getRelationship(relName) != null || entity.getAttribute(relName) != null) {
-            relName = preferredName + currentSuffix;
-            currentSuffix++;
-        }
-        return relName;
-    }
+    private boolean creatingMeaningfulPK;
+
+    private DatabaseMetaData metaData;
 
-    protected Connection connection;
-    protected DbAdapter adapter;
-    protected DatabaseMetaData metaData;
-    protected DbLoaderDelegate delegate;
-    protected String genericClassName;
-    protected boolean creatingMeaningfulPK;
 
     /**
      * Strategy for choosing names for entities, attributes and relationships
      */
-    protected ObjectNameGenerator nameGenerator;
+    private ObjectNameGenerator nameGenerator;
 
     /**
      * Creates new DbLoader.
@@ -131,7 +116,7 @@ public class DbLoader {
     /**
      * Returns DatabaseMetaData object associated with this DbLoader.
      */
-    public DatabaseMetaData getMetaData() throws SQLException {
+    private DatabaseMetaData getMetaData() throws SQLException {
         if (metaData == null) {
             metaData = connection.getMetaData();
         }
@@ -148,7 +133,7 @@ public class DbLoader {
     /**
      * Returns true if the generator should map all primary key columns as
      * ObjAttributes.
-     * 
+     *
      * @since 3.0
      */
     public boolean isCreatingMeaningfulPK() {
@@ -165,32 +150,6 @@ public class DbLoader {
     }
 
     /**
-     * Returns a name of a generic class that should be used for all
-     * ObjEntities. The most common generic class is
-     * {@link org.apache.cayenne.CayenneDataObject}. If generic class name is
-     * null (which is the default), DbLoader will assign each entity a unique
-     * class name derived from the table name.
-     * 
-     * @since 1.2
-     */
-    public String getGenericClassName() {
-        return genericClassName;
-    }
-
-    /**
-     * Sets a name of a generic class that should be used for all ObjEntities.
-     * The most common generic class is
-     * {@link org.apache.cayenne.CayenneDataObject}. If generic class name is
-     * set to null (which is the default), DbLoader will assign each entity a
-     * unique class name derived from the table name.
-     * 
-     * @since 1.2
-     */
-    public void setGenericClassName(String genericClassName) {
-        this.genericClassName = genericClassName;
-    }
-
-    /**
      * Returns DbAdapter associated with this DbLoader.
      * 
      * @since 1.1
@@ -200,31 +159,12 @@ public class DbLoader {
     }
 
     /**
-     * A method that return true if the given table name should be included. The
-     * default implementation include all tables.
-     */
-    public boolean includeTableName(String tableName) {
-        return true;
-    }
-
-    /**
      * Retrieves catalogs for the database associated with this DbLoader.
      * 
      * @return List with the catalog names, empty Array if none found.
      */
     public List<String> getCatalogs() throws SQLException {
-        List<String> catalogs = new ArrayList<String>();
-        ResultSet rs = getMetaData().getCatalogs();
-
-        try {
-            while (rs.next()) {
-                String catalog_name = rs.getString(1);
-                catalogs.add(catalog_name);
-            }
-        } finally {
-            rs.close();
-        }
-        return catalogs;
+        return getStrings(getMetaData().getCatalogs());
     }
 
     /**
@@ -233,23 +173,26 @@ public class DbLoader {
      * @return List with the schema names, empty Array if none found.
      */
     public List<String> getSchemas() throws SQLException {
-        List<String> schemas = new ArrayList<String>();
-        ResultSet rs = getMetaData().getSchemas();
+        return getStrings(getMetaData().getSchemas());
+    }
 
+    private static List<String> getStrings(ResultSet rs) throws SQLException {
+        List<String> strings = new ArrayList<String>();
         try {
             while (rs.next()) {
-                String schema_name = rs.getString(1);
-                schemas.add(schema_name);
+                strings.add(rs.getString(1));
             }
         } finally {
             rs.close();
         }
-        return schemas;
+        return strings;
     }
 
     /**
      * Returns all the table types for the given database. Types may be such as
-     * "TABLE", "VIEW", "SYSTEM TABLE", etc.
+     * Typical types are "TABLE",
+     *                  "VIEW", "SYSTEM TABLE", "GLOBAL TEMPORARY",
+     *                  "LOCAL TEMPORARY", "ALIAS", "SYNONYM"., etc.
      * 
      * @return List of Strings, empty array if nothing found.
      */
@@ -271,32 +214,33 @@ public class DbLoader {
      * Returns all tables for given combination of the criteria. Tables returned
      * as DbEntities without any attributes or relationships.
      * 
-     * @param catalogPattern
-     *            The name of the catalog, may be null.
-     * @param schemaPattern
-     *            The pattern for schema name, use "%" for wildcard.
-     * @param tableNamePattern
-     *            The pattern for table names, % for wildcard, if null or ""
-     *            defaults to "%".
+     *
+     * @param config
+     *
      * @param types
      *            The types of table names to retrieve, null returns all types.
-     * @return List of TableInfo objects, empty array if nothing found.
+     * @return
+     * @since 3.2
      */
-    public List<DbEntity> getTables(String catalogPattern, String schemaPattern, String tableNamePattern, String[] types)
+    public List<DbEntity> getTables(DbLoaderConfiguration config, String[] types)
             throws SQLException {
 
-        if (logger.isDebugEnabled()) {
-            logger.debug("Read tables: catalog=" + catalogPattern + ", schema=" + schemaPattern + ", tableNames="
-                    + tableNamePattern);
+        List<DbEntity> tables = new LinkedList<DbEntity>();
+        FiltersConfig filters = config.getFiltersConfig();
+        for (DbPath path : filters.pathsForQueries()) {
+            tables.addAll(getDbEntities(filters, path, types));
+        }
 
-            if (types != null && types.length > 0) {
-                for (String type : types) {
-                    logger.debug("Read tables: table type=" + type);
-                }
-            }
+        return tables;
+    }
+
+    private List<DbEntity> getDbEntities(FiltersConfig filters, DbPath dbPath, String[] types) throws SQLException {
+        if (LOGGER.isDebugEnabled()) {
+            LOGGER.debug("Read tables: catalog=" + dbPath.catalog + ", schema=" + dbPath.schema + ", types="
+                    + Arrays.toString(types));
         }
 
-        ResultSet rs = getMetaData().getTables(catalogPattern, schemaPattern, tableNamePattern, types);
+        ResultSet rs = getMetaData().getTables(dbPath.catalog, dbPath.schema, WILDCARD, types);
 
         List<DbEntity> tables = new ArrayList<DbEntity>();
         try {
@@ -306,16 +250,22 @@ public class DbLoader {
                 // in errors on reverse engineering as their names have special chars like
                 // "/", etc. So skip them all together
 
-                // TODO: Andrus, 10/29/2005 - this type of filtering should be delegated to adapter
                 String name = rs.getString("TABLE_NAME");
-                if (name == null || name.startsWith("BIN$") || !includeTableName(name)) {
+                if (name == null) {
                     continue;
                 }
 
                 DbEntity table = new DetectedDbEntity(name);
-                table.setCatalog(rs.getString("TABLE_CAT"));
-                table.setSchema(rs.getString("TABLE_SCHEM"));
-                tables.add(table);
+
+                String catalog = rs.getString("TABLE_CAT");
+                table.setCatalog(catalog);
+
+                String schema = rs.getString("TABLE_SCHEM");
+                table.setSchema(schema);
+
+                if (filters.filter(new DbPath(catalog, schema)).tableFilter().isInclude(table)) {
+                    tables.add(table);
+                }
             }
         } finally {
             rs.close();
@@ -328,104 +278,46 @@ public class DbLoader {
      * 
      * @param map
      *            DataMap to be populated with DbEntities.
+     * @param config
      * @param tables
      *            The list of org.apache.cayenne.ashwood.dbutil.Table objects
-     *            for which DbEntities must be created.
-     * @return false if loading must be immediately aborted.
+     *            for which DbEntities must be created.  @return false if loading must be immediately aborted.
      */
-    public boolean loadDbEntities(DataMap map, List<? extends DbEntity> tables) throws SQLException {
-        this.dbEntityList = new ArrayList<DbEntity>();
+    public List<DbEntity> loadDbEntities(DataMap map, DbLoaderConfiguration config, Collection<? extends DbEntity> tables) throws SQLException {
+        /** List of db entities to process. */
+        List<DbEntity> dbEntityList = new ArrayList<DbEntity>();
         for (DbEntity dbEntity : tables) {
+
             // Check if there already is a DbEntity under such name
             // if so, consult the delegate what to do
             DbEntity oldEnt = map.getDbEntity(dbEntity.getName());
             if (oldEnt != null) {
                 if (delegate == null) {
-                    // no delegate, don't know what to do, cancel import
-                    break;
+                    break; // no delegate, don't know what to do, cancel import
+                    // TODO continue?
                 }
 
                 try {
                     if (delegate.overwriteDbEntity(oldEnt)) {
-                        logger.debug("Overwrite: " + oldEnt.getName());
+                        LOGGER.debug("Overwrite: " + oldEnt.getName());
                         map.removeDbEntity(oldEnt.getName(), true);
                         delegate.dbEntityRemoved(oldEnt);
                     } else {
-                        logger.debug("Keep old: " + oldEnt.getName());
+                        LOGGER.debug("Keep old: " + oldEnt.getName());
 
-                        // cay-479 - need to track entities that were not loaded
-                        // for
+                        // cay-479 - need to track entities that were not loaded for
                         // relationships exported to entities that were
                         skippedEntities.add(oldEnt);
                         continue;
                     }
                 } catch (CayenneException ex) {
-                    logger.debug("Load canceled.");
+                    LOGGER.debug("Load canceled.");
 
-                    // cancel immediately
-                    return false;
+                    return null; // cancel immediately
                 }
             }
 
-            // Create DbAttributes from column information --
-            ResultSet rs = getMetaData().getColumns(dbEntity.getCatalog(), dbEntity.getSchema(), dbEntity.getName(),
-                    "%");
-
-            try {
-                while (rs.next()) {
-                    // for a reason not quiet apparent to me, Oracle sometimes
-                    // returns duplicate record sets for the same table, messing
-                    // up table
-                    // names. E.g. for the system table "WK$_ATTR_MAPPING"
-                    // columns are
-                    // returned twice - as "WK$_ATTR_MAPPING" and
-                    // "WK$$_ATTR_MAPPING"...
-                    // Go figure
-
-                    String tableName = rs.getString("TABLE_NAME");
-                    if (!dbEntity.getName().equals(tableName)) {
-                        logger.info("Incorrectly returned columns for '" + tableName + ", skipping.");
-                        continue;
-                    }
-
-                    // gets attribute's (column's) information
-                    String columnName = rs.getString("COLUMN_NAME");
-
-                    boolean allowNulls = rs.getBoolean("NULLABLE");
-                    int columnType = rs.getInt("DATA_TYPE");
-                    int columnSize = rs.getInt("COLUMN_SIZE");
-                    String typeName = rs.getString("TYPE_NAME");
-
-                    // ignore precision of non-decimal columns
-                    int decimalDigits = -1;
-                    if (TypesMapping.isDecimal(columnType)) {
-                        decimalDigits = rs.getInt("DECIMAL_DIGITS");
-                        if (rs.wasNull()) {
-                            decimalDigits = -1;
-                        }
-                    }
-
-                    // create attribute delegating this task to adapter
-                    DbAttribute attr = adapter.buildAttribute(columnName, typeName, columnType, columnSize,
-                            decimalDigits, allowNulls);
-
-                    if (adapter.supportsGeneratedKeys()) {
-
-                        // TODO: this actually throws on some drivers... need to
-                        // ensure that 'supportsGeneratedKeys' check is enough
-                        // to prevent an exception here.
-                        String autoIncrement = rs.getString("IS_AUTOINCREMENT");
-                        if ("YES".equals(autoIncrement)) {
-                            attr.setGenerated(true);
-                        }
-                    }
 
-                    attr.setEntity(dbEntity);
-                    dbEntity.addAttribute(attr);
-                }
-            } finally {
-                rs.close();
-            }
 
             map.addDbEntity(dbEntity);
 
@@ -433,108 +325,163 @@ public class DbLoader {
             if (delegate != null) {
                 delegate.dbEntityAdded(dbEntity);
             }
+            loadDbAttributes(config.getFiltersConfig(), dbEntity);
 
-            // delegate might have thrown this entity out... so check if it is
-            // still
+            // delegate might have thrown this entity out... so check if it is still
             // around before continuing processing
             if (map.getDbEntity(dbEntity.getName()) == dbEntity) {
-                this.dbEntityList.add(dbEntity);
+                dbEntityList.add(dbEntity);
             }
         }
 
         // get primary keys for each table and store it in dbEntity
+        getPrimaryKeysForEachTableAndStoreItInDbEntity(map, tables);
+
+        // cay-479 - iterate skipped DbEntities to populate exported keys
+        for (DbEntity skippedEntity : skippedEntities) {
+            loadDbRelationships(map, skippedEntity, config);
+        }
+
+        return dbEntityList;
+
+    }
+
+    private void getPrimaryKeysForEachTableAndStoreItInDbEntity(DataMap map, Collection<? extends DbEntity> tables)
+            throws SQLException {
+
         for (DbEntity dbEntity : map.getDbEntities()) {
-            if (tables.contains(dbEntity)) {
-                String tableName = dbEntity.getName();
-                ResultSet rs = metaData.getPrimaryKeys(dbEntity.getCatalog(), dbEntity.getSchema(), tableName);
-                try {
-                    while (rs.next()) {
-                        String columnName = rs.getString(4);
-                        DbAttribute attribute = dbEntity.getAttribute(columnName);
-
-                        if (attribute != null) {
-                            attribute.setPrimaryKey(true);
-                        } else {
-                            // why an attribute might be null is not quiet clear
-                            // but there is a bug report 731406 indicating that
-                            // it is
-                            // possible
-                            // so just print the warning, and ignore
-                            logger.warn("Can't locate attribute for primary key: " + columnName);
-                        }
-
-                        String pkName = rs.getString(6);
-                        if ((pkName != null) && (dbEntity instanceof DetectedDbEntity)) {
-                            ((DetectedDbEntity) dbEntity).setPrimaryKeyName(pkName);
-                        }
+            if (!tables.contains(dbEntity)) { // TODO is it ok? equals is not overridden
+                continue;
+            }
+
+            ResultSet rs = getMetaData().getPrimaryKeys(dbEntity.getCatalog(), dbEntity.getSchema(), dbEntity.getName());
+            try {
+                while (rs.next()) {
+                    String columnName = rs.getString(4); // COLUMN_NAME
+                    DbAttribute attribute = dbEntity.getAttribute(columnName);
+
+                    if (attribute != null) {
+                        attribute.setPrimaryKey(true);
+                    } else {
+                        // why an attribute might be null is not quiet clear
+                        // but there is a bug report 731406 indicating that it is possible
+                        // so just print the warning, and ignore
+                        LOGGER.warn("Can't locate attribute for primary key: " + columnName);
+                    }
+
+                    String pkName = rs.getString(6); // PK_NAME
+                    if (pkName != null && dbEntity instanceof DetectedDbEntity) {
+                        ((DetectedDbEntity) dbEntity).setPrimaryKeyName(pkName);
                     }
-                } finally {
-                    rs.close();
                 }
+            } finally {
+                rs.close();
             }
         }
+    }
 
-        // cay-479 - iterate skipped DbEntities to populate exported keys
-        for (DbEntity skippedEntity : skippedEntities) {
-            loadDbRelationships(skippedEntity, map);
+    private void loadDbAttributes(FiltersConfig filters, DbEntity dbEntity) throws SQLException {
+        ResultSet rs = getMetaData().getColumns(dbEntity.getCatalog(), dbEntity.getSchema(), dbEntity.getName(), "%");
+
+        try {
+            while (rs.next()) {
+                // for a reason not quiet apparent to me, Oracle sometimes
+                // returns duplicate record sets for the same table, messing up table
+                // names. E.g. for the system table "WK$_ATTR_MAPPING" columns are
+                // returned twice - as "WK$_ATTR_MAPPING" and "WK$$_ATTR_MAPPING"... Go figure
+
+                String tableName = rs.getString("TABLE_NAME");
+                if (!dbEntity.getName().equals(tableName)) {
+                    LOGGER.info("Incorrectly returned columns for '" + tableName + ", skipping.");
+                    continue;
+                }
+
+                DbAttribute attr = loadDbAttribute(rs);
+                attr.setEntity(dbEntity);
+                if (!filters.filter(new DbPath(dbEntity.getCatalog(), dbEntity.getSchema(), tableName))
+                        .columnFilter().isInclude(attr)) {
+
+                    continue;
+                }
+
+
+                dbEntity.addAttribute(attr);
+            }
+        } finally {
+            rs.close();
         }
+    }
+
+    private DbAttribute loadDbAttribute(ResultSet rs) throws SQLException {
+        // gets attribute's (column's) information
+        int columnType = rs.getInt("DATA_TYPE");
+
+        // ignore precision of non-decimal columns
+        int decimalDigits = -1;
+        if (TypesMapping.isDecimal(columnType)) {
+            decimalDigits = rs.getInt("DECIMAL_DIGITS");
+            if (rs.wasNull()) {
+                decimalDigits = -1;
+            }
+        }
+
+        // create attribute delegating this task to adapter
+        DbAttribute attr = adapter.buildAttribute(
+                rs.getString("COLUMN_NAME"),
+                rs.getString("TYPE_NAME"),
+                columnType,
+                rs.getInt("COLUMN_SIZE"),
+                decimalDigits,
+                rs.getBoolean("NULLABLE"));
 
-        return true;
+        if (adapter.supportsGeneratedKeys()) {
 
+            // TODO: this actually throws on some drivers... need to
+            // ensure that 'supportsGeneratedKeys' check is enough
+            // to prevent an exception here.
+            String autoIncrement = rs.getString("IS_AUTOINCREMENT");
+            if ("YES".equals(autoIncrement)) {
+                attr.setGenerated(true);
+            }
+        }
+        return attr;
     }
 
     /**
      * Creates an ObjEntity for each DbEntity in the map. ObjEntities are
      * created empty without
      */
-    public void loadObjEntities(DataMap map) {
-
-        Iterator<DbEntity> dbEntities = dbEntityList.iterator();
-        if (!dbEntities.hasNext()) {
+    protected void loadObjEntities(DataMap map, DbLoaderConfiguration config, Collection<DbEntity> entities) {
+        if (entities.isEmpty()) {
             return;
         }
 
-        List<ObjEntity> loadedEntities = new ArrayList<ObjEntity>(dbEntityList.size());
+        Collection<ObjEntity> loadedEntities = new ArrayList<ObjEntity>(entities.size());
 
-        String packageName = map.getDefaultPackage();
-        if (Util.isEmptyString(packageName)) {
-            packageName = "";
-        } else if (!packageName.endsWith(".")) {
-            packageName = packageName + ".";
-        }
-
-        // load empty ObjEntities for all the tables
-        while (dbEntities.hasNext()) {
-            DbEntity dbEntity = dbEntities.next();
+        // doLoad empty ObjEntities for all the tables
+        for (DbEntity dbEntity : entities) {
 
             // check if there are existing entities
             Collection<ObjEntity> existing = map.getMappedEntities(dbEntity);
-            if (existing.size() > 0) {
+            if (!existing.isEmpty()) {
                 loadedEntities.addAll(existing);
                 continue;
             }
 
-            String objEntityName = nameGenerator.createObjEntityName(dbEntity);
-            // this loop will terminate even if no valid name is found
-            // to prevent loader from looping forever (though such case is very
-            // unlikely)
-            String baseName = objEntityName;
-            for (int i = 1; i < 1000 && map.getObjEntity(objEntityName) != null; i++) {
-                objEntityName = baseName + i;
-            }
+            String objEntityName = DefaultUniqueNameGenerator.generate(NameCheckers.objEntity, map,
+                    nameGenerator.createObjEntityName(dbEntity));
 
             ObjEntity objEntity = new ObjEntity(objEntityName);
             objEntity.setDbEntity(dbEntity);
+            objEntity.setClassName(config.getGenericClassName() != null ? config.getGenericClassName()
+                    : map.getNameWithDefaultPackage(objEntity.getName()));
 
-            objEntity.setClassName(getGenericClassName() != null ? getGenericClassName() : packageName
-                    + objEntity.getName());
             map.addObjEntity(objEntity);
             loadedEntities.add(objEntity);
         }
 
         // update ObjEntity attributes and relationships
-        EntityMergeSupport objEntityMerger = createEntityMerger(map);
-        objEntityMerger.synchronizeWithDbEntities(loadedEntities);
+        createEntityMerger(map).synchronizeWithDbEntities(loadedEntities);
     }
 
     /**
@@ -545,24 +492,23 @@ public class DbLoader {
     }
 
     /** Loads database relationships into a DataMap. */
-    public void loadDbRelationships(DataMap map) throws SQLException {
-        for (DbEntity pkEntity : dbEntityList) {
-            loadDbRelationships(pkEntity, map);
+    protected void loadDbRelationships(DataMap map, DbLoaderConfiguration config, Iterable<DbEntity> entities) throws SQLException {
+        for (DbEntity pkEntity : entities) {
+            loadDbRelationships(map, pkEntity, config);
         }
     }
 
-    private void loadDbRelationships(DbEntity pkEntity, DataMap map) throws SQLException {
-        DatabaseMetaData md = getMetaData();
-        String pkEntName = pkEntity.getName();
+    protected void loadDbRelationships(DataMap map, DbEntity entity, DbLoaderConfiguration config) throws SQLException {
+        String pkEntName = entity.getName();
 
         // Get all the foreign keys referencing this table
-        ResultSet rs = null;
+        ResultSet rs;
 
         try {
-            rs = md.getExportedKeys(pkEntity.getCatalog(), pkEntity.getSchema(), pkEntity.getName());
+            rs = getMetaData().getExportedKeys(entity.getCatalog(), entity.getSchema(), entity.getName());
         } catch (SQLException cay182Ex) {
             // Sybase-specific - the line above blows on VIEWS, see CAY-182.
-            logger.info("Error getting relationships for '" + pkEntName + "', ignoring.");
+            LOGGER.info("Error getting relationships for '" + pkEntName + "', ignoring.");
             return;
         }
 
@@ -571,24 +517,21 @@ public class DbLoader {
                 return;
             }
 
-            // these will be initailzed every time a new target entity
-            // is found in the result set (which should be ordered by table name
-            // among
-            // other things)
+            // these will be initialized every time a new target entity is found
+            // in the result set (which should be ordered by table name among other things)
             DbRelationship forwardRelationship = null;
             DbRelationshipDetected reverseRelationship = null;
             DbEntity fkEntity = null;
-            ExportedKey key = null;
+            ExportedKey key;
 
             do {
-                // extract data from resultset
                 key = ExportedKey.extractData(rs);
 
                 short keySeq = rs.getShort("KEY_SEQ");
                 if (keySeq == 1) {
 
                     if (forwardRelationship != null) {
-                        postprocessMasterDbRelationship(forwardRelationship, key);
+                        postProcessMasterDbRelationship(forwardRelationship, key);
                         forwardRelationship = null;
                     }
 
@@ -596,33 +539,35 @@ public class DbLoader {
                     String fkEntityName = key.getFKTableName();
                     String fkName = key.getFKName();
 
-                    if (!includeTableName(fkEntityName)) {
+                    fkEntity = map.getDbEntity(fkEntityName);
+                    DbPath path = new DbPath(entity.getCatalog(), entity.getSchema(), entity.getName());
+                    if (!config.getFiltersConfig().filter(path).tableFilter().isInclude(fkEntity)) {
                         continue;
                     }
 
-                    fkEntity = map.getDbEntity(fkEntityName);
-
                     if (fkEntity == null) {
-                        logger.info("FK warning: no entity found for name '" + fkEntityName + "'");
-                    } else if (skippedEntities.contains(pkEntity) && skippedEntities.contains(fkEntity)) {
-                        // cay-479 - don't load relationships between two
-                        // skipped entities.
+                        LOGGER.info("FK warning: no entity found for name '" + fkEntityName + "'");
+                    } else if (skippedEntities.contains(entity) && skippedEntities.contains(fkEntity)) {
+                        // cay-479 - don't doLoad relationships between two skipped entities.
                         continue;
                     } else {
                         // init relationship
                         String forwardPreferredName = nameGenerator.createDbRelationshipName(key, true);
-                        forwardRelationship = new DbRelationship(uniqueRelName(pkEntity, forwardPreferredName));
+                        forwardRelationship = new DbRelationship(DefaultUniqueNameGenerator
+                                .generate(NameCheckers.dbRelationship, entity, forwardPreferredName));
 
-                        forwardRelationship.setSourceEntity(pkEntity);
+                        forwardRelationship.setSourceEntity(entity);
                         forwardRelationship.setTargetEntity(fkEntity);
-                        pkEntity.addRelationship(forwardRelationship);
+                        entity.addRelationship(forwardRelationship);
 
                         String reversePreferredName = nameGenerator.createDbRelationshipName(key, false);
-                        reverseRelationship = new DbRelationshipDetected(uniqueRelName(fkEntity, reversePreferredName));
+                        reverseRelationship = new DbRelationshipDetected(DefaultUniqueNameGenerator
+                                .generate(NameCheckers.dbRelationship, fkEntity, reversePreferredName));
+
                         reverseRelationship.setFkName(fkName);
                         reverseRelationship.setToMany(false);
                         reverseRelationship.setSourceEntity(fkEntity);
-                        reverseRelationship.setTargetEntity(pkEntity);
+                        reverseRelationship.setTargetEntity(entity);
                         fkEntity.addRelationship(reverseRelationship);
                     }
                 }
@@ -633,15 +578,15 @@ public class DbLoader {
                     String fkName = key.getFKColumnName();
 
                     // skip invalid joins...
-                    DbAttribute pkAtt = pkEntity.getAttribute(pkName);
+                    DbAttribute pkAtt = entity.getAttribute(pkName);
                     if (pkAtt == null) {
-                        logger.info("no attribute for declared primary key: " + pkName);
+                        LOGGER.info("no attribute for declared primary key: " + pkName);
                         continue;
                     }
 
                     DbAttribute fkAtt = fkEntity.getAttribute(fkName);
                     if (fkAtt == null) {
-                        logger.info("no attribute for declared foreign key: " + fkName);
+                        LOGGER.info("no attribute for declared foreign key: " + fkName);
                         continue;
                     }
 
@@ -656,7 +601,7 @@ public class DbLoader {
             } while (rs.next());
 
             if (forwardRelationship != null) {
-                postprocessMasterDbRelationship(forwardRelationship, key);
+                postProcessMasterDbRelationship(forwardRelationship, key);
                 forwardRelationship = null;
             }
 
@@ -669,7 +614,7 @@ public class DbLoader {
      * Detects correct relationship multiplicity and "to dep pk" flag. Only
      * called on relationships from PK to FK, not the reverse ones.
      */
-    protected void postprocessMasterDbRelationship(DbRelationship relationship, ExportedKey key) {
+    protected void postProcessMasterDbRelationship(DbRelationship relationship, ExportedKey key) {
         boolean toPK = true;
         List<DbJoin> joins = relationship.getJoins();
 
@@ -681,33 +626,25 @@ public class DbLoader {
 
         }
 
-        boolean toDependentPK = false;
-        boolean toMany = true;
-
-        if (toPK) {
-            toDependentPK = true;
-            if (relationship.getTargetEntity().getPrimaryKeys().size() == joins.size()) {
-                toMany = false;
-            }
-        }
+        relationship.setToDependentPK(toPK);
+        relationship.setToMany(!(toPK && relationship.getTargetEntity().getPrimaryKeys().size() == joins.size()));
 
         // if this is really to-one we need to rename the relationship
-        if (!toMany) {
+        if (!relationship.isToMany()) {
             Entity source = relationship.getSourceEntity();
             source.removeRelationship(relationship.getName());
-            relationship.setName(DbLoader.uniqueRelName(source, nameGenerator.createDbRelationshipName(key, false)));
+            relationship.setName(DefaultUniqueNameGenerator.generate(NameCheckers.dbRelationship, source,
+                    nameGenerator.createDbRelationshipName(key, false)));
+
             source.addRelationship(relationship);
         }
-
-        relationship.setToDependentPK(toDependentPK);
-        relationship.setToMany(toMany);
     }
 
     /**
      * Flattens many-to-many relationships in the generated model.
      */
     private void flattenManyToManyRelationships(DataMap map) {
-        List<ObjEntity> entitiesForDelete = new ArrayList<ObjEntity>();
+        Collection<ObjEntity> entitiesForDelete = new LinkedList<ObjEntity>();
 
         for (ObjEntity curEntity : map.getObjEntities()) {
             ManyToManyCandidateEntity entity = ManyToManyCandidateEntity.build(curEntity);
@@ -734,24 +671,26 @@ public class DbLoader {
     }
 
     /**
+     * By default we want to load Tables and Views for mo types
+     *
+     * @see DbLoader#getTableTypes()
+     *
      * @since 3.2
      */
     public String[] getDefaultTableTypes() {
-        String viewType = adapter.tableTypeForView();
-        String tableType = adapter.tableTypeForTable();
-
-        // use types that are not null
         List<String> list = new ArrayList<String>(2);
+
+        String viewType = adapter.tableTypeForView();
         if (viewType != null) {
             list.add(viewType);
         }
+
+        String tableType = adapter.tableTypeForTable();
         if (tableType != null) {
             list.add(tableType);
         }
 
-        String[] types = new String[list.size()];
-        list.toArray(types);
-        return types;
+        return list.toArray(new String[list.size()]);
     }
 
     /**
@@ -761,7 +700,7 @@ public class DbLoader {
      * 
      * @since 1.0.7
      * @deprecated since 3.2 use
-     *             {@link #load(DataMap, String, String, String, String...)}
+     *             {@link #load(org.apache.cayenne.map.DataMap, DbLoaderConfiguration, String...)}
      *             method that supports catalogs.
      */
     @Deprecated
@@ -772,7 +711,10 @@ public class DbLoader {
             throw new SQLException("No supported table types found.");
         }
 
-        load(dataMap, null, schemaPattern, tablePattern, types);
+        DbLoaderConfiguration configuration = new DbLoaderConfiguration();
+        configuration.setFiltersConfig(new FiltersConfig(new EntityFilters(
+                new DbPath(null, schemaPattern), include(tablePattern), TRUE, NULL)));
+        load(dataMap, configuration, types);
         return dataMap;
     }
 
@@ -782,7 +724,7 @@ public class DbLoader {
      * of tables to read.
      * 
      * @deprecated since 3.2 use
-     *             {@link #load(DataMap, String, String, String, String...)}
+     *             {@link #load(org.apache.cayenne.map.DataMap, DbLoaderConfiguration, String...)}
      *             method that supports catalogs.
      */
     @Deprecated
@@ -790,10 +732,24 @@ public class DbLoader {
             throws SQLException {
         dataMap.clear();
 
-        load(dataMap, null, schemaPattern, tablePattern, tableTypes);
+        DbLoaderConfiguration config = new DbLoaderConfiguration();
+        config.setFiltersConfig(new FiltersConfig(new EntityFilters(
+                new DbPath(null, schemaPattern), transformPatternToFilter(tablePattern), TRUE, NULL)));
+
+        load(dataMap, config, tableTypes);
         return dataMap;
     }
 
+    private Filter<String> transformPatternToFilter(String tablePattern) {
+        Filter<String> table;
+        if (tablePattern == null) {
+            table = NULL;
+        } else {
+            table = include(tablePattern.replaceAll("%", ".*"));
+        }
+        return table;
+    }
+
     /**
      * Performs database reverse engineering to match the specified catalog,
      * schema, table name and table type patterns and fills the specified
@@ -801,25 +757,38 @@ public class DbLoader {
      * 
      * @since 3.2
      */
-    public void load(DataMap dataMap, String catalogPattern, String schemaPattern, String tablePattern,
-            String... tableTypes) throws SQLException {
-
-        if (tablePattern == null) {
-            tablePattern = WILDCARD;
-        }
+    public void load(DataMap dataMap, DbLoaderConfiguration config, String... tableTypes) throws SQLException {
 
-        List<DbEntity> tables = getTables(catalogPattern, schemaPattern, tablePattern, tableTypes);
+        List<DbEntity> entities = loadDbEntities(dataMap, config, getTables(config, tableTypes));
 
-        if (loadDbEntities(dataMap, tables)) {
-            loadDbRelationships(dataMap);
+        if (entities != null) {
+            loadDbRelationships(dataMap, config, entities);
+            loadObjEntities(dataMap, config, entities);
 
-            loadObjEntities(dataMap);
             flattenManyToManyRelationships(dataMap);
             fireObjEntitiesAddedEvents(dataMap);
         }
     }
 
     /**
+     * Performs database reverse engineering to match the specified catalog,
+     * schema, table name and table type patterns and fills the specified
+     * DataMap object with DB and object mapping info.
+     *
+     * @since 3.2
+     */
+    public DataMap load(DbLoaderConfiguration config) throws SQLException {
+
+        DataMap dataMap = new DataMap();
+        String[] tableTypes = config.getTableTypes() == null ? this.getDefaultTableTypes() : config.getTableTypes();
+
+        load(dataMap, config, tableTypes);
+        loadProcedures(dataMap, config);
+
+        return dataMap;
+    }
+
+    /**
      * Loads database stored procedures into the DataMap.
      * <p>
      * <i>As of 1.1 there is no boolean property or delegate method to make
@@ -830,11 +799,16 @@ public class DbLoader {
      * 
      * @since 1.1
      * @deprecated since 3.2 use
-     *             {@link #loadProcedures(DataMap, String, String, String)} that
+     *             {@link #loadProcedures(org.apache.cayenne.map.DataMap, org.apache.cayenne.access.loader.DbLoaderConfiguration)} that
      *             supports "catalog" pattern.
      */
+    @Deprecated
     public void loadProceduresFromDB(String schemaPattern, String namePattern, DataMap dataMap) throws SQLException {
-        loadProcedures(dataMap, null, schemaPattern, namePattern);
+        DbLoaderConfiguration configuration = new DbLoaderConfiguration();
+        configuration.setFiltersConfig(new FiltersConfig(new EntityFilters(
+                new DbPath(null, schemaPattern), NULL, NULL, include(namePattern))));
+
+        loadProcedures(dataMap, configuration);
     }
 
     /**
@@ -848,103 +822,56 @@ public class DbLoader {
      * 
      * @since 3.2
      */
-    public void loadProcedures(DataMap dataMap, String catalogPattern, String schemaPattern, String namePattern)
+    public Map<String, Procedure> loadProcedures(DataMap dataMap, DbLoaderConfiguration config)
             throws SQLException {
 
-        Map<String, Procedure> procedures = null;
-
-        // get procedures
-        ResultSet rs = getMetaData().getProcedures(catalogPattern, schemaPattern, namePattern);
-        try {
-            while (rs.next()) {
-                String name = rs.getString("PROCEDURE_NAME");
-
-                // TODO: this will be moved to Delegate...
-                if (EXCLUDED_PROCEDURES.contains(name)) {
-                    logger.info("skipping Cayenne PK procedure: " + name);
-                    continue;
-                }
-
-                String catalog = rs.getString("PROCEDURE_CAT");
-                String schema = rs.getString("PROCEDURE_SCHEM");
-
-                short type = rs.getShort("PROCEDURE_TYPE");
-
-                Procedure procedure = new Procedure(name);
-                procedure.setCatalog(catalog);
-                procedure.setSchema(schema);
-
-                switch (type) {
-                case DatabaseMetaData.procedureNoResult:
-                case DatabaseMetaData.procedureResultUnknown:
-                    procedure.setReturningValue(false);
-                    break;
-                case DatabaseMetaData.procedureReturnsResult:
-                    procedure.setReturningValue(true);
-                    break;
-                }
+        Map<String, Procedure> procedures = loadProcedures(config);
+        if (procedures.isEmpty()) {
+            return procedures;
+        }
 
-                if (procedures == null) {
-                    procedures = new HashMap<String, Procedure>();
-                }
+        loadProceduresColumns(procedures);
 
-                procedures.put(procedure.getFullyQualifiedName(), procedure);
-            }
-        } finally {
-            rs.close();
+        for (Procedure procedure : procedures.values()) {
+            dataMap.addProcedure(procedure);
         }
 
-        // if nothing found, return
-        if (procedures == null) {
-            return;
-        }
+        return procedures;
+    }
 
-        // get columns
-        ResultSet columnsRS = getMetaData().getProcedureColumns(null, schemaPattern, namePattern, null);
+    private void loadProceduresColumns(Map<String, Procedure> procedures) throws SQLException {
+        ResultSet columnsRS = getMetaData().getProcedureColumns(null, null, null, null); // TODO catalog, schema
         try {
             while (columnsRS.next()) {
 
                 String schema = columnsRS.getString("PROCEDURE_SCHEM");
                 String name = columnsRS.getString("PROCEDURE_NAME");
-
-                // TODO: this will be moved to Delegate...
-                if (EXCLUDED_PROCEDURES.contains(name)) {
+                String key = (schema == null ? "" : schema + '.') + name ;
+                Procedure procedure = procedures.get(key);
+                if (procedure == null) {
                     continue;
                 }
 
                 String columnName = columnsRS.getString("COLUMN_NAME");
-                short type = columnsRS.getShort("COLUMN_TYPE");
 
-                String key = (schema != null) ? schema + '.' + name : name;
-
-                // skip ResultSet columns, as they are not described in Cayenne
-                // procedures
-                // yet...
+                // skip ResultSet columns, as they are not described in Cayenne procedures yet...
+                short type = columnsRS.getShort("COLUMN_TYPE");
                 if (type == DatabaseMetaData.procedureColumnResult) {
-                    logger.debug("skipping ResultSet column: " + key + "." + columnName);
+                    LOGGER.debug("skipping ResultSet column: " + key + "." + columnName);
                 }
 
-                Procedure procedure = procedures.get(key);
-
-                if (procedure == null) {
-                    logger.info("invalid procedure column, no procedure found: " + key + "." + columnName);
-                    continue;
-                }
-
-                ProcedureParameter column = new ProcedureParameter(columnName);
-
                 if (columnName == null) {
                     if (type == DatabaseMetaData.procedureColumnReturn) {
-                        logger.debug("null column name, assuming result column: " + key);
-                        column.setName("_return_value");
+                        LOGGER.debug("null column name, assuming result column: " + key);
+                        columnName = "_return_value";
+                        procedure.setReturningValue(true);
                     } else {
-                        logger.info("invalid null column name, skipping column : " + key);
+                        LOGGER.info("invalid null column name, skipping column : " + key);
                         continue;
                     }
                 }
 
                 int columnType = columnsRS.getInt("DATA_TYPE");
-                int columnSize = columnsRS.getInt("LENGTH");
 
                 // ignore precision of non-decimal columns
                 int decimalDigits = -1;
@@ -955,35 +882,86 @@ public class DbLoader {
                     }
                 }
 
-                switch (type) {
-                case DatabaseMetaData.procedureColumnIn:
-                    column.setDirection(ProcedureParameter.IN_PARAMETER);
-                    break;
-                case DatabaseMetaData.procedureColumnInOut:
-                    column.setDirection(ProcedureParameter.IN_OUT_PARAMETER);
-                    break;
-                case DatabaseMetaData.procedureColumnOut:
-                    column.setDirection(ProcedureParameter.OUT_PARAMETER);
-                    break;
-                case DatabaseMetaData.procedureColumnReturn:
-                    procedure.setReturningValue(true);
-                    break;
+                ProcedureParameter column = new ProcedureParameter(columnName);
+                int direction = getDirection(type);
+                if (direction != -1) {
+                    column.setDirection(direction);
                 }
 
-                column.setMaxLength(columnSize);
+                column.setType(columnType);
+                column.setMaxLength(columnsRS.getInt("LENGTH"));
                 column.setPrecision(decimalDigits);
+
                 column.setProcedure(procedure);
-                column.setType(columnType);
                 procedure.addCallParameter(column);
             }
         } finally {
             columnsRS.close();
         }
+    }
 
-        for (final Procedure procedure : procedures.values()) {
-            // overwrite existing procedures...
-            dataMap.addProcedure(procedure);
+    private static int getDirection(short type) {
+        switch (type) {
+            case DatabaseMetaData.procedureColumnIn:
+                return ProcedureParameter.IN_PARAMETER;
+            case DatabaseMetaData.procedureColumnInOut:
+                return ProcedureParameter.IN_OUT_PARAMETER;
+            case DatabaseMetaData.procedureColumnOut:
+                return ProcedureParameter.OUT_PARAMETER;
+            default:
+                return -1;
+        }
+    }
+
+    private Map<String, Procedure> loadProcedures(DbLoaderConfiguration config) throws SQLException {
+        Map<String, Procedure> procedures = new HashMap<String, Procedure>();
+
+        FiltersConfig filters = config.getFiltersConfig();
+        for (DbPath dbPath : filters.pathsForQueries()) {
+            if (filters.filter(dbPath).procedureFilter().equals(NULL)) {
+                continue;
+            }
+
+            procedures.putAll(loadProcedures(filters, dbPath));
+        }
+
+        return procedures;
+    }
+
+    private Map<String, Procedure> loadProcedures(FiltersConfig filters, DbPath dbPath) throws SQLException {
+        Map<String, Procedure> procedures = new HashMap<String, Procedure>();
+        // get procedures
+        ResultSet rs = getMetaData().getProcedures(dbPath.catalog, dbPath.schema, WILDCARD);
+        try {
+            while (rs.next()) {
+
+                String name = rs.getString("PROCEDURE_NAME");
+                Procedure procedure = new Procedure(name);
+                procedure.setCatalog(rs.getString("PROCEDURE_CAT"));
+                procedure.setSchema(rs.getString("PROCEDURE_SCHEM"));
+
+                if (filters.filter(new DbPath(procedure.getCatalog(), procedure.getSchema()))
+                                .procedureFilter().isInclude(procedure)) {
+                    LOGGER.info("skipping Cayenne PK procedure: " + name);
+                    continue;
+                }
+
+                switch (rs.getShort("PROCEDURE_TYPE")) {
+                    case DatabaseMetaData.procedureNoResult:
+                    case DatabaseMetaData.procedureResultUnknown:
+                        procedure.setReturningValue(false);
+                        break;
+                    case DatabaseMetaData.procedureReturnsResult:
+                        procedure.setReturningValue(true);
+                        break;
+                }
+
+                procedures.put(procedure.getFullyQualifiedName(), procedure);
+            }
+        } finally {
+            rs.close();
         }
+        return procedures;
     }
 
     /**
@@ -992,7 +970,6 @@ public class DbLoader {
      * @since 3.0
      */
     public void setNameGenerator(ObjectNameGenerator strategy) {
-        // null values are not allowed
         if (strategy == null) {
             throw new NullPointerException("Null strategy not allowed");
         }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/fde7761f/cayenne-server/src/main/java/org/apache/cayenne/access/DbLoaderConfiguration.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/DbLoaderConfiguration.java b/cayenne-server/src/main/java/org/apache/cayenne/access/DbLoaderConfiguration.java
deleted file mode 100644
index 9d56964..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/DbLoaderConfiguration.java
+++ /dev/null
@@ -1,48 +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.access;
-
-/**
- * @since 3.2.
- */
-public class DbLoaderConfiguration {
-
-    private String catalog;
-
-    private String schema;
-
-
-    public String getCatalog() {
-        return catalog;
-    }
-
-    public void setCatalog(String catalog) {
-        this.catalog = catalog;
-    }
-
-    public String getSchema() {
-        return schema;
-    }
-
-    public void setSchema(String schema) {
-        this.schema = schema;
-    }
-
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/fde7761f/cayenne-server/src/main/java/org/apache/cayenne/access/DefaultDbLoaderDelegate.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/DefaultDbLoaderDelegate.java b/cayenne-server/src/main/java/org/apache/cayenne/access/DefaultDbLoaderDelegate.java
deleted file mode 100644
index 08e229e..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/DefaultDbLoaderDelegate.java
+++ /dev/null
@@ -1,54 +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.access;
-
-import org.apache.cayenne.CayenneException;
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.map.ObjEntity;
-
-/**
- * @since 3.2.
- */
-public class DefaultDbLoaderDelegate implements DbLoaderDelegate {
-
-    @Override
-    public boolean overwriteDbEntity(DbEntity entity) throws CayenneException {
-        return false;
-    }
-
-    @Override
-    public void dbEntityAdded(DbEntity entity) {
-
-    }
-
-    @Override
-    public void dbEntityRemoved(DbEntity entity) {
-
-    }
-
-    @Override
-    public void objEntityAdded(ObjEntity entity) {
-
-    }
-
-    @Override
-    public void objEntityRemoved(ObjEntity entity) {
-
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/fde7761f/cayenne-server/src/main/java/org/apache/cayenne/access/ManyToManyCandidateEntity.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/ManyToManyCandidateEntity.java b/cayenne-server/src/main/java/org/apache/cayenne/access/ManyToManyCandidateEntity.java
deleted file mode 100644
index 4f68344..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/ManyToManyCandidateEntity.java
+++ /dev/null
@@ -1,126 +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.access;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import org.apache.cayenne.map.DbRelationship;
-import org.apache.cayenne.map.ObjEntity;
-import org.apache.cayenne.map.ObjRelationship;
-import org.apache.cayenne.map.naming.DefaultUniqueNameGenerator;
-import org.apache.cayenne.map.naming.ExportedKey;
-import org.apache.cayenne.map.naming.NameCheckers;
-import org.apache.cayenne.map.naming.ObjectNameGenerator;
-
-/**
- * Class represent ObjEntity that may be optimized using flattened relationships
- * as many to many table
- */
-class ManyToManyCandidateEntity {
-    private final ObjEntity joinEntity;
-
-    private final DbRelationship dbRel1;
-    private final DbRelationship dbRel2;
-
-    private final ObjEntity entity1;
-    private final ObjEntity entity2;
-
-    private final DbRelationship reverseRelationship1;
-    private final DbRelationship reverseRelationship2;
-
-    private ManyToManyCandidateEntity(ObjEntity entityValue, List<ObjRelationship> relationships) {
-        joinEntity = entityValue;
-
-        ObjRelationship rel1 = relationships.get(0);
-        ObjRelationship rel2 = relationships.get(1);
-
-        dbRel1 = rel1.getDbRelationships().get(0);
-        dbRel2 = rel2.getDbRelationships().get(0);
-
-        reverseRelationship1 = dbRel1.getReverseRelationship();
-        reverseRelationship2 = dbRel2.getReverseRelationship();
-
-        entity1 = rel1.getTargetEntity();
-        entity2 = rel2.getTargetEntity();
-    }
-
-    /**
-     * Method check - if current entity represent many to many temporary table
-     * @return true if current entity is represent many to many table; otherwise returns false
-     */
-    public static ManyToManyCandidateEntity build(ObjEntity joinEntity) {
-        ArrayList<ObjRelationship> relationships = new ArrayList<ObjRelationship>(joinEntity.getRelationships());
-        if (relationships.size() != 2) {
-            return null;
-        }
-
-        ManyToManyCandidateEntity candidateEntity = new ManyToManyCandidateEntity(joinEntity, relationships);
-        if (candidateEntity.isManyToMany()) {
-            return candidateEntity;
-        }
-
-        return null;
-    }
-
-    private boolean isManyToMany() {
-        boolean isNotHaveAttributes = joinEntity.getAttributes().size() == 0;
-
-        return isNotHaveAttributes && reverseRelationship1.isToDependentPK() && reverseRelationship2.isToDependentPK()
-                && !entity1.equals(entity2);
-    }
-
-    private void addFlattenedRelationship(ObjectNameGenerator nameGenerator, ObjEntity srcEntity, ObjEntity dstEntity,
-                                          DbRelationship rel1, DbRelationship rel2) {
-
-        ExportedKey key = new ExportedKey(rel1.getSourceEntity().getName(),
-                rel1.getSourceAttributes().iterator().next().getName(),
-                null,
-                rel2.getTargetEntity().getName(),
-                rel2.getTargetAttributes().iterator().next().getName(),
-                null);
-
-        ObjRelationship newRelationship = new ObjRelationship();
-        newRelationship.setName(DefaultUniqueNameGenerator.generate(NameCheckers.objRelationship, srcEntity,
-                nameGenerator.createDbRelationshipName(key, true)));
-
-        newRelationship.setSourceEntity(srcEntity);
-        newRelationship.setTargetEntity(dstEntity);
-
-        newRelationship.addDbRelationship(rel1);
-        newRelationship.addDbRelationship(rel2);
-
-        srcEntity.addRelationship(newRelationship);
-    }
-
-    /**
-     * Method make direct relationships between 2 entities and remove relationships to
-     * many to many entity
-     *
-     * @param nameGenerator
-     */
-    public void optimizeRelationships(ObjectNameGenerator nameGenerator) {
-        entity1.removeRelationship(reverseRelationship1.getName());
-        entity2.removeRelationship(reverseRelationship2.getName());
-
-        addFlattenedRelationship(nameGenerator, entity1, entity2, reverseRelationship1, dbRel2);
-        addFlattenedRelationship(nameGenerator, entity2, entity1, reverseRelationship2, dbRel1);
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/fde7761f/cayenne-server/src/main/java/org/apache/cayenne/access/loader/BooleanNameFilter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/loader/BooleanNameFilter.java b/cayenne-server/src/main/java/org/apache/cayenne/access/loader/BooleanNameFilter.java
new file mode 100644
index 0000000..c816af8
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/loader/BooleanNameFilter.java
@@ -0,0 +1,35 @@
+/*****************************************************************
+ *   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.access.loader;
+
+/**
+ * @since 3.2.
+ */
+public class BooleanNameFilter implements NameFilter {
+    private final boolean isInclude;
+
+    public BooleanNameFilter(boolean isInclude) {
+        this.isInclude = isInclude;
+    }
+
+    @Override
+    public boolean isIncluded(String string) {
+        return this.isInclude;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/fde7761f/cayenne-server/src/main/java/org/apache/cayenne/access/loader/DbLoaderConfiguration.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/loader/DbLoaderConfiguration.java b/cayenne-server/src/main/java/org/apache/cayenne/access/loader/DbLoaderConfiguration.java
new file mode 100644
index 0000000..461c687
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/loader/DbLoaderConfiguration.java
@@ -0,0 +1,115 @@
+/*****************************************************************
+ *   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.access.loader;
+
+import org.apache.cayenne.access.loader.filters.DbPath;
+import org.apache.cayenne.access.loader.filters.EntityFilters;
+import org.apache.cayenne.access.loader.filters.FilterFactory;
+import org.apache.cayenne.access.loader.filters.FiltersConfig;
+
+/**
+ * @since 3.2.
+ */
+public class DbLoaderConfiguration {
+
+    /**
+     * Returns a name of a generic class that should be used for all
+     * ObjEntities. The most common generic class is
+     * {@link org.apache.cayenne.CayenneDataObject}. If generic class name is
+     * null (which is the default), DbLoader will assign each entity a unique
+     * class name derived from the table name.
+     *
+     */
+    private String genericClassName;
+
+/*
+    // TODO: Andrus, 10/29/2005 - this type of filtering should be delegated to adapter
+    */
+/* TODO by default should skip name.startsWith("BIN$") *//*
+
+    private NameFilter tableFilter = NamePatternMatcher.build(null, null, "BIN$");
+
+    private NameFilter columnFilter;
+
+    private NameFilter proceduresFilter = new NameFilter() {
+        private final Collection<String> excludedProcedures = Arrays.asList(
+                "auto_pk_for_table",
+                "auto_pk_for_table;1" // the last name is some Mac OS X Sybase artifact
+        );
+
+        @Override
+        public boolean isIncluded(String string) {
+            return !excludedProcedures.contains(string);
+        }
+    };
+*/
+
+
+    /**
+     * Java class implementing org.apache.cayenne.map.naming.NamingStrategy.
+     * This is used to specify how ObjEntities will be mapped from the imported
+     * DB schema.
+     */
+    private String namingStrategy;
+
+    private String[] tableTypes;
+
+    private FiltersConfig filtersConfig;
+
+    public String getGenericClassName() {
+        return genericClassName;
+    }
+
+    public void setGenericClassName(String genericClassName) {
+        this.genericClassName = genericClassName;
+    }
+
+    public String[] getTableTypes() {
+        return tableTypes;
+    }
+
+    public void setTableTypes(String[] tableTypes) {
+        this.tableTypes = tableTypes;
+    }
+
+    public String getNamingStrategy() {
+        return namingStrategy;
+    }
+
+    public void setNamingStrategy(String namingStrategy) {
+        this.namingStrategy = namingStrategy;
+    }
+
+    public FiltersConfig getFiltersConfig() {
+        if (filtersConfig == null) {
+            // this case is used often in tests where config not initialized properly
+            return new FiltersConfig(new EntityFilters(new DbPath(), FilterFactory.TRUE, FilterFactory.TRUE, FilterFactory.TRUE));
+        }
+        return filtersConfig;
+    }
+
+    public void setFiltersConfig(FiltersConfig filtersConfig) {
+        this.filtersConfig = filtersConfig;
+    }
+
+    @Override
+    public String toString() {
+        return "EntitiesFilters: " + getFiltersConfig();
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/fde7761f/cayenne-server/src/main/java/org/apache/cayenne/access/loader/DefaultDbLoaderDelegate.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/loader/DefaultDbLoaderDelegate.java b/cayenne-server/src/main/java/org/apache/cayenne/access/loader/DefaultDbLoaderDelegate.java
new file mode 100644
index 0000000..2ca2d97
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/loader/DefaultDbLoaderDelegate.java
@@ -0,0 +1,55 @@
+/*****************************************************************
+ *   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.access.loader;
+
+import org.apache.cayenne.CayenneException;
+import org.apache.cayenne.access.DbLoaderDelegate;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.ObjEntity;
+
+/**
+ * @since 3.2.
+ */
+public class DefaultDbLoaderDelegate implements DbLoaderDelegate {
+
+    @Override
+    public boolean overwriteDbEntity(DbEntity entity) throws CayenneException {
+        return false;
+    }
+
+    @Override
+    public void dbEntityAdded(DbEntity entity) {
+
+    }
+
+    @Override
+    public void dbEntityRemoved(DbEntity entity) {
+
+    }
+
+    @Override
+    public void objEntityAdded(ObjEntity entity) {
+
+    }
+
+    @Override
+    public void objEntityRemoved(ObjEntity entity) {
+
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/fde7761f/cayenne-server/src/main/java/org/apache/cayenne/access/loader/ManyToManyCandidateEntity.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/loader/ManyToManyCandidateEntity.java b/cayenne-server/src/main/java/org/apache/cayenne/access/loader/ManyToManyCandidateEntity.java
new file mode 100644
index 0000000..8cf4ae1
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/loader/ManyToManyCandidateEntity.java
@@ -0,0 +1,126 @@
+/*****************************************************************
+ *   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.access.loader;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.cayenne.map.DbRelationship;
+import org.apache.cayenne.map.ObjEntity;
+import org.apache.cayenne.map.ObjRelationship;
+import org.apache.cayenne.map.naming.DefaultUniqueNameGenerator;
+import org.apache.cayenne.map.naming.ExportedKey;
+import org.apache.cayenne.map.naming.NameCheckers;
+import org.apache.cayenne.map.naming.ObjectNameGenerator;
+
+/**
+ * Class represent ObjEntity that may be optimized using flattened relationships
+ * as many to many table
+ */
+public class ManyToManyCandidateEntity {
+    private final ObjEntity joinEntity;
+
+    private final DbRelationship dbRel1;
+    private final DbRelationship dbRel2;
+
+    private final ObjEntity entity1;
+    private final ObjEntity entity2;
+
+    private final DbRelationship reverseRelationship1;
+    private final DbRelationship reverseRelationship2;
+
+    private ManyToManyCandidateEntity(ObjEntity entityValue, List<ObjRelationship> relationships) {
+        joinEntity = entityValue;
+
+        ObjRelationship rel1 = relationships.get(0);
+        ObjRelationship rel2 = relationships.get(1);
+
+        dbRel1 = rel1.getDbRelationships().get(0);
+        dbRel2 = rel2.getDbRelationships().get(0);
+
+        reverseRelationship1 = dbRel1.getReverseRelationship();
+        reverseRelationship2 = dbRel2.getReverseRelationship();
+
+        entity1 = rel1.getTargetEntity();
+        entity2 = rel2.getTargetEntity();
+    }
+
+    /**
+     * Method check - if current entity represent many to many temporary table
+     * @return true if current entity is represent many to many table; otherwise returns false
+     */
+    public static ManyToManyCandidateEntity build(ObjEntity joinEntity) {
+        ArrayList<ObjRelationship> relationships = new ArrayList<ObjRelationship>(joinEntity.getRelationships());
+        if (relationships.size() != 2) {
+            return null;
+        }
+
+        ManyToManyCandidateEntity candidateEntity = new ManyToManyCandidateEntity(joinEntity, relationships);
+        if (candidateEntity.isManyToMany()) {
+            return candidateEntity;
+        }
+
+        return null;
+    }
+
+    private boolean isManyToMany() {
+        boolean isNotHaveAttributes = joinEntity.getAttributes().size() == 0;
+
+        return isNotHaveAttributes && reverseRelationship1.isToDependentPK() && reverseRelationship2.isToDependentPK()
+                && !entity1.equals(entity2);
+    }
+
+    private void addFlattenedRelationship(ObjectNameGenerator nameGenerator, ObjEntity srcEntity, ObjEntity dstEntity,
+                                          DbRelationship rel1, DbRelationship rel2) {
+
+        ExportedKey key = new ExportedKey(rel1.getSourceEntity().getName(),
+                rel1.getSourceAttributes().iterator().next().getName(),
+                null,
+                rel2.getTargetEntity().getName(),
+                rel2.getTargetAttributes().iterator().next().getName(),
+                null);
+
+        ObjRelationship newRelationship = new ObjRelationship();
+        newRelationship.setName(DefaultUniqueNameGenerator.generate(NameCheckers.objRelationship, srcEntity,
+                nameGenerator.createDbRelationshipName(key, true)));
+
+        newRelationship.setSourceEntity(srcEntity);
+        newRelationship.setTargetEntity(dstEntity);
+
+        newRelationship.addDbRelationship(rel1);
+        newRelationship.addDbRelationship(rel2);
+
+        srcEntity.addRelationship(newRelationship);
+    }
+
+    /**
+     * Method make direct relationships between 2 entities and remove relationships to
+     * many to many entity
+     *
+     * @param nameGenerator
+     */
+    public void optimizeRelationships(ObjectNameGenerator nameGenerator) {
+        entity1.removeRelationship(reverseRelationship1.getName());
+        entity2.removeRelationship(reverseRelationship2.getName());
+
+        addFlattenedRelationship(nameGenerator, entity1, entity2, reverseRelationship1, dbRel2);
+        addFlattenedRelationship(nameGenerator, entity2, entity1, reverseRelationship2, dbRel1);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/fde7761f/cayenne-server/src/main/java/org/apache/cayenne/access/loader/NameFilter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/loader/NameFilter.java b/cayenne-server/src/main/java/org/apache/cayenne/access/loader/NameFilter.java
new file mode 100644
index 0000000..6e28b33
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/loader/NameFilter.java
@@ -0,0 +1,27 @@
+/*****************************************************************
+ *   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.access.loader;
+
+/**
+ * @since 3.2.
+ */
+public interface NameFilter {
+
+    boolean isIncluded(String string);
+}