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:41 UTC

[6/7] CAY-1946 CDbimport improvements

http://git-wip-us.apache.org/repos/asf/cayenne/blob/fde7761f/cayenne-server/src/main/java/org/apache/cayenne/access/loader/NamePatternMatcher.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/loader/NamePatternMatcher.java b/cayenne-server/src/main/java/org/apache/cayenne/access/loader/NamePatternMatcher.java
new file mode 100644
index 0000000..6faa7e4
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/loader/NamePatternMatcher.java
@@ -0,0 +1,225 @@
+/*****************************************************************
+ *   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.Iterator;
+import java.util.List;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+import org.apache.cayenne.util.CayenneMapEntry;
+import org.apache.commons.logging.Log;
+
+/**
+ * Provides name pattern matching functionality.
+ * 
+ * @since 1.2
+ */
+public class NamePatternMatcher implements NameFilter {
+
+    private static final String[] EMPTY_ARRAY = new String[0];
+    private static final Pattern COMMA = Pattern.compile(",");
+
+    private final Pattern[] itemIncludeFilters;
+    private final Pattern[] itemExcludeFilters;
+
+    public static NamePatternMatcher build(Log logger, String includePattern, String excludePattern) {
+        return new NamePatternMatcher(createPatterns(logger, includePattern), createPatterns(logger, excludePattern));
+    }
+
+    public NamePatternMatcher(Pattern[] itemIncludeFilters, Pattern[] itemExcludeFilters) {
+        this.itemIncludeFilters = itemIncludeFilters;
+        this.itemExcludeFilters = itemExcludeFilters;
+    }
+
+    /**
+     * Applies preconfigured list of filters to the list, removing entities that do not
+     * pass the filter.
+     * 
+     * @deprecated since 3.0 still used by AntDataPortDelegate, which itself should
+     *             probably be deprecated
+     */
+    @Deprecated
+    public List<?> filter(List<?> items) {
+        if (items == null || items.isEmpty()) {
+            return items;
+        }
+
+        if (itemIncludeFilters.length == 0 && itemExcludeFilters.length == 0) {
+            return items;
+        }
+
+        Iterator<?> it = items.iterator();
+        while (it.hasNext()) {
+            CayenneMapEntry entity = (CayenneMapEntry) it.next();
+
+            if (!passedIncludeFilter(entity.getName())) {
+                it.remove();
+                continue;
+            }
+
+            if (!passedExcludeFilter(entity.getName())) {
+                it.remove();
+            }
+        }
+
+        return items;
+    }
+
+    /**
+     * Returns an array of Patterns. Takes a comma-separated list of patterns, attempting
+     * to convert them to the java.util.regex.Pattern syntax. E.g.
+     * <p>
+     * <code>"billing_*,user?"</code> will become an array of two expressions:
+     * <p>
+     * <code>^billing_.*$</code><br>
+     * <code>^user.?$</code><br>
+     */
+    public static Pattern[] createPatterns(Log logger, String patternString) {
+        if (patternString == null) {
+            return new Pattern[0];
+        }
+        String[] patternStrings = tokenizePattern(patternString);
+        List<Pattern> patterns = new ArrayList<Pattern>(patternStrings.length);
+
+        for (String patternString1 : patternStrings) {
+
+            // test the pattern
+            try {
+                patterns.add(Pattern.compile(patternString1));
+            } catch (PatternSyntaxException e) {
+
+                if (logger != null) {
+                    logger.warn("Ignoring invalid pattern [" + patternString1 + "], reason: " + e.getMessage());
+                }
+            }
+        }
+
+        return patterns.toArray(new Pattern[patterns.size()]);
+    }
+
+    /**
+     * Returns an array of valid regular expressions. Takes a comma-separated list of
+     * patterns, attempting to convert them to the java.util.regex.Pattern syntax. E.g.
+     * <p>
+     * <code>"billing_*,user?"</code> will become an array of two expressions:
+     * <p>
+     * <code>^billing_.*$</code><br>
+     * <code>^user.?$</code><br>
+     */
+    public static String[] tokenizePattern(String pattern) {
+        if (pattern == null || pattern.isEmpty()) {
+            return EMPTY_ARRAY;
+        }
+
+        String[] patterns = COMMA.split(pattern);
+        if (patterns.length == 0) {
+            return EMPTY_ARRAY;
+        }
+
+        for (int i = 0; i < patterns.length; i++) {
+            // convert * into regex syntax
+            // e.g. abc*x becomes ^abc.*x$
+            // or abc?x becomes ^abc.?x$
+            patterns[i] = "^" + patterns[i].replaceAll("[*?]", ".$0") + "$";
+        }
+
+        return patterns;
+    }
+
+    /**
+     * Returns true if a given object property satisfies the include/exclude patterns.
+     * 
+     * @since 3.0
+     */
+    @Override
+    public boolean isIncluded(String string) {
+        return passedIncludeFilter(string) && passedExcludeFilter(string);
+    }
+
+    /**
+     * Returns true if an object matches any one of the "include" patterns, or if there is
+     * no "include" patterns defined.
+     * 
+     * @since 3.0
+     */
+    private boolean passedIncludeFilter(String item) {
+        if (itemIncludeFilters.length == 0) {
+            return true;
+        }
+
+        for (Pattern itemIncludeFilter : itemIncludeFilters) {
+            if (itemIncludeFilter.matcher(item).find()) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Returns true if an object does not match any one of the "exclude" patterns, or if
+     * there is no "exclude" patterns defined.
+     * 
+     * @since 3.0
+     */
+    private boolean passedExcludeFilter(String item) {
+        if (itemExcludeFilters.length == 0) {
+            return true;
+        }
+
+        for (Pattern itemExcludeFilter : itemExcludeFilters) {
+            if (itemExcludeFilter.matcher(item).find()) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    public static String replaceWildcardInStringWithString(
+            String wildcard,
+            String pattern,
+            String replacement) {
+
+        if (pattern == null || wildcard == null) {
+            return pattern;
+        }
+
+        StringBuilder buffer = new StringBuilder();
+        int lastPos = 0;
+        int wildCardPos = pattern.indexOf(wildcard);
+        while (wildCardPos != -1) {
+            if (lastPos != wildCardPos) {
+                buffer.append(pattern.substring(lastPos, wildCardPos));
+            }
+            buffer.append(replacement);
+            lastPos += wildCardPos + wildcard.length();
+            wildCardPos = pattern.indexOf(wildcard, lastPos);
+        }
+
+        if (lastPos < pattern.length()) {
+            buffer.append(pattern.substring(lastPos));
+        }
+
+        return buffer.toString();
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/fde7761f/cayenne-server/src/main/java/org/apache/cayenne/access/loader/filters/DbPath.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/loader/filters/DbPath.java b/cayenne-server/src/main/java/org/apache/cayenne/access/loader/filters/DbPath.java
new file mode 100644
index 0000000..b9db92d
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/loader/filters/DbPath.java
@@ -0,0 +1,154 @@
+/*****************************************************************
+ *   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.filters;
+
+import java.util.regex.Pattern;
+
+/**
+* @since 3.2.
+* @Immutable
+*/
+public class DbPath implements Comparable<DbPath> {
+
+    public static final DbPath EMPTY = new DbPath();
+
+    public static final String SEPARATOR = "/";
+    public final String catalog;
+    public final String schema;
+    public final String tablePattern;
+
+    private final String path;
+
+    public DbPath() {
+        this(null, null, null);
+    }
+
+    public DbPath(String catalog) {
+        this(catalog, null, null);
+    }
+
+    public DbPath(String catalog, String schema) {
+        this(catalog, schema, null);
+    }
+
+    public DbPath(String catalog, String schema, String tablePattern) {
+        this.catalog = prepareValue(catalog);
+        this.schema = prepareValue(schema);
+        this.tablePattern = prepareValue(tablePattern);
+
+        this.path = join(this.catalog, this.schema, this.tablePattern);
+    }
+
+    private static String join(String first, String second) {
+        if (second == null || second.equals("%")) {
+            return first;
+        } else {
+            return escapeNull(first) + SEPARATOR + second;
+        }
+    }
+
+    private static String join(String catalog, String schema, String table) {
+        String join = join(catalog, join(schema, table));
+        return escapeNull(join);
+    }
+
+    private static String escapeNull(String join) {
+        return join == null ? "%" : join;
+    }
+
+    private String prepareValue(String value) {
+        return value == null ? null : value.trim();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        DbPath dbPath = (DbPath) o;
+
+        return path.equals(dbPath.path);
+    }
+
+
+    @Override
+    public int hashCode() {
+        return path.hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return path;
+    }
+
+    @Override
+    public int compareTo(DbPath o) {
+        return path.compareTo(o.path);
+    }
+
+    public boolean isCover(String catalog, String schema) {
+        return isCover(catalog, schema, null);
+    }
+
+    public boolean isCover(String catalog, String schema, String table) {
+        if (this.catalog == null && catalog == null) {
+            return schemaCover(schema, table);
+        } else if (this.catalog == null) {
+            return schemaCover(schema, table);
+        } else {
+            return this.catalog.equalsIgnoreCase(catalog) && schemaCover(schema, table);
+        }
+    }
+
+    private boolean schemaCover(String schema, String table) {
+        if (this.schema == null && schema == null) {
+            return tableCover(table);
+        } else if (this.schema == null) {
+            return tableCover(table);
+        } else {
+            return this.schema.equalsIgnoreCase(schema) && tableCover(table);
+        }
+    }
+
+    private boolean tableCover(String table) {
+        return this.tablePattern == null
+                || table != null && Pattern.compile(this.tablePattern, Pattern.CASE_INSENSITIVE).matcher(table).matches();
+    }
+
+    public boolean isCover(DbPath dbPath) {
+        if (dbPath == null) {
+            throw new IllegalArgumentException("dbPath can't be null");
+        }
+        return isCover(dbPath.catalog, dbPath.schema, dbPath.tablePattern);
+    }
+
+    public DbPath merge(DbPath path) {
+        if (this.isCover(path)) {
+            return this;
+        } else if (path.isCover(this)) {
+            return path;
+        } else {
+            return null;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/fde7761f/cayenne-server/src/main/java/org/apache/cayenne/access/loader/filters/EntityFilters.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/loader/filters/EntityFilters.java b/cayenne-server/src/main/java/org/apache/cayenne/access/loader/filters/EntityFilters.java
new file mode 100644
index 0000000..5fde570
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/loader/filters/EntityFilters.java
@@ -0,0 +1,399 @@
+/*****************************************************************
+ *   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.filters;
+
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.Procedure;
+import org.apache.cayenne.util.EqualsBuilder;
+import org.apache.cayenne.util.HashCodeBuilder;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import static org.apache.cayenne.access.loader.filters.FilterFactory.*;
+
+/**
+ * @since 3.2.
+ * @Immutable
+ */
+public class EntityFilters {
+
+    private static final Log LOG = LogFactory.getLog(Filter.class);
+
+    private final DbPath dbPath;
+
+    private final Filter<String> tableFilters;
+    private final Filter<String> columnFilters;
+    private final Filter<String> proceduresFilters;
+
+
+    public EntityFilters(DbPath dbPath) {
+        this(dbPath, NULL, NULL, NULL);
+    }
+    public EntityFilters(DbPath dbPath, Filter<String> tableFilters, Filter<String> columnFilters, Filter<String> proceduresFilters) {
+        this.dbPath = dbPath;
+        this.tableFilters = set(tableFilters);
+        this.columnFilters = set(columnFilters);
+        this.proceduresFilters = set(proceduresFilters);
+    }
+
+    public boolean isEmpty() {
+        return (tableFilters == null || NULL.equals(tableFilters))
+                && (columnFilters == null || NULL.equals(columnFilters))
+                && (proceduresFilters == null || NULL.equals(proceduresFilters));
+    }
+
+    public DbPath getDbPath() {
+        return dbPath;
+    }
+
+    private Filter<String> set(Filter<String> tableFilters) {
+        return tableFilters == null ? NULL : tableFilters;
+    }
+
+    public Filter<DbEntity> tableFilter() {
+        return new DbEntityFilter(dbPath, tableFilters);
+    }
+
+    public Filter<DbAttribute> columnFilter() {
+        return new DbAttributeFilter(dbPath, columnFilters);
+    }
+
+    public Filter<Procedure> procedureFilter() {
+        return new ProcedureFilter(dbPath, proceduresFilters);
+    }
+
+    public EntityFilters join(EntityFilters filter) {
+        if (filter == null) {
+            return this;
+        }
+
+        DbPath path;
+        if (this.dbPath == null) {
+            path = filter.dbPath;
+        } else if (filter.dbPath == null) {
+            path = this.dbPath;
+        } else {
+            path = this.dbPath.merge(filter.dbPath);
+        }
+
+        return new EntityFilters(path,
+                this.tableFilters.join(filter.tableFilters),
+                this.columnFilters.join(filter.columnFilters),
+                this.proceduresFilters.join(filter.proceduresFilters));
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder res = new StringBuilder();
+        res.append(dbPath).append(":\n");
+        if (tableFilters != null) {
+            res.append("    Table: ").append(tableFilters).append("\n");
+        }
+
+        if (columnFilters != null) {
+            res.append("    Column: ").append(columnFilters).append("\n");
+        }
+
+        if (proceduresFilters != null) {
+            res.append("    Procedures: ").append(proceduresFilters).append("\n");
+        }
+
+        return res.toString();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null) {
+            return false;
+        }
+        if (obj == this) {
+            return true;
+        }
+        if (obj.getClass() != getClass()) {
+            return false;
+        }
+
+        EntityFilters rhs = (EntityFilters) obj;
+        return new EqualsBuilder()
+                .append(this.dbPath, rhs.dbPath)
+                .append(this.tableFilters, rhs.tableFilters)
+                .append(this.columnFilters, rhs.columnFilters)
+                .append(this.proceduresFilters, rhs.proceduresFilters)
+                .isEquals();
+    }
+
+    @Override
+    public int hashCode() {
+        return new HashCodeBuilder()
+                .append(dbPath)
+                .append(tableFilters)
+                .append(columnFilters)
+                .append(proceduresFilters)
+                .toHashCode();
+    }
+
+    /**
+     * @Immutable
+     * @param <T>
+     */
+    private abstract static class EntityFilter<T> implements Filter<T> {
+
+        private final DbPath dbPath;
+        private final Filter<String> filter;
+
+        protected EntityFilter(DbPath dbPath, Filter<String> filter) {
+            this.dbPath = dbPath;
+            this.filter = filter;
+        }
+
+        DbPath getDbPath() {
+            return dbPath;
+        }
+
+        Filter<String> getFilter() {
+            return filter;
+        }
+
+        @Override
+        public EntityFilter<T> join(Filter<T> filter) {
+            if (!(filter instanceof EntityFilter)) {
+                throw new IllegalArgumentException("Unexpected filter join '" + this + "' and '" + filter + "'");
+            }
+
+            EntityFilter<T> entityFilter = (EntityFilter<T>) filter;
+            DbPath dbPath;
+            if (entityFilter.dbPath.isCover(this.dbPath)) {
+                dbPath = entityFilter.dbPath;
+            } else if (this.dbPath.isCover(entityFilter.dbPath)) {
+                dbPath = this.dbPath;
+            } else {
+                throw new IllegalArgumentException("Attempt to merge filter with incompatible tuples: '" + entityFilter.dbPath + "'");
+            }
+
+            return create(dbPath, this.filter.join(entityFilter.filter));
+        }
+
+        @Override
+        public String toString() {
+            return getClass().getSimpleName() + " (" + dbPath + " -> " + filter + ")";
+        }
+
+        public abstract EntityFilter<T> create(DbPath dbPath, Filter<String> filter);
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+
+            if (o == null) {
+                return false;
+            }
+
+            if (o instanceof Filter) { // TODO
+                return filter.equals(o);
+            }
+
+            if (getClass() != o.getClass()) {
+                return false;
+            }
+
+            return filter.equals(((EntityFilter) o).filter);
+
+        }
+
+        @Override
+        public int hashCode() {
+            return filter.hashCode();
+        }
+    }
+
+    private static class DbEntityFilter extends EntityFilter<DbEntity> {
+
+        public DbEntityFilter(DbPath dbPath, Filter<String> filter) {
+            super(dbPath, filter);
+        }
+
+        @Override
+        public boolean isInclude(DbEntity obj) {
+            if (LOG.isTraceEnabled()
+                    && this.getDbPath().isCover(obj.getCatalog(), obj.getSchema())) {
+
+                LOG.warn("Attempt to apply inconvenient filter '" + this + "' for dbEntity '" + obj + "'");
+            }
+
+            return this.getFilter().isInclude(obj.getName());
+        }
+
+        @Override
+        public EntityFilter<DbEntity> create(DbPath dbPath, Filter<String> filter) {
+            return new DbEntityFilter(dbPath, filter);
+        }
+    }
+
+    private static class DbAttributeFilter extends EntityFilter<DbAttribute> {
+
+        public DbAttributeFilter(DbPath dbPath, Filter<String> filter) {
+            super(dbPath, filter);
+        }
+
+        @Override
+        public boolean isInclude(DbAttribute obj) {
+            DbEntity entity = obj.getEntity();
+            if (LOG.isTraceEnabled()
+                    && this.getDbPath().isCover(entity.getCatalog(), entity.getSchema(), entity.getName())) {
+
+                LOG.warn("Attempt to apply inconvenient filter '" + this + "' for attribute '" + obj + "'");
+            }
+
+            return this.getFilter().isInclude(obj.getName());
+        }
+
+        @Override
+        public EntityFilter<DbAttribute> create(DbPath dbPath, Filter<String> filter) {
+            return new DbAttributeFilter(dbPath, filter);
+        }
+    }
+
+    private static class ProcedureFilter extends EntityFilter<Procedure> {
+
+        public ProcedureFilter(DbPath dbPath, Filter<String> filter) {
+            super(dbPath, filter);
+        }
+
+        @Override
+        public boolean isInclude(Procedure obj) {
+            if (LOG.isTraceEnabled()
+                    && this.getDbPath().isCover(obj.getCatalog(), obj.getSchema())) {
+
+                LOG.warn("Attempt to apply inconvenient filter '" + this + "' for procedure '" + obj + "'");
+            }
+            return this.getFilter().isInclude(obj.getName());
+        }
+
+        @Override
+        public EntityFilter<Procedure> create(DbPath dbPath, Filter<String> filter) {
+            return new ProcedureFilter(dbPath, filter);
+        }
+    }
+
+
+    public static final class Builder {
+        private String catalog;
+        private String schema;
+
+        private Filter<String> tableFilters = NULL;
+        private Filter<String> columnFilters = NULL;
+        private Filter<String> proceduresFilters = NULL;
+
+        public Builder() {
+        }
+
+        public Builder catalog(String catalog) {
+            this.catalog = catalog;
+            return this;
+        }
+
+        public Builder schema(String schema) {
+            this.schema = schema;
+            return this;
+        }
+
+        public String schema() {
+            return schema;
+        }
+
+        public Builder includeTables(String tableFilters) {
+            for (String pattern : tableFilters.split(",")) {
+                this.tableFilters = this.tableFilters.join(include(pattern));
+            }
+
+            return this;
+        }
+
+        public Builder includeColumns(String columnFilters) {
+            for (String pattern : columnFilters.split(",")) {
+                this.columnFilters = this.columnFilters.join(include(pattern));
+            }
+
+            return this;
+        }
+
+        public Builder includeProcedures(String proceduresFilters) {
+            for (String pattern : proceduresFilters.split(",")) {
+                this.proceduresFilters = this.proceduresFilters.join(include(pattern));
+            }
+
+            return this;
+        }
+
+        public Builder excludeTables(String tableFilters) {
+            for (String pattern : tableFilters.split(",")) {
+                this.tableFilters = this.tableFilters.join(exclude(pattern));
+            }
+
+            return this;
+        }
+
+        public Builder excludeColumns(String columnFilters) {
+            for (String pattern : columnFilters.split(",")) {
+                this.columnFilters = this.columnFilters.join(exclude(pattern));
+            }
+
+            return this;
+        }
+
+        public Builder excludeProcedures(String proceduresFilters) {
+            for (String pattern : proceduresFilters.split(",")) {
+                this.proceduresFilters = this.proceduresFilters.join(exclude(pattern));
+            }
+
+            return this;
+        }
+
+        public Filter<String> tableFilters() {
+            return tableFilters;
+        }
+
+        public Filter<String> columnFilters() {
+            return columnFilters;
+        }
+
+        public Filter<String> proceduresFilters() {
+            return proceduresFilters;
+        }
+
+        public void setTableFilters(Filter<String> tableFilters) {
+            this.tableFilters = tableFilters;
+        }
+
+        public void setColumnFilters(Filter<String> columnFilters) {
+            this.columnFilters = columnFilters;
+        }
+
+        public void setProceduresFilters(Filter<String> proceduresFilters) {
+            this.proceduresFilters = proceduresFilters;
+        }
+
+        public EntityFilters build() {
+            return new EntityFilters(new DbPath(catalog, schema), tableFilters, columnFilters, proceduresFilters);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/fde7761f/cayenne-server/src/main/java/org/apache/cayenne/access/loader/filters/ExcludeFilter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/loader/filters/ExcludeFilter.java b/cayenne-server/src/main/java/org/apache/cayenne/access/loader/filters/ExcludeFilter.java
new file mode 100644
index 0000000..5bc794a
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/loader/filters/ExcludeFilter.java
@@ -0,0 +1,42 @@
+/*****************************************************************
+ *   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.filters;
+
+import java.util.regex.Pattern;
+
+/**
+ * @since 3.2.
+ * @Immutable
+ */
+public class ExcludeFilter extends IncludeFilter {
+
+    ExcludeFilter(Pattern pattern) {
+        super(pattern);
+    }
+
+    @Override
+    public boolean isInclude(String obj) {
+        return !super.isInclude(obj);
+    }
+
+    @Override
+    public String toString() {
+        return "-(" + super.getPattern() + ')';
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/fde7761f/cayenne-server/src/main/java/org/apache/cayenne/access/loader/filters/Filter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/loader/filters/Filter.java b/cayenne-server/src/main/java/org/apache/cayenne/access/loader/filters/Filter.java
new file mode 100644
index 0000000..c499276
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/loader/filters/Filter.java
@@ -0,0 +1,31 @@
+/*****************************************************************
+ *   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.filters;
+
+/**
+ * @since 3.2.
+ * @Immutable
+ */
+public interface Filter<T> {
+
+    boolean isInclude(T obj);
+
+    Filter<T> join(Filter<T> filter);
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/fde7761f/cayenne-server/src/main/java/org/apache/cayenne/access/loader/filters/FilterFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/loader/filters/FilterFactory.java b/cayenne-server/src/main/java/org/apache/cayenne/access/loader/filters/FilterFactory.java
new file mode 100644
index 0000000..88f3d8d
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/loader/filters/FilterFactory.java
@@ -0,0 +1,94 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+package org.apache.cayenne.access.loader.filters;
+
+import java.util.regex.Pattern;
+
+/**
+ * @since 3.2.
+ */
+public class FilterFactory {
+
+    public static Filter<String> TRUE = new Filter<String>() {
+        @Override
+        public boolean isInclude(String obj) {
+            return true;
+        }
+
+        @Override
+        public Filter<String> join(Filter<String> filter) {
+            return filter == null || NULL.equals(filter) ? this : filter;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            return this == o || o != null && getClass() == o.getClass();
+        }
+
+        @Override
+        public String toString() {
+            return "true";
+        }
+    };
+
+    public static Filter<String> NULL = new Filter<String>() {
+
+        @Override
+        public boolean isInclude(String obj) {
+            return false;
+        }
+
+        @Override
+        public Filter<String> join(Filter<String> filter) {
+            return filter == null ? this : filter;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            return this == o || o != null && getClass() == o.getClass();
+        }
+
+        @Override
+        public String toString() {
+            return "null";
+        }
+    };
+
+    public static Filter<String> include(String tablePattern) {
+        return new IncludeFilter(pattern(tablePattern));
+    }
+
+    public static Filter<String> exclude(String tablePattern) {
+        return new ExcludeFilter(pattern(tablePattern));
+    }
+
+    public static Filter<String> list(Filter<String> ... filters) {
+        Filter<String> res = NULL;
+        for (Filter<String> filter : filters) {
+            res = res.join(filter);
+        }
+        return res;
+    }
+
+    public static Pattern pattern(String tablePattern) {
+        return Pattern.compile(tablePattern, Pattern.CASE_INSENSITIVE);
+    }
+
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/fde7761f/cayenne-server/src/main/java/org/apache/cayenne/access/loader/filters/FiltersConfig.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/loader/filters/FiltersConfig.java b/cayenne-server/src/main/java/org/apache/cayenne/access/loader/filters/FiltersConfig.java
new file mode 100644
index 0000000..4e79811
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/loader/filters/FiltersConfig.java
@@ -0,0 +1,180 @@
+/*****************************************************************
+ *   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.filters;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @since 3.2.
+ * @Immutable
+ */
+public class FiltersConfig {
+
+    private final List<DbPath> dbPaths;
+    private final Map<DbPath, EntityFilters> filters;
+
+    private List<DbPath> pathsForQueries;
+
+    public FiltersConfig(EntityFilters ... filters) {
+        this(Arrays.asList(filters));
+    }
+
+    public FiltersConfig(Collection<EntityFilters> filters) {
+        this.dbPaths = new LinkedList<DbPath>();
+        this.filters = new HashMap<DbPath, EntityFilters>();
+        for (EntityFilters filter : filters) {
+            if (filter == null) {
+                continue;
+            }
+
+            DbPath path = filter.getDbPath();
+            if (this.dbPaths.contains(path)) {
+                this.filters.put(path, this.filters.get(path).join(filter));
+                continue;
+            }
+
+            this.dbPaths.add(path);
+            this.filters.put(path, filter);
+        }
+
+        Collections.sort(this.dbPaths);
+    }
+
+    /**
+     * Used for loading tables and procedures, it's aim avoid unnecessary queries by compacting pairs of
+     * (Catalog, Schema)
+     *
+     * Example:
+     * <ul>
+     *      <li>"aaa", null</li>
+     *      <li>"aaa", "11"</li>
+     *      <li>"aa", null</li>
+     *      <li>"aa", "a"</li>
+     *      <li>"aa", "aa"</li>
+     *      <li>"aa", "aa"</li>
+     * </ul>
+     *
+     * Should return
+     * <ul>
+     *      <li>"aa", null</li>
+     *      <li>"aaa", null</li>
+     * </ul>
+     * For more examples please see tests.
+     *
+     * @return list of pairs (Catalog, Schema) for which getTables and getProcedures should be called
+     *
+     **/
+    public List<DbPath> pathsForQueries() {
+        if (pathsForQueries != null) {
+            return pathsForQueries;
+        }
+
+        pathsForQueries = new LinkedList<DbPath>();
+        if (filters.isEmpty()) {
+            return pathsForQueries;
+        }
+
+        boolean save = true;
+        String catalog = null;
+        String schema = null;
+        for (DbPath path : dbPaths) {
+            if (save || catalog != null && !catalog.equals(path.catalog)) {
+                catalog = path.catalog;
+                schema = null;
+                save = true;
+            }
+
+            if (save || schema != null && !schema.equals(path.schema)) {
+                schema = path.schema;
+                save = true;
+            }
+
+            if (save) {
+                save = false;
+                pathsForQueries.add(new DbPath(catalog, schema));
+            }
+        }
+
+        return pathsForQueries;
+    }
+
+    /**
+     * TODO comment
+     *
+     * Return filters that applicable for path (filters which path covering path passed in method)
+     * */
+    public EntityFilters filter(DbPath path) {
+        EntityFilters res = new EntityFilters(path);
+        for (Map.Entry<DbPath, EntityFilters> entry : filters.entrySet()) {
+            if (entry.getKey().isCover(path)) {
+                res = res.join(entry.getValue());
+            }
+        }
+
+        return res;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        Map<DbPath, EntityFilters> filters = ((FiltersConfig) o).filters;
+        if (this.filters.size() != filters.size()) {
+            return false;
+        }
+
+        for (Map.Entry<DbPath, EntityFilters> entry : this.filters.entrySet()) {
+            EntityFilters f = filters.get(entry.getKey());
+            if (f == null || !f.equals(entry.getValue())) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder res = new StringBuilder();
+        for (DbPath dbPath : dbPaths) {
+            res.append("    ").append(dbPath).append(" -> ").append(filters.get(dbPath)).append("\n");
+        }
+
+        return res.toString();
+    }
+
+    @Override
+    public int hashCode() {
+        return filters.hashCode();
+    }
+
+    public List<DbPath> getDbPaths() {
+        return dbPaths;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/fde7761f/cayenne-server/src/main/java/org/apache/cayenne/access/loader/filters/IncludeFilter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/loader/filters/IncludeFilter.java b/cayenne-server/src/main/java/org/apache/cayenne/access/loader/filters/IncludeFilter.java
new file mode 100644
index 0000000..eeb90dc
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/loader/filters/IncludeFilter.java
@@ -0,0 +1,76 @@
+/*****************************************************************
+ *   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.filters;
+
+import java.util.regex.Pattern;
+
+/**
+ * @since 3.2.
+ * @Immutable
+ */
+public class IncludeFilter implements Filter<String> {
+
+    private final Pattern pattern;
+
+    IncludeFilter(Pattern pattern) {
+        this.pattern = pattern;
+    }
+
+    @Override
+    public boolean isInclude(String obj) {
+        return pattern.matcher(obj).matches();
+    }
+
+    @Override
+    public Filter join(Filter filter) {
+        return ListFilter.create(this, filter);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+
+        if (o instanceof ListFilter) {
+            return o.equals(this);
+        }
+
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        return pattern.toString().equals(((IncludeFilter) o).pattern.toString());
+
+    }
+
+    @Override
+    public int hashCode() {
+        return pattern.hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return "+(" + pattern + ')';
+    }
+
+    protected Pattern getPattern() {
+        return pattern;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/fde7761f/cayenne-server/src/main/java/org/apache/cayenne/access/loader/filters/ListFilter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/loader/filters/ListFilter.java b/cayenne-server/src/main/java/org/apache/cayenne/access/loader/filters/ListFilter.java
new file mode 100644
index 0000000..49fdb38
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/loader/filters/ListFilter.java
@@ -0,0 +1,123 @@
+/*****************************************************************
+ *   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.filters;
+
+import org.apache.commons.lang.StringUtils;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.LinkedList;
+
+import static org.apache.cayenne.access.loader.filters.FilterFactory.NULL;
+import static org.apache.cayenne.access.loader.filters.FilterFactory.TRUE;
+
+/**
+ * @since 3.2.
+ * @Immutable
+ */
+public class ListFilter<T> implements Filter<T> {
+
+    private final Collection<Filter<T>> filters;
+
+    public ListFilter(Collection<Filter<T>> filters) {
+        this.filters = filters;
+    }
+
+    @Override
+    public boolean isInclude(T obj) {
+        for (Filter<T> filter : filters) {
+            if (!filter.isInclude(obj)) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    @Override
+    public Filter<T> join(Filter<T> filter) {
+        LinkedList<Filter<T>> list = new LinkedList<Filter<T>>(filters);
+        if (TRUE.equals(filter) || NULL.equals(filter)) {
+            // Do nothing.
+        } else if (filter instanceof ListFilter) {
+            list.addAll(((ListFilter<T>) filter).filters);
+        } else {
+            list.add(filter);
+        }
+
+        return new ListFilter<T>(list);
+    }
+
+    public static <T> Filter<T> create(Filter<T> filter1, Filter<T> filter2) {
+        if (filter1 == null || TRUE.equals(filter1) || NULL.equals(filter1)) {
+            return filter2;
+        }
+
+        if (filter2 == null || TRUE.equals(filter2) || NULL.equals(filter2)) {
+            return filter1;
+        }
+
+        if (filter1 instanceof ListFilter) {
+            return filter1.join(filter2);
+        }
+
+        if (filter2 instanceof ListFilter) {
+            return filter2.join(filter1);
+        }
+
+        if (filter1.equals(filter2)) {
+            return filter1;
+        }
+
+        return new ListFilter<T>(Arrays.asList(filter1, filter2));
+    }
+
+    @Override
+    public String toString() {
+        return StringUtils.join(filters, " & ");
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+
+        if (o == null) {
+            return false;
+        }
+
+        if (getClass() != o.getClass()) {
+            if (o instanceof Filter && filters.size() == 1) {
+                return o.equals(filters.iterator().next());
+            } else {
+                return false;
+            }
+        }
+
+        ListFilter that = (ListFilter) o;
+        return filters != null ? filters.equals(that.filters) : that.filters == null;
+
+    }
+
+    @Override
+    public int hashCode() {
+        return filters != null ? filters.hashCode() : 0;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/fde7761f/cayenne-server/src/main/java/org/apache/cayenne/access/loader/mapper/DbType.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/loader/mapper/DbType.java b/cayenne-server/src/main/java/org/apache/cayenne/access/loader/mapper/DbType.java
new file mode 100644
index 0000000..731f824
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/loader/mapper/DbType.java
@@ -0,0 +1,195 @@
+/*****************************************************************
+ *   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.mapper;
+
+import org.apache.cayenne.util.EqualsBuilder;
+import org.apache.cayenne.util.HashCodeBuilder;
+import org.apache.commons.lang.builder.CompareToBuilder;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import static org.apache.commons.lang.StringUtils.isBlank;
+
+/**
+ * @since 3.2.
+ * @Immutable
+ */
+public class DbType implements Comparable<DbType> {
+
+    private static final Log LOG = LogFactory.getLog(DbType.class);
+
+    public final String jdbc;
+
+    public final Integer length;
+    public final Integer precision;
+    public final Integer scale;
+    public final Boolean notNull;
+
+    public DbType(String jdbc) {
+        this(jdbc, null, null, null, null);
+    }
+
+    public DbType(String jdbc, Integer length, Integer precision, Integer scale, Boolean notNull) {
+        if (isBlank(jdbc)) {
+            throw new IllegalArgumentException("Jdbc type can't be null");
+        }
+        this.jdbc = jdbc;
+
+        this.length = getValidInt(length);
+        this.precision = getValidInt(precision);
+        this.scale = getValidInt(scale);
+        this.notNull = notNull;
+    }
+
+    public String getJdbc() {
+        return jdbc;
+    }
+
+    public Integer getLength() {
+        return length;
+    }
+
+    public Integer getPrecision() {
+        return precision;
+    }
+
+    public Integer getScale() {
+        return scale;
+    }
+
+    public Boolean getNotNull() {
+        return notNull;
+    }
+
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null) {
+            return false;
+        }
+        if (obj == this) {
+            return true;
+        }
+        if (obj.getClass() != getClass()) {
+            return false;
+        }
+        DbType rhs = (DbType) obj;
+        return new EqualsBuilder()
+                .append(this.jdbc, rhs.jdbc)
+                .append(this.length, rhs.length)
+                .append(this.precision, rhs.precision)
+                .append(this.scale, rhs.scale)
+                .append(this.notNull, rhs.notNull)
+                .isEquals();
+    }
+
+    @Override
+    public int hashCode() {
+        return new HashCodeBuilder()
+                .append(jdbc)
+                .append(length)
+                .append(precision)
+                .append(scale)
+                .append(notNull)
+                .toHashCode();
+    }
+
+
+    @Override
+    public String toString() {
+        String res = jdbc;
+
+        String len = "*";
+        if (isPositive(length)) {
+            len = length.toString();
+        }
+        if (isPositive(precision)) {
+            len = precision.toString();
+        }
+
+        res += " (" + len;
+        if (isPositive(scale)) {
+            res += ", " + scale;
+        }
+        res += ")";
+
+        if (notNull != null && notNull) {
+            res += " NOT NULL";
+        }
+
+        return res;
+    }
+
+    private boolean isPositive(Integer num) {
+        return num != null && num > 0;
+    }
+
+    private Integer getValidInt(Integer num) {
+        if (num == null || num > 0) {
+            return num;
+        }
+
+        LOG.warn("Invalid int value '" + num + "'");
+        return null;
+    }
+
+    /**
+     * Compare by specificity the most specific DbPath should be first in ordered list
+     */
+    @Override
+    public int compareTo(DbType dbType) {
+        return new CompareToBuilder()
+                .append(dbType.jdbc, jdbc)
+                .append(dbType.getSpecificity(), getSpecificity())
+                .append(dbType.length, length)
+                .append(dbType.precision, precision)
+                .append(dbType.scale, scale)
+                .append(dbType.notNull, notNull)
+                .toComparison();
+    }
+
+    private int getSpecificity() {
+        int res = 0;
+        if (isPositive(length)) {
+            res += 100;
+        }
+        if (isPositive(precision)) {
+            res += 100;
+        }
+        if (isPositive(scale)) {
+            res += 10;
+        }
+        if (this.notNull != null) {
+            res += 5;
+        }
+
+        return res;
+    }
+
+    public boolean isCover(DbType type) {
+        return this.jdbc.equals(type.jdbc)
+            && (isCover(length, type.length) || length == null && type.length == null && isCover(precision, type.precision))
+            && isCover(scale, type.scale)
+            && isCover(notNull, type.notNull);
+    }
+
+    private boolean isCover(Object a, Object b) {
+        return a == null || a.equals(b);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/fde7761f/cayenne-server/src/main/java/org/apache/cayenne/access/loader/mapper/DefaultJdbc2JavaTypeMapper.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/loader/mapper/DefaultJdbc2JavaTypeMapper.java b/cayenne-server/src/main/java/org/apache/cayenne/access/loader/mapper/DefaultJdbc2JavaTypeMapper.java
new file mode 100644
index 0000000..7f37eb7
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/loader/mapper/DefaultJdbc2JavaTypeMapper.java
@@ -0,0 +1,282 @@
+/*****************************************************************
+ *   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.mapper;
+
+import org.apache.cayenne.dba.TypesMapping;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.util.Util;
+
+import java.io.Serializable;
+import java.math.BigInteger;
+import java.sql.Types;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+/**
+ * @since 3.2.
+ */
+public class DefaultJdbc2JavaTypeMapper implements Jdbc2JavaTypeMapper {
+
+    // Never use "-1" or any other normal integer, since there
+    // is a big chance it is being reserved in java.sql.Types
+    public static final int NOT_DEFINED = Integer.MAX_VALUE;
+
+    // char constants for Java data types
+    public static final String JAVA_LONG = "java.lang.Long";
+    public static final String JAVA_BYTES = "byte[]";
+    public static final String JAVA_BOOLEAN = "java.lang.Boolean";
+    public static final String JAVA_STRING = "java.lang.String";
+    public static final String JAVA_SQLDATE = "java.sql.Date";
+    public static final String JAVA_UTILDATE = "java.util.Date";
+    public static final String JAVA_BIGDECIMAL = "java.math.BigDecimal";
+    public static final String JAVA_DOUBLE = "java.lang.Double";
+    public static final String JAVA_FLOAT = "java.lang.Float";
+    public static final String JAVA_INTEGER = "java.lang.Integer";
+    public static final String JAVA_SHORT = "java.lang.Short";
+    public static final String JAVA_BYTE = "java.lang.Byte";
+    public static final String JAVA_TIME = "java.sql.Time";
+    public static final String JAVA_TIMESTAMP = "java.sql.Timestamp";
+    public static final String JAVA_BLOB = "java.sql.Blob";
+
+    /**
+     * Keys: java class names, Values: SQL int type definitions from java.sql.Types
+     */
+    private final Map<String, Integer> javaSqlEnum = new HashMap<String, Integer>();
+
+    private final Map<DbType, String> mapping = new HashMap<DbType, String>();
+    private final SortedSet<DbType> dbTypes = new TreeSet<DbType>();
+
+    private Map<String, String> classToPrimitive;
+
+    {
+        add(Types.BIGINT, JAVA_LONG);
+        add(Types.BINARY, JAVA_BYTES);
+        add(Types.BIT, JAVA_BOOLEAN);
+        add(Types.BOOLEAN, JAVA_BOOLEAN);
+        add(Types.BLOB, JAVA_BYTES);
+        add(Types.CLOB, JAVA_STRING);
+        add(Types.CHAR, JAVA_STRING);
+        add(Types.DATE, JAVA_UTILDATE);
+        add(Types.DECIMAL, JAVA_BIGDECIMAL);
+        add(Types.DOUBLE, JAVA_DOUBLE);
+        add(Types.FLOAT, JAVA_FLOAT);
+        add(Types.INTEGER, JAVA_INTEGER);
+        add(Types.LONGVARCHAR, JAVA_STRING);
+        add(Types.LONGVARBINARY, JAVA_BYTES);
+        add(Types.NUMERIC, JAVA_BIGDECIMAL);
+        add(Types.REAL, JAVA_FLOAT);
+        add(Types.SMALLINT, JAVA_SHORT);
+        add(Types.TINYINT, JAVA_SHORT);
+        add(Types.TIME, JAVA_UTILDATE);
+        add(Types.TIMESTAMP, JAVA_UTILDATE);
+        add(Types.VARBINARY, JAVA_BYTES);
+        add(Types.VARCHAR, JAVA_STRING);
+
+        javaSqlEnum.put(JAVA_LONG, Types.BIGINT);
+        javaSqlEnum.put(JAVA_BYTES, Types.BINARY);
+        javaSqlEnum.put(JAVA_BOOLEAN, Types.BIT);
+        javaSqlEnum.put(JAVA_STRING, Types.VARCHAR);
+        javaSqlEnum.put(JAVA_SQLDATE, Types.DATE);
+        javaSqlEnum.put(JAVA_UTILDATE, Types.DATE);
+        javaSqlEnum.put(JAVA_TIMESTAMP, Types.TIMESTAMP);
+        javaSqlEnum.put(JAVA_BIGDECIMAL, Types.DECIMAL);
+        javaSqlEnum.put(JAVA_DOUBLE, Types.DOUBLE);
+        javaSqlEnum.put(JAVA_FLOAT, Types.FLOAT);
+        javaSqlEnum.put(JAVA_INTEGER, Types.INTEGER);
+        javaSqlEnum.put(JAVA_SHORT, Types.SMALLINT);
+        javaSqlEnum.put(JAVA_BYTE, Types.SMALLINT);
+        javaSqlEnum.put(JAVA_TIME, Types.TIME);
+        javaSqlEnum.put(JAVA_TIMESTAMP, Types.TIMESTAMP);
+
+        // add primitives
+        javaSqlEnum.put("byte", Types.TINYINT);
+        javaSqlEnum.put("int", Types.INTEGER);
+        javaSqlEnum.put("short", Types.SMALLINT);
+        javaSqlEnum.put("char", Types.CHAR);
+        javaSqlEnum.put("double", Types.DOUBLE);
+        javaSqlEnum.put("long", Types.BIGINT);
+        javaSqlEnum.put("float", Types.FLOAT);
+        javaSqlEnum.put("boolean", Types.BIT);
+
+        classToPrimitive = new HashMap<String, String>();
+        classToPrimitive.put(Byte.class.getName(), "byte");
+        classToPrimitive.put(Long.class.getName(), "long");
+        classToPrimitive.put(Double.class.getName(), "double");
+        classToPrimitive.put(Boolean.class.getName(), "boolean");
+        classToPrimitive.put(Float.class.getName(), "float");
+        classToPrimitive.put(Short.class.getName(), "short");
+        classToPrimitive.put(Integer.class.getName(), "int");
+    }
+
+    private Boolean usePrimitives;
+
+
+    /**
+     * Returns default java.sql.Types type by the Java type name.
+     *
+     * @param className Fully qualified Java Class name.
+     * @return The SQL type or NOT_DEFINED if no type found.
+     */
+    public int getJdbcTypeByJava(DbAttribute attribute, String className) {
+        if (className == null) {
+            return NOT_DEFINED;
+        }
+
+        Integer type = javaSqlEnum.get(className);
+        if (type != null) {
+            return type;
+        }
+
+        // try to load a Java class - some nonstandard mappings may work
+        try {
+            return getSqlTypeByJava(attribute, Util.getJavaClass(className));
+        } catch (Throwable th) {
+            return NOT_DEFINED;
+        }
+    }
+
+
+    public void add(int jdbcType, String java) {
+        add(new DbType(TypesMapping.getSqlNameByType(jdbcType)), java);
+    }
+
+    @Override
+    public void add(DbType type, String java) {
+        mapping.put(type, java);
+        dbTypes.add(type);
+    }
+
+    /**
+     * Guesses a default JDBC type for the Java class.
+     *
+     * @since 1.1
+     */
+    protected int getSqlTypeByJava(DbAttribute attribute, Class<?> javaClass) {
+        if (javaClass == null) {
+            return NOT_DEFINED;
+        }
+
+        // check standard mapping of class and superclasses
+        Class<?> aClass = javaClass;
+        while (aClass != null) {
+
+            String name;
+
+            if (aClass.isArray()) {
+                name = aClass.getComponentType().getName() + "[]";
+            }  else {
+                name = aClass.getName();
+            }
+
+            Object type = javaSqlEnum.get(name);
+            if (type != null) {
+                return ((Number) type).intValue();
+            }
+
+            aClass = aClass.getSuperclass();
+        }
+
+        // check non-standard JDBC types that are still supported by JPA
+        if (javaClass.isArray()) {
+
+            Class<?> elementType = javaClass.getComponentType();
+            if (Character.class.isAssignableFrom(elementType)
+                    || Character.TYPE.isAssignableFrom(elementType)) {
+                return Types.VARCHAR;
+            }
+            else if (Byte.class.isAssignableFrom(elementType)
+                    || Byte.TYPE.isAssignableFrom(elementType)) {
+                return Types.VARBINARY;
+            }
+        }
+
+        if (Calendar.class.isAssignableFrom(javaClass)) {
+            return Types.TIMESTAMP;
+        }
+
+        if (BigInteger.class.isAssignableFrom(javaClass)) {
+            return Types.BIGINT;
+        }
+
+        if (Serializable.class.isAssignableFrom(javaClass)) {
+            // serializable check should be the last one when all other mapping attempts failed
+            return Types.VARBINARY;
+        }
+
+        return NOT_DEFINED;
+    }
+
+    /**
+     * Get the corresponding Java type by its java.sql.Types counterpart. Note that this
+     * method should be used as a last resort, with explicit mapping provided by user used
+     * as a first choice, as it can only guess how to map certain types, such as NUMERIC,
+     * etc.
+     *
+     * @return Fully qualified Java type name or null if not found.
+     */
+    @Override
+    public String getJavaByJdbcType(DbAttribute attribute, int type) {
+        String jdbcType = TypesMapping.getSqlNameByType(type);
+        DbType dbType;
+        if (attribute != null) {
+            dbType = new DbType(
+                    jdbcType,
+                    attribute.getMaxLength(),
+                    attribute.getAttributePrecision(),
+                    attribute.getScale(),
+                    attribute.isMandatory());
+        } else {
+            dbType = new DbType(jdbcType);
+        }
+
+        String typeName = getJavaByJdbcType(dbType);
+
+        if (usePrimitives != null && usePrimitives) {
+            String primitive = classToPrimitive.get(typeName);
+            if (primitive != null) {
+                return primitive;
+            }
+        }
+
+        return typeName;
+    }
+
+    public String getJavaByJdbcType(DbType type) {
+        for (DbType t : dbTypes) {
+            if (t.isCover(type)) {
+                // because dbTypes sorted by specificity we will take first and the most specific matching
+                // that applicable for attribute
+                return mapping.get(t);
+            }
+        }
+
+        return null;
+    }
+
+    public Boolean getUsePrimitives() {
+        return usePrimitives;
+    }
+
+    public void setUsePrimitives(Boolean usePrimitives) {
+        this.usePrimitives = usePrimitives;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/fde7761f/cayenne-server/src/main/java/org/apache/cayenne/access/loader/mapper/Jdbc2JavaTypeMapper.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/loader/mapper/Jdbc2JavaTypeMapper.java b/cayenne-server/src/main/java/org/apache/cayenne/access/loader/mapper/Jdbc2JavaTypeMapper.java
new file mode 100644
index 0000000..8e4510c
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/loader/mapper/Jdbc2JavaTypeMapper.java
@@ -0,0 +1,33 @@
+/*****************************************************************
+ *   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.mapper;
+
+import org.apache.cayenne.map.DbAttribute;
+
+/**
+ * @since 3.2.
+ */
+public interface Jdbc2JavaTypeMapper {
+
+    String getJavaByJdbcType(DbAttribute attribute, int type);
+
+    int getJdbcTypeByJava(DbAttribute attribute, String className);
+
+    void add(DbType type, String java);
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/fde7761f/cayenne-server/src/main/java/org/apache/cayenne/configuration/DefaultConfigurationNameMapper.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/configuration/DefaultConfigurationNameMapper.java b/cayenne-server/src/main/java/org/apache/cayenne/configuration/DefaultConfigurationNameMapper.java
index ba7dcd4..08bf584 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/configuration/DefaultConfigurationNameMapper.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/configuration/DefaultConfigurationNameMapper.java
@@ -1,21 +1,21 @@
-/*****************************************************************
- *   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
+/*
+ * 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
+ *      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.
- ****************************************************************/
+ *    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.configuration;
 
 import org.apache.cayenne.map.DataMap;
@@ -31,7 +31,7 @@ public class DefaultConfigurationNameMapper implements ConfigurationNameMapper {
 
     private static final String DATA_MAP_SUFFIX = ".map.xml";
 
-    protected ConfigurationNodeVisitor<String> nameMapper;
+    private final ConfigurationNodeVisitor<String> nameMapper;
 
     public DefaultConfigurationNameMapper() {
         nameMapper = new NameMapper();
@@ -41,23 +41,18 @@ public class DefaultConfigurationNameMapper implements ConfigurationNameMapper {
         return node.acceptVisitor(nameMapper);
     }
 
-    public String configurationLocation(
-            Class<? extends ConfigurationNode> type,
-            String name) {
+    public String configurationLocation(Class<? extends ConfigurationNode> type, String name) {
         if (DataChannelDescriptor.class.isAssignableFrom(type)) {
             return getDataChannelName(name);
         }
-        else if (DataMap.class.isAssignableFrom(type)) {
+        if (DataMap.class.isAssignableFrom(type)) {
             return getDataMapName(name);
         }
 
-        throw new IllegalArgumentException("Unrecognized configuration type: "
-                + type.getName());
+        throw new IllegalArgumentException("Unrecognized configuration type: " + type.getName());
     }
 
-    public String configurationNodeName(
-            Class<? extends ConfigurationNode> type,
-            Resource resource) {
+    public String configurationNodeName(Class<? extends ConfigurationNode> type, Resource resource) {
 
         String path = resource.getURL().getPath();
         if (path == null || path.length() == 0) {
@@ -80,37 +75,36 @@ public class DefaultConfigurationNameMapper implements ConfigurationNameMapper {
                 return null;
             }
 
-            return path.substring(CAYENNE_PREFIX.length(), path.length()
-                    - CAYENNE_SUFFIX.length());
+            return path.substring(CAYENNE_PREFIX.length(), path.length() - CAYENNE_SUFFIX.length());
         }
-        else if (DataMap.class.isAssignableFrom(type)) {
+
+        if (DataMap.class.isAssignableFrom(type)) {
             if (!path.endsWith(DATA_MAP_SUFFIX)) {
                 return null;
             }
             return path.substring(0, path.length() - DATA_MAP_SUFFIX.length());
         }
 
-        throw new IllegalArgumentException("Unrecognized configuration type: "
-                + type.getName());
+        throw new IllegalArgumentException("Unrecognized configuration type: " + type.getName());
     }
 
-    protected String getDataChannelName(String name) {
+    protected static String getDataChannelName(String name) {
         if (name == null) {
-            throw new NullPointerException("Null DataChannelDescriptor name");
+            throw new IllegalArgumentException("Null DataChannelDescriptor name");
         }
 
         return CAYENNE_PREFIX + name + CAYENNE_SUFFIX;
     }
 
-    protected String getDataMapName(String name) {
+    protected static String getDataMapName(String name) {
         if (name == null) {
-            throw new NullPointerException("Null DataMap name");
+            throw new IllegalArgumentException("Null DataMap name");
         }
 
         return name + DATA_MAP_SUFFIX;
     }
 
-    final class NameMapper extends BaseConfigurationNodeVisitor<String> {
+    private final class NameMapper extends BaseConfigurationNodeVisitor<String> {
 
         @Override
         public String visitDataChannelDescriptor(DataChannelDescriptor descriptor) {

http://git-wip-us.apache.org/repos/asf/cayenne/blob/fde7761f/cayenne-server/src/main/java/org/apache/cayenne/dba/JdbcAdapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/JdbcAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/JdbcAdapter.java
index 5d6a372..4764b69 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/JdbcAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/JdbcAdapter.java
@@ -1,21 +1,21 @@
-/*****************************************************************
- *   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
+/*
+ * 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
+ *      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.
- ****************************************************************/
+ *    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.dba;
 
@@ -335,10 +335,11 @@ public class JdbcAdapter implements DbAdapter {
             boolean firstPk = true;
 
             while (pkit.hasNext()) {
-                if (firstPk)
+                if (firstPk) {
                     firstPk = false;
-                else
+                } else {
                     sqlBuffer.append(", ");
+                }
 
                 DbAttribute at = pkit.next();
 
@@ -355,43 +356,41 @@ public class JdbcAdapter implements DbAdapter {
      */
     @Override
     public void createTableAppendColumn(StringBuffer sqlBuffer, DbAttribute column) {
-
-        String[] types = externalTypesForJdbcType(column.getType());
-        if (types == null || types.length == 0) {
-            String entityName = column.getEntity() != null ? ((DbEntity) column.getEntity()).getFullyQualifiedName()
-                    : "<null>";
-            throw new CayenneRuntimeException("Undefined type for attribute '" + entityName + "." + column.getName()
-                    + "': " + column.getType());
-        }
-
-        String type = types[0];
         sqlBuffer.append(quotingStrategy.quotedName(column));
-        sqlBuffer.append(' ').append(type);
-
-        // append size and precision (if applicable)s
-        if (typeSupportsLength(column.getType())) {
-            int len = column.getMaxLength();
+        sqlBuffer.append(' ').append(getType(this, column));
 
-            int scale = (TypesMapping.isDecimal(column.getType()) && column.getType() != Types.FLOAT) ? column
-                    .getScale() : -1;
+        sqlBuffer.append(sizeAndPrecision(this, column));
+        sqlBuffer.append(column.isMandatory() ? " NOT NULL" : " NULL");
+    }
 
-            // sanity check
-            if (scale > len) {
-                scale = -1;
-            }
+    public static String sizeAndPrecision(DbAdapter adapter, DbAttribute column) {
+        if (!adapter.typeSupportsLength(column.getType())) {
+            return "";
+        }
 
-            if (len > 0) {
-                sqlBuffer.append('(').append(len);
+        int len = column.getMaxLength();
+        int scale = TypesMapping.isDecimal(column.getType()) && column.getType() != Types.FLOAT ? column.getScale() : -1;
 
-                if (scale >= 0) {
-                    sqlBuffer.append(", ").append(scale);
-                }
+        // sanity check
+        if (scale > len) {
+            scale = -1;
+        }
 
-                sqlBuffer.append(')');
-            }
+        if (len > 0) {
+            return "(" + len + (scale >= 0 ? ", " + scale : "") + ")";
         }
 
-        sqlBuffer.append(column.isMandatory() ? " NOT NULL" : " NULL");
+        return "";
+    }
+
+    public static String getType(DbAdapter adapter, DbAttribute column) {
+        String[] types = adapter.externalTypesForJdbcType(column.getType());
+        if (types == null || types.length == 0) {
+            String entityName = column.getEntity() != null ? column.getEntity().getFullyQualifiedName() : "<null>";
+            throw new CayenneRuntimeException("Undefined type for attribute '"
+                    + entityName + "." + column.getName() + "': " + column.getType());
+        }
+        return types[0];
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/cayenne/blob/fde7761f/cayenne-server/src/main/java/org/apache/cayenne/map/DataMap.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/map/DataMap.java b/cayenne-server/src/main/java/org/apache/cayenne/map/DataMap.java
index 5f1f10c..6441070 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/map/DataMap.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/map/DataMap.java
@@ -1356,4 +1356,39 @@ public class DataMap implements Serializable, ConfigurationNode, XMLSerializable
 		clearQueries();
 		clearResultSets();
 	}
+
+    /**
+     *
+     * @return package + "." + name when it is possible otherwise just name
+     *
+     * @since 3.2
+     */
+    public String getNameWithDefaultPackage(String name) {
+        return getNameWithPackage(defaultPackage, name);
+    }
+
+    /**
+     *
+     * @return package + "." + name when it is possible otherwise just name
+     *
+     * @since 3.2
+     */
+    public static String getNameWithPackage(String pack, String name) {
+        if (Util.isEmptyString(pack)) {
+            return name;
+        } else {
+            return pack + (pack.endsWith(".") ? ""  : ".") + name;
+        }
+    }
+
+    /**
+     *
+     * @param name
+     * @return package + "." + name when it is possible otherwise just name
+     *
+     * @since 3.2
+     */
+    public String getNameWithDefaultClientPackage(String name) {
+        return getNameWithPackage(defaultClientPackage, name);
+    }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/fde7761f/cayenne-server/src/main/java/org/apache/cayenne/map/DbRelationship.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/map/DbRelationship.java b/cayenne-server/src/main/java/org/apache/cayenne/map/DbRelationship.java
index e84a5a8..69f661a 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/map/DbRelationship.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/map/DbRelationship.java
@@ -196,8 +196,9 @@ public class DbRelationship extends Relationship implements ConfigurationNode {
 
         TestJoin testJoin = new TestJoin(this);
         for (DbRelationship rel : target.getRelationships()) {
-            if (rel.getTargetEntity() != src)
+            if (rel.getTargetEntity() != src) {
                 continue;
+            }
 
             List<DbJoin> otherJoins = rel.getJoins();
             if (otherJoins.size() != joins.size()) {
@@ -273,7 +274,7 @@ public class DbRelationship extends Relationship implements ConfigurationNode {
         }
 
         DbRelationship revRel = getReverseRelationship();
-        return (revRel != null) ? revRel.isToDependentPK() : false;
+        return revRel != null && revRel.isToDependentPK();
     }
     
     /**
@@ -315,7 +316,7 @@ public class DbRelationship extends Relationship implements ConfigurationNode {
             DbAttribute target = join.getTarget();
             DbAttribute source = join.getSource();
 
-            if ((target != null && !target.isPrimaryKey()) || (source != null && !source.isPrimaryKey())) {
+            if (target != null && !target.isPrimaryKey() || source != null && !source.isPrimaryKey()) {
                 return false;
             }
         }
@@ -390,8 +391,7 @@ public class DbRelationship extends Relationship implements ConfigurationNode {
         // handle generic case: numJoins > 1
         else {
             idMap = new HashMap<String, Object>(numJoins * 2);
-            for (int i = 0; i < numJoins; i++) {
-                DbJoin join = joins.get(i);
+            for (DbJoin join : joins) {
                 DbAttribute source = join.getSource();
                 Object val = srcSnapshot.get(join.getSourceName());
 
@@ -437,8 +437,7 @@ public class DbRelationship extends Relationship implements ConfigurationNode {
 
         // general case
         Map<String, Object> idMap = new HashMap<String, Object>(len * 2);
-        for (int i = 0; i < len; i++) {
-            DbJoin join = joins.get(i);
+        for (DbJoin join : joins) {
             Object val = targetSnapshot.get(join.getTargetName());
             idMap.put(join.getSourceName(), val);
         }
@@ -468,8 +467,10 @@ public class DbRelationship extends Relationship implements ConfigurationNode {
      * relationship is "to one".
      */
     public Map<String, Object> srcPkSnapshotWithTargetSnapshot(Map<String, Object> targetSnapshot) {
-        if (!isToMany())
+        if (!isToMany()) {
             throw new CayenneRuntimeException("Only 'to many' relationships support this method.");
+        }
+
         return srcSnapshotWithTargetSnapshot(targetSnapshot);
     }
 
@@ -491,7 +492,7 @@ public class DbRelationship extends Relationship implements ConfigurationNode {
         return false;
     }
 
-    final static class JoinTransformers {
+    static final class JoinTransformers {
 
         static final Transformer targetExtractor = new Transformer() {
 
@@ -509,7 +510,7 @@ public class DbRelationship extends Relationship implements ConfigurationNode {
     }
 
     // a join used for comparison
-    final static class TestJoin extends DbJoin {
+    static final class TestJoin extends DbJoin {
 
         TestJoin(DbRelationship relationship) {
             super(relationship);

http://git-wip-us.apache.org/repos/asf/cayenne/blob/fde7761f/cayenne-server/src/main/java/org/apache/cayenne/map/ObjEntity.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/map/ObjEntity.java b/cayenne-server/src/main/java/org/apache/cayenne/map/ObjEntity.java
index 6fb47de..67317e3 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/map/ObjEntity.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/map/ObjEntity.java
@@ -55,8 +55,8 @@ import org.apache.commons.collections.Transformer;
  */
 public class ObjEntity extends Entity implements ObjEntityListener, ConfigurationNode {
 
-    final public static int LOCK_TYPE_NONE = 0;
-    final public static int LOCK_TYPE_OPTIMISTIC = 1;
+    public static final int LOCK_TYPE_NONE = 0;
+    public static final int LOCK_TYPE_OPTIMISTIC = 1;
 
     // do not import CayenneDataObject as it introduces unneeded client
     // dependency
@@ -320,7 +320,7 @@ public class ObjEntity extends Entity implements ObjEntityListener, Configuratio
         try {
             return Util.getJavaClass(name);
         } catch (ClassNotFoundException e) {
-            throw new CayenneRuntimeException("Failed to load class " + name + ": " + e.getMessage(), e);
+            throw new CayenneRuntimeException("Failed to doLoad class " + name + ": " + e.getMessage(), e);
         }
     }
 
@@ -461,7 +461,7 @@ public class ObjEntity extends Entity implements ObjEntityListener, Configuratio
      * @since 1.2
      */
     public boolean isClientAllowed() {
-        return (getDataMap() == null || isServerOnly()) ? false : getDataMap().isClientSupported();
+        return getDataMap() != null && !isServerOnly() && getDataMap().isClientSupported();
     }
 
     public boolean isAbstract() {
@@ -948,12 +948,13 @@ public class ObjEntity extends Entity implements ObjEntityListener, Configuratio
      * Clears mapping between entities, attributes and relationships.
      */
     public void clearDbMapping() {
-        if (dbEntityName == null)
+        if (dbEntityName == null) {
             return;
+        }
 
         for (ObjAttribute attribute : getAttributeMap().values()) {
             DbAttribute dbAttr = attribute.getDbAttribute();
-            if (null != dbAttr) {
+            if (dbAttr != null) {
                 attribute.setDbAttributePath(null);
             }
         }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/fde7761f/cayenne-server/src/main/java/org/apache/cayenne/map/naming/DefaultUniqueNameGenerator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/map/naming/DefaultUniqueNameGenerator.java b/cayenne-server/src/main/java/org/apache/cayenne/map/naming/DefaultUniqueNameGenerator.java
index c9989d4..95ab877 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/map/naming/DefaultUniqueNameGenerator.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/map/naming/DefaultUniqueNameGenerator.java
@@ -49,12 +49,7 @@ public class DefaultUniqueNameGenerator implements UniqueNameGenerator {
             generator = new DefaultUniqueNameGenerator(NameCheckers.embeddable, pattern) {
                 @Override
                 public String generate(Object namingContext, String nameBase) {
-                    String name = super.generate(namingContext, nameBase);
-                    DataMap map = (DataMap) namingContext;
-                    if (map.getDefaultPackage() != null) {
-                        return map.getDefaultPackage() + "." + name;
-                    }
-                    return name;
+                    return ((DataMap) namingContext).getNameWithDefaultPackage(super.generate(namingContext, nameBase));
                 }
             };
         } else {

http://git-wip-us.apache.org/repos/asf/cayenne/blob/fde7761f/cayenne-server/src/main/java/org/apache/cayenne/map/naming/NameCheckers.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/map/naming/NameCheckers.java b/cayenne-server/src/main/java/org/apache/cayenne/map/naming/NameCheckers.java
index ea8f393..f86f043 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/map/naming/NameCheckers.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/map/naming/NameCheckers.java
@@ -75,11 +75,7 @@ public enum NameCheckers implements NameChecker {
 		@Override
 		public boolean isNameInUse(Object namingContext, String name) {
 			DataMap map = (DataMap) namingContext;
-			if (map.getDefaultPackage() != null) {
-				return map
-						.getEmbeddable((map.getDefaultPackage() + "." + name)) != null;
-			}
-			return map.getEmbeddable(name) != null;
+            return map.getEmbeddable(map.getNameWithDefaultPackage(name)) != null;
 		}
 	},