EMPIREDB-362 Split DBModelChecker, DBModelParser and improved CodeGenerator
diff --git a/empire-db-codegen/src/main/java/org/apache/empire/db/codegen/CodeGenConfig.java b/empire-db-codegen/src/main/java/org/apache/empire/db/codegen/CodeGenConfig.java
index ff747ef..94e0198 100644
--- a/empire-db-codegen/src/main/java/org/apache/empire/db/codegen/CodeGenConfig.java
+++ b/empire-db-codegen/src/main/java/org/apache/empire/db/codegen/CodeGenConfig.java
@@ -42,6 +42,8 @@
 

 	private String jdbcPwd;

 

+    private String dbmsHandlerClass;

+	

 	// generation options

 	/**

 	 * name of the database catalog (may be null)

@@ -65,7 +67,13 @@
      * flag whether to parse and generate views

      */

     private boolean generateRecords = true;

-	/**

+

+    /**

+     * Name of the identity column used for Autoincrement

+     */

+    private String identityColumn = null;

+    

+    /**

 	 * Name of the timestamp column used for optimistic locking (may be null)

 	 * e.g. "UPDATE_TIMESTAMP";

 	 */

@@ -307,6 +315,18 @@
 	public void setJdbcPwd(String jdbcPwd) {

 		this.jdbcPwd = jdbcPwd;

 	}

+	

+	// ------- DBMS Handler -------

+

+    public String getDbmsHandlerClass()

+    {

+        return dbmsHandlerClass;

+    }

+

+    public void setDbmsHandlerClass(String dbmsHandlerClass)

+    {

+        this.dbmsHandlerClass = dbmsHandlerClass;

+    }

 

 	// ------- generation options -------

 

@@ -314,7 +334,7 @@
 		return dbCatalog;

 	}

 

-	public void setDbCatalog(String dbCatalog) {

+    public void setDbCatalog(String dbCatalog) {

 		this.dbCatalog = dbCatalog;

 	}

 

@@ -352,6 +372,16 @@
         this.generateRecords = generateRecords;

     }

 

+    public String getIdentityColumn()

+    {

+        return identityColumn;

+    }

+

+    public void setIdentityColumn(String identityColumn)

+    {

+        this.identityColumn = identityColumn;

+    }

+

     public String getTimestampColumn() {

 		return timestampColumn;

 	}

diff --git a/empire-db-codegen/src/main/java/org/apache/empire/db/codegen/CodeGenConfigInvalidException.java b/empire-db-codegen/src/main/java/org/apache/empire/db/codegen/CodeGenConfigInvalidException.java
new file mode 100644
index 0000000..5940c4b
--- /dev/null
+++ b/empire-db-codegen/src/main/java/org/apache/empire/db/codegen/CodeGenConfigInvalidException.java
@@ -0,0 +1,49 @@
+/*
+ * 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.empire.db.codegen;
+
+import org.apache.empire.exceptions.InvalidPropertyException;
+import org.slf4j.Logger;
+
+public class CodeGenConfigInvalidException extends InvalidPropertyException
+{
+    private static final Logger log = org.slf4j.LoggerFactory.getLogger(CodeGenConfig.class);
+    
+    private static final long serialVersionUID = 1L;
+    
+    public CodeGenConfigInvalidException(String property, Object value, Exception cause)
+    {
+        super(property, value, cause);
+    }
+    
+    public CodeGenConfigInvalidException(String property, Object value)
+    {
+        this(property, value, null);
+    }
+    
+    /**
+     * log the error (info must be enabled)
+     */
+    @Override
+    protected void log()
+    {
+        log.error("Invalid configuration: "+getMessage(), this);
+    }
+    
+}
diff --git a/empire-db-codegen/src/main/java/org/apache/empire/db/codegen/CodeGenParser.java b/empire-db-codegen/src/main/java/org/apache/empire/db/codegen/CodeGenParser.java
deleted file mode 100644
index ab1b851..0000000
--- a/empire-db-codegen/src/main/java/org/apache/empire/db/codegen/CodeGenParser.java
+++ /dev/null
@@ -1,589 +0,0 @@
-/*

- * Licensed to the Apache Software Foundation (ASF) under one

- * or more contributor license agreements.  See the NOTICE file

- * distributed with this work for additional information

- * regarding copyright ownership.  The ASF licenses this file

- * to you under the Apache License, Version 2.0 (the

- * "License"); you may not use this file except in compliance

- * with the License.  You may obtain a copy of the License at

- *

- *  http://www.apache.org/licenses/LICENSE-2.0

- *

- * Unless required by applicable law or agreed to in writing,

- * software distributed under the License is distributed on an

- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY

- * KIND, either express or implied.  See the License for the

- * specific language governing permissions and limitations

- * under the License.

- */

-package org.apache.empire.db.codegen;

-

-import java.sql.Connection;

-import java.sql.DatabaseMetaData;

-import java.sql.DriverManager;

-import java.sql.ResultSet;

-import java.sql.ResultSetMetaData;

-import java.sql.SQLException;

-import java.sql.Types;

-import java.text.MessageFormat;

-import java.util.ArrayList;

-import java.util.List;

-

-import org.apache.empire.commons.StringUtils;

-import org.apache.empire.data.DataType;

-import org.apache.empire.db.DBColumn;

-import org.apache.empire.db.DBCommandExpr;

-import org.apache.empire.db.DBDatabase;

-import org.apache.empire.db.DBRelation;

-import org.apache.empire.db.DBTable;

-import org.apache.empire.db.DBTableColumn;

-import org.apache.empire.db.DBView;

-import org.apache.empire.db.DBView.DBViewColumn;

-import org.apache.empire.exceptions.ItemNotFoundException;

-import org.slf4j.Logger;

-import org.slf4j.LoggerFactory;

-

-/**

- * This class is used to create a in memory DBDatabase of a given SQLConnection

- * and Configuration

- * 

- * @author Benjamin Venditti

- */

-public class CodeGenParser {

-

-	public static class InMemoryDatabase extends DBDatabase {

-        // *Deprecated* private static final long serialVersionUID = 1L;

-        

-        @Override

-        public List<DBRelation> getRelations()

-        {

-            return relations;

-        }

-	}

-	

-	public static class InMemoryView extends DBView {

-    // *Deprecated* private static final long serialVersionUID = 1L;

-

-		public InMemoryView(String name, DBDatabase db) {

-			super(name, db);

-		}

-		

-		public DBViewColumn addCol(String columnName,DataType dataType)

-		{

-			return addColumn(columnName, dataType);

-		}

-		

-		@Override

-		public DBCommandExpr createCommand() {

-			return null;

-		}

-	}

-

-	private static final Logger log = LoggerFactory.getLogger(CodeGenParser.class);

-	

-	private DatabaseMetaData dbMeta;

-	private Connection con;

-	private CodeGenConfig config;

-

-	/**

-	 * create a empty in memory Database and populates it

-	 */

-	public CodeGenParser(CodeGenConfig config) {

-	    this.config = config;

-	}

-

-	/**

-	 * returns the populated DBDatabase

-	 */

-	public DBDatabase loadDbModel() {

-		DBDatabase db = new InMemoryDatabase();

-	    try {           

-            con = openJDBCConnection(config);

-            populateDatabase(db);

-        } 

-        catch (SQLException e) 

-        {

-            throw new RuntimeException("Unable to read database metadata: " + e.getMessage(), e);

-        }

-        finally 

-        {

-            close(con);

-        }

-        return db;

-	}

-

-	/**

-     * Opens and returns a JDBC-Connection.

-     * JDBC url, user and password for the connection are obained from the SampleConfig bean

-     * Please use the config.xml file to change connection params.

-     */

-	protected Connection openJDBCConnection(CodeGenConfig config) throws SQLException{

-        log.info("Connecting to Database'" + config.getJdbcURL() + "' / User=" + config.getJdbcUser());

-        Connection conn = null;

-        try {

-            Class.forName(config.getJdbcClass()).newInstance();

-        }catch(Exception ex){

-        	throw new SQLException("Could not load database dbms: " + config.getJdbcClass());

-        }

-        conn = DriverManager.getConnection(config.getJdbcURL(), config.getJdbcUser(), config.getJdbcPwd());

-        log.info("Connected successfully");

-        return conn;

-    }

-

-	/**

-	 * Returns whether to add the table or ignore it

-	 * @param tableName

-	 */

-    protected boolean isPopulateTable(String tableName)

-    {

-        if (tableName.indexOf('$') >= 0)

-            return false;

-        return true;

-    }

-    

-    /**

-     * Returns whether to add the column or ignore it

-     * @param columnName

-     */

-    protected boolean isPopulateColumn(String columnName)

-    {

-        return true;

-    }

-	

-	/**

-	 * Queries the metadata of the database for tables and vies and populates the

-	 * database with those

-	 * @throws SQLException 

-	 */

-    protected void populateDatabase(DBDatabase db) throws SQLException {

-		ResultSet tables = null;

-		ArrayList<String> populatedTables=new ArrayList<String>();

-		try{

-            this.dbMeta = con.getMetaData();

-            String[] tablePatterns = {null}; // Could be null, so start that way.

-			if(config.getDbTablePattern() != null)

-				tablePatterns = config.getDbTablePattern().split(","); // Support a comma separated list of table patterns (i.e. specify a list of table names in the config file).

-            

-            int tableCount = 0; // Moved to be outside table pattern loop.

-            int viewCount = 0;

-            for(String pattern : tablePatterns){

-                // types

-                String[] types = config.isGenerateViews() ? new String[] { "TABLE", "VIEW" }

-                                                          : new String[] { "TABLE" }; 

-			    // Get table metadata

-	            tables = dbMeta.getTables(

-			            config.getDbCatalog(), 

-			            config.getDbSchema(), 

-			            pattern == null ? pattern: pattern.trim(),

-						types);

-	            

-	            // Add all tables and views 

-				while (tables.next()) {

-					String tableName = tables.getString("TABLE_NAME");

-			        if (!isPopulateTable(tableName)) {

-                        log.info("Ignoring table " + tableName);

-			            continue;

-			        }

-					// show

-					String tableType = tables.getString("TABLE_TYPE");

-					String tableSchema = tables.getString("TABLE_SCHEM");

-					String templ = StringUtils.isNotEmpty(tableSchema) ? "{0}: {1} ({2})" : "{0}: {1}"; 

-				    log.info(MessageFormat.format(templ, tableType, tableName, tableSchema));

-					// Table or View

-					if(tableType.equalsIgnoreCase("VIEW")){

-						InMemoryView view = new InMemoryView(tableName, db);

-						populateView(view);

-						viewCount++;

-					} else {

-						DBTable table = new DBTable(tableName, db);

-						populateTable(table);

-						populatedTables.add(tableName);

-						tableCount++;

-					}

-				}

-			}

-			// Add all relations

-			gatherRelations(db, dbMeta, populatedTables);

-

-			if (tableCount==0 && viewCount==0) {

-			    // getTables returned no result

-			    String info = "catalog="+config.getDbCatalog(); 

-                info += "/ schema="+config.getDbSchema(); 

-                info += "/ pattern="+config.getDbTablePattern(); 

-			    log.warn("DatabaseMetaData.getTables() returned no tables or views! Please check parameters: "+info);

-				log.info("Available catalogs: " + getCatalogs(dbMeta));

-				log.info("Available schemata: " + getSchemata(dbMeta));

-			}

-		} finally {

-			close(tables);

-		}

-	}

-	

-    protected void gatherRelations(DBDatabase db, DatabaseMetaData dbMeta, ArrayList<String> tables) throws SQLException{

-		ResultSet relations = null;

-		String fkTableName, pkTableName, fkColName, pkColName, relName;

-		DBTableColumn fkCol, pkCol;

-		DBTable fkTable, pkTable;

-		DBColumn col;

-		

-		// Add all Relations

-		for (String tableName :tables) {

-			

-			// check for foreign-keys

-			relations = dbMeta.getImportedKeys(config.getDbCatalog(), config .getDbSchema(), tableName);

-			while (relations.next()) {

-				pkCol=fkCol=null;

-				

-				fkTableName=relations.getString("FKTABLE_NAME");

-				pkTableName=relations.getString("PKTABLE_NAME");

-				fkColName=relations.getString("FKCOLUMN_NAME");

-				pkColName=relations.getString("PKCOLUMN_NAME");

-

-				// Detect relation name

-				relName=relations.getString("FK_NAME");

-				if (StringUtils.isEmpty(relName))

-					relName=fkTableName+"."+fkColName+"-"+pkTableName+"."+pkColName;

-				

-				pkTable = db.getTable(pkTableName);

-				fkTable = db.getTable(fkTableName);

-				

-				// check if both tables really exist in the model

-				if(pkTable==null || fkTable==null){

-					log.error("Unable to add the relation \""+relName+"\"! One of the tables could not be found.");

-					continue;

-				}

-				

-				col=pkTable.getColumn(pkColName);

-				if(col instanceof DBTableColumn)

-					pkCol = (DBTableColumn) col;

-	

-				col=fkTable.getColumn(fkColName);

-				if(col instanceof DBTableColumn)

-					fkCol = (DBTableColumn) col;

-				

-				// check if both columns really exist in the model

-				if(fkCol==null || pkCol==null){

-					log.error("Unable to add the relation \""+relName+"\"! One of the columns could not be found.");

-					continue;

-				}

-				

-				// add the relation

-				DBRelation.DBReference reference = fkCol.referenceOn(pkCol);

-				DBRelation.DBReference[] refs = null;

-		    	DBRelation r = db.getRelation(relName);

-		        if (r!=null) {

-		        	DBRelation.DBReference[] refsOld = r.getReferences();

-		        	refs = new DBRelation.DBReference[refsOld.length+1];

-		        	int i=0;

-		        	for (; i<refsOld.length; i++)

-		        		refs[i]=refsOld[i];

-	        		refs[i]=reference;

-		        	// remove old relation

-                    log.warn("Duplicate relation {}", r.getName());

-	        		db.getRelations().remove(r);

-		        } else {

-		        	refs = new DBRelation.DBReference[] { reference };

-		        }

-				// Add a new relation

-				db.addRelation(relName, refs);

-				log.info("Added relation (FK-PK): "+relName);

-			}

-		}

-	}

-

-    protected String getCatalogs(DatabaseMetaData dbMeta) throws SQLException {

-		String retVal = "";

-		ResultSet rs = dbMeta.getCatalogs();

-		while (rs.next()) {

-			retVal += rs.getString("TABLE_CAT") + ", ";

-		}

-		if(retVal.length()>2)

-			retVal=retVal.substring(0,retVal.length()-2);

-		

-		return retVal;

-	}

-

-    protected String getSchemata(DatabaseMetaData dbMeta) throws SQLException {

-		String retVal = "";

-		ResultSet rs = dbMeta.getSchemas();

-		while (rs.next()) {

-			retVal += rs.getString("TABLE_SCHEM") + ", ";

-		}

-		if(retVal.length()>2)

-			retVal=retVal.substring(0,retVal.length()-2);

-		return retVal;

-	}

-

-	/**

-	 * queries the metadata for columns of a specific table and populates the

-	 * table with that information

-	 * @throws SQLException 

-	 */

-    protected void populateTable(DBTable t) throws SQLException {

-		List<String> pkCols = this.findPkColumns(t.getName());

-		String lockColName = config.getTimestampColumn();

-		DBColumn[] key = new DBColumn[pkCols.size()];

-		ResultSet rs = null;

-		try {

-			rs = dbMeta.getColumns(config.getDbCatalog(), config.getDbSchema(),	t.getName(), null);

-	        int i=0;

-			while (rs.next()) {

-				DBTableColumn c = addColumn(t, rs, lockColName);

-				if (c==null)

-				    continue;

-				// check if it is a KeyColumn

-				if (pkCols.contains(c.getName()))

-					key[i++] = c;

-			}

-	        // Check whether all key columns have been set

-	        for (i=0; i<key.length; i++)

-	            if (key[i]==null){

-	            	throw new ItemNotFoundException(pkCols.get(i));

-	            }

-	        if(key.length > 0){

-	        	t.setPrimaryKey(key);

-	        }

-		} finally {

-			close(rs);

-		}

-	}

-	

-	/**

-	 * queries the metadata for columns of a specific table and populates the

-	 * table with that information

-	 * @throws SQLException 

-	 */

-    protected void populateView(InMemoryView v) throws SQLException {

-		ResultSet rs = null;

-		try {

-			rs = dbMeta.getColumns(config.getDbCatalog(), config.getDbSchema(),

-					v.getName(), null);

-			while (rs.next()) {

-				addColumn(v, rs);

-			}

-		} finally {

-			close(rs);

-		}

-	}

-

-	/**

-	 * Returns a list of column names that define the primarykey of the given

-	 * table.

-	 * @throws SQLException 

-	 */

-    protected List<String> findPkColumns(String tableName) throws SQLException {

-		List<String> cols = new ArrayList<String>();

-		ResultSet rs = null;

-		try {

-			rs = dbMeta.getPrimaryKeys(config.getDbCatalog(), config

-					.getDbSchema(), tableName);

-			while (rs.next()) {

-				cols.add(rs.getString("COLUMN_NAME"));

-			}

-		} finally {

-			close(rs);

-		}

-		return cols;

-	}

-

-	/**

-	 * Adds DBColumn object to the given DBTable. The DBColumn is created from

-	 * the given ResultSet

-	 */

-    protected DBTableColumn addColumn(DBTable t, ResultSet rs, String lockColName)

-			throws SQLException {

-		

-	    String name = rs.getString("COLUMN_NAME");

-	    if (!isPopulateColumn(name)) {

-            log.info("Ignoring column " + name);

-            return null;

-	    }

-	    

-		DataType empireType = getEmpireDataType(rs.getInt("DATA_TYPE"));

-		double colSize = getColumnSize(empireType, rs.getInt("DATA_TYPE"), rs.getInt("COLUMN_SIZE"));

-

-		// Timestamp column

-        if (empireType==DataType.DATETIME && StringUtils.isNotEmpty(lockColName) && name.equalsIgnoreCase(lockColName))

-        {

-            empireType=DataType.TIMESTAMP;

-            colSize=0;

-        }

-

-        // Number column

-		if (empireType==DataType.DECIMAL || empireType==DataType.FLOAT)

-		{	// decimal digits

-			int decimalDig = rs.getInt("DECIMAL_DIGITS");

-			if (decimalDig>0)

-			{	// parse

-				try {

-					int intSize = rs.getInt("COLUMN_SIZE");

-					colSize = Double.parseDouble(String.valueOf(intSize)+'.'+decimalDig);

-				} catch(Exception e) {

-					log.error("Failed to parse decimal digits for column "+name);

-				}

-			}

-			// make integer?

-			if (colSize<1.0d)

-			{	// Turn into an integer

-				empireType=DataType.INTEGER;

-			}

-		}

-		

-		// mandatory field?

-		boolean required = false;

-		String defaultValue = rs.getString("COLUMN_DEF");

-		if (rs.getString("IS_NULLABLE").equalsIgnoreCase("NO"))

-			required = true;

-		

-		// The following is a hack for MySQL which currently gets sent a string "CURRENT_TIMESTAMP" from the Empire-db dbms for MySQL.

-		// This will avoid the dbms problem because CURRENT_TIMESTAMP in the db will just do the current datetime.

-		// Essentially, Empire-db needs the concept of default values of one type that get mapped to another.

-		// In this case, MySQL "CURRENT_TIMESTAMP" for Types.TIMESTAMP needs to emit from the Empire-db dbms the null value and not "CURRENT_TIMESTAMP".

-		if(rs.getInt("DATA_TYPE") == Types.TIMESTAMP && defaultValue != null && defaultValue.equals("CURRENT_TIMESTAMP")){

-			required = false; // It is in fact not required even though MySQL schema is required because it has a default value. Generally, should Empire-db emit (required && defaultValue != null) to truly determine if a column is required?

-			defaultValue = null; // If null (and required per schema?) MySQL will apply internal default value.

-		}

-		

-		// AUTOINC indicator is not in java.sql.Types but rather meta data from DatabaseMetaData.getColumns()

-		// getEmpireDataType() above is not enough to support AUTOINC as it will only return DataType.INTEGER

-		DataType originalType = empireType;

-		ResultSetMetaData metaData = rs.getMetaData();

-		int colCount = metaData.getColumnCount();

-		String colName;

-		for (int i = 1; i <= colCount; i++) {

-			colName = metaData.getColumnName(i);

-			// MySQL matches on IS_AUTOINCREMENT column.

-			// SQL Server matches on TYPE_NAME column with identity somewhere in the string value.

-			if ((colName.equalsIgnoreCase("IS_AUTOINCREMENT") && rs.getString(i).equalsIgnoreCase("YES")) ||

-					(colName.equals("TYPE_NAME") && rs.getString(i).matches(".*(?i:identity).*"))){

-				empireType = DataType.AUTOINC;

-				

-			}

-		}

-		

-		// Move from the return statement below so we can add

-		// some AUTOINC meta data to the column to be used by

-		// the ParserUtil and ultimately the template.

-		log.info("\tCOLUMN:\t" + name + " ("+empireType+")");

-		DBTableColumn col = t.addColumn(name, empireType, colSize, required, defaultValue);

-		

-		// We still need to know the base data type for this AUTOINC

-		// because the Record g/setters need to know this, right?

-		// So, let's add it as meta data every time the column is AUTOINC

-		// and reference it in the template.

-		if(empireType.equals(DataType.AUTOINC))

-			col.setAttribute("AutoIncDataType", originalType);

-		return col;

-		

-	}

-	

-	protected double getColumnSize(DataType empireType, int dataType, int columnSize) {

-		return columnSize;

-	}

-

-	/**

-	 * Adds DBColumn object to the given DBTable. The DBColumn is created from

-	 * the given ResultSet

-	 */

-	private DBViewColumn addColumn(InMemoryView v, ResultSet rs)

-			throws SQLException {

-		String name = rs.getString("COLUMN_NAME");

-		DataType empireType = getEmpireDataType(rs.getInt("DATA_TYPE"));

-		

-		log.info("\tCOLUMN:\t" + name + " ("+empireType+")");

-		return v.addCol(name, empireType);

-	}

-

-	/**

-	 * converts a SQL DataType to a EmpireDataType

-	 */

-	private DataType getEmpireDataType(int sqlType) {

-		DataType empireType = DataType.UNKNOWN;

-		switch (sqlType) {

-		case Types.INTEGER:

-		case Types.SMALLINT:

-		case Types.TINYINT:

-		case Types.BIGINT:

-			empireType = DataType.INTEGER;

-			break;

-		case Types.VARCHAR:

-		case Types.NVARCHAR:

-			empireType = DataType.VARCHAR;

-			break;

-		case Types.DATE:

-			empireType = DataType.DATE;

-			break;

-		case Types.TIMESTAMP:

-		case Types.TIME:

-			empireType = DataType.DATETIME;

-			break;

-		case Types.CHAR:

-        case Types.NCHAR:

-			empireType = DataType.CHAR;

-			break;

-		case Types.DOUBLE:

-		case Types.FLOAT:

-		case Types.REAL:

-			empireType = DataType.FLOAT;

-			break;

-		case Types.DECIMAL:

-		case Types.NUMERIC:

-			empireType = DataType.DECIMAL;

-			break;

-		case Types.BIT:

-		case Types.BOOLEAN:

-			empireType = DataType.BOOL;

-			break;

-		case Types.CLOB:

-		case Types.NCLOB:

-		case Types.LONGVARCHAR:

-		case Types.LONGNVARCHAR:

-			empireType = DataType.CLOB;

-			break;

-		case Types.BINARY:

-		case Types.VARBINARY:

-		case Types.LONGVARBINARY:

-		case Types.BLOB:

-			empireType = DataType.BLOB;

-			break;

-		default:

-			empireType = DataType.UNKNOWN;

-			log.warn("SQL column type " + sqlType + " not supported.");

-		}

-		log.debug("Mapping date type " + String.valueOf(sqlType) + " to "

-				+ empireType);

-		return empireType;

-	}

-

-    /**

-     * Closes a sql resultset and logs exceptions.

-     * 

-     * @param rs the resultset to close

-     * @param log the logger instance to use for logging

-     * @return true on succes

-     */

-    protected boolean close(ResultSet rs)

-    {   try {

-            if (rs != null)

-                rs.close();

-            return true;

-        } catch (SQLException e) {

-            log.error("The resultset could not be closed!", e);

-            return false;

-        }

-    }

-

-    /**

-     * Closes a JDBC-Connection and logs exceptions.

-     */

-    protected void close(Connection conn)

-    {   try  {

-            log.info("Closing database connection");

-            if (conn != null)

-                conn.close();

-        } catch (Exception e) {

-            log.error("Error closing connection", e);

-        }

-    }

-	

-}

diff --git a/empire-db-codegen/src/main/java/org/apache/empire/db/codegen/CodeGenParserMySQL.java b/empire-db-codegen/src/main/java/org/apache/empire/db/codegen/CodeGenParserMySQL.java
deleted file mode 100644
index 242244f..0000000
--- a/empire-db-codegen/src/main/java/org/apache/empire/db/codegen/CodeGenParserMySQL.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*

- * Licensed to the Apache Software Foundation (ASF) under one

- * or more contributor license agreements.  See the NOTICE file

- * distributed with this work for additional information

- * regarding copyright ownership.  The ASF licenses this file

- * to you under the Apache License, Version 2.0 (the

- * "License"); you may not use this file except in compliance

- * with the License.  You may obtain a copy of the License at

- *

- *  http://www.apache.org/licenses/LICENSE-2.0

- *

- * Unless required by applicable law or agreed to in writing,

- * software distributed under the License is distributed on an

- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY

- * KIND, either express or implied.  See the License for the

- * specific language governing permissions and limitations

- * under the License.

- */

-package org.apache.empire.db.codegen;

-

-import java.sql.Types;

-

-import org.apache.empire.data.DataType;

-

-public class CodeGenParserMySQL extends CodeGenParser {

-

-	// private static final Logger log = LoggerFactory.getLogger(CodeGenParserMySQL.class);

-	

-	public CodeGenParserMySQL(CodeGenConfig config) {

-		super(config);

-	}

-

-	@Override

-	protected double getColumnSize(DataType empireType, int dataType, int columnSize) {

-		

-		switch (empireType) {

-			

-			case INTEGER: {

-				// return size in byte, depending on MySQL Integer Types

-				// see http://dev.mysql.com/doc/refman/5.7/en/integer-types.html

-				// ignore the "real" columnsize as its just a "format hint"

-				switch(dataType) {

-					case Types.TINYINT:

-						return 1; // TINYINT, 1 byte

-					case Types.SMALLINT:

-						return 2; // SMALLINT, 2 byte

-					case Types.BIGINT:

-						return 8; // BIGINT, 8 byte

-					default: 

-						return 4; // Types.INTEGER, INT, 4 byte

-				}

-			}

-			

-			default:

-				return super.getColumnSize(empireType, dataType, columnSize);

-			

-		}

-		

-	}

-	

-}

diff --git a/empire-db-codegen/src/main/java/org/apache/empire/db/codegen/CodeGenerator.java b/empire-db-codegen/src/main/java/org/apache/empire/db/codegen/CodeGenerator.java
index 25325a6..c11b907 100644
--- a/empire-db-codegen/src/main/java/org/apache/empire/db/codegen/CodeGenerator.java
+++ b/empire-db-codegen/src/main/java/org/apache/empire/db/codegen/CodeGenerator.java
@@ -18,7 +18,13 @@
  */
 package org.apache.empire.db.codegen;
 
-import org.apache.empire.db.DBDatabase;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+
+import org.apache.empire.commons.StringUtils;
+import org.apache.empire.db.validation.DBModelParser;
+import org.apache.empire.dbms.DBMSHandler;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -44,34 +50,54 @@
 		CodeGenerator app = new CodeGenerator();
 		app.generate((args.length > 0 ? args[0] : DEFAULT_CONFIG_FILE));
 	}	
+
+    /**
+     * Starts the actual generation according to the provided config file
+     */
+    public void generate(final String configFile) {
+        // load configuration file
+        CodeGenConfig config = loadConfig(configFile);
+        // generate now
+        generate(config);
+    }   
+
+    /**
+     * Starts the actual generation according to the provided config file
+     */
+    public void generate(final CodeGenConfig config) {
+        // get the DBMS
+        DBMSHandler dbms = getDBMSHandler(config);
+        // get the JDBC-Connection
+        Connection conn = getJDBCConnection(config);
+        // generate now
+        generate(dbms, conn, config);
+    }   
 	
 	/**
 	 * Starts the actual generation according to the provided configuration
 	 */
-	public void generate(CodeGenConfig config) {
-		// log all options
+	public void generate(DBMSHandler dbms, Connection conn, CodeGenConfig config) {
+		
+	    // log all options
 		listOptions(config);
 		
 		// read the database model
-		CodeGenParser parser = new CodeGenParser(config);
-		// CodeGenParser parser = new CodeGenParserMySQL(config);
-		DBDatabase db = parser.loadDbModel();
-		
+		// CodeGenParser parser = new CodeGenParser(config);
+		DBModelParser modelParser = dbms.createModelParser(config.getDbCatalog(), config.getDbSchema());
+		// set options
+		modelParser.setStandardIdentityColumnName (config.getIdentityColumn());
+		modelParser.setStandardTimestampColumnName(config.getTimestampColumn());
+		// parse now
+        modelParser.parseModel(conn);
+
+        log.info("Model parsing completed successfully!");
+        
 		// create the source-code for that database
 		CodeGenWriter codeGen = new CodeGenWriter(config);
-		codeGen.generateCodeFiles(db);
+		codeGen.generateCodeFiles(modelParser.getDatabase());
 		
 		log.info("Code generation completed successfully!");
 	}
-
-	/**
-	 * Starts the actual generation according to the provided config file
-	 */
-	public void generate(final String file) {
-		// load configuration file
-		CodeGenConfig config = loadConfig(file);
-		generate(config);
-	}	
 	
 	/**
 	 * Loads the configuration file and
@@ -87,7 +113,7 @@
 	
 	protected void listOptions(CodeGenConfig config){
 		// List options
-		log.info("Database connection successful. Config options are:");
+		log.info("Config options are:");
 		log.info("SchemaName=" + String.valueOf(config.getDbSchema()));
 		log.info("TimestampColumn="	+ String.valueOf(config.getTimestampColumn()));
 		log.info("TargetFolder=" + config.getTargetFolder());
@@ -102,5 +128,75 @@
 		log.info("NestViews=" + config.isNestViews());
 		log.info("CreateRecordProperties=" + config.isCreateRecordProperties());
 	}
+    
+    /**
+     * <PRE>
+     * Opens and returns a JDBC-Connection.
+     * JDBC url, user and password for the connection are obtained from the SampleConfig bean
+     * Please use the config.xml file to change connection params.
+     * </PRE>
+     */
+	protected Connection getJDBCConnection(CodeGenConfig config)
+    {
+        String jdbcDriverClass = config.getJdbcClass();
+        try
+        {   // Get Driver Class Name
+            if (StringUtils.isEmpty(jdbcDriverClass))
+                throw new CodeGenConfigInvalidException("jdbcClass", jdbcDriverClass);
+            // Getting the JDBC-Driver
+            Class.forName(jdbcDriverClass).newInstance();
+            // Connect to the database
+            String jdbcURL = config.getJdbcURL();
+            if (StringUtils.isEmpty(jdbcURL))
+                throw new CodeGenConfigInvalidException("jdbcURL", jdbcURL);
+            String jdbcUser = config.getJdbcUser();
+            if (StringUtils.isEmpty(jdbcUser))
+                throw new CodeGenConfigInvalidException("jdbcUser", jdbcUser);
+            log.info("Connecting to Database'" + jdbcURL + "' / User=" + jdbcUser);
+            Connection conn = DriverManager.getConnection(config.getJdbcURL(), config.getJdbcUser(), config.getJdbcPwd());
+            log.info("Connected successfully");
+            // set the AutoCommit to false for this connection. 
+            // commit must be called explicitly! 
+            conn.setAutoCommit(false);
+            log.info("AutoCommit has been set to " + conn.getAutoCommit());
+            return conn;
+        }
+        catch (InstantiationException | IllegalAccessException | ClassNotFoundException e)
+        {
+            throw new CodeGenConfigInvalidException("jdbcClass", jdbcDriverClass, e);
+        }
+        catch (SQLException e)
+        {
+            throw new CodeGenConfigInvalidException("jdbcURL/jdbUser", config.getJdbcURL()+"/"+config.getJdbcUser(), e);
+        }
+    }
+
+    /**
+     * Creates an Empire-db DatabaseDriver for the given provider and applies dbms specific configuration 
+     */
+	protected DBMSHandler getDBMSHandler(CodeGenConfig config)
+    {
+        // Create dbms
+        String dbmsHandlerClass = config.getDbmsHandlerClass();
+        try
+        {   // Get DBMSHandler class
+            if (StringUtils.isEmpty(dbmsHandlerClass))
+                throw new CodeGenConfigInvalidException("dbmsHandlerClass", dbmsHandlerClass);
+            // Find class
+            DBMSHandler dbms = (DBMSHandler) Class.forName(dbmsHandlerClass).newInstance();
+            // Configure dbms
+            try {
+                config.readProperties(dbms, "dbmsHandlerClass-properties");
+            } catch(Exception e) {
+                log.info("No DbmsHandlerClass-properties provieded.");
+            }
+            // done
+            return dbms;
+        }
+        catch (InstantiationException | IllegalAccessException | ClassNotFoundException e)
+        {
+            throw new CodeGenConfigInvalidException("dbmsHandlerClass", dbmsHandlerClass, e);
+        }
+    }
 
 }
diff --git a/empire-db-codegen/src/main/java/org/apache/empire/db/codegen/WriterService.java b/empire-db-codegen/src/main/java/org/apache/empire/db/codegen/WriterService.java
index d95cf19..48661e5 100644
--- a/empire-db-codegen/src/main/java/org/apache/empire/db/codegen/WriterService.java
+++ b/empire-db-codegen/src/main/java/org/apache/empire/db/codegen/WriterService.java
@@ -281,6 +281,21 @@
 		return "\"" + String.valueOf(val) + "\"";

 	}

 	

+    /**

+     * Returns the list of key columns

+     */

+    public String getKeyColumns(DBTable t)

+    {

+        DBColumn[] keyColumns =t.getKeyColumns();

+        StringBuilder b = new StringBuilder();

+        for (int i=0; i<keyColumns.length; i++)

+        {

+            if (i>0)

+                b.append(", ");

+            b.append(keyColumns[i].getName());

+        }

+        return b.toString();

+    }

 	

 	/**

 	 * Derives a java class name from a database table name.

diff --git a/empire-db-codegen/src/main/resources/templates/Table.vm b/empire-db-codegen/src/main/resources/templates/Table.vm
index aac0b1c..e72cc68 100644
--- a/empire-db-codegen/src/main/resources/templates/Table.vm
+++ b/empire-db-codegen/src/main/resources/templates/Table.vm
@@ -55,21 +55,12 @@
 

 #if($table.keyColumns && $table.keyColumns.size()>0)

 		// configure key columns (primary key)

-#if($table.keyColumns.size()==1)

-		setPrimaryKey(${parser.getColumnName($table.keyColumns[0])});

-#else

-		DBTableColumn[] keyColumns = new DBTableColumn[] {

-#foreach ($col in $table.keyColumns)

-#if($foreach.isLast()==false)

-			${parser.getColumnName($col)},

-#else

-			${parser.getColumnName($col)} };

-		setPrimaryKey(keyColumns);

+		setPrimaryKey($parser.getKeyColumns($table));

 #end

-#end

+#if($table.timestampColumn)

+		setTimestampColumn($table.getTimestampColumn().name);

 #end

 

-#end

 	}

 #if($nestTables == true)

   }

diff --git a/empire-db-codegen/src/test/java/org/apache/empire/db/codegen/CodeGenParserTest.java b/empire-db-codegen/src/test/java/org/apache/empire/db/codegen/CodeGenParserTest.java
index 745b578..6848c71 100644
--- a/empire-db-codegen/src/test/java/org/apache/empire/db/codegen/CodeGenParserTest.java
+++ b/empire-db-codegen/src/test/java/org/apache/empire/db/codegen/CodeGenParserTest.java
@@ -24,33 +24,61 @@
 import static org.junit.Assert.assertNotNull;

 import static org.junit.Assert.assertThat;

 

+import java.sql.Connection;

 import java.util.List;

 

 import org.apache.empire.db.DBColumn;

 import org.apache.empire.db.DBDatabase;

 import org.apache.empire.db.DBRelation;

-import org.apache.empire.db.DBTable;

 import org.apache.empire.db.DBRelation.DBReference;

+import org.apache.empire.db.validation.DBModelParser;

+import org.apache.empire.dbms.DBMSHandler;

+import org.apache.empire.db.DBTable;

 import org.junit.Before;

 import org.junit.Test;

 

 

 public class CodeGenParserTest {

-    private transient CodeGenParser parser;

 

+    private static class TestCodeGenerator extends CodeGenerator

+    {

+        /**

+         * Starts the actual generation according to the provided config file

+         */

+        public DBDatabase parseModel(final CodeGenConfig config) {

+            // get the DBMS

+            DBMSHandler dbms = getDBMSHandler(config);

+            // get the JDBC-Connection

+            Connection conn = getJDBCConnection(config);

+            // read the database model

+            // CodeGenParser parser = new CodeGenParser(config);

+            DBModelParser modelParser = dbms.createModelParser(config.getDbCatalog(), config.getDbSchema());

+            // set options

+            modelParser.setStandardIdentityColumnName (config.getIdentityColumn());

+            modelParser.setStandardTimestampColumnName(config.getTimestampColumn());

+            // parse now

+            modelParser.parseModel(conn);

+            // done

+            return modelParser.getDatabase();

+        }

+    }

+    

+    private transient CodeGenConfig config; 

+    private transient TestCodeGenerator codeGen = new TestCodeGenerator();

+    

     @Before

     public void setUp() throws Exception {

-        final CodeGenConfig config = new CodeGenConfig();

+        config = new CodeGenConfig();

         config.init("src/test/resources/testconfig.xml");

         config.setDbSchema("PUBLIC");

         config.setDbTablePattern("DEPARTMENTS,EMPLOYEES,ORGANIZATIONS");

-        parser = new CodeGenParser(config);

     }

 

     @Test

     public void testLoadDbModel() {

-        final DBDatabase db = parser.loadDbModel();

-

+        

+        DBDatabase db = codeGen.parseModel(config);

+        

         final DBTable departments = db.getTable("DEPARTMENTS");

         final DBTable employees = db.getTable("EMPLOYEES");

 

diff --git a/empire-db-codegen/src/test/resources/testconfig.xml b/empire-db-codegen/src/test/resources/testconfig.xml
index 1d45a32..b793faf 100644
--- a/empire-db-codegen/src/test/resources/testconfig.xml
+++ b/empire-db-codegen/src/test/resources/testconfig.xml
@@ -26,6 +26,9 @@
 		<jdbcUser>sa</jdbcUser>

 		<jdbcPwd></jdbcPwd>

 		<packageName>org.apache.empire.db.example</packageName>

+

+		<!-- Empire-db DBMS Handler class -->

+		<dbmsHandlerClass>org.apache.empire.dbms.hsql.DBMSHandlerHSql</dbmsHandlerClass>

 		

 		<!-- Schema options -->

 		<dbCatalog></dbCatalog>

@@ -47,6 +50,10 @@
 		<nestViews>false</nestViews>

 		<createRecordProperties>true</createRecordProperties>

 	</properties>

+

+	<dbmsHandlerClass-properties>

+		<!-- add handler properties here (if any) -->

+	</dbmsHandlerClass-properties>

 	

 	<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">

 

@@ -59,7 +66,7 @@
 		</appender>

 	

 		<!-- log detail configuration -->

-		<logger name="org.apache.empire.commons" additivity="false">

+		<logger name="org.apache.empire.dbms" additivity="false">

 			<level value="warn"/>

 			<appender-ref ref="default"/>

 		</logger>

diff --git a/empire-db-examples/empire-db-example-codegen/generate-config.xml b/empire-db-examples/empire-db-example-codegen/generate-config.xml
index 241e6a4..0b819b5 100644
--- a/empire-db-examples/empire-db-example-codegen/generate-config.xml
+++ b/empire-db-examples/empire-db-example-codegen/generate-config.xml
@@ -25,6 +25,9 @@
 		<jdbcURL>jdbc:hsqldb:file:src/test/resources/hsqldb/sample;shutdown=true</jdbcURL>

 		<jdbcUser>sa</jdbcUser>

 		<jdbcPwd></jdbcPwd>

+		

+		<!-- Empire-db DBMS Handler class -->

+		<dbmsHandlerClass>org.apache.empire.dbms.hsql.DBMSHandlerHSql</dbmsHandlerClass>

 

 		<!-- Schema options -->

 		<dbCatalog></dbCatalog>

diff --git a/empire-db-examples/empire-db-example-codegen/generate-example.xml b/empire-db-examples/empire-db-example-codegen/generate-example.xml
index 52e5e4e..3f03783 100644
--- a/empire-db-examples/empire-db-example-codegen/generate-example.xml
+++ b/empire-db-examples/empire-db-example-codegen/generate-example.xml
@@ -20,35 +20,34 @@
 <config>
 
 	<properties>
-		<!-- provider name must match the property-section containing the connection data -->
+		<!-- JDBC-Connection to connect to server -->
 		<jdbcClass>org.hsqldb.jdbc.JDBCDriver</jdbcClass>
 		<jdbcURL>jdbc:hsqldb:file:src/test/resources/hsqldb/sample;shutdown=true</jdbcURL>
 		<jdbcUser>sa</jdbcUser>
 		<jdbcPwd></jdbcPwd>
+		
+		<!-- Empire-db DBMS Handler class -->
+		<dbmsHandlerClass>org.apache.empire.dbms.hsql.DBMSHandlerHSql</dbmsHandlerClass>
 
 		<!-- Schema options -->
 		<dbCatalog></dbCatalog>
 		<dbSchema></dbSchema>
 		<dbTablePattern></dbTablePattern>
-		<timestampColumn>UPDATE_TIMESTAMP</timestampColumn>
 		
-		<!-- generation options -->
-		<!-- 
-		<targetFolder>target/generated-sources/java</targetFolder>
-		 -->
+		<!-- generation options (required) -->
 		<targetFolder>src/main/java</targetFolder>
-		<packageName>org.apache.empire.example.db</packageName>
-		<dbClassName>CarSalesDB</dbClassName>
 		<nestTables>true</nestTables>
 		<nestViews>false</nestViews>
 		<createRecordProperties>true</createRecordProperties>
 		<preserverCharacterCase>false</preserverCharacterCase>
 		<preserveRelationNames>false</preserveRelationNames>
+		<packageName>org.apache.empire.example.db</packageName>
 		
 		<!-- generation options (optional) --> 
 		<tablePackageName></tablePackageName>
 		<viewPackageName></viewPackageName>
 		<recordPackageName></recordPackageName>
+		<dbClassName>CarSalesDB</dbClassName>
 		<tableBaseName>CarSalesTable</tableBaseName>
 		<viewBaseName>CarSalesView</viewBaseName>
 		<recordBaseName>CarSalesRecord</recordBaseName>
@@ -59,9 +58,14 @@
 		<viewClassPrefix></viewClassPrefix>
 		<viewClassSuffix></viewClassSuffix>
 		<columnNamePrefix></columnNamePrefix>
+		<timestampColumn>UPDATE_TIMESTAMP</timestampColumn>
 		
 	</properties>
 	
+	<dbmsHandlerClass-properties>
+		<!-- add handler properties here (if any) -->
+	</dbmsHandlerClass-properties>
+	
 	<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
 
 		<!-- Console -->
diff --git a/empire-db-examples/empire-db-example-codegen/pom.xml b/empire-db-examples/empire-db-example-codegen/pom.xml
index 9d45686..ecc3f2e 100644
--- a/empire-db-examples/empire-db-example-codegen/pom.xml
+++ b/empire-db-examples/empire-db-example-codegen/pom.xml
@@ -78,6 +78,7 @@
 					<jdbcURL>jdbc:hsqldb:file:${project.basedir}/src/test/resources/hsqldb/sample;shutdown=true</jdbcURL>
 					<jdbcUser>sa</jdbcUser>
 					<jdbcPwd></jdbcPwd>
+					<dbmsHandlerClass>org.apache.empire.dbms.hsql.DBMSHandlerHSql</dbmsHandlerClass>
 					<packageName>org.apache.empire.db.example</packageName>
 					<dbClassName>MyDatabase</dbClassName>
 					-->
diff --git a/empire-db-maven-plugin/src/main/java/org/apache/empire/db/maven/CodeGenMojo.java b/empire-db-maven-plugin/src/main/java/org/apache/empire/db/maven/CodeGenMojo.java
index cf9cfdd..6b2a820 100644
--- a/empire-db-maven-plugin/src/main/java/org/apache/empire/db/maven/CodeGenMojo.java
+++ b/empire-db-maven-plugin/src/main/java/org/apache/empire/db/maven/CodeGenMojo.java
@@ -20,10 +20,8 @@
 
 import java.io.File;
 
-import org.apache.empire.db.DBDatabase;
 import org.apache.empire.db.codegen.CodeGenConfig;
-import org.apache.empire.db.codegen.CodeGenParser;
-import org.apache.empire.db.codegen.CodeGenWriter;
+import org.apache.empire.db.codegen.CodeGenerator;
 import org.apache.empire.exceptions.InvalidPropertyException;
 import org.apache.log4j.AppenderSkeleton;
 import org.apache.log4j.Level;
@@ -87,6 +85,12 @@
 	 */
     @Parameter(property = "empiredb.jdbcPwd")
 	private String jdbcPwd;
+    
+    /**
+     * DBMSHandler class name
+     */
+    @Parameter(property = "empiredb.dbmsHandlerClass")
+    private String dbmsHandlerClass;
 	
 	/**
 	 * Code generator template directory, if not set the default templates
@@ -147,6 +151,7 @@
 			config.setJdbcClass(jdbcClass);
 			config.setJdbcUser(jdbcUser);
 			config.setJdbcPwd(jdbcPwd);
+			config.setDbmsHandlerClass(dbmsHandlerClass);
 			config.setTargetFolder(targetDirectory.getAbsolutePath());
 			config.setTemplateFolder(templateDirectory);
 			config.setPackageName(packageName);
@@ -162,11 +167,9 @@
 		
 		getLog().info("Generating code for " + jdbcURL + " ...");
 		
-		CodeGenParser parser = new CodeGenParser(config);
-		DBDatabase db = parser.loadDbModel();
-		
-		CodeGenWriter codeGen = new CodeGenWriter(config);
-		codeGen.generateCodeFiles(db);
+		// generate now
+		CodeGenerator codeGen = new CodeGenerator();
+		codeGen.generate(config);
 		
 		getLog().info("Code successfully generated in: " + targetDirectory);
 		
diff --git a/empire-db/src/main/java/org/apache/empire/db/DBUtils.java b/empire-db/src/main/java/org/apache/empire/db/DBUtils.java
index 92b1890..2e1fda8 100644
--- a/empire-db/src/main/java/org/apache/empire/db/DBUtils.java
+++ b/empire-db/src/main/java/org/apache/empire/db/DBUtils.java
@@ -95,7 +95,7 @@
             int affected = dbms.executeSQL(sqlCmd, sqlParams, context.getConnection(), setGenKeys);
             // number of affected records
             if (affected < 0)
-                throw new UnexpectedReturnValueException(affected, "dbms.executeSQL()");
+                log.warn("Unexpected return value {} from dbms.executeSQL(\"{}\")", affected, sqlCmd);
             // Log
             long execTime = (System.currentTimeMillis() - start);
             if (log.isInfoEnabled())
diff --git a/empire-db/src/main/java/org/apache/empire/db/validation/DBModelChecker.java b/empire-db/src/main/java/org/apache/empire/db/validation/DBModelChecker.java
index ad09038..817c8bd 100644
--- a/empire-db/src/main/java/org/apache/empire/db/validation/DBModelChecker.java
+++ b/empire-db/src/main/java/org/apache/empire/db/validation/DBModelChecker.java
@@ -19,30 +19,15 @@
 package org.apache.empire.db.validation;

 

 import java.sql.Connection;

-import java.sql.DatabaseMetaData;

-import java.sql.ResultSet;

-import java.sql.ResultSetMetaData;

-import java.sql.SQLException;

-import java.sql.Types;

-import java.util.ArrayList;

-import java.util.Collection;

-import java.util.HashMap;

 import java.util.List;

-import java.util.Map;

 

-import org.apache.empire.commons.StringUtils;

-import org.apache.empire.data.DataType;

 import org.apache.empire.db.DBColumn;

-import org.apache.empire.db.DBCommandExpr;

 import org.apache.empire.db.DBDatabase;

 import org.apache.empire.db.DBRelation;

 import org.apache.empire.db.DBRelation.DBReference;

-import org.apache.empire.db.DBRowSet;

 import org.apache.empire.db.DBTable;

 import org.apache.empire.db.DBTableColumn;

 import org.apache.empire.db.DBView;

-import org.apache.empire.exceptions.InternalException;

-import org.apache.empire.exceptions.NotSupportedException;

 import org.apache.empire.exceptions.ObjectNotValidException;

 import org.slf4j.Logger;

 import org.slf4j.LoggerFactory;

@@ -50,66 +35,19 @@
 public class DBModelChecker

 {

     private static final Logger log = LoggerFactory.getLogger(DBModelChecker.class);

-

-    /**

-     * The remote Database

-     * This will be populated by the ModelChecker

-     */

-    private static class RemoteDatabase extends DBDatabase

-    {

-        /*

-         * Will be dynamically populated

-         */

-    }

     

-    private static class RemoteView extends DBView

-    {

-        public RemoteView(String name, DBDatabase db)

-        {

-            super(name, db);

-        }

+    protected final DBModelParser modelParser;

 

-        public DBColumn addColumn(String columnName, DataType dataType, double size, boolean dummy)

-        {

-            return super.addColumn(columnName, dataType, size);

-        }

-        

-        @Override

-        public DBCommandExpr createCommand()

-        {

-            throw new NotSupportedException(this, "createCommand");

-        }

-    }

-

-    protected final String catalog;

-    protected final String schemaPattern;

-

-    protected final String remoteName;

-    protected DBDatabase remoteDb = null;  /* will be recreated on every call to checkModel */

-

-    protected final Map<String, DBRowSet> tableMap = new HashMap<String, DBRowSet>();

-    

+    protected DBDatabase remoteDb = null; 

+            

     /**

      * Creates a new Model Checker

      * @param catalog

      * @param schemaPattern

      */

-    public DBModelChecker(String catalog, String schemaPattern)

+    public DBModelChecker(DBModelParser modelParser)

     {

-        this.catalog = catalog;

-        this.schemaPattern = schemaPattern;

-        // set origin

-        StringBuilder b = new StringBuilder();

-        if (StringUtils.isNotEmpty(catalog))

-            b.append(catalog);

-        if (StringUtils.isNotEmpty(schemaPattern))

-        {   if (b.length()>0)

-                b.append(".");

-            b.append(schemaPattern);

-        }

-        if (b.length()==0)

-            b.append("[Unknown]");

-        this.remoteName = b.toString();

+        this.modelParser = modelParser;

     }

 

     /**

@@ -131,256 +69,12 @@
     public void checkModel(DBDatabase db, Connection conn, DBModelErrorHandler handler)

     {

         // parse first

-        parseModel(conn);

+        modelParser.parseModel(conn);

+        // set remote

+        this.remoteDb = modelParser.getDatabase();

         // check database

         checkRemoteAgainst(db, handler);

     }

-    

-    /**

-     * This method is used to parse the populate the remote database

-     * @param conn the connection for retrieving the remote database metadata

-     */

-    public void parseModel(Connection conn)

-    {

-        try

-        {   // create remote db instance

-            remoteDb = createRemoteDatabase();

-            // populate

-            DatabaseMetaData dbMeta = conn.getMetaData();

-            populateRemoteDatabase(dbMeta);

-        }

-        catch (SQLException e)

-        {

-            log.error("checkModel failed for {}", remoteName);

-            throw new InternalException(e);

-        }

-        finally 

-        {   // cleanup

-            tableMap.clear();            

-        }

-    }

-

-    protected void populateRemoteDatabase(DatabaseMetaData dbMeta)

-        throws SQLException

-    {

-        // collect tables & views

-        int count = collectTablesAndViews(dbMeta, null);

-        log.info("{} tables and views added for schema \"{}\"", count, remoteName);

-

-        // Collect all columns

-        count = collectColumns(dbMeta);

-        log.info("{} columns added for schema \"{}\"", count, remoteName);

-

-        // Collect PKs

-        count = collectPrimaryKeys(dbMeta);

-        log.info("{} primary keys added for schema \"{}\"", count, remoteName);

-

-        // Collect FKs

-        count = collectForeignKeys(dbMeta);

-        log.info("{} foreign keys added for schema \"{}\"", count, remoteName);

-    }

-

-    /**

-     * Checks if the tableName belongs to a system or hidden table

-     * @param tableName the table name

-     * @param tableMeta the table meta

-     * @return true if the table is hidden or false otherwise

-     */

-    protected boolean isSystemTable(String tableName, ResultSet tableMeta)

-    {   // system tables containing a '$' symbol (required for Oracle!)

-        return (tableName.indexOf('$') >= 0);

-    }

-    

-    /**

-     * collects table and view information from database meta data

-     * @param dbMeta

-     * @param dbSchema

-     * @throws SQLException

-     */

-    protected int collectTablesAndViews(DatabaseMetaData dbMeta, String tablePattern)

-        throws SQLException

-    {

-        tableMap.clear();

-        ResultSet dbTables = dbMeta.getTables(catalog, schemaPattern, tablePattern, new String[] { "TABLE", "VIEW" });

-        try {

-            // ResultSet dbTables = dbMeta.getTables("PATOOL", "DBO", null, new String[] { "TABLE", "VIEW" });

-            int count = 0;

-            while (dbTables.next())

-            {

-                String tableName = dbTables.getString("TABLE_NAME");

-                String tableType = dbTables.getString("TABLE_TYPE");

-                if (isSystemTable(tableName, dbTables))

-                {   // ignore system table

-                    DBModelChecker.log.info("Ignoring system table " + tableName);

-                    continue;

-                }

-                if ("VIEW".equalsIgnoreCase(tableType))

-                    addView(tableName);

-                else

-                    addTable(tableName);

-                count++;

-            }

-            return count;

-        } finally {

-            dbTables.close();

-        }

-    }

-

-    /**

-     * collects column information from database meta data for each table

-     */

-    protected int collectColumns(DatabaseMetaData dbMeta)

-            throws SQLException

-    {

-        int count = 0;

-        for (DBRowSet t : getTables())

-        {

-            ResultSet dbColumns = dbMeta.getColumns(catalog, schemaPattern, t.getName(), null);

-            try {

-                while (dbColumns.next())

-                {   // add the column

-                    addColumn(t, dbColumns);

-                    count++;

-                }

-            } finally {

-                dbColumns.close();

-            }

-        }

-        return count;

-    }

-

-    /**

-     * collects column information from database meta data for whole schema

-     */

-    protected int collectColumns(DatabaseMetaData dbMeta, String tablePattern)

-        throws SQLException

-    {

-        ResultSet dbColumns = dbMeta.getColumns(catalog, schemaPattern, tablePattern, null);

-        try {

-            int count = 0;

-            while (dbColumns.next())

-            {

-                String tableName = dbColumns.getString("TABLE_NAME");

-                DBRowSet t = getTable(tableName);

-                if (t == null)

-                {   log.error("Table not found: {}", tableName);

-                    continue;

-                }

-                addColumn(t, dbColumns);

-                count++;

-            }

-            return count;

-        } finally {

-            dbColumns.close();

-        }

-    }

-    

-    /**

-     * collects primary key information from database meta data

-     * @param dbMeta

-     * @param dbSchema

-     * @throws SQLException

-     */

-    protected int collectPrimaryKeys(DatabaseMetaData dbMeta)

-        throws SQLException

-    {

-        int count = 0;

-        for (DBRowSet rs : getTables())

-        {

-            if (!(rs instanceof DBTable))

-                continue; // not a table

-            // read pk

-            DBTable t = (DBTable)rs;

-            List<String> pkCols = new ArrayList<String>();

-            ResultSet primaryKeys = dbMeta.getPrimaryKeys(catalog, schemaPattern, t.getName());

-            try {

-                while (primaryKeys.next())

-                {

-                    pkCols.add(primaryKeys.getString("COLUMN_NAME"));

-                }

-                if (pkCols.size() > 0)

-                {

-                    DBColumn[] keys = new DBColumn[pkCols.size()];

-                    for (int i = 0; i < keys.length; i++)

-                    {

-                        keys[i] = t.getColumn(pkCols.get(i).toUpperCase());

-                    }

-                    t.setPrimaryKey(keys);

-                    count++;

-                }

-            } finally {

-                primaryKeys.close();

-            }

-        }

-        return count;

-    }

-

-    /**

-     * collects foreign key information from database meta data

-     * @throws SQLException

-     */

-    protected int collectForeignKeys(DatabaseMetaData dbMeta)

-            throws SQLException

-    {

-        int count = 0;

-        for (DBRowSet t : getTables())

-        {

-            if (t instanceof DBTable)

-                count += collectForeignKeys(dbMeta, t.getName());

-        }

-        return count;

-    }

-    

-    /**

-     * collects foreign key information from database meta data

-     * @throws SQLException

-     */

-    protected int collectForeignKeys(DatabaseMetaData dbMeta, String tablePattern)

-        throws SQLException

-    {

-        ResultSet foreignKeys = dbMeta.getImportedKeys(catalog, schemaPattern, tablePattern);

-        try {

-            int count = 0;

-            while (foreignKeys.next())

-            {

-                String fkTable = foreignKeys.getString("FKTABLE_NAME");

-                String fkColumn = foreignKeys.getString("FKCOLUMN_NAME");

-

-                String pkTable = foreignKeys.getString("PKTABLE_NAME");

-                String pkColumn = foreignKeys.getString("PKCOLUMN_NAME");

-

-                String fkName = foreignKeys.getString("FK_NAME");

-

-                DBTableColumn c1 = (DBTableColumn) getTable(fkTable).getColumn(fkColumn.toUpperCase());

-                DBTableColumn c2 = (DBTableColumn) getTable(pkTable).getColumn(pkColumn.toUpperCase());

-

-                DBRelation relation = this.remoteDb.getRelation(fkName);

-                if (relation == null)

-                {

-                    addRelation(fkName, c1.referenceOn(c2));

-                    count++;

-                }

-                else

-                {   // get existing references

-                    DBReference[] refs = relation.getReferences();

-                    // remove old

-                    this.remoteDb.removeRelation(relation);

-                    DBReference[] newRefs = new DBReference[refs.length + 1];

-                    // copy existing

-                    DBReference newRef = new DBReference(c1, c2);

-                    for (int i = 0; i < refs.length; i++)

-                    {

-                        newRefs[i] = refs[i];

-                    }

-                    newRefs[newRefs.length - 1] = newRef;

-                    addRelation(fkName, newRefs);

-                }

-            }

-            return count;

-        } finally {

-            foreignKeys.close();

-        }

-    }

 

     /**

      * Check the remote database against an existing model

@@ -715,194 +409,4 @@
         checkGenericColumn(column, remoteColumn, handler);

     }

 

-    /*

-     * internal methods

-     */

-    protected final Collection<DBRowSet> getTables()

-    {

-        return this.tableMap.values();

-    }

-    

-    protected final DBRowSet getTable(String tableName)

-    {

-        return this.tableMap.get(tableName.toUpperCase());

-    }

-

-    protected DBDatabase createRemoteDatabase()

-    {

-        return new RemoteDatabase();

-    }

-    

-    protected void addTable(String tableName)

-    {

-        this.tableMap.put(tableName.toUpperCase(), new DBTable(tableName, this.remoteDb));

-    }

-    

-    protected void addView(String viewName)

-    {

-        this.tableMap.put(viewName.toUpperCase(), new RemoteView(viewName, this.remoteDb));

-    }

-    

-    protected void addRelation(String relName, DBReference... references)

-    {

-        this.remoteDb.addRelation(relName, references);

-    }

-    

-    protected DBColumn addColumn(DBRowSet t, ResultSet rs)

-        throws SQLException

-    {

-        String name = rs.getString("COLUMN_NAME");

-        DataType empireType = getEmpireDataType(rs.getInt("DATA_TYPE"));

-

-        double colSize = rs.getInt("COLUMN_SIZE");

-        if (empireType == DataType.DECIMAL || empireType == DataType.FLOAT)

-        { // decimal digits

-            int decimalDig = rs.getInt("DECIMAL_DIGITS");

-            if (decimalDig > 0)

-            {   try

-                {   // concat and parse

-                    int intSize = rs.getInt("COLUMN_SIZE");

-                    colSize = Double.parseDouble(String.valueOf(intSize) + '.' + decimalDig);

-                }

-                catch (Exception e)

-                {

-                    DBModelChecker.log.error("Failed to parse decimal digits for column " + name);

-                }

-            }

-            // make integer?

-            if (colSize < 1.0d)

-            { // Turn into an integer

-                empireType = DataType.INTEGER;

-            }

-        } 

-        else if (empireType == DataType.INTEGER || empireType == DataType.CLOB || empireType == DataType.BLOB)

-        {

-            colSize = 0.0;

-        }

-

-        // mandatory field?

-        boolean required = false;

-        String defaultValue = rs.getString("COLUMN_DEF");

-        if (rs.getString("IS_NULLABLE").equalsIgnoreCase("NO"))

-        {

-            required = true;

-        }

-

-        // The following is a hack for MySQL which currently gets sent a string "CURRENT_TIMESTAMP" from the Empire-db dbms for MySQL.

-        // This will avoid the dbms problem because CURRENT_TIMESTAMP in the db will just do the current datetime.

-        // Essentially, Empire-db needs the concept of default values of one type that get mapped to another.

-        // In this case, MySQL "CURRENT_TIMESTAMP" for Types.TIMESTAMP needs to emit from the Empire-db dbms the null value and not "CURRENT_TIMESTAMP".

-        if (rs.getInt("DATA_TYPE") == Types.TIMESTAMP && defaultValue != null && defaultValue.equals("CURRENT_TIMESTAMP"))

-        {

-            required = false; // It is in fact not required even though MySQL schema is required because it has a default value. Generally, should Empire-db emit (required && defaultValue != null) to truly determine if a column is required?

-            defaultValue = null; // If null (and required per schema?) MySQL will apply internal default value.

-        }

-

-        // AUTOINC indicator is not in java.sql.Types but rather meta data from DatabaseMetaData.getColumns()

-        // getEmpireDataType() above is not enough to support AUTOINC as it will only return DataType.INTEGER

-        DataType originalType = empireType;

-        ResultSetMetaData metaData = rs.getMetaData();

-        int colCount = metaData.getColumnCount();

-        String colName;

-        for (int i = 1; i <= colCount; i++)

-        {

-            colName = metaData.getColumnName(i);

-            // MySQL matches on IS_AUTOINCREMENT column.

-            // SQL Server matches on TYPE_NAME column with identity somewhere in the string value.

-            if ((colName.equalsIgnoreCase("IS_AUTOINCREMENT") && rs.getString(i).equalsIgnoreCase("YES"))

-                || (colName.equals("TYPE_NAME") && rs.getString(i).matches(".*(?i:identity).*")))

-            {

-                empireType = DataType.AUTOINC;

-

-            }

-        }

-

-        // Move from the return statement below so we can add

-        // some AUTOINC meta data to the column to be used by

-        // the ParserUtil and ultimately the template.

-        //        DBModelChecker.log.info("\tCOLUMN:\t" + name + " (" + empireType + ")");

-        DBColumn col;

-        if (t instanceof DBTable)

-        {

-            col = ((DBTable)t).addColumn(name, empireType, colSize, required, defaultValue);

-            // We still need to know the base data type for this AUTOINC

-            // because the Record g/setters need to know this, right?

-            // So, let's add it as meta data every time the column is AUTOINC

-            // and reference it in the template.

-            if (empireType.equals(DataType.AUTOINC))

-            {

-                col.setAttribute("AutoIncDataType", originalType);

-            }

-        }

-        else if (t instanceof RemoteView)

-        {

-           col = ((RemoteView)t).addColumn(name, empireType, colSize, false);

-        }

-        else

-        {   // Unknown type

-            log.error("Unknown Object Type {}", t.getClass().getName());

-            col = null;

-        }

-        // done

-        return col;

-

-    }

-

-    protected DataType getEmpireDataType(int sqlType)

-    {

-        DataType empireType = DataType.UNKNOWN;

-        switch (sqlType)

-        {

-            case Types.INTEGER:

-            case Types.SMALLINT:

-            case Types.TINYINT:

-            case Types.BIGINT:

-                empireType = DataType.INTEGER;

-                break;

-            case Types.VARCHAR:

-            case Types.NVARCHAR:

-                empireType = DataType.VARCHAR;

-                break;

-            case Types.DATE:

-                empireType = DataType.DATE;

-                break;

-            case Types.TIMESTAMP:

-            case Types.TIME:

-                empireType = DataType.DATETIME;

-                break;

-            case Types.CHAR:

-            case Types.NCHAR:

-                empireType = DataType.CHAR;

-                break;

-            case Types.DOUBLE:

-            case Types.FLOAT:

-            case Types.REAL:

-                empireType = DataType.FLOAT;

-                break;

-            case Types.DECIMAL:

-            case Types.NUMERIC:

-                empireType = DataType.DECIMAL;

-                break;

-            case Types.BIT:

-            case Types.BOOLEAN:

-                empireType = DataType.BOOL;

-                break;

-            case Types.CLOB:

-            case Types.LONGVARCHAR:

-            case Types.LONGNVARCHAR:

-                empireType = DataType.CLOB;

-                break;

-            case Types.BINARY:

-            case Types.VARBINARY:

-            case Types.LONGVARBINARY:

-            case Types.BLOB:

-                empireType = DataType.BLOB;

-                break;

-            default:

-                empireType = DataType.UNKNOWN;

-                DBModelChecker.log.warn("SQL column type " + sqlType + " not supported.");

-        }

-        DBModelChecker.log.debug("Mapping date type " + String.valueOf(sqlType) + " to " + empireType);

-        return empireType;

-    }

 }

diff --git a/empire-db/src/main/java/org/apache/empire/db/validation/DBModelParser.java b/empire-db/src/main/java/org/apache/empire/db/validation/DBModelParser.java
new file mode 100644
index 0000000..b2492ef
--- /dev/null
+++ b/empire-db/src/main/java/org/apache/empire/db/validation/DBModelParser.java
@@ -0,0 +1,584 @@
+/*
+ * 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.empire.db.validation;
+
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Types;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.empire.commons.StringUtils;
+import org.apache.empire.data.DataType;
+import org.apache.empire.db.DBColumn;
+import org.apache.empire.db.DBCommandExpr;
+import org.apache.empire.db.DBDatabase;
+import org.apache.empire.db.DBRelation;
+import org.apache.empire.db.DBRelation.DBReference;
+import org.apache.empire.db.DBRowSet;
+import org.apache.empire.db.DBTable;
+import org.apache.empire.db.DBTableColumn;
+import org.apache.empire.db.DBView;
+import org.apache.empire.exceptions.InternalException;
+import org.apache.empire.exceptions.NotSupportedException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class DBModelParser
+{
+    protected static final Logger log = LoggerFactory.getLogger(DBModelParser.class);
+
+    /**
+     * The remote Database
+     * This will be populated by the ModelChecker
+     */
+    private static class RemoteDatabase extends DBDatabase
+    {
+        /*
+         * Will be dynamically populated
+         */
+    }
+    
+    private static class RemoteView extends DBView
+    {
+        public RemoteView(String name, DBDatabase db)
+        {
+            super(name, db);
+        }
+
+        public DBColumn addColumn(String columnName, DataType dataType, double size, boolean dummy)
+        {
+            return super.addColumn(columnName, dataType, size);
+        }
+        
+        @Override
+        public DBCommandExpr createCommand()
+        {
+            throw new NotSupportedException(this, "createCommand");
+        }
+    }
+
+    protected final String catalog;
+    protected final String schemaPattern;
+
+    protected final String remoteName;
+    protected DBDatabase remoteDb = null;  /* will be recreated on every call to checkModel */
+
+    protected final Map<String, DBRowSet> tableMap = new HashMap<String, DBRowSet>();
+    
+    private String standardIdentityColumnName  = null;
+    private String standardTimestampColumnName = null;
+    
+    /**
+     * Creates a new Model Checker
+     * @param catalog
+     * @param schemaPattern
+     */
+    public DBModelParser(String catalog, String schemaPattern)
+    {
+        this.catalog = catalog;
+        this.schemaPattern = schemaPattern;
+        // set origin
+        StringBuilder b = new StringBuilder();
+        if (StringUtils.isNotEmpty(catalog))
+            b.append(catalog);
+        if (StringUtils.isNotEmpty(schemaPattern))
+        {   if (b.length()>0)
+                b.append(".");
+            b.append(schemaPattern);
+        }
+        if (b.length()==0)
+            b.append("[Unknown]");
+        this.remoteName = b.toString();
+    }
+
+    public void setStandardIdentityColumnName(String standardIdentityColumnName)
+    {
+        this.standardIdentityColumnName = standardIdentityColumnName;
+    }
+
+    public void setStandardTimestampColumnName(String standardTimestampColumnName)
+    {
+        this.standardTimestampColumnName = standardTimestampColumnName;
+    }
+
+    /**
+     * Returns the RemoteDatabase
+     * Only available after parseModel() is called 
+     * @return the remote Database
+     */
+    public DBDatabase getDatabase()
+    {
+        return remoteDb;
+    }
+    
+    /**
+     * This method is used to parse the populate the remote database
+     * @param conn the connection for retrieving the remote database metadata
+     */
+    public void parseModel(Connection conn)
+    {
+        try
+        {   // create remote db instance
+            remoteDb = createRemoteDatabase();
+            // populate
+            DatabaseMetaData dbMeta = conn.getMetaData();
+            populateRemoteDatabase(dbMeta);
+        }
+        catch (SQLException e)
+        {
+            log.error("checkModel failed for {}", remoteName);
+            throw new InternalException(e);
+        }
+        finally 
+        {   // cleanup
+            tableMap.clear();            
+        }
+    }
+
+    protected void populateRemoteDatabase(DatabaseMetaData dbMeta)
+        throws SQLException
+    {
+        // collect tables & views
+        int count = collectTablesAndViews(dbMeta, null);
+        log.info("{} tables and views added for schema \"{}\"", count, remoteName);
+
+        // Collect all columns
+        count = collectColumns(dbMeta);
+        log.info("{} columns added for schema \"{}\"", count, remoteName);
+
+        // Collect PKs
+        count = collectPrimaryKeys(dbMeta);
+        log.info("{} primary keys added for schema \"{}\"", count, remoteName);
+
+        // Collect FKs
+        count = collectForeignKeys(dbMeta);
+        log.info("{} foreign keys added for schema \"{}\"", count, remoteName);
+    }
+
+    /**
+     * Checks if the tableName belongs to a system or hidden table
+     * @param tableName the table name
+     * @param tableMeta the table meta
+     * @return true if the table is hidden or false otherwise
+     */
+    protected boolean isSystemTable(String tableName, ResultSet tableMeta)
+    {   // system tables containing a '$' symbol (required for Oracle!)
+        return (tableName.indexOf('$') >= 0);
+    }
+    
+    /**
+     * collects table and view information from database meta data
+     * @param dbMeta
+     * @param dbSchema
+     * @throws SQLException
+     */
+    protected int collectTablesAndViews(DatabaseMetaData dbMeta, String tablePattern)
+        throws SQLException
+    {
+        tableMap.clear();
+        ResultSet dbTables = dbMeta.getTables(catalog, schemaPattern, tablePattern, new String[] { "TABLE", "VIEW" });
+        try {
+            // ResultSet dbTables = dbMeta.getTables("PATOOL", "DBO", null, new String[] { "TABLE", "VIEW" });
+            int count = 0;
+            while (dbTables.next())
+            {
+                String tableName = dbTables.getString("TABLE_NAME");
+                String tableType = dbTables.getString("TABLE_TYPE");
+                if (isSystemTable(tableName, dbTables))
+                {   // ignore system table
+                    DBModelParser.log.info("Ignoring system table " + tableName);
+                    continue;
+                }
+                if ("VIEW".equalsIgnoreCase(tableType))
+                    addView(tableName);
+                else
+                    addTable(tableName);
+                count++;
+            }
+            return count;
+        } finally {
+            dbTables.close();
+        }
+    }
+
+    /**
+     * collects column information from database meta data for each table
+     */
+    protected int collectColumns(DatabaseMetaData dbMeta)
+            throws SQLException
+    {
+        int count = 0;
+        for (DBRowSet t : getTables())
+        {
+            ResultSet dbColumns = dbMeta.getColumns(catalog, schemaPattern, t.getName(), null);
+            try {
+                while (dbColumns.next())
+                {   // add the column
+                    addColumn(t, dbColumns);
+                    count++;
+                }
+            } finally {
+                dbColumns.close();
+            }
+        }
+        return count;
+    }
+
+    /**
+     * collects column information from database meta data for whole schema
+     */
+    protected int collectColumns(DatabaseMetaData dbMeta, String tablePattern)
+        throws SQLException
+    {
+        ResultSet dbColumns = dbMeta.getColumns(catalog, schemaPattern, tablePattern, null);
+        try {
+            int count = 0;
+            while (dbColumns.next())
+            {
+                String tableName = dbColumns.getString("TABLE_NAME");
+                DBRowSet t = getTable(tableName);
+                if (t == null)
+                {   log.error("Table not found: {}", tableName);
+                    continue;
+                }
+                addColumn(t, dbColumns);
+                count++;
+            }
+            return count;
+        } finally {
+            dbColumns.close();
+        }
+    }
+    
+    /**
+     * collects primary key information from database meta data
+     * @param dbMeta
+     * @param dbSchema
+     * @throws SQLException
+     */
+    protected int collectPrimaryKeys(DatabaseMetaData dbMeta)
+        throws SQLException
+    {
+        int count = 0;
+        for (DBRowSet rs : getTables())
+        {
+            if (!(rs instanceof DBTable))
+                continue; // not a table
+            // read pk
+            DBTable t = (DBTable)rs;
+            List<String> pkCols = new ArrayList<String>();
+            ResultSet primaryKeys = dbMeta.getPrimaryKeys(catalog, schemaPattern, t.getName());
+            try {
+                while (primaryKeys.next())
+                {
+                    pkCols.add(primaryKeys.getString("COLUMN_NAME"));
+                }
+                if (pkCols.size() > 0)
+                {
+                    DBColumn[] keys = new DBColumn[pkCols.size()];
+                    for (int i = 0; i < keys.length; i++)
+                    {
+                        keys[i] = t.getColumn(pkCols.get(i).toUpperCase());
+                    }
+                    t.setPrimaryKey(keys);
+                    count++;
+                }
+            } finally {
+                primaryKeys.close();
+            }
+        }
+        return count;
+    }
+
+    /**
+     * collects foreign key information from database meta data
+     * @throws SQLException
+     */
+    protected int collectForeignKeys(DatabaseMetaData dbMeta)
+            throws SQLException
+    {
+        int count = 0;
+        for (DBRowSet t : getTables())
+        {
+            if (t instanceof DBTable)
+                count += collectForeignKeys(dbMeta, t.getName());
+        }
+        return count;
+    }
+    
+    /**
+     * collects foreign key information from database meta data
+     * @throws SQLException
+     */
+    protected int collectForeignKeys(DatabaseMetaData dbMeta, String tablePattern)
+        throws SQLException
+    {
+        ResultSet foreignKeys = dbMeta.getImportedKeys(catalog, schemaPattern, tablePattern);
+        try {
+            int count = 0;
+            while (foreignKeys.next())
+            {
+                String fkTable = foreignKeys.getString("FKTABLE_NAME");
+                String fkColumn = foreignKeys.getString("FKCOLUMN_NAME");
+
+                String pkTable = foreignKeys.getString("PKTABLE_NAME");
+                String pkColumn = foreignKeys.getString("PKCOLUMN_NAME");
+
+                String fkName = foreignKeys.getString("FK_NAME");
+
+                DBTableColumn c1 = (DBTableColumn) getTable(fkTable).getColumn(fkColumn.toUpperCase());
+                DBTableColumn c2 = (DBTableColumn) getTable(pkTable).getColumn(pkColumn.toUpperCase());
+
+                DBRelation relation = this.remoteDb.getRelation(fkName);
+                if (relation == null)
+                {
+                    addRelation(fkName, c1.referenceOn(c2));
+                    count++;
+                }
+                else
+                {   // get existing references
+                    DBReference[] refs = relation.getReferences();
+                    // remove old
+                    this.remoteDb.removeRelation(relation);
+                    DBReference[] newRefs = new DBReference[refs.length + 1];
+                    // copy existing
+                    DBReference newRef = new DBReference(c1, c2);
+                    for (int i = 0; i < refs.length; i++)
+                    {
+                        newRefs[i] = refs[i];
+                    }
+                    newRefs[newRefs.length - 1] = newRef;
+                    addRelation(fkName, newRefs);
+                }
+            }
+            return count;
+        } finally {
+            foreignKeys.close();
+        }
+    }
+
+    /*
+     * internal methods
+     */
+    protected final Collection<DBRowSet> getTables()
+    {
+        return this.tableMap.values();
+    }
+    
+    protected final DBRowSet getTable(String tableName)
+    {
+        return this.tableMap.get(tableName.toUpperCase());
+    }
+
+    protected DBDatabase createRemoteDatabase()
+    {
+        return new RemoteDatabase();
+    }
+    
+    protected void addTable(String tableName)
+    {
+        this.tableMap.put(tableName.toUpperCase(), new DBTable(tableName, this.remoteDb));
+    }
+    
+    protected void addView(String viewName)
+    {
+        this.tableMap.put(viewName.toUpperCase(), new RemoteView(viewName, this.remoteDb));
+    }
+    
+    protected void addRelation(String relName, DBReference... references)
+    {
+        this.remoteDb.addRelation(relName, references);
+    }
+    
+    protected DBColumn addColumn(DBRowSet t, ResultSet rs)
+        throws SQLException
+    {
+        String name = rs.getString("COLUMN_NAME");
+        DataType empireType = getEmpireDataType(rs.getInt("DATA_TYPE"));
+
+        // get Size
+        double colSize = getColumnSize(empireType, rs);
+
+        // mandatory field?
+        boolean required = isColumnRequired(rs);
+        Object defaultValue = getColumnDefault(rs);
+
+        // Now add the column to table / view
+        DBColumn col;
+        if (t instanceof DBTable)
+        {   // check Identity and Timestamp
+            boolean timestampColumn = false;
+            if (empireType==DataType.INTEGER && isIdentityColumn(rs))
+                empireType= DataType.AUTOINC;
+            if (empireType.isDate() && (timestampColumn=isTimestampColumn(rs)))
+                empireType= DataType.TIMESTAMP;
+            // Add Column
+            col = ((DBTable)t).addColumn(name, empireType, colSize, required, defaultValue);
+            // Set Timestamp
+            if (empireType==DataType.AUTOINC)
+                ((DBTable) t).setPrimaryKey(col);
+            if (timestampColumn)
+                t.setTimestampColumn(col);
+            // info
+            log.info("Added table column {}.{} of type {}", t.getName(), name, empireType);
+        }
+        else if (t instanceof DBView)
+        {
+            col = ((RemoteView)t).addColumn(name, empireType, colSize, false);
+            log.info("Added view column {}.{} of type {}", t.getName(), name, empireType);
+        }
+        else
+        {   // Unknown type
+            log.error("Unknown Object Type {}", t.getClass().getName());
+            col = null;
+        }
+        // done
+        return col;
+    }
+    
+    protected double getColumnSize(DataType empireType, ResultSet rs)
+        throws SQLException
+    {
+        double colSize = rs.getInt("COLUMN_SIZE");
+        if (empireType == DataType.DECIMAL || empireType == DataType.FLOAT)
+        { // decimal digits
+            int decimalDig = rs.getInt("DECIMAL_DIGITS");
+            if (decimalDig > 0)
+            {   try
+                {   // concat and parse
+                    int intSize = rs.getInt("COLUMN_SIZE");
+                    colSize = Double.parseDouble(String.valueOf(intSize) + '.' + decimalDig);
+                }
+                catch (Exception e)
+                {
+                    String name = rs.getString("COLUMN_NAME");
+                    DBModelParser.log.error("Failed to parse decimal digits for column " + name);
+                }
+            }
+            // make integer?
+            if (colSize < 1.0d)
+            { // Turn into an integer
+                empireType = DataType.INTEGER;
+            }
+        } 
+        else if (empireType.isDate())
+        {
+            colSize = 0.0;
+        }
+        else if (empireType == DataType.INTEGER || empireType == DataType.CLOB || empireType == DataType.BLOB)
+        {
+            colSize = 0.0;
+        }
+        return colSize;
+    }
+    
+    protected boolean isColumnRequired(ResultSet rs)
+        throws SQLException
+    {
+        return rs.getString("IS_NULLABLE").equalsIgnoreCase("NO");
+    }
+    
+    protected Object getColumnDefault(ResultSet rs)
+        throws SQLException
+    {
+        return rs.getString("COLUMN_DEF");
+    }
+    
+    protected boolean isIdentityColumn(ResultSet rs)
+    {   try {
+            return (standardIdentityColumnName!=null &&
+                    standardIdentityColumnName.equalsIgnoreCase(rs.getString("COLUMN_NAME")));
+        } catch(SQLException e) {
+            return false;
+        }
+    }
+    
+    protected boolean isTimestampColumn(ResultSet rs)
+    {   try {
+            return (standardTimestampColumnName!=null &&
+                    standardTimestampColumnName.equalsIgnoreCase(rs.getString("COLUMN_NAME")));
+        } catch(SQLException e) {
+            return false;
+        }
+    }
+
+    protected DataType getEmpireDataType(int sqlType)
+    {
+        DataType empireType = DataType.UNKNOWN;
+        switch (sqlType)
+        {
+            case Types.INTEGER:
+            case Types.SMALLINT:
+            case Types.TINYINT:
+            case Types.BIGINT:
+                empireType = DataType.INTEGER;
+                break;
+            case Types.VARCHAR:
+            case Types.NVARCHAR:
+                empireType = DataType.VARCHAR;
+                break;
+            case Types.DATE:
+                empireType = DataType.DATE;
+                break;
+            case Types.TIMESTAMP:
+            case Types.TIME:
+                empireType = DataType.DATETIME;
+                break;
+            case Types.CHAR:
+            case Types.NCHAR:
+                empireType = DataType.CHAR;
+                break;
+            case Types.DOUBLE:
+            case Types.FLOAT:
+            case Types.REAL:
+                empireType = DataType.FLOAT;
+                break;
+            case Types.DECIMAL:
+            case Types.NUMERIC:
+                empireType = DataType.DECIMAL;
+                break;
+            case Types.BIT:
+            case Types.BOOLEAN:
+                empireType = DataType.BOOL;
+                break;
+            case Types.CLOB:
+            case Types.LONGVARCHAR:
+            case Types.LONGNVARCHAR:
+                empireType = DataType.CLOB;
+                break;
+            case Types.BINARY:
+            case Types.VARBINARY:
+            case Types.LONGVARBINARY:
+            case Types.BLOB:
+                empireType = DataType.BLOB;
+                break;
+            default:
+                empireType = DataType.UNKNOWN;
+                DBModelParser.log.warn("SQL column type " + sqlType + " not supported.");
+        }
+        DBModelParser.log.debug("Mapping date type " + String.valueOf(sqlType) + " to " + empireType);
+        return empireType;
+    }
+}
diff --git a/empire-db/src/main/java/org/apache/empire/dbms/DBMSHandler.java b/empire-db/src/main/java/org/apache/empire/dbms/DBMSHandler.java
index 3da3c1e..e3b7cd4 100644
--- a/empire-db/src/main/java/org/apache/empire/dbms/DBMSHandler.java
+++ b/empire-db/src/main/java/org/apache/empire/dbms/DBMSHandler.java
@@ -34,6 +34,7 @@
 import org.apache.empire.db.DBSQLScript;
 import org.apache.empire.db.DBTableColumn;
 import org.apache.empire.db.validation.DBModelChecker;
+import org.apache.empire.db.validation.DBModelParser;
 
 /**
  * The DBMSHandler interface implements all RDBMS specific logic
@@ -240,6 +241,12 @@
      * @param script the script to which to add the DDL command(s)
      */
     void appendEnableRelationStmt(DBRelation r, boolean enable, DBSQLScript script);
+    
+    /**
+     * Creates a DataModelParser instance of this DBMSHandler
+     * @return
+     */
+    DBModelParser createModelParser(String catalog, String schema);
  
     /**
      * Creates a DataModelChecker instance of this DBMSHandler
diff --git a/empire-db/src/main/java/org/apache/empire/dbms/DBMSHandlerBase.java b/empire-db/src/main/java/org/apache/empire/dbms/DBMSHandlerBase.java
index d59223d..2f10919 100644
--- a/empire-db/src/main/java/org/apache/empire/dbms/DBMSHandlerBase.java
+++ b/empire-db/src/main/java/org/apache/empire/dbms/DBMSHandlerBase.java
@@ -56,6 +56,7 @@
 import org.apache.empire.db.exceptions.EmpireSQLException;
 import org.apache.empire.db.exceptions.QueryFailedException;
 import org.apache.empire.db.validation.DBModelChecker;
+import org.apache.empire.db.validation.DBModelParser;
 import org.apache.empire.exceptions.InvalidArgumentException;
 import org.apache.empire.exceptions.NotSupportedException;
 import org.apache.empire.exceptions.UnexpectedReturnValueException;
@@ -786,6 +787,16 @@
     }
 
     /**
+     * Creates a DataModelParser instance of this DBMSHandler
+     * @return
+     */
+    @Override
+    public DBModelParser createModelParser(String catalog, String schema)
+    {
+        return new DBModelParser(catalog, schema);
+    }
+    
+    /**
      * Creates a DataModelChecker instance of this DBMSHandler
      * @return
      */
@@ -794,7 +805,8 @@
     {
         log.warn("A general and possibly untested DBModelChecker is used for DBMSHandler {}. Please override to inklude DBMS specific features.", getClass().getSimpleName());
         // the default model checker
-        return new DBModelChecker(null, null);
+        DBModelParser modelParser = createModelParser(null, db.getSchema());
+        return new DBModelChecker(modelParser);
     }
     
     /**
diff --git a/empire-db/src/main/java/org/apache/empire/dbms/DBSqlPhrase.java b/empire-db/src/main/java/org/apache/empire/dbms/DBSqlPhrase.java
index a964f74..1225ab9 100644
--- a/empire-db/src/main/java/org/apache/empire/dbms/DBSqlPhrase.java
+++ b/empire-db/src/main/java/org/apache/empire/dbms/DBSqlPhrase.java
@@ -61,10 +61,10 @@
     SQL_FUNC_YEAR           ("year(?)"),                // Oracle: extract(year from ?)
 
     // Aggregation
-    SQL_FUNC_SUM            ("sum(?)"),
-    SQL_FUNC_MAX            ("max(?)"),
-    SQL_FUNC_MIN            ("min(?)"),
-    SQL_FUNC_AVG            ("avg(?)"),
+    SQL_FUNC_SUM            ("sum(?)", true),
+    SQL_FUNC_MAX            ("max(?)", true),
+    SQL_FUNC_MIN            ("min(?)", true),
+    SQL_FUNC_AVG            ("avg(?)", true),
 
     // Decode
     SQL_FUNC_DECODE         ("case ? {0} end"),         // Oracle: decode(? {0})
@@ -72,15 +72,27 @@
     SQL_FUNC_DECODE_PART    ("when {0} then {1}"),      // Oracle: {0}, {1}
     SQL_FUNC_DECODE_ELSE    ("else {0}");               // Oracle: {0}
 
-    private String sqlDefault;
+    private final String sqlDefault;
+    private final boolean aggregate;
+    
+    private DBSqlPhrase(String sqlDefault, boolean aggregate)
+    {
+        this.sqlDefault = sqlDefault;
+        this.aggregate = aggregate;
+    }
     
     private DBSqlPhrase(String sqlDefault)
     {
-        this.sqlDefault = sqlDefault;
+        this(sqlDefault, false);
     }
 
     public String getSqlDefault()
     {
         return sqlDefault;
     }
+
+    public boolean isAggregate()
+    {
+        return aggregate;
+    }
 }
diff --git a/empire-db/src/main/java/org/apache/empire/dbms/hsql/DBMSHandlerHSql.java b/empire-db/src/main/java/org/apache/empire/dbms/hsql/DBMSHandlerHSql.java
index 2415305..b4c0825 100644
--- a/empire-db/src/main/java/org/apache/empire/dbms/hsql/DBMSHandlerHSql.java
+++ b/empire-db/src/main/java/org/apache/empire/dbms/hsql/DBMSHandlerHSql.java
@@ -92,7 +92,7 @@
         switch (phrase)
         {
             // sql-phrases
-            case SQL_NULL:        return "null";
+            case SQL_NULL:              return "null";
             case SQL_PARAMETER:         return " ? ";
             case SQL_RENAME_TABLE:      return " ";
             case SQL_RENAME_COLUMN:     return " AS ";
@@ -271,14 +271,14 @@
     }
 
     /**
-     * Creates a DataModelChecker instance of this DBMSHandler
-     * @return
+     * Creates a DBModelChecker instance of this DBMSHandler
+     * @return the DBModelChecker
      */
     @Override
     public DBModelChecker createModelChecker(DBDatabase db)
     {
         // the default model checker
-        return new DBModelChecker("PUBLIC", "PUBLIC");
+        return new DBModelChecker(createModelParser("PUBLIC", "PUBLIC")); 
     }
     
 }
diff --git a/empire-db/src/main/java/org/apache/empire/dbms/mysql/MySQLDBModelParser.java b/empire-db/src/main/java/org/apache/empire/dbms/mysql/MySQLDBModelParser.java
new file mode 100644
index 0000000..76a6481
--- /dev/null
+++ b/empire-db/src/main/java/org/apache/empire/dbms/mysql/MySQLDBModelParser.java
@@ -0,0 +1,100 @@
+/*
+ * 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.empire.dbms.mysql;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Types;
+
+import org.apache.empire.data.DataType;
+import org.apache.empire.db.DBDatabase;
+import org.apache.empire.db.validation.DBModelParser;
+
+public class MySQLDBModelParser extends DBModelParser
+{
+    public MySQLDBModelParser(String catalog, String schemaPattern)
+    {
+        super(catalog, schemaPattern);
+    }
+    
+    @Override
+    protected double getColumnSize(DataType empireType, ResultSet rs)
+            throws SQLException
+    {
+        switch (empireType) 
+        {   
+            case INTEGER: {
+                // return size in byte, depending on MySQL Integer Types
+                // see http://dev.mysql.com/doc/refman/5.7/en/integer-types.html
+                // ignore the "real" columnsize as its just a "format hint"
+                int sqlType = rs.getInt("DATA_TYPE");
+                switch(sqlType) {
+                    case Types.TINYINT:
+                        return 1; // TINYINT, 1 byte
+                    case Types.SMALLINT:
+                        return 2; // SMALLINT, 2 byte
+                    case Types.BIGINT:
+                        return 8; // BIGINT, 8 byte
+                    default: 
+                        return 4; // Types.INTEGER, INT, 4 byte
+                }
+            }
+            default:
+                return super.getColumnSize(empireType, rs);
+        }
+    }
+    
+    @Override
+    protected Object getColumnDefault(ResultSet rs)
+        throws SQLException
+    {
+        String defaultValue = rs.getString("COLUMN_DEF");
+        if (defaultValue != null && defaultValue.equals("CURRENT_TIMESTAMP"))
+            return DBDatabase.SYSDATE;
+        return defaultValue;
+    }
+    
+    @Override
+    protected boolean isIdentityColumn(ResultSet rs)
+    {
+        try {
+            int i = rs.findColumn("IS_AUTOINCREMENT");
+            return rs.getString(i).equalsIgnoreCase("YES");
+        } catch(SQLException e) {
+            log.warn("Missing column IS_AUTOINCREMENT. Unable to detect Identity column");
+            return false;
+        }
+    }
+    
+    @Override
+    protected boolean isTimestampColumn(ResultSet rs)
+    {   try {
+            String defaultValue = rs.getString("COLUMN_DEF");
+            if (rs.getInt("DATA_TYPE") == Types.TIMESTAMP && defaultValue != null && defaultValue.equals("CURRENT_TIMESTAMP"))
+            {
+                return true;
+            }
+            return super.isTimestampColumn(rs);
+        } catch(SQLException e) {
+            log.warn("Missing column COLUMN_DEF. Unable to detect Timestamp column");
+            return false;
+        }
+    }
+
+}
diff --git a/empire-db/src/main/java/org/apache/empire/dbms/oracle/DBMSHandlerOracle.java b/empire-db/src/main/java/org/apache/empire/dbms/oracle/DBMSHandlerOracle.java
index d30941b..3756b6f 100644
--- a/empire-db/src/main/java/org/apache/empire/dbms/oracle/DBMSHandlerOracle.java
+++ b/empire-db/src/main/java/org/apache/empire/dbms/oracle/DBMSHandlerOracle.java
@@ -524,6 +524,17 @@
             rd.close();
         }
     }
+    
+    @Override
+    public OracleDBModelParser createModelParser(String catalog, String schema)
+    {
+        // Check schema
+        String schemaPattern = StringUtils.coalesce(schema, this.schemaName);
+        if (StringUtils.isEmpty(schemaPattern))
+            throw new InvalidPropertyException("schemaName", null);
+        // create parser
+        return new OracleDBModelParser(schemaPattern);
+    }
 
     /**
      * Creates a DataModelChecker instance of this DBMSHandler
@@ -531,13 +542,9 @@
      */
     @Override
     public DBModelChecker createModelChecker(DBDatabase db)
-    {
-        // detect schemaPattern
-        String schemaPattern = (db!=null ? StringUtils.coalesce(db.getSchema(), this.schemaName) : this.schemaName);
-        if (StringUtils.isEmpty(schemaPattern))
-            throw new InvalidPropertyException("schemaName", null);
-        // the default model checker
-        return new OracleDBModelChecker(schemaPattern, getBooleanType());
+    {   // the default model checker
+        OracleDBModelParser modelParser = createModelParser(null, (db!=null ? db.getSchema() : null));
+        return new OracleDBModelChecker(modelParser, getBooleanType());
     }
 
 }
diff --git a/empire-db/src/main/java/org/apache/empire/dbms/oracle/OracleDBModelChecker.java b/empire-db/src/main/java/org/apache/empire/dbms/oracle/OracleDBModelChecker.java
index 646b692..d21d3c4 100644
--- a/empire-db/src/main/java/org/apache/empire/dbms/oracle/OracleDBModelChecker.java
+++ b/empire-db/src/main/java/org/apache/empire/dbms/oracle/OracleDBModelChecker.java
@@ -18,9 +18,6 @@
  */

 package org.apache.empire.dbms.oracle;

 

-import java.sql.DatabaseMetaData;

-import java.sql.SQLException;

-

 import org.apache.empire.data.DataType;

 import org.apache.empire.db.DBColumn;

 import org.apache.empire.db.validation.DBModelChecker;

@@ -39,33 +36,13 @@
     

     private final BooleanType booleanType;

     

-    public OracleDBModelChecker(String schemaName, BooleanType booleanType)

+    public OracleDBModelChecker(OracleDBModelParser modelParser, BooleanType booleanType)

     {

-        super(null, schemaName);

+        super(modelParser);

         // Detect boolean type

         this.booleanType = booleanType;

         // ok

-        log.info("OracleDBModelChecker created for {} with booleanType {}", schemaName, booleanType);

-    }

-

-    /**

-     * collects all column information at once

-     */

-    @Override

-    protected int collectColumns(DatabaseMetaData dbMeta)

-            throws SQLException

-    {

-        return super.collectColumns(dbMeta, null);

-    }

-

-    /**

-     * collects all foreign keys at once

-     */

-    @Override

-    protected int collectForeignKeys(DatabaseMetaData dbMeta)

-            throws SQLException

-    {

-        return super.collectForeignKeys(dbMeta, null);

+        log.info("OracleDBModelChecker created for {} with booleanType {}", modelParser.getSchemaName(), booleanType);

     }

     

     @Override

diff --git a/empire-db/src/main/java/org/apache/empire/dbms/sqlserver/MSSqlDBModelChecker.java b/empire-db/src/main/java/org/apache/empire/dbms/oracle/OracleDBModelParser.java
similarity index 65%
copy from empire-db/src/main/java/org/apache/empire/dbms/sqlserver/MSSqlDBModelChecker.java
copy to empire-db/src/main/java/org/apache/empire/dbms/oracle/OracleDBModelParser.java
index 1dc83e0..1a374d4 100644
--- a/empire-db/src/main/java/org/apache/empire/dbms/sqlserver/MSSqlDBModelChecker.java
+++ b/empire-db/src/main/java/org/apache/empire/dbms/oracle/OracleDBModelParser.java
@@ -16,29 +16,26 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.empire.dbms.sqlserver;
+package org.apache.empire.dbms.oracle;
 
 import java.sql.DatabaseMetaData;
 import java.sql.SQLException;
 
-import org.apache.empire.commons.StringUtils;
-import org.apache.empire.db.validation.DBModelChecker;
+import org.apache.empire.db.validation.DBModelParser;
 
-/**
- * MSSqlDBModelChecker
- * DataModel checker implementation for Microsoft SQLServer
- * @author doebele
- */
-public class MSSqlDBModelChecker extends DBModelChecker
+public class OracleDBModelParser extends DBModelParser
 {
-    /**
-     * create a MSSqlDBModelChecker
-     * @param db the database 
-     * @param catalog the catalog
-     */
-    public MSSqlDBModelChecker(String catalog, String schema)
+    public OracleDBModelParser(String schemaName)    
     {
-        super(catalog, StringUtils.coalesce(schema, "DBO"));
+        super(null, schemaName);
+    }
+    
+    /**
+     * @return the database schema name
+     */
+    public String getSchemaName()
+    {
+        return schemaPattern;
     }
 
     /**
@@ -50,5 +47,15 @@
     {
         return super.collectColumns(dbMeta, null);
     }
-    
+
+    /**
+     * collects all foreign keys at once
+     */
+    @Override
+    protected int collectForeignKeys(DatabaseMetaData dbMeta)
+            throws SQLException
+    {
+        return super.collectForeignKeys(dbMeta, null);
+    }
+
 }
diff --git a/empire-db/src/main/java/org/apache/empire/dbms/sqlserver/DBMSHandlerMSSQL.java b/empire-db/src/main/java/org/apache/empire/dbms/sqlserver/DBMSHandlerMSSQL.java
index 79f6cf4..6faf707 100644
--- a/empire-db/src/main/java/org/apache/empire/dbms/sqlserver/DBMSHandlerMSSQL.java
+++ b/empire-db/src/main/java/org/apache/empire/dbms/sqlserver/DBMSHandlerMSSQL.java
@@ -39,6 +39,7 @@
 import org.apache.empire.db.DBTableColumn;
 import org.apache.empire.db.exceptions.EmpireSQLException;
 import org.apache.empire.db.validation.DBModelChecker;
+import org.apache.empire.db.validation.DBModelParser;
 import org.apache.empire.dbms.DBMSFeature;
 import org.apache.empire.dbms.DBMSHandler;
 import org.apache.empire.dbms.DBMSHandlerBase;
@@ -562,6 +563,12 @@
      * @return
      */
     @Override
+    public DBModelParser createModelParser(String catalog, String schema)
+    {   // the default model checker
+        return new MSSqlDBModelParser(catalog, schema);
+    }
+    
+    @Override
     public DBModelChecker createModelChecker(DBDatabase db)
     {
         // detect catalog
@@ -576,8 +583,8 @@
             catalog = catalog.substring(schemaSep);
             schema  = catalog.substring(schemaSep+1);
         }
-        // the default model checker
-        return new MSSqlDBModelChecker(catalog, schema);
+        // create the checker
+        return new DBModelChecker(createModelParser(catalog, schema));
     }
 
 }
diff --git a/empire-db/src/main/java/org/apache/empire/dbms/sqlserver/MSSqlDBModelChecker.java b/empire-db/src/main/java/org/apache/empire/dbms/sqlserver/MSSqlDBModelParser.java
similarity index 72%
rename from empire-db/src/main/java/org/apache/empire/dbms/sqlserver/MSSqlDBModelChecker.java
rename to empire-db/src/main/java/org/apache/empire/dbms/sqlserver/MSSqlDBModelParser.java
index 1dc83e0..ef78a21 100644
--- a/empire-db/src/main/java/org/apache/empire/dbms/sqlserver/MSSqlDBModelChecker.java
+++ b/empire-db/src/main/java/org/apache/empire/dbms/sqlserver/MSSqlDBModelParser.java
@@ -19,24 +19,25 @@
 package org.apache.empire.dbms.sqlserver;
 
 import java.sql.DatabaseMetaData;
+import java.sql.ResultSet;
 import java.sql.SQLException;
 
 import org.apache.empire.commons.StringUtils;
-import org.apache.empire.db.validation.DBModelChecker;
+import org.apache.empire.db.validation.DBModelParser;
 
 /**
  * MSSqlDBModelChecker
  * DataModel checker implementation for Microsoft SQLServer
  * @author doebele
  */
-public class MSSqlDBModelChecker extends DBModelChecker
+public class MSSqlDBModelParser extends DBModelParser
 {
     /**
      * create a MSSqlDBModelChecker
      * @param db the database 
      * @param catalog the catalog
      */
-    public MSSqlDBModelChecker(String catalog, String schema)
+    public MSSqlDBModelParser(String catalog, String schema)
     {
         super(catalog, StringUtils.coalesce(schema, "DBO"));
     }
@@ -51,4 +52,15 @@
         return super.collectColumns(dbMeta, null);
     }
     
+    @Override
+    protected boolean isIdentityColumn(ResultSet rs)
+    {   try {
+            int i = rs.findColumn("TYPE_NAME");
+            return rs.getString(i).matches(".*(?i:identity).*");
+        } catch(SQLException e) {
+            log.warn("Missing column TYPE_NAME. Unable to detect Identity column");
+            return false;
+        }
+    }
+    
 }
diff --git a/empire-db/src/main/java/org/apache/empire/exceptions/InvalidPropertyException.java b/empire-db/src/main/java/org/apache/empire/exceptions/InvalidPropertyException.java
index 47c3e05..2b120b0 100644
--- a/empire-db/src/main/java/org/apache/empire/exceptions/InvalidPropertyException.java
+++ b/empire-db/src/main/java/org/apache/empire/exceptions/InvalidPropertyException.java
@@ -27,6 +27,11 @@
     

     public static final ErrorType errorType = new ErrorType("error.propertyInvalid", "The property {0} is not valid. Current value is {1}.");

     

+    public InvalidPropertyException(String property, Object value, Exception cause)

+    {

+        super(errorType, new String[] { property, StringUtils.valueOf(value) }, cause);

+    }

+    

     public InvalidPropertyException(String property, Object value)

     {

         super(errorType, new String[] { property, StringUtils.valueOf(value) });

diff --git a/empire-db/src/main/java/org/apache/empire/xml/XMLConfiguration.java b/empire-db/src/main/java/org/apache/empire/xml/XMLConfiguration.java
index 3c54700..2823a9d 100644
--- a/empire-db/src/main/java/org/apache/empire/xml/XMLConfiguration.java
+++ b/empire-db/src/main/java/org/apache/empire/xml/XMLConfiguration.java
@@ -155,7 +155,7 @@
             propertiesNode = XMLUtil.findFirstChild(propertiesNode, nodeName);
             if (propertiesNode == null)
             { // Configuration
-                log.error("Property-Node {} has not been found.", nodeName);
+                log.warn("Property-Node {} has not been found.", nodeName);
                 throw new ItemNotFoundException(nodeName);
             }
         }