Merge branch 'master' into feature/fasterJoin

Conflicts:
	CHANGES.md
diff --git a/CHANGES.md b/CHANGES.md
index 2a5aa51..f5cb59a 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -8,6 +8,7 @@
  * [METAMODEL-1140] - Allowed SalesforceDataContext without a security token.
  * [METAMODEL-1141] - Added RFC 4180 compliant CSV parsing.
  * [METAMODEL-1144] - Optimized evaluation of conditional client-side JOIN statements.
+ * [METAMODEL-1145] - Fixed bug with modelling JDBC table relationships when there are multiple keys involved in the relationship.
 
 ### Apache MetaModel 4.6.0
 
diff --git a/README.md b/README.md
index e5cf17a..3156d74 100644
--- a/README.md
+++ b/README.md
@@ -1,40 +1,40 @@
-## Apache MetaModel

-

-MetaModel is a data access framework, providing a common interface for exploration and querying of different types of datastores.

-

-<div>

-<img src="http://metamodel.apache.org/img/logo.png" style="float: right; margin-left: 20px;" alt="MetaModel logo" />

-</div>

-

-### Mailing lists

-

- * Developer list:  dev@metamodel.apache.org

- * User list:  user@metamodel.apache.org

- * Commits list:    commits@metamodel.apache.org

-

-### Website

-

-http://metamodel.apache.org/

-

-### Documentation

-

-Please check out our [wiki for user documentation](https://cwiki.apache.org/confluence/display/METAMODEL).

-

-### Building the code

-

-MetaModel uses maven as it's build tool. Code can be built with:

-

-```

-mvn clean install

-```

-

-### Running the integration tests

-

- 1. Copy the file 'example-metamodel-integrationtest-configuration.properties' to your user home.

- 2. Remove the 'example-' prefix from its filename

- 3. Modify the file to enable properties of the integration tests that you're interested in.

- 4. Re-run "mvn clean install".

-

-### Contributing

-

+## Apache MetaModel
+
+MetaModel is a data access framework, providing a common interface for exploration and querying of different types of datastores.
+
+<div>
+<img src="http://metamodel.apache.org/img/logo.png" style="float: right; margin-left: 20px;" alt="MetaModel logo" />
+</div>
+
+### Mailing lists
+
+ * Developer list:  dev@metamodel.apache.org
+ * User list:  user@metamodel.apache.org
+ * Commits list:    commits@metamodel.apache.org
+
+### Website
+
+http://metamodel.apache.org/
+
+### Documentation
+
+Please check out our [wiki for user documentation](https://cwiki.apache.org/confluence/display/METAMODEL).
+
+### Building the code
+
+MetaModel uses maven as it's build tool. Code can be built with:
+
+```
+mvn clean install
+```
+
+### Running the integration tests
+
+ 1. Copy the file 'example-metamodel-integrationtest-configuration.properties' to your user home.
+ 2. Remove the 'example-' prefix from its filename
+ 3. Modify the file to enable properties of the integration tests that you're interested in.
+ 4. Re-run "mvn clean install".
+
+### Contributing
+
 Please see [CONTRIBUTE.md](CONTRIBUTE.md)
\ No newline at end of file
diff --git a/core/src/main/java/org/apache/metamodel/factory/DataContextPropertiesImpl.java b/core/src/main/java/org/apache/metamodel/factory/DataContextPropertiesImpl.java
index e66a811..13ce9c1 100644
--- a/core/src/main/java/org/apache/metamodel/factory/DataContextPropertiesImpl.java
+++ b/core/src/main/java/org/apache/metamodel/factory/DataContextPropertiesImpl.java
@@ -288,6 +288,9 @@
     @Override
     public SimpleTableDef[] getTableDefs() {
         final Object obj = get(PROPERTY_TABLE_DEFS);
+        if (obj == null) {
+            return null;
+        }
         if (obj instanceof SimpleTableDef[]) {
             return (SimpleTableDef[]) obj;
         }
diff --git a/jdbc/pom.xml b/jdbc/pom.xml
index 91aefc5..858e0ab 100644
--- a/jdbc/pom.xml
+++ b/jdbc/pom.xml
@@ -29,6 +29,10 @@
 			<groupId>commons-pool</groupId>
 			<artifactId>commons-pool</artifactId>
 		</dependency>
+		<dependency>
+			<groupId>com.google.guava</groupId>
+			<artifactId>guava</artifactId>
+		</dependency>
 
 		<!-- Test dependencies -->
 		<dependency>
diff --git a/jdbc/src/main/java/org/apache/metamodel/jdbc/JdbcMetadataLoader.java b/jdbc/src/main/java/org/apache/metamodel/jdbc/JdbcMetadataLoader.java
index 837fb18..2c29405 100644
--- a/jdbc/src/main/java/org/apache/metamodel/jdbc/JdbcMetadataLoader.java
+++ b/jdbc/src/main/java/org/apache/metamodel/jdbc/JdbcMetadataLoader.java
@@ -22,7 +22,12 @@
 import java.sql.DatabaseMetaData;
 import java.sql.ResultSet;
 import java.sql.SQLException;
+import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.StringTokenizer;
 import java.util.concurrent.ConcurrentHashMap;
@@ -58,7 +63,8 @@
     private final Set<Integer> _loadedIndexes;
     private final Set<Integer> _loadedPrimaryKeys;
 
-    public JdbcMetadataLoader(JdbcDataContext dataContext, boolean usesCatalogsAsSchemas, String identifierQuoteString) {
+    public JdbcMetadataLoader(JdbcDataContext dataContext, boolean usesCatalogsAsSchemas,
+            String identifierQuoteString) {
         _dataContext = dataContext;
         _usesCatalogsAsSchemas = usesCatalogsAsSchemas;
         _identifierQuoteString = identifierQuoteString;
@@ -92,7 +98,7 @@
     }
 
     private String getJdbcSchemaName(Schema schema) {
-        if(_usesCatalogsAsSchemas) {
+        if (_usesCatalogsAsSchemas) {
             return null;
         } else {
             return schema.getName();
@@ -100,7 +106,7 @@
     }
 
     private String getCatalogName(Schema schema) {
-        if(_usesCatalogsAsSchemas) {
+        if (_usesCatalogsAsSchemas) {
             return schema.getName();
         } else {
             return _dataContext.getCatalogName();
@@ -109,8 +115,8 @@
 
     private void loadTables(JdbcSchema schema, DatabaseMetaData metaData, String[] types) {
         try (ResultSet rs = metaData.getTables(getCatalogName(schema), getJdbcSchemaName(schema), null, types)) {
-            logger.debug("Querying for table types {}, in catalog: {}, schema: {}", types,
-                    _dataContext.getCatalogName(), schema.getName());
+            logger.debug("Querying for table types {}, in catalog: {}, schema: {}", types, _dataContext
+                    .getCatalogName(), schema.getName());
 
             schema.clearTables();
             int tableNumber = -1;
@@ -138,15 +144,15 @@
             if (tablesReturned == 0) {
                 logger.info("No table metadata records returned for schema '{}'", schema.getName());
             } else {
-                logger.debug("Returned {} table metadata records for schema '{}'", new Object[] { tablesReturned,
-                        schema.getName() });
+                logger.debug("Returned {} table metadata records for schema '{}'", new Object[] { tablesReturned, schema
+                        .getName() });
             }
 
         } catch (SQLException e) {
             throw JdbcUtils.wrapException(e, "retrieve table metadata for " + schema.getName());
         }
     }
-    
+
     @Override
     public void loadIndexes(JdbcTable jdbcTable) {
         final int identity = System.identityHashCode(jdbcTable);
@@ -182,7 +188,7 @@
             }
         }
     }
-    
+
     @Override
     public void loadPrimaryKeys(JdbcTable jdbcTable) {
         final int identity = System.identityHashCode(jdbcTable);
@@ -220,7 +226,8 @@
 
     private void loadPrimaryKeys(JdbcTable table, DatabaseMetaData metaData) throws MetaModelException {
         Schema schema = table.getSchema();
-        try (ResultSet rs = metaData.getPrimaryKeys(getCatalogName(schema), getJdbcSchemaName(schema), table.getName());){
+        try (ResultSet rs = metaData.getPrimaryKeys(getCatalogName(schema), getJdbcSchemaName(schema), table
+                .getName());) {
             while (rs.next()) {
                 String columnName = rs.getString(4);
                 if (columnName != null) {
@@ -242,7 +249,8 @@
 
         // Ticket #170: IndexInfo is nice-to-have, not need-to-have, so
         // we will do a nice failover on SQLExceptions
-        try (ResultSet rs = metaData.getIndexInfo(getCatalogName(schema), getJdbcSchemaName(schema), table.getName(), false, true)) {
+        try (ResultSet rs = metaData.getIndexInfo(getCatalogName(schema), getJdbcSchemaName(schema), table.getName(),
+                false, true)) {
             while (rs.next()) {
                 String columnName = rs.getString(9);
                 if (columnName != null) {
@@ -258,7 +266,7 @@
             throw JdbcUtils.wrapException(e, "retrieve index information for " + table.getName());
         }
     }
-    
+
     @Override
     public void loadColumns(JdbcTable jdbcTable) {
         final int identity = System.identityHashCode(jdbcTable);
@@ -309,7 +317,8 @@
         final boolean convertLobs = isLobConversionEnabled();
         final Schema schema = table.getSchema();
 
-        try (ResultSet rs = metaData.getColumns(getCatalogName(schema), getJdbcSchemaName(schema), table.getName(), null)) {
+        try (ResultSet rs = metaData.getColumns(getCatalogName(schema), getJdbcSchemaName(schema), table.getName(),
+                null)) {
             if (logger.isDebugEnabled()) {
                 logger.debug("Querying for columns in table: " + table.getName());
             }
@@ -327,8 +336,8 @@
                 final Integer columnSize = rs.getInt(7);
 
                 if (logger.isDebugEnabled()) {
-                    logger.debug("Found column: table=" + table.getName() + ",columnName=" + columnName
-                            + ",nativeType=" + nativeType + ",columnSize=" + columnSize);
+                    logger.debug("Found column: table=" + table.getName() + ",columnName=" + columnName + ",nativeType="
+                            + nativeType + ",columnSize=" + columnSize);
                 }
 
                 ColumnType columnType = _dataContext.getQueryRewriter().getColumnType(jdbcType, nativeType, columnSize);
@@ -362,17 +371,17 @@
 
             final int columnsReturned = columnNumber + 1;
             if (columnsReturned == 0) {
-                logger.info("No column metadata records returned for table '{}' in schema '{}'", table.getName(),
-                        schema.getName());
+                logger.info("No column metadata records returned for table '{}' in schema '{}'", table.getName(), schema
+                        .getName());
             } else {
-                logger.debug("Returned {} column metadata records for table '{}' in schema '{}'", columnsReturned,
-                        table.getName(), schema.getName());
+                logger.debug("Returned {} column metadata records for table '{}' in schema '{}'", columnsReturned, table
+                        .getName(), schema.getName());
             }
         } catch (SQLException e) {
             throw JdbcUtils.wrapException(e, "retrieve table metadata for " + table.getName());
         }
     }
-    
+
     @Override
     public void loadRelations(JdbcSchema jdbcSchema) {
         final int identity = System.identityHashCode(jdbcSchema);
@@ -412,7 +421,8 @@
 
     private void loadRelations(Table table, DatabaseMetaData metaData) {
         Schema schema = table.getSchema();
-        try (ResultSet rs = metaData.getImportedKeys(getCatalogName(schema), getJdbcSchemaName(schema), table.getName())) {
+        try (ResultSet rs = metaData.getImportedKeys(getCatalogName(schema), getJdbcSchemaName(schema), table
+                .getName())) {
             loadRelations(rs, schema);
         } catch (SQLException e) {
             throw JdbcUtils.wrapException(e, "retrieve imported keys for " + table.getName());
@@ -420,7 +430,12 @@
     }
 
     private void loadRelations(ResultSet rs, Schema schema) throws SQLException {
+        // by using nested maps, we can associate a list of pk/fk columns with
+        // the tables they belong to
+        // the result set comes flattened out.
+        Map<Table, Map<Table, ColumnsTuple>> relations = new HashMap<>();
         while (rs.next()) {
+
             String pkTableName = rs.getString(3);
             String pkColumnName = rs.getString(4);
 
@@ -453,9 +468,42 @@
                 logger.error("pkColumn={}", pkColumn);
                 logger.error("fkColumn={}", fkColumn);
             } else {
-                MutableRelationship.createRelationship(new Column[] { pkColumn }, new Column[] { fkColumn });
+
+                if (!relations.containsKey(pkTable)) {
+                    relations.put(pkTable, new HashMap<>());
+                }
+
+                // get or init the columns tuple
+                ColumnsTuple ct = relations.get(pkTable).get(fkTable);
+                if (Objects.isNull(ct)) {
+                    ct = new ColumnsTuple();
+                    relations.get(pkTable).put(fkTable, ct);
+                }
+                // we can now safely add the columns
+                ct.getPkCols().add(pkColumn);
+                ct.getFkCols().add(fkColumn);
             }
         }
+
+        relations.values().stream().flatMap(map -> map.values().stream()).forEach(ct -> MutableRelationship
+                .createRelationship(ct.getPkCols().toArray(new Column[0]), ct.getFkCols().toArray(new Column[0])));
+    }
+
+    /**
+     * Represents the columns of a relationship while it is being built from a
+     * {@link ResultSet}.
+     */
+    private static class ColumnsTuple {
+        private final List<Column> pkCols = new ArrayList<>();
+        private final List<Column> fkCols = new ArrayList<>();
+
+        public List<Column> getFkCols() {
+            return fkCols;
+        }
+
+        public List<Column> getPkCols() {
+            return pkCols;
+        }
     }
 
 }
diff --git a/jdbc/src/main/java/org/apache/metamodel/jdbc/dialects/DefaultQueryRewriter.java b/jdbc/src/main/java/org/apache/metamodel/jdbc/dialects/DefaultQueryRewriter.java
index 90a2411..a9881fa 100644
--- a/jdbc/src/main/java/org/apache/metamodel/jdbc/dialects/DefaultQueryRewriter.java
+++ b/jdbc/src/main/java/org/apache/metamodel/jdbc/dialects/DefaultQueryRewriter.java
@@ -45,11 +45,10 @@
 import org.slf4j.LoggerFactory;
 
 /**
- * Generic query rewriter that adds syntax enhancements that are only possible
- * to resolve just before execution time.
+ * Generic query rewriter that adds syntax enhancements that are only possible to resolve just before execution time.
  */
 public class DefaultQueryRewriter extends AbstractQueryRewriter {
-    
+
     private static final Logger logger = LoggerFactory.getLogger(DefaultQueryRewriter.class);
 
     private static final String SPECIAL_ALIAS_CHARACTERS = "- ,.|*%()!#¤/\\=?;:~";
@@ -101,7 +100,7 @@
         return super.rewriteColumnType(columnType, columnSize);
     }
 
-    private boolean needsQuoting(String alias, String identifierQuoteString) {
+    protected boolean needsQuoting(String alias, String identifierQuoteString) {
         boolean result = false;
         if (alias != null && identifierQuoteString != null) {
             if (alias.indexOf(identifierQuoteString) == -1) {
@@ -139,11 +138,10 @@
                 // operand is a set of values (typically in combination with an
                 // IN or NOT IN operator). Each individual element must be escaped.
 
-                assert OperatorType.IN.equals(item.getOperator()) ||
-                        OperatorType.NOT_IN.equals(item.getOperator());
+                assert OperatorType.IN.equals(item.getOperator()) || OperatorType.NOT_IN.equals(item.getOperator());
 
-                @SuppressWarnings("unchecked")
-                final List<Object> elements = (List<Object>) CollectionUtils.toList(operand);
+                @SuppressWarnings("unchecked") final List<Object> elements =
+                        (List<Object>) CollectionUtils.toList(operand);
 
                 for (ListIterator<Object> it = elements.listIterator(); it.hasNext();) {
                     Object next = it.next();
@@ -169,8 +167,7 @@
     }
 
     /**
-     * Rewrites a (non-compound) {@link FilterItem} when it's operand has
-     * already been rewritten into a SQL literal.
+     * Rewrites a (non-compound) {@link FilterItem} when it's operand has already been rewritten into a SQL literal.
      * 
      * @param item
      * @param operandLiteral
@@ -187,11 +184,10 @@
     }
 
     /**
-     * Rewrites a {@link Timestamp} into it's literal representation as known by
-     * this SQL dialect.
+     * Rewrites a {@link Timestamp} into it's literal representation as known by this SQL dialect.
      * 
-     * This default implementation returns the JDBC spec's escape syntax for a
-     * timestamp: {ts 'yyyy-mm-dd hh:mm:ss.f . . .'}
+     * This default implementation returns the JDBC spec's escape syntax for a timestamp: {ts 'yyyy-mm-dd hh:mm:ss.f . .
+     * .'}
      * 
      * @param ts
      * @return
diff --git a/jdbc/src/main/java/org/apache/metamodel/jdbc/dialects/HsqldbQueryRewriter.java b/jdbc/src/main/java/org/apache/metamodel/jdbc/dialects/HsqldbQueryRewriter.java
index 5d64b31..e98ec48 100644
--- a/jdbc/src/main/java/org/apache/metamodel/jdbc/dialects/HsqldbQueryRewriter.java
+++ b/jdbc/src/main/java/org/apache/metamodel/jdbc/dialects/HsqldbQueryRewriter.java
@@ -26,6 +26,8 @@
 import org.apache.metamodel.schema.Column;
 import org.apache.metamodel.schema.ColumnType;
 
+import com.google.common.base.CharMatcher;
+
 /**
  * Query rewriter for HSQLDB
  */
@@ -99,4 +101,15 @@
         return super.rewriteFilterItem(item);
     }
 
+    /**
+     * HSQL converts all non-escaped characters to uppercases, this is prevented by always escaping
+     */
+    @Override
+    public boolean needsQuoting(String alias, String identifierQuoteString) {
+
+        boolean containsLowerCase = CharMatcher.JAVA_LOWER_CASE.matchesAnyOf(identifierQuoteString);
+
+        return containsLowerCase || super.needsQuoting(alias, identifierQuoteString);
+    }
+
 }
diff --git a/jdbc/src/test/java/org/apache/metamodel/jdbc/H2databaseTest.java b/jdbc/src/test/java/org/apache/metamodel/jdbc/H2databaseTest.java
index 6e21ae5..d46bc31 100644
--- a/jdbc/src/test/java/org/apache/metamodel/jdbc/H2databaseTest.java
+++ b/jdbc/src/test/java/org/apache/metamodel/jdbc/H2databaseTest.java
@@ -22,6 +22,7 @@
 import java.sql.DriverManager;
 import java.sql.PreparedStatement;
 import java.sql.SQLException;
+import java.sql.Statement;
 import java.util.Arrays;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
@@ -41,6 +42,7 @@
 import org.apache.metamodel.query.SelectItem;
 import org.apache.metamodel.schema.Column;
 import org.apache.metamodel.schema.ColumnType;
+import org.apache.metamodel.schema.Relationship;
 import org.apache.metamodel.schema.Schema;
 import org.apache.metamodel.schema.Table;
 import org.apache.metamodel.update.Update;
@@ -52,12 +54,12 @@
  * Test case that tests interaction with the H2 embedded database
  */
 public class H2databaseTest extends TestCase {
-    
+
     public static final String DRIVER_CLASS = "org.h2.Driver";
     public static final String URL_MEMORY_DATABASE = "jdbc:h2:mem:";
 
-    private final String[] FIRST_NAMES = { "Suzy", "Barbara", "John", "Ken", "Billy", "Larry", "Joe", "Margareth", "Bobby",
-            "Elizabeth" };
+    private final String[] FIRST_NAMES = { "Suzy", "Barbara", "John", "Ken", "Billy", "Larry", "Joe", "Margareth",
+            "Bobby", "Elizabeth" };
     private final String[] LAST_NAMES = { "Doe", "Gates", "Jobs", "Ellison", "Trump" };
 
     private Connection conn;
@@ -74,7 +76,7 @@
         super.tearDown();
         conn.close();
     }
-    
+
     public void testCreateInsertAndUpdate() throws Exception {
         JdbcDataContext dc = new JdbcDataContext(conn);
         JdbcTestTemplates.simpleCreateInsertUpdateAndDrop(dc, "metamodel_test_simple");
@@ -84,14 +86,14 @@
         JdbcDataContext dc = new JdbcDataContext(conn);
         JdbcTestTemplates.compositeKeyCreation(dc, "metamodel_test_composite_keys");
     }
-    
+
     public void testTimestampValueInsertSelect() throws Exception {
         JdbcTestTemplates.timestampValueInsertSelect(conn, TimeUnit.NANOSECONDS);
     }
 
     public void testUsingSingleUpdates() throws Exception {
         final JdbcDataContext dc = new JdbcDataContext(conn);
-        
+
         final Schema schema = dc.getDefaultSchema();
         dc.executeUpdate(new CreateTable(schema, "test_table").withColumn("id").ofType(ColumnType.VARCHAR));
 
@@ -108,7 +110,6 @@
         ds.close();
 
         dc.executeUpdate(new DeleteFrom(table).where("id").eq("bar"));
-        
 
         ds = dc.query().from(table).selectCount().execute();
         assertTrue(ds.next());
@@ -117,7 +118,7 @@
         ds.close();
 
         dc.executeUpdate(new Update(table).where("id").eq("foo").value("id", "baz"));
-        
+
         ds = dc.query().from(table).selectAll().execute();
         assertTrue(ds.next());
         assertEquals("Row[values=[baz]]", ds.getRow().toString());
@@ -166,7 +167,8 @@
 
         Query q = dc.query().from(table).selectCount().and(FunctionType.MAX, ageColumn).and(FunctionType.MIN, ageColumn)
                 .toQuery();
-        assertEquals("SELECT COUNT(*), MAX(\"TEST_TABLE\".\"AGE\"), MIN(\"TEST_TABLE\".\"AGE\") FROM PUBLIC.\"TEST_TABLE\"",
+        assertEquals(
+                "SELECT COUNT(*), MAX(\"TEST_TABLE\".\"AGE\"), MIN(\"TEST_TABLE\".\"AGE\") FROM PUBLIC.\"TEST_TABLE\"",
                 q.toSql());
 
         assertEquals(1, dc.getFetchSizeCalculator().getFetchSize(q));
@@ -182,9 +184,10 @@
         int minAge = ((Number) row.getValue(2)).intValue();
         assertTrue("Minimum age was: " + minAge, minAge < 10 && minAge >= 0);
 
-        q = dc.query().from(table).as("t").select(ageColumn).selectCount().where(ageColumn).greaterThan(50).groupBy(ageColumn)
-                .toQuery();
-        assertEquals("SELECT t.\"AGE\", COUNT(*) FROM PUBLIC.\"TEST_TABLE\" t WHERE t.\"AGE\" > 50 GROUP BY t.\"AGE\"", q.toSql());
+        q = dc.query().from(table).as("t").select(ageColumn).selectCount().where(ageColumn).greaterThan(50).groupBy(
+                ageColumn).toQuery();
+        assertEquals("SELECT t.\"AGE\", COUNT(*) FROM PUBLIC.\"TEST_TABLE\" t WHERE t.\"AGE\" > 50 GROUP BY t.\"AGE\"",
+                q.toSql());
 
         ds = dc.executeQuery(q);
         List<Object[]> objectArrays = ds.toObjectArrays();
@@ -232,8 +235,10 @@
 
             @Override
             public void run(UpdateCallback cb) {
-                JdbcCreateTableBuilder createTableBuilder = (JdbcCreateTableBuilder) cb.createTable(schema, "test_table");
-                Table writtenTable = createTableBuilder.withColumn("id").asPrimaryKey().ofType(ColumnType.INTEGER).execute();
+                JdbcCreateTableBuilder createTableBuilder = (JdbcCreateTableBuilder) cb.createTable(schema,
+                        "test_table");
+                Table writtenTable = createTableBuilder.withColumn("id").asPrimaryKey().ofType(ColumnType.INTEGER)
+                        .execute();
 
                 for (int i = 0; i < 10; i++) {
                     cb.insertInto(writtenTable).value("id", i + 1).execute();
@@ -276,12 +281,15 @@
 
             @Override
             public void run(UpdateCallback cb) {
-                JdbcCreateTableBuilder createTableBuilder = (JdbcCreateTableBuilder) cb.createTable(schema, "test_table");
+                JdbcCreateTableBuilder createTableBuilder = (JdbcCreateTableBuilder) cb.createTable(schema,
+                        "test_table");
                 Table writtenTable = createTableBuilder.withColumn("id").asPrimaryKey().ofType(ColumnType.INTEGER)
-                        .withColumn("name").ofSize(255).ofType(ColumnType.VARCHAR).withColumn("age").ofType(ColumnType.INTEGER)
-                        .execute();
+                        .withColumn("name").ofSize(255).ofType(ColumnType.VARCHAR).withColumn("age").ofType(
+                                ColumnType.INTEGER).execute();
                 String sql = createTableBuilder.createSqlStatement();
-                assertEquals("CREATE TABLE PUBLIC.test_table (id INTEGER, name VARCHAR(255), age INTEGER, PRIMARY KEY(id))", sql);
+                assertEquals(
+                        "CREATE TABLE PUBLIC.test_table (id INTEGER, name VARCHAR(255), age INTEGER, PRIMARY KEY(id))",
+                        sql);
                 assertNotNull(writtenTable);
                 assertEquals("[ID, NAME, AGE]", Arrays.toString(writtenTable.getColumnNames()));
 
@@ -310,11 +318,13 @@
             @Override
             public void run(UpdateCallback cb) {
                 cb.insertInto(writtenTableRef.get()).value("age", 14).value("name", "hello").value("id", 1).execute();
-                JdbcInsertBuilder insertBuilder = (JdbcInsertBuilder) cb.insertInto(writtenTableRef.get()).value("age", 15)
-                        .value("name", "wor'ld").value("id", 2);
-                assertEquals("INSERT INTO PUBLIC.\"TEST_TABLE\" (ID,NAME,AGE) VALUES (?,?,?)", insertBuilder.createSqlStatement());
+                JdbcInsertBuilder insertBuilder = (JdbcInsertBuilder) cb.insertInto(writtenTableRef.get()).value("age",
+                        15).value("name", "wor'ld").value("id", 2);
+                assertEquals("INSERT INTO PUBLIC.\"TEST_TABLE\" (ID,NAME,AGE) VALUES (?,?,?)", insertBuilder
+                        .createSqlStatement());
                 insertBuilder.execute();
-                cb.insertInto(writtenTableRef.get()).value("age", 16).value("name", "escobar!").value("id", 3).execute();
+                cb.insertInto(writtenTableRef.get()).value("age", 16).value("name", "escobar!").value("id", 3)
+                        .execute();
             }
         });
 
@@ -331,10 +341,10 @@
         dc.executeUpdate(new UpdateScript() {
             @Override
             public void run(UpdateCallback callback) {
-                JdbcUpdateBuilder updateCallback = (JdbcUpdateBuilder) callback.update("test_table").value("age", 18).where("id")
-                        .greaterThan(1);
-                assertEquals("UPDATE PUBLIC.\"TEST_TABLE\" SET AGE=? WHERE \"TEST_TABLE\".\"ID\" > ?",
-                        updateCallback.createSqlStatement());
+                JdbcUpdateBuilder updateCallback = (JdbcUpdateBuilder) callback.update("test_table").value("age", 18)
+                        .where("id").greaterThan(1);
+                assertEquals("UPDATE PUBLIC.\"TEST_TABLE\" SET AGE=? WHERE \"TEST_TABLE\".\"ID\" > ?", updateCallback
+                        .createSqlStatement());
                 updateCallback.execute();
             }
         });
@@ -388,8 +398,8 @@
         dc.executeUpdate(new UpdateScript() {
             @Override
             public void run(UpdateCallback callback) {
-                Table table = callback.createTable(dc.getDefaultSchema(), "test_table").withColumn("foo")
-                        .ofType(ColumnType.INTEGER).withColumn("bar").ofType(ColumnType.VARCHAR).execute();
+                Table table = callback.createTable(dc.getDefaultSchema(), "test_table").withColumn("foo").ofType(
+                        ColumnType.INTEGER).withColumn("bar").ofType(ColumnType.VARCHAR).execute();
                 callback.insertInto(table).value("foo", 1).value("bar", "hello").execute();
                 callback.insertInto(table).value("foo", 2).value("bar", "there").execute();
                 callback.insertInto(table).value("foo", 3).value("bar", "world").execute();
@@ -400,8 +410,8 @@
         Query query = new Query().from(table, "a").from(table, "b");
         query.select(table.getColumnByName("foo"), query.getFromClause().getItem(0));
         query.select(table.getColumnByName("foo"), query.getFromClause().getItem(1));
-        query.where(new SelectItem(table.getColumnByName("bar"), query.getFromClause().getItem(0)), OperatorType.EQUALS_TO,
-                "hello");
+        query.where(new SelectItem(table.getColumnByName("bar"), query.getFromClause().getItem(0)),
+                OperatorType.EQUALS_TO, "hello");
 
         assertEquals(
                 "SELECT a.\"FOO\", b.\"FOO\" FROM PUBLIC.\"TEST_TABLE\" a, PUBLIC.\"TEST_TABLE\" b WHERE a.\"BAR\" = 'hello'",
@@ -440,8 +450,8 @@
         dc.executeUpdate(new UpdateScript() {
             @Override
             public void run(UpdateCallback callback) {
-                Table table = callback.createTable(dc.getDefaultSchema(), "test_table").withColumn("foo")
-                        .ofType(ColumnType.INTEGER).withColumn("bar").ofType(ColumnType.VARCHAR).execute();
+                Table table = callback.createTable(dc.getDefaultSchema(), "test_table").withColumn("foo").ofType(
+                        ColumnType.INTEGER).withColumn("bar").ofType(ColumnType.VARCHAR).execute();
                 callback.insertInto(table).value("foo", 1).value("bar", "hello").execute();
                 callback.insertInto(table).value("foo", 2).value("bar", "there").execute();
                 callback.insertInto(table).value("foo", 3).value("bar", "world").execute();
@@ -517,8 +527,36 @@
     public void testCharOfSizeOne() throws Exception {
         JdbcTestTemplates.meaningOfOneSizeChar(conn);
     }
-    
+
     public void testInterpretationOfNull() throws Exception {
         JdbcTestTemplates.interpretationOfNulls(conn);
     }
+
+    public void testCompositeFkRelation() throws Exception {
+
+        try (Statement stmt = conn.createStatement()) {
+            stmt.execute(
+                    "CREATE TABLE PARENT (P1 INTEGER, P2 INTEGER, P3 INTEGER, P4 INTEGER, PRIMARY  KEY (P1,P2, P3, P4))");
+            stmt.execute(
+                    "CREATE TABLE CHILD (C1 INTEGER PRIMARY KEY, CP1 INTEGER , CP2 INTEGER, CP3 INTEGER, CP4 INTEGER, FOREIGN  KEY (CP1,CP2,CP3,CP4) REFERENCES  PARENT(P1,P2,P3,P4))");
+        }
+
+        final JdbcDataContext dc = new JdbcDataContext(conn);
+
+        final Schema schema = dc.getDefaultSchema();
+
+        assertEquals(1, schema.getRelationships().length);
+
+        Relationship rel = schema.getRelationships()[0];
+
+        assertEquals("CP1", rel.getForeignColumns()[0].getName());
+        assertEquals("CP2", rel.getForeignColumns()[1].getName());
+        assertEquals("CP3", rel.getForeignColumns()[2].getName());
+        assertEquals("CP4", rel.getForeignColumns()[3].getName());
+
+        assertEquals("P1", rel.getPrimaryColumns()[0].getName());
+        assertEquals("P2", rel.getPrimaryColumns()[1].getName());
+        assertEquals("P3", rel.getPrimaryColumns()[2].getName());
+        assertEquals("P4", rel.getPrimaryColumns()[3].getName());
+    }
 }
\ No newline at end of file
diff --git a/jdbc/src/test/java/org/apache/metamodel/jdbc/HsqldbTest.java b/jdbc/src/test/java/org/apache/metamodel/jdbc/HsqldbTest.java
index 12d5fe2..d9f0587 100644
--- a/jdbc/src/test/java/org/apache/metamodel/jdbc/HsqldbTest.java
+++ b/jdbc/src/test/java/org/apache/metamodel/jdbc/HsqldbTest.java
@@ -23,12 +23,11 @@
 import java.sql.PreparedStatement;
 import java.sql.Statement;
 import java.util.Arrays;
+import java.util.List;
 import java.util.concurrent.TimeUnit;
 
 import javax.swing.table.TableModel;
 
-import junit.framework.TestCase;
-
 import org.apache.metamodel.DataContext;
 import org.apache.metamodel.MetaModelHelper;
 import org.apache.metamodel.UpdateCallback;
@@ -47,10 +46,11 @@
 import org.apache.metamodel.schema.Schema;
 import org.apache.metamodel.schema.Table;
 
+import junit.framework.TestCase;
+
 /**
- * Test case that tests hsqldb interaction. The test uses an embedded copy of
- * the "pentaho sampledata" sample database that can be found at
- * http://pentaho.sourceforge.net.
+ * Test case that tests hsqldb interaction. The test uses an embedded copy of the "pentaho sampledata" sample database
+ * that can be found at http://pentaho.sourceforge.net.
  */
 public class HsqldbTest extends TestCase {
 
@@ -71,7 +71,7 @@
         super.tearDown();
         _connection.close();
     }
-    
+
     public void testApproximateCount() throws Exception {
         final JdbcDataContext dataContext = new JdbcDataContext(_connection);
         final DataSet dataSet = dataContext.executeQuery("SELECT APPROXIMATE COUNT(*) FROM customers");
@@ -79,7 +79,7 @@
         assertEquals(122, dataSet.getRow().getValue(0));
         assertFalse(dataSet.next());
     }
-    
+
     public void testTimestampValueInsertSelect() throws Exception {
         Connection connection = DriverManager.getConnection("jdbc:hsqldb:mem:" + getName(), USERNAME, PASSWORD);
         JdbcTestTemplates.timestampValueInsertSelect(connection, TimeUnit.NANOSECONDS);
@@ -111,8 +111,7 @@
                 + "Table[name=CUSTOMER_W_TER,type=TABLE,remarks=null], "
                 + "Table[name=DEPARTMENT_MANAGERS,type=TABLE,remarks=null], "
                 + "Table[name=DIM_TIME,type=TABLE,remarks=null], " + "Table[name=EMPLOYEES,type=TABLE,remarks=null], "
-                + "Table[name=OFFICES,type=TABLE,remarks=null], "
-                + "Table[name=ORDERDETAILS,type=TABLE,remarks=null], "
+                + "Table[name=OFFICES,type=TABLE,remarks=null], " + "Table[name=ORDERDETAILS,type=TABLE,remarks=null], "
                 + "Table[name=ORDERFACT,type=TABLE,remarks=null], " + "Table[name=ORDERS,type=TABLE,remarks=null], "
                 + "Table[name=PAYMENTS,type=TABLE,remarks=null], " + "Table[name=PRODUCTS,type=TABLE,remarks=null], "
                 + "Table[name=QUADRANT_ACTUALS,type=TABLE,remarks=null], "
@@ -146,8 +145,8 @@
         Table productsTable = schema.getTableByName("PRODUCTS");
         Table factTable = schema.getTableByName("ORDERFACT");
 
-        Query q = new Query().from(new FromItem(JoinType.INNER, productsTable.getRelationships(factTable)[0])).select(
-                productsTable.getColumns()[0], factTable.getColumns()[0]);
+        Query q = new Query().from(new FromItem(JoinType.INNER, productsTable.getRelationships(factTable)[0]))
+                .select(productsTable.getColumns()[0], factTable.getColumns()[0]);
         assertEquals(
                 "SELECT \"PRODUCTS\".\"PRODUCTCODE\", \"ORDERFACT\".\"ORDERNUMBER\" FROM PUBLIC.\"PRODUCTS\" INNER JOIN PUBLIC.\"ORDERFACT\" ON \"PRODUCTS\".\"PRODUCTCODE\" = \"ORDERFACT\".\"PRODUCTCODE\"",
                 q.toString());
@@ -158,8 +157,8 @@
         assertEquals(2, tableModel.getColumnCount());
         assertEquals(2996, tableModel.getRowCount());
 
-        assertEquals(110, MetaModelHelper.executeSingleRowQuery(dc, new Query().selectCount().from(productsTable))
-                .getValue(0));
+        assertEquals(110,
+                MetaModelHelper.executeSingleRowQuery(dc, new Query().selectCount().from(productsTable)).getValue(0));
     }
 
     public void testLimit() throws Exception {
@@ -215,23 +214,21 @@
         Schema schema = dc.getSchemaByName("PUBLIC");
         Table productsTable = schema.getTableByName("PRODUCTS");
 
-        Query q = new Query().from(productsTable, "pro-ducts").select(
-                new SelectItem(productsTable.getColumnByName("PRODUCTCODE")).setAlias("c|o|d|e"));
+        Query q = new Query().from(productsTable, "pro-ducts")
+                .select(new SelectItem(productsTable.getColumnByName("PRODUCTCODE")).setAlias("c|o|d|e"));
         q.setMaxRows(5);
 
         assertEquals("SELECT pro-ducts.\"PRODUCTCODE\" AS c|o|d|e FROM PUBLIC.\"PRODUCTS\" pro-ducts", q.toString());
 
         String queryString = queryRewriter.rewriteQuery(q);
-        assertEquals(
-                "SELECT TOP 5 \"pro-ducts\".\"PRODUCTCODE\" AS \"c|o|d|e\" FROM PUBLIC.\"PRODUCTS\" \"pro-ducts\"",
+        assertEquals("SELECT TOP 5 \"pro-ducts\".\"PRODUCTCODE\" AS \"c|o|d|e\" FROM PUBLIC.\"PRODUCTS\" \"pro-ducts\"",
                 queryString);
 
         // We have to test that no additional quoting characters are added every
         // time we run the rewriting
         queryString = queryRewriter.rewriteQuery(q);
         queryString = queryRewriter.rewriteQuery(q);
-        assertEquals(
-                "SELECT TOP 5 \"pro-ducts\".\"PRODUCTCODE\" AS \"c|o|d|e\" FROM PUBLIC.\"PRODUCTS\" \"pro-ducts\"",
+        assertEquals("SELECT TOP 5 \"pro-ducts\".\"PRODUCTCODE\" AS \"c|o|d|e\" FROM PUBLIC.\"PRODUCTS\" \"pro-ducts\"",
                 queryString);
 
         // Test that the original query is still the same (ie. it has been
@@ -248,9 +245,10 @@
 
         Column column = dc.getDefaultSchema().getTableByName("PRODUCTS").getColumnByName("PRODUCTCODE");
         assertEquals("PUBLIC.PRODUCTS.PRODUCTCODE", column.getQualifiedLabel());
-        assertEquals("Table[name=PRODUCTS,type=TABLE,remarks=null]", dc.getTableByQualifiedLabel("PUBLIC.PRODUCTS")
-                .toString());
-        assertEquals("Table[name=PRODUCTS,type=TABLE,remarks=null]", dc.getTableByQualifiedLabel("PRODUCTS").toString());
+        assertEquals("Table[name=PRODUCTS,type=TABLE,remarks=null]",
+                dc.getTableByQualifiedLabel("PUBLIC.PRODUCTS").toString());
+        assertEquals("Table[name=PRODUCTS,type=TABLE,remarks=null]",
+                dc.getTableByQualifiedLabel("PRODUCTS").toString());
         assertEquals(
                 "Column[name=PRODUCTCODE,columnNumber=0,type=VARCHAR,nullable=false,nativeType=VARCHAR,columnSize=50]",
                 dc.getColumnByQualifiedLabel("PUBLIC.PRODUCTS.PRODUCTCODE").toString());
@@ -293,8 +291,8 @@
         q = dc.query().from(table).selectCount().where("name").isEquals("m'jello").toQuery();
 
         assertEquals("SELECT COUNT(*) FROM PUBLIC.\"TESTTABLE\" WHERE \"TESTTABLE\".\"NAME\" = 'm'jello'", q.toSql());
-        assertEquals("SELECT COUNT(*) FROM PUBLIC.\"TESTTABLE\" WHERE \"TESTTABLE\".\"NAME\" = 'm''jello'", dc
-                .getQueryRewriter().rewriteQuery(q));
+        assertEquals("SELECT COUNT(*) FROM PUBLIC.\"TESTTABLE\" WHERE \"TESTTABLE\".\"NAME\" = 'm''jello'",
+                dc.getQueryRewriter().rewriteQuery(q));
 
         row = MetaModelHelper.executeSingleRowQuery(dc, q);
         assertEquals(1, ((Number) row.getValue(0)).intValue());
@@ -335,8 +333,8 @@
     }
 
     public void testInsertOfDifferentTypes() throws Exception {
-        Connection connection = DriverManager.getConnection("jdbc:hsqldb:mem:different_types_insert", USERNAME,
-                PASSWORD);
+        Connection connection =
+                DriverManager.getConnection("jdbc:hsqldb:mem:different_types_insert", USERNAME, PASSWORD);
 
         try {
             connection.createStatement().execute("DROP TABLE my_table");
@@ -417,4 +415,69 @@
         Connection conn = DriverManager.getConnection("jdbc:hsqldb:mem:interpretation_of_null", USERNAME, PASSWORD);
         JdbcTestTemplates.interpretationOfNulls(conn);
     }
+
+    public void testCapitalization() throws Exception {
+        try (Connection connection =
+                DriverManager.getConnection("jdbc:hsqldb:mem:different_types_insert", USERNAME, PASSWORD)) {
+
+            final JdbcDataContext dcon = new JdbcDataContext(connection);
+
+            final String mixedcap = "MixedCapitalization";
+            final String idCol = "Id";
+            final String nameCol = "name";
+            final String emailCol = "EMAIL";
+            final String createTable = "CREATE TABLE \"" + mixedcap + "\" (\"" + idCol + "\" INTEGER, \"" + nameCol
+                    + "\" LONGVARCHAR,  \"" + emailCol + "\" LONGVARCHAR)";
+
+            try (Statement stmt = connection.createStatement();) {
+
+                stmt.execute(createTable);
+
+            }
+
+            dcon.refreshSchemas();
+            assertEquals(mixedcap, dcon.getDefaultSchema().getTable(0).getName());
+            assertEquals(idCol, dcon.getDefaultSchema().getTable(0).getColumn(0).getName());
+
+            dcon.query().from(mixedcap).select(idCol, nameCol, emailCol).execute();
+
+            try (Statement stmt = connection.createStatement()) {
+                stmt.execute("DROP TABLE \"" + mixedcap + "\"");
+            }
+            dcon.refreshSchemas();
+            dcon.executeUpdate(new UpdateScript() {
+
+                @Override
+                public void run(UpdateCallback callback) {
+                    callback.createTable(dcon.getDefaultSchemaName(), mixedcap).withColumn(idCol).asPrimaryKey()
+                            .ofType(ColumnType.INTEGER).withColumn(nameCol).ofType(ColumnType.STRING)
+                            .withColumn(emailCol).ofType(ColumnType.STRING).execute();
+                }
+            });
+
+            dcon.executeUpdate(new UpdateScript() {
+
+                @Override
+                public void run(UpdateCallback callback) {
+                    callback.insertInto(mixedcap).value(idCol, 1).value(nameCol, "Sarah")
+                            .value(emailCol, "sarah@example.com").execute();
+                }
+            });
+
+            String queryNoQuotes = "SELECT " + nameCol + ", " + idCol + ", " + emailCol + " FROM " + mixedcap
+                    + " WHERE " + idCol + "= 1";
+            DataSet dsStringQueryNQ = dcon.executeQuery(queryNoQuotes);
+            List<Row> rowsQueryNoQuotes = dsStringQueryNQ.toRows();
+            assertEquals(1, rowsQueryNoQuotes.size());
+            List<Row> rowsQueryObject = dcon.query().from(mixedcap).select(nameCol).select(idCol).select(emailCol)
+                    .where(idCol).eq(1).execute().toRows();
+            assertEquals(1, rowsQueryObject.size());
+
+            assertEquals(rowsQueryObject.get(0).getValue(0), rowsQueryNoQuotes.get(0).getValue(0));
+            assertEquals(rowsQueryObject.get(0).getValue(1), rowsQueryNoQuotes.get(0).getValue(1));
+            assertEquals(rowsQueryObject.get(0).getValue(2), rowsQueryNoQuotes.get(0).getValue(2));
+
+        }
+
+    }
 }
\ No newline at end of file
diff --git a/pojo/src/main/java/org/apache/metamodel/pojo/PojoDataContext.java b/pojo/src/main/java/org/apache/metamodel/pojo/PojoDataContext.java
index 9369e96..340228f 100644
--- a/pojo/src/main/java/org/apache/metamodel/pojo/PojoDataContext.java
+++ b/pojo/src/main/java/org/apache/metamodel/pojo/PojoDataContext.java
@@ -49,6 +49,8 @@
 public class PojoDataContext extends QueryPostprocessDataContext implements UpdateableDataContext, Serializable {
 
     private static final long serialVersionUID = 1L;
+    
+    public static final String DEFAULT_SCHEMA_NAME = "Schema";
 
     private final Map<String, TableDataProvider<?>> _tables;
     private final String _schemaName;
@@ -68,7 +70,7 @@
      * @param tables
      */
     public PojoDataContext(List<TableDataProvider<?>> tables) {
-        this("Schema", tables);
+        this(DEFAULT_SCHEMA_NAME, tables);
     }
 
     /**
diff --git a/pojo/src/main/java/org/apache/metamodel/pojo/PojoDataContextFactory.java b/pojo/src/main/java/org/apache/metamodel/pojo/PojoDataContextFactory.java
new file mode 100644
index 0000000..35842bf
--- /dev/null
+++ b/pojo/src/main/java/org/apache/metamodel/pojo/PojoDataContextFactory.java
@@ -0,0 +1,71 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.metamodel.pojo;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.metamodel.ConnectionException;
+import org.apache.metamodel.DataContext;
+import org.apache.metamodel.factory.DataContextFactory;
+import org.apache.metamodel.factory.DataContextProperties;
+import org.apache.metamodel.factory.ResourceFactoryRegistry;
+import org.apache.metamodel.factory.UnsupportedDataContextPropertiesException;
+import org.apache.metamodel.util.SimpleTableDef;
+
+public class PojoDataContextFactory implements DataContextFactory {
+
+    public static final String PROPERTY_TYPE = "pojo";
+
+    @Override
+    public boolean accepts(DataContextProperties properties, ResourceFactoryRegistry resourceFactoryRegistry) {
+        return PROPERTY_TYPE.equals(properties.getDataContextType());
+    }
+
+    @Override
+    public DataContext create(DataContextProperties properties, ResourceFactoryRegistry resourceFactoryRegistry)
+            throws UnsupportedDataContextPropertiesException, ConnectionException {
+
+        assert accepts(properties, resourceFactoryRegistry);
+
+        final String schemaName;
+        if (properties.getDatabaseName() != null) {
+            schemaName = properties.getDatabaseName();
+        } else {
+            schemaName = "Schema";
+        }
+
+        final List<TableDataProvider<?>> tableDataProviders;
+
+        final SimpleTableDef[] tableDefs = properties.getTableDefs();
+        if (tableDefs == null) {
+            tableDataProviders = new ArrayList<>();
+        } else {
+            tableDataProviders = new ArrayList<>(tableDefs.length);
+            for (int i = 0; i < tableDefs.length; i++) {
+                final TableDataProvider<?> tableDataProvider = new ArrayTableDataProvider(tableDefs[i],
+                        new ArrayList<Object[]>());
+                tableDataProviders.add(tableDataProvider);
+            }
+        }
+
+        return new PojoDataContext(schemaName, tableDataProviders);
+    }
+
+}
diff --git a/pojo/src/main/resources/META-INF/services/org.apache.metamodel.factory.DataContextFactory b/pojo/src/main/resources/META-INF/services/org.apache.metamodel.factory.DataContextFactory
new file mode 100644
index 0000000..76f808d
--- /dev/null
+++ b/pojo/src/main/resources/META-INF/services/org.apache.metamodel.factory.DataContextFactory
@@ -0,0 +1 @@
+org.apache.metamodel.pojo.PojoDataContextFactory
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 50f5674..6d66536 100644
--- a/pom.xml
+++ b/pom.xml
@@ -29,6 +29,7 @@
 		<hadoop.version>2.6.0</hadoop.version>
 		<jackson.version>2.6.3</jackson.version>
 		<easymock.version>3.2</easymock.version>
+		<spring.version>4.2.6.RELEASE</spring.version>
 		<httpcomponents.version>4.4.1</httpcomponents.version>
 		<checksum-maven-plugin.version>1.2</checksum-maven-plugin.version>
 		<skipTests>false</skipTests>
@@ -42,7 +43,7 @@
 		<url>https://git-wip-us.apache.org/repos/asf?p=metamodel.git</url>
 		<connection>scm:git:http://git-wip-us.apache.org/repos/asf/metamodel.git</connection>
 		<developerConnection>scm:git:https://git-wip-us.apache.org/repos/asf/metamodel.git</developerConnection>
-		<tag>5.x</tag>
+		<tag>HEAD</tag>
 	</scm>
 	<groupId>org.apache.metamodel</groupId>
 	<artifactId>MetaModel</artifactId>
@@ -105,20 +106,54 @@
 	</organization>
 	<developers>
 		<developer>
-			<id>kasper</id>
+			<id>kaspersor</id>
 			<name>Kasper Sørensen</name>
-			<email>kasper.sorensen@humaninference.com</email>
-			<url>http://kasper.eobjects.org</url>
-			<organization>Human Inference</organization>
-			<organizationUrl>http://www.humaninference.com</organizationUrl>
+			<email>i.am.kasper.sorensen@gmail.com</email>
+			<url>https://github.com/kaspersorensen</url>
+		</developer>
+		<developer>
+			<name>Dennis Du Krøger</name>
+			<url>https://github.com/losd</url>
+		</developer>
+		<developer>
+			<name>Ankit Kumar</name>
+			<url>https://github.com/ankit2711</url>
+		</developer>
+		<developer>
+			<name>Alberto Rodriguez</name>
+			<url>https://github.com/albertostratio</url>
+		</developer>
+		<developer>
+			<name>Tomasz Guzialek</name>
+			<url>https://github.com/tomaszguzialek</url>
+		</developer>
+		<developer>
+			<name>Henry Saputra</name>
+			<url>https://github.com/hsaputra</url>
 		</developer>
 	</developers>
 	<contributors>
 		<contributor>
+			<name>Arjan Seijkens</name>
+			<url>https://github.com/arjansh</url>
+		</contributor>
+		<contributor>
+			<name>Jakub Horčička</name>
+			<url>https://github.com/jhorcicka</url>
+		</contributor>
+		<contributor>
 			<name>Francisco Javier Cano</name>
 			<organization>Stratio</organization>
 		</contributor>
 		<contributor>
+			<name>Harel Efraim</name>
+			<url>https://github.com/harel-e</url>
+		</contributor>
+		<contributor>
+			<name>Joerg Unbehauen</name>
+			<url>https://github.com/tomatophantastico</url>
+		</contributor>
+		<contributor>
 			<name>Saurabh Gupta</name>
 			<organization>Xebia India</organization>
 		</contributor>
@@ -372,7 +407,7 @@
 						<excludeSubProjects>false</excludeSubProjects>
 						<excludes>
 							<exclude>KEYS</exclude>
-							<exclude>*.md</exclude>
+							<exclude>**/*.md</exclude>
 							<exclude>example-metamodel-integrationtest-configuration.properties</exclude>
 							<exclude>travis-metamodel-integrationtest-configuration.properties</exclude>
 							<exclude>**/src/assembly/metamodel-packaged-assembly-descriptor.xml</exclude>
@@ -386,6 +421,7 @@
 							<exclude>**/.project</exclude>
 							<exclude>**/.classpath</exclude>
 							<exclude>**/.settings/**</exclude>
+							<exclude>**/.vscode/**</exclude>
 							<exclude>**/.travis.yml</exclude>
 							<exclude>**/target/**</exclude>
 							<exclude>**/*.iml/**</exclude>
@@ -393,6 +429,7 @@
 							<exclude>**/*.ipr/**</exclude>
 							<exclude>**/.idea/**</exclude>
 							<exclude>**/tattletale-filters.properties</exclude>
+							<exclude>**/swagger-ui/**</exclude>
 							<exclude>DEPENDENCIES</exclude>
 							<exclude>DISCLAIMER</exclude>
 							<exclude>neo4j-community-*/**</exclude>
@@ -515,6 +552,11 @@
 				<version>${jackson.version}</version>
 			</dependency>
 			<dependency>
+				<groupId>com.fasterxml.jackson.dataformat</groupId>
+				<artifactId>jackson-dataformat-yaml</artifactId>
+				<version>${jackson.version}</version>
+			</dependency>
+			<dependency>
 				<groupId>com.fasterxml.jackson.core</groupId>
 				<artifactId>jackson-annotations</artifactId>
 				<version>${jackson.version}</version>
@@ -539,6 +581,34 @@
 				<artifactId>hsqldb</artifactId>
 				<version>1.8.0.10</version>
 			</dependency>
+			
+			<!-- Spring -->
+			<dependency>
+				<groupId>org.springframework</groupId>
+				<artifactId>spring-core</artifactId>
+				<version>${spring.version}</version>
+			    <exclusions>
+			    	<exclusion>
+			    		<groupId>commons-logging</groupId>
+			    		<artifactId>commons-logging</artifactId>
+			    	</exclusion>
+			    </exclusions>
+			</dependency>
+			<dependency>
+			    <groupId>org.springframework</groupId>
+			    <artifactId>spring-context</artifactId>
+			    <version>${spring.version}</version>
+			</dependency>
+			<dependency>
+			    <groupId>org.springframework</groupId>
+			    <artifactId>spring-test</artifactId>
+			    <version>${spring.version}</version>
+			</dependency>
+			<dependency>
+				<groupId>org.springframework</groupId>
+				<artifactId>spring-webmvc</artifactId>
+				<version>${spring.version}</version>
+			</dependency>
 
 			<!-- Hadoop -->
 			<dependency>
diff --git a/spring/pom.xml b/spring/pom.xml
index 6792582..b6d09b4 100644
--- a/spring/pom.xml
+++ b/spring/pom.xml
@@ -27,10 +27,6 @@
 	<artifactId>MetaModel-spring</artifactId>
 	<name>MetaModel module for Spring enabled configuration</name>
 	
-	<properties>
-		<spring.version>3.0.7.RELEASE</spring.version>
-	</properties>
-
 	<dependencies>
 		<dependency>
 			<groupId>org.apache.metamodel</groupId>
@@ -46,14 +42,7 @@
 		<dependency>
 		    <groupId>org.springframework</groupId>
 		    <artifactId>spring-context</artifactId>
-		    <version>${spring.version}</version>
 		    <scope>provided</scope>
-		    <exclusions>
-		    	<exclusion>
-		    		<groupId>commons-logging</groupId>
-		    		<artifactId>commons-logging</artifactId>
-		    	</exclusion>
-		    </exclusions>
 		</dependency>
 
 		<!-- test -->
@@ -70,7 +59,6 @@
 		<dependency>
 		    <groupId>org.springframework</groupId>
 		    <artifactId>spring-test</artifactId>
-		    <version>${spring.version}</version>
 		    <scope>test</scope>
 		</dependency>
 	</dependencies>