EMPIREDB-334 Improve customizablity of data model code generation
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 92f7d93..848cbf2 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
@@ -61,6 +61,10 @@
 	 * flag whether to parse and generate views

 	 */

 	private boolean generateViews = true;

+    /**

+     * flag whether to parse and generate views

+     */

+    private boolean generateRecords = true;

 	/**

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

 	 * e.g. "UPDATE_TIMESTAMP";

@@ -223,7 +227,12 @@
 	/**

 	 * true if names of foreign-key-relations should be preserved

 	 */

-	private boolean preserveRelationNames = false; 

+	private boolean preserveRelationNames = false;

+	

+	/**

+	 * classname of the writerService

+	 */

+	private String writerServiceClass;

 

 	/**

 	 * Initialize the configuration.

@@ -333,6 +342,16 @@
         this.generateViews = generateViews;

     }

 

+    public boolean isGenerateRecords()

+    {

+        return generateRecords;

+    }

+

+    public void setGenerateRecords(boolean generateRecords)

+    {

+        this.generateRecords = generateRecords;

+    }

+

     public String getTimestampColumn() {

 		return timestampColumn;

 	}

@@ -516,7 +535,15 @@
 		this.preserveRelationNames = preserveRelationNames;

 	}

 	

-	private String fallback(String packageName, String defaultSubpackage){

+	public String getWriterServiceClass() {

+        return writerServiceClass;

+    }

+

+    public void setWriterServiceClass(String writerServiceClass)  {

+        this.writerServiceClass = writerServiceClass;

+    }

+

+    private String fallback(String packageName, String defaultSubpackage){

 		String pkg = packageName;

 		if( pkg == null && this.packageName != null){

 			pkg = this.packageName + "." + defaultSubpackage;

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
index 4f2b112..5fc8a5a 100644
--- 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
@@ -25,6 +25,7 @@
 import java.sql.ResultSetMetaData;

 import java.sql.SQLException;

 import java.sql.Types;

+import java.text.MessageFormat;

 import java.util.ArrayList;

 import java.util.List;

 

@@ -117,7 +118,7 @@
      * JDBC url, user and password for the connection are obained from the SampleConfig bean

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

      */

-    private Connection openJDBCConnection(CodeGenConfig config) throws SQLException{

+	protected Connection openJDBCConnection(CodeGenConfig config) throws SQLException{

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

         Connection conn = null;

         try {

@@ -129,13 +130,33 @@
         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 

 	 */

-	private void populateDatabase(DBDatabase db) throws SQLException {

+    protected void populateDatabase(DBDatabase db) throws SQLException {

 		ResultSet tables = null;

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

 		try{

@@ -160,13 +181,16 @@
 	            // 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");

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

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

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

-						continue;

-					}

-					log.info(tableType + ": " + tableName);

+					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);

@@ -196,7 +220,7 @@
 		}

 	}

 	

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

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

 		ResultSet relations = null;

 		String fkTableName, pkTableName, fkColName, pkColName, relName;

 		DBTableColumn fkCol, pkCol;

@@ -268,7 +292,7 @@
 		}

 	}

 

-	private String getCatalogs(DatabaseMetaData dbMeta) throws SQLException {

+    protected String getCatalogs(DatabaseMetaData dbMeta) throws SQLException {

 		String retVal = "";

 		ResultSet rs = dbMeta.getCatalogs();

 		while (rs.next()) {

@@ -280,7 +304,7 @@
 		return retVal;

 	}

 

-	private String getSchemata(DatabaseMetaData dbMeta) throws SQLException {

+    protected String getSchemata(DatabaseMetaData dbMeta) throws SQLException {

 		String retVal = "";

 		ResultSet rs = dbMeta.getSchemas();

 		while (rs.next()) {

@@ -296,7 +320,7 @@
 	 * table with that information

 	 * @throws SQLException 

 	 */

-	private void populateTable(DBTable t) throws SQLException {

+    protected void populateTable(DBTable t) throws SQLException {

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

 		String lockColName = config.getTimestampColumn();

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

@@ -306,6 +330,8 @@
 	        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()))

 					keys[i++] = c;

@@ -328,7 +354,7 @@
 	 * table with that information

 	 * @throws SQLException 

 	 */

-	private void populateView(InMemoryView v) throws SQLException {

+    protected void populateView(InMemoryView v) throws SQLException {

 		ResultSet rs = null;

 		try {

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

@@ -346,7 +372,7 @@
 	 * table.

 	 * @throws SQLException 

 	 */

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

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

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

 		ResultSet rs = null;

 		try {

@@ -365,9 +391,15 @@
 	 * Adds DBColumn object to the given DBTable. The DBColumn is created from

 	 * the given ResultSet

 	 */

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

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

 			throws SQLException {

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

+		

+	    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"));

 

diff --git a/empire-db-codegen/src/main/java/org/apache/empire/db/codegen/CodeGenWriter.java b/empire-db-codegen/src/main/java/org/apache/empire/db/codegen/CodeGenWriter.java
index b9eafb9..220b06d 100644
--- a/empire-db-codegen/src/main/java/org/apache/empire/db/codegen/CodeGenWriter.java
+++ b/empire-db-codegen/src/main/java/org/apache/empire/db/codegen/CodeGenWriter.java
@@ -89,24 +89,23 @@
 	/**

 	 * Constructor

 	 */

-	public CodeGenWriter(CodeGenConfig config) {

-		this.writerService = new WriterService(config);

+	public CodeGenWriter(CodeGenConfig config, WriterService writerService) {

+		this.writerService = writerService;

 		this.config = config;

-		this.engine = new VelocityEngine();

+        this.engine = new VelocityEngine();

 		// we have to keep this in sync with our logging system

 		// http://velocity.apache.org/engine/releases/velocity-1.5/developer-guide.html#simpleexampleofacustomlogger

-		engine.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM,

-				new CommonsLogLogChute());

+		engine.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM, new CommonsLogLogChute());

 		if(config.getTemplateFolder() == null){

 			engine.setProperty(RuntimeConstants.RESOURCE_LOADER, "classpath");

 			engine.setProperty("classpath." + RuntimeConstants.RESOURCE_LOADER + ".class", ClasspathResourceLoader.class.getName());

-		}else{

+		} else {

 			File templateFolder = new File(config.getTemplateFolder());

 			if(!templateFolder.canRead()){

 				throw new RuntimeException("Provided template folder missing or not readable: " + config.getTemplateFolder());

 			}

 		}

-		

+		// init engine

 		try {

 			engine.init();

 		} catch (Exception e) {

@@ -116,6 +115,14 @@
 	}

 

 	/**

+	 * Overload using standard WriterService

+	 * @param config

+	 */

+    public CodeGenWriter(CodeGenConfig config) {

+        this(config, new WriterService(config));

+    }

+	

+	/**

 	 * Generates the java code files for the database

 	 * 

 	 * @param db

@@ -131,25 +138,35 @@
 		generatedFiles.add(this.createDatabaseClass(db));

 

 		// Create base table class

-		generatedFiles.add(this.createBaseTableClass(db));

+        if (config.getTableBaseName().equals("DBTable")==false) {

+            generatedFiles.add(this.createBaseTableClass(db));

+        }    

+

+        // Create base record class

+        if (config.isGenerateRecords()) {

+            generatedFiles.add(this.createBaseRecordClass(db));

+        }    

 		

 		// Create base view class

-		generatedFiles.add(this.createBaseViewClass(db));

-

-		// Create base record class

-		generatedFiles.add(this.createBaseRecordClass(db));

+        if (config.isGenerateViews() && config.getViewBaseName().equals("DBView")==false) {

+    		generatedFiles.add(this.createBaseViewClass(db));

+        }

+        

 		// Create table classes, record interfaces and record classes

 		for (DBTable table : db.getTables()) {

 			if (!config.isNestTables()) {

 				// if table nesting is disabled, create separate table classes 

 				generatedFiles.add(this.createTableClass(db, table));

 			}

-			generatedFiles.add(this.createRecordClass(db, table));

+            if (config.isGenerateRecords()) {

+                // generate record 

+                generatedFiles.add(this.createRecordClass(db, table));

+            }

 		}

 		

 		// Create view classes

 		for (DBView view : db.getViews()) {

-			if (!config.isNestViews()) {

+			if (config.isGenerateViews() && !config.isNestViews()) {

 				// if table nesting is disabled, create separate table classes 

 				generatedFiles.add(this.createViewClass(db, view));

 			}

@@ -170,15 +187,23 @@
 

 		// Clean out the directory so old code is wiped out.

 		FileUtils.cleanDirectory(this.baseDir);

-

+		

+		boolean createTables  = (!config.isNestTables() || !config.getTableBaseName().equals("DBTable"));

+        boolean createViews   = config.isGenerateViews()  && (!config.isNestViews()  || !config.getViewBaseName().equals("DBView"));

+		boolean craeteRecords = config.isGenerateRecords();

+		

+		// createViews

+		if (!createViews)

+            config.setViewPackageName(config.getPackageName());

+		

 		// Create the table package directory

-		this.tableDir = FileUtils.getFileFromPackage(targetDir, config.getTablePackageName());

+		this.tableDir = (createTables ? FileUtils.getFileFromPackage(targetDir, config.getTablePackageName()) : null);

 

 		// Create the record package directory

-		this.recordDir = FileUtils.getFileFromPackage(targetDir, config.getRecordPackageName());

+		this.recordDir = (craeteRecords ? FileUtils.getFileFromPackage(targetDir, config.getRecordPackageName()) : null);

 		

 		// Create the record package directory

-		this.viewDir = FileUtils.getFileFromPackage(targetDir, config.getViewPackageName());

+		this.viewDir = (createViews ? FileUtils.getFileFromPackage(targetDir, config.getViewPackageName()) : null);

 	}

 

 	private File createDatabaseClass(DBDatabase db) {

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 cb1fb1f..bb158b1 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
@@ -57,7 +57,7 @@
 	 * the method names.

 	 * @return the DBRecord method's names

 	 */

-	private Set<String> loadDBRecordMethodNames()

+	protected Set<String> loadDBRecordMethodNames()

 	{

 		Method[] dbrecMethods = DBRecord.class.getMethods();

 		Set<String> names = new HashSet<String>(dbrecMethods.length);

@@ -267,7 +267,7 @@
 	/**

 	 * Returns the default value of the given DBColumn.

 	 */

-	public String getDefaultValue(DBColumn c)

+	protected String getDefaultValue(DBColumn c)

 	{

 		DBTableColumn dbC = (DBTableColumn) c;

 		Object val = dbC.getDefaultValue();

@@ -286,7 +286,7 @@
 	/**

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

 	 */

-	private String deriveClassName(String name)

+	protected String deriveClassName(String name)

 	{

 		// PreserverCharacterCase

 		if (config.isPreserverCharacterCase()) {

@@ -328,7 +328,7 @@
 	 * @param isBoolean

 	 * @return

 	 */

-	private String deriveAccessorName(String attribute, Class<?> type)

+	protected String deriveAccessorName(String attribute, Class<?> type)

 	{

 		return deriveRecordMethodName(attribute, type, true);

 	}

@@ -339,7 +339,7 @@
 	 * so that getter and setter have matching suffixes if one or 

 	 * the other conflicts with an existing method.

 	 */

-	private String deriveRecordMethodName(String attribute, Class<?> type, boolean isGetter) {

+	protected String deriveRecordMethodName(String attribute, Class<?> type, boolean isGetter) {

 		attribute = deriveAttributeName(attribute);

 		StringBuilder attributeName = new StringBuilder();

 		attributeName.append(Character.toUpperCase(attribute.charAt(0)));

@@ -374,7 +374,7 @@
 		return attributeName.toString();

 	}

 	

-	private String getGetterPrefix(Class<?> type){

+	protected String getGetterPrefix(Class<?> type){

 		if (type == boolean.class || type == Boolean.class)

 		{

 			return "is";

@@ -391,7 +391,7 @@
 	 * @param attribute

 	 * @return

 	 */

-	private String deriveMutatorName(String attribute, Class<?> type)

+	protected String deriveMutatorName(String attribute, Class<?> type)

 	{

 		return deriveRecordMethodName(attribute, type, false);

 	}

@@ -402,8 +402,20 @@
 	 * @param attribute

 	 * @return

 	 */

-	private String deriveAttributeName(String column)

+	protected String deriveAttributeName(String column)

 	{

+        // find invalid chars

+        char[] invalidChars = new char[] { '$','#' };

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

+        {   // Remove

+            char c = invalidChars[i];

+            if (column.indexOf(c)>=0)

+                column=StringUtils.remove(column, c);

+        }

+        // replace dash

+        if (column.indexOf('-')>=0)

+            column=column.replace('-','_');

+        // replace space

 		return column.replace(' ', '_');

 	}

 

diff --git a/empire-db-codegen/src/main/resources/templates/BaseRecord.vm b/empire-db-codegen/src/main/resources/templates/BaseRecord.vm
index 06b0563..b23f7e4 100644
--- a/empire-db-codegen/src/main/resources/templates/BaseRecord.vm
+++ b/empire-db-codegen/src/main/resources/templates/BaseRecord.vm
@@ -19,8 +19,11 @@
 package ${recordPackageName};

 

 import org.apache.empire.db.DBRecord;

+#if($baseTableClassName.equals('DBTable'))

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

+#else

 import ${tablePackageName}.${baseTableClassName};

-

+#end

 

 public abstract class ${baseRecordClassName}<T extends ${baseTableClassName}> extends DBRecord {

 

diff --git a/empire-db-codegen/src/main/resources/templates/Database.vm b/empire-db-codegen/src/main/resources/templates/Database.vm
index aafa65c..bfb7184 100644
--- a/empire-db-codegen/src/main/resources/templates/Database.vm
+++ b/empire-db-codegen/src/main/resources/templates/Database.vm
@@ -28,18 +28,23 @@
 #if($preserveRelationNames == true)

 import org.apache.empire.db.DBRelation;

 #end

+#if($nestTables==true && $baseTableClassName.equals('DBTable'))

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

+#end

 #if($nestTables == true)

 import org.apache.empire.db.DBTableColumn;

 #end

+#if($nestViews==true && $baseViewClassName.equals('DBView'))

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

+#end

 #if($nestViews == true)

 import org.apache.empire.exceptions.NotImplementedException;

 #end

 

-#if($nestTables==false || !($tablePackageName.equalsIgnoreCase($basePackageName)))

+#if($nestTables==false && !($tablePackageName.equalsIgnoreCase($basePackageName)))

 import $tablePackageName.*;

-// $tablePackageName vs $basePackageName 

 #end

-#if($nestViews==false || ($viewPackageName!=$basePackageName))

+#if($nestViews==false && !($viewPackageName.equalsIgnoreCase($basePackageName)))

 import $viewPackageName.*;

 #end

 

diff --git a/empire-db-codegen/src/main/resources/templates/Table.vm b/empire-db-codegen/src/main/resources/templates/Table.vm
index 4f8d382..aac0b1c 100644
--- a/empire-db-codegen/src/main/resources/templates/Table.vm
+++ b/empire-db-codegen/src/main/resources/templates/Table.vm
@@ -19,6 +19,9 @@
 #if($nestTables == false)

 package $tablePackageName;

 

+#if($baseTableClassName.equals('DBTable'))

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

+#end

 import org.apache.empire.data.DataType;

 import org.apache.empire.db.DBTableColumn;

 import $basePackageName.${dbClassName};

@@ -50,8 +53,8 @@
 #end

 

 

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

 		// configure key columns (primary key)

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

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

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

 #else

diff --git a/empire-db-codegen/src/main/resources/templates/View.vm b/empire-db-codegen/src/main/resources/templates/View.vm
index 0c875b9..9400c34 100644
--- a/empire-db-codegen/src/main/resources/templates/View.vm
+++ b/empire-db-codegen/src/main/resources/templates/View.vm
@@ -20,12 +20,14 @@
 #if($nestViews == false)

 package $viewPackageName;

 

+#if($baseViewClassName.equals('DBView'))

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

+#end

 import org.apache.empire.data.DataType;

 import org.apache.empire.db.DBCommandExpr;

 import org.apache.empire.exceptions.NotImplementedException;

 

 import $basePackageName.${dbClassName};

-

 #end

 

 #if($nestViews == true)