/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package com.venky.swf.db.table; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import com.venky.cache.Cache; import com.venky.core.collections.IgnoreCaseMap; import com.venky.core.collections.IgnoreCaseSet; import com.venky.core.collections.LowerCaseStringCache; import com.venky.core.collections.SequenceSet; import com.venky.core.log.SWFLogger; import com.venky.core.log.TimerStatistics.Timer; import com.venky.core.string.StringUtil; import com.venky.core.util.ObjectUtil; import com.venky.swf.db.Database; import com.venky.swf.db.JdbcTypeHelper; import com.venky.swf.db.JdbcTypeHelper.BooleanConverter; import com.venky.swf.db.JdbcTypeHelper.IntegerConverter; import com.venky.swf.db.JdbcTypeHelper.TypeRef; import com.venky.swf.db.Transaction; import com.venky.swf.db.annotations.column.IS_VIRTUAL; import com.venky.swf.db.annotations.model.TABLE_NAME; import com.venky.swf.db.model.Count; import com.venky.swf.db.model.Model; import com.venky.swf.db.model.reflection.ModelReflector; import com.venky.swf.exceptions.AccessDeniedException; import com.venky.swf.routing.Config; import com.venky.swf.sql.DDL; import com.venky.swf.sql.DDL.AlterTable; import com.venky.swf.sql.DDL.CreateTable; import com.venky.swf.sql.DDL.DropTable; import com.venky.swf.sql.DataManupulationStatement; import com.venky.swf.sql.Expression; import com.venky.swf.sql.Operator; import com.venky.swf.sql.Select; import com.venky.swf.sql.Update; /** * * @author venky */ public class Table<M extends Model> { private final String tableName ; private final Class<M> modelClass; private final ModelReflector<M> reflector; private final String pool; public ModelReflector<M> getReflector() { return reflector; } public boolean isReal(){ if (reflector != null ){ IS_VIRTUAL isVirtual = reflector.getAnnotation(IS_VIRTUAL.class); if (isVirtual != null && isVirtual.value()) { return false; } return true; }else { return StringUtil.equals(getRealTableName(),getTableName()); } } public boolean isVirtual(){ return !isReal(); } public String getPool(){ return pool; } private boolean existingInDatabase = false; public boolean isExistingInDatabase() { return existingInDatabase; } public void setExistingInDatabase(boolean existingInDatabase) { this.existingInDatabase = existingInDatabase; } @SuppressWarnings("unchecked") public Table(String tableName,String pool){ this(tableName, (Class<M>)modelClass(tableName,pool), pool); } public Table(Class<M> modelClass){ this(tableName(modelClass),modelClass, null); } private Table(String tableName, Class<M> modelClass,String pool){ this.tableName = tableName; this.modelClass = modelClass; if (modelClass != null){ this.reflector = ModelReflector.instance(modelClass); this.realTableName = reflector.getTableName(); if (pool != null ) { this.pool = pool; }else { this.pool = reflector.getPool(); } }else { this.reflector = null; this.realTableName = this.tableName; this.pool = pool; } cat = Config.instance().getLogger(getClass().getName()+"."+getTableName()); } public static <M extends Model> String tableName(Class<M> modelClass){ if (modelClass == null){ return null; }else { return modelClassTableNameCache.get(modelClass); } } public static <M extends Model> String tableName(String modelClassSimpleName){ return modelNameTableNameCache.get(modelClassSimpleName); } private static Cache<Class<? extends Model>,String> modelClassTableNameCache = new Cache<Class<? extends Model>,String>(){ private static final long serialVersionUID = 468418078793388786L; @Override protected String getValue(Class<? extends Model> modelClass) { String modelClassSimpleName = modelClass.getSimpleName(); TABLE_NAME name = modelClass.getAnnotation(TABLE_NAME.class); if (name != null) { modelNameTableNameCache.put(modelClassSimpleName, name.value()); } return modelNameTableNameCache.get(modelClassSimpleName); } }; private static Cache<String,String> modelNameTableNameCache = new Cache<String,String>(){ private static final long serialVersionUID = 468418078793388786L; @Override protected String getValue(String modelClassSimpleName) { return StringUtil.underscorize(StringUtil.pluralize(modelClassSimpleName)); } }; public static String getSimpleModelClassName(String tableName){ return StringUtil.camelize(StringUtil.singularize(tableName)); } public static <M extends Model> Class<M> modelClass(String tableName,String pool){ for (String className : Config.instance().getModelClasses(getSimpleModelClassName(tableName))){ try { @SuppressWarnings("unchecked") Class<M> modelClass = (Class<M>) Class.forName(className); if (ObjectUtil.equals(ModelReflector.instance(modelClass).getPool(),pool)){ return modelClass; } } catch (ClassNotFoundException ex) { // } } return null; } private final String realTableName; public String getRealTableName(){ return realTableName; } public String getTableName() { return tableName; } public Class<M> getModelClass() { return modelClass; } public void dropTable(){ Transaction txn = Database.getInstance().getCurrentTransaction(); DDL.DropTable q = new DDL.DropTable(getPool(),getRealTableName()); q.executeUpdate(); if (Database.getJdbcTypeHelper(getPool()).isAutoCommitOnDDL()) { txn.registerCommit(); }else { txn.commit(); } } public void createTable() { Transaction txn = Database.getInstance().getCurrentTransaction(); CreateTable q = createTableQuery(); q.executeUpdate(); if (Database.getJdbcTypeHelper(getPool()).isAutoCommitOnDDL()) { txn.registerCommit(); }else { txn.commit(); } } private CreateTable createTableQuery(){ return createTableQuery(getRealTableName()); } private CreateTable createTableQuery(String tableName){ CreateTable q = new CreateTable(getPool(),tableName); createFields(q); if (getReflector().getRealFields().contains("id")){ q.addPrimaryKeyColumn(getReflector().getColumnDescriptor("id").getName()); } return q; } private void createFields(CreateTable q){ List<String> fields = reflector.getRealFields(); SequenceSet<String> columnSpecs = new SequenceSet<String>(); Iterator<String> fieldIterator = fields.iterator(); while( fieldIterator.hasNext() ){ String fieldName = fieldIterator.next(); ColumnDescriptor d = reflector.getColumnDescriptor(fieldName); columnSpecs.add(d.toString()); } for (String columnSpec:columnSpecs){ q.addColumn(columnSpec); } } public static final String FIELDS_ADDED = "ADD"; public static final String COLUMNS_DROPPED = "DROP"; public static final String FIELDS_MODIFIED = "ALTER"; public Map<String,Set<String>> getFieldsAltered(){ Map<String,Set<String>> fieldsAltered = new IgnoreCaseMap<Set<String>>(); fieldsAltered.put(FIELDS_ADDED, new IgnoreCaseSet()); fieldsAltered.put(COLUMNS_DROPPED, new IgnoreCaseSet()); fieldsAltered.put(FIELDS_MODIFIED, new IgnoreCaseSet()); List<String> fields = reflector.getRealFields(); List<String> columns = reflector.getRealColumns(); Iterator<String> fieldIterator = fields.iterator(); while( fieldIterator.hasNext() ){ String fieldName = fieldIterator.next(); ColumnDescriptor modelColumn = reflector.getColumnDescriptor(fieldName); ColumnDescriptor tableColumn = getColumnDescriptor(modelColumn.getName()); if (tableColumn == null){ fieldsAltered.get(FIELDS_ADDED).add(fieldName); }else if (!modelColumn.equals(tableColumn)){ Config.instance().getLogger(Table.class.getName()).info("Model: " + modelColumn.toString()); Config.instance().getLogger(Table.class.getName()).info("Table: " + tableColumn.toString()); fieldsAltered.get(FIELDS_MODIFIED).add(fieldName); } } for (ColumnDescriptor tableColumn : getColumnDescriptors()){ if (!columns.contains(tableColumn.getName())){ fieldsAltered.get(COLUMNS_DROPPED).add(tableColumn.getName()); } } return fieldsAltered; } public boolean sync(){ Map<String,Set<String>> fields = getFieldsAltered(); Set<String> addedFields = fields.get(FIELDS_ADDED); Set<String> droppedColumns = fields.get(COLUMNS_DROPPED); Set<String> alteredFields = fields.get(FIELDS_MODIFIED); if (addedFields.isEmpty() && droppedColumns.isEmpty() && alteredFields.isEmpty()){ return false; } Transaction txn = Database.getInstance().getCurrentTransaction(); for (String columnName:droppedColumns){ AlterTable q = new AlterTable(getPool(),getRealTableName()); q.dropColumn(columnName); q.executeUpdate(); } boolean dropAndReCreateTable = false; for (String fieldName:addedFields){ AlterTable q = new AlterTable(getPool(),getRealTableName()); ColumnDescriptor cd = reflector.getColumnDescriptor(fieldName); q.addColumn(cd.toString()); q.executeUpdate(); } for (String fieldName:alteredFields){ if (fieldName.equalsIgnoreCase("ID")){ dropAndReCreateTable = true; continue; } ColumnDescriptor cd = reflector.getColumnDescriptor(fieldName); if (!cd.isNullable() && Database.getJdbcTypeHelper(getPool()).isVoid(cd.getColumnDefault())){ dropAndReCreateTable = true; continue; } String columnName = cd.getName(); AlterTable q = new AlterTable(getPool(),getRealTableName()); q.addColumn("NEW_"+cd.toString()); q.executeUpdate(); Update u = new Update(getPool(),getRealTableName()); u.setUnBounded("NEW_"+columnName, columnName); u.executeUpdate(); q = new AlterTable(getPool(),getRealTableName()); q.dropColumn(columnName); q.executeUpdate(); q = new AlterTable(getPool(),getRealTableName()); q.addColumn(cd.toString()); q.executeUpdate(); u = new Update(getPool(),getRealTableName()); u.setUnBounded(columnName,"NEW_" + columnName); u.executeUpdate(); q = new AlterTable(getPool(),getRealTableName()); q.dropColumn("NEW_" + columnName); q.executeUpdate(); } if (dropAndReCreateTable){ // Rare event. Drop and recreate table. String tmpTable = "temp_"+getRealTableName(); CreateTable create = createTableQuery(tmpTable); create.executeUpdate(); SequenceSet<String> columns = new SequenceSet<String>(); columns.addAll(reflector.getRealColumns()); DataManupulationStatement insert = new DataManupulationStatement(getPool()); insert.add("insert into ").add(tmpTable).add("("); Iterator<String> columnIterator = columns.iterator(); while (columnIterator.hasNext()){ insert.add(columnIterator.next()).add(columnIterator.hasNext()? "," : ""); } insert.add(") select "); columnIterator = columns.iterator(); while (columnIterator.hasNext()){ insert.add(columnIterator.next()).add(columnIterator.hasNext()? "," : ""); } insert.add(" from " + getRealTableName()); insert.executeUpdate(); DropTable drop = new DropTable(getPool(),getRealTableName()); drop.executeUpdate(); create = createTableQuery(); create.executeUpdate(); insert = new DataManupulationStatement(getPool()); insert.add("insert into ").add(getRealTableName()).add("("); columnIterator = columns.iterator(); while (columnIterator.hasNext()){ insert.add(columnIterator.next()).add(columnIterator.hasNext()? "," : ""); } insert.add(") select "); columnIterator = columns.iterator(); while (columnIterator.hasNext()){ insert.add(columnIterator.next()).add(columnIterator.hasNext()? "," : ""); } insert.add(" from temp_" + getRealTableName()); insert.executeUpdate(); drop = new DropTable(getPool(),"temp_"+getRealTableName()); drop.executeUpdate(); } if (Database.getJdbcTypeHelper(getPool()).isAutoCommitOnDDL()) { txn.registerCommit(); }else { txn.commit(); } return true; } public int recordCount(){ Select sel = new Select("COUNT(1) AS COUNT").from(getModelClass()); Count count = sel.execute(Count.class).get(0); // number of records would be one.!! return count.getCount(); } public M lock(int id){ return lock(id,true); } public M lock(int id,boolean wait){ return get(id,true,wait); } public M get(int id) { return get(id,false,false); } private SWFLogger cat = null; public M get(int id,boolean locked,boolean wait) { Timer timer = cat.startTimer(null, Config.instance().isTimerAdditive()); try { Select q = new Select(locked,wait); q.from(getModelClass()); String idColumn = getReflector().getColumnDescriptor("id").getName(); q.where(new Expression(q.getPool(),idColumn,Operator.EQ,new BindVariable(getPool(),id))); List<M> result = q.execute(getModelClass(),1); if (result.isEmpty()){ return null; }else { return result.get(0); } }finally{ timer.stop(); } } public int truncate(){ Select sel = new Select().from(getModelClass()); int numRecords = 0; for (M m : sel.execute(getModelClass(),new Select.AccessibilityFilter<M>())){ m.destroy(); numRecords ++; } return numRecords; } public M newRecord(){ return ModelInvocationHandler.getProxy(modelClass,new Record(getPool())); } private Map<String,ColumnDescriptor> columnDescriptors = new IgnoreCaseMap<ColumnDescriptor>(); public Map<String,ColumnDescriptor> columnDescriptors(){ if (isReal() || ObjectUtil.equals(getRealTableName(), getTableName())){ return columnDescriptors; }else { return Database.getTable(getRealTableName()).columnDescriptors(); } } public Set<String> getColumnNames(){ return columnDescriptors().keySet(); } public Collection<ColumnDescriptor> getColumnDescriptors(){ return columnDescriptors().values(); } public ColumnDescriptor getColumnDescriptor(String columnName){ return getColumnDescriptor(columnName, false); } public ColumnDescriptor getColumnDescriptor(String columnName,boolean createIfRequired){ Map<String,ColumnDescriptor> cds = columnDescriptors(); ColumnDescriptor c = cds.get(columnName); if (c == null && createIfRequired ){ c = new ColumnDescriptor(getPool()); cds.put(columnName, c); } return c; } /* public Set<String> getAutoIncrementColumns(){ Set<String> columns = new IgnoreCaseSet(); for (String name :getColumnNames()){ if (getColumnDescriptor(name).isAutoIncrement()){ columns.add(name); } } return columns; }*/ public static class ColumnDescriptor extends Record{ public ColumnDescriptor(String pool){ super(pool); } public int getOrdinalPosition(){ Integer pos = ic.valueOf(get("ORDINAL_POSITION")); return (pos == null? 0 : pos); } public String getName(){ return ((String)get("COLUMN_NAME")); } public int getJDBCType(){ Object dataType = get("DATA_TYPE"); return ic.valueOf(dataType); } public void setName(String name){ put("COLUMN_NAME",name); } public void setJDBCType(int sqlType){ put("DATA_TYPE",sqlType); } public int getSize(){ Integer ret = ic.valueOf(get("COLUMN_SIZE")); if (ret == null){ return 0; }else { return ret; } } public void setSize(int size){ put("COLUMN_SIZE",size); } public void setPrecision(int size){ setSize(size); } public int getPrecision(){ return getSize(); } public void setScale(int scale){ put("DECIMAL_DIGITS",scale); } public int getScale(){ Integer retval = ic.valueOf(get("DECIMAL_DIGITS")); if (retval == null){ return 0; }else { return retval; } } private BooleanConverter bc = (BooleanConverter) Database.getJdbcTypeHelper(getPool()).getTypeRef(Boolean.class).getTypeConverter(); private IntegerConverter ic = (IntegerConverter) Database.getJdbcTypeHelper(getPool()).getTypeRef(Integer.class).getTypeConverter(); public boolean isNullable(){ return bc.valueOf(get("IS_NULLABLE")); } public void setNullable(boolean nullable){ put("IS_NULLABLE",nullable ? "YES" : "NO"); } public boolean isAutoIncrement(){ return bc.valueOf(get("IS_AUTOINCREMENT")); } public void setAutoIncrement(boolean autoincrement){ put("IS_AUTOINCREMENT",autoincrement ? "YES": "NO"); } private boolean virtual = false; public boolean isVirtual() { return virtual; } public void setVirtual(boolean virtual) { this.virtual = virtual; } public void setColumnDefault(String defaultValue){ put("COLUMN_DEF",defaultValue); } public String getColumnDefault(){ return (String)get("COLUMN_DEF"); } @Override public String toString(){ StringBuilder buff = new StringBuilder(); JdbcTypeHelper helper = Database.getJdbcTypeHelper(getPool()); TypeRef<?> ref = helper.getTypeRef(getJDBCType()); if (ref == null){ throw new RuntimeException("Unknown JDBCType:" + getJDBCType() + " for column " + getName()); } if (helper.isColumnNameAutoLowerCasedInDB()){ buff.append(LowerCaseStringCache.instance().get(getName())); }else { buff.append(getName()); } if (isAutoIncrement()){ buff.append(helper.getAutoIncrementInstruction()); }else { buff.append(" "); buff.append(ref.getSqlType()); if (ref.getSize() > 0 && getSize() > 0){ buff.append("(").append(getSize()); if (ref.getScale() > 0 && getScale() > 0){ buff.append(",").append(getScale()); } buff.append(")"); } if (!isNullable()){ String def = getColumnDefault(); if (def != null){ buff.append(" DEFAULT " ); if (ref.isColumnDefaultQuoted()){ if (def.startsWith("'")){ buff.append(def); }else { buff.append("'").append(def).append("'"); } }else { buff.append(def); } } buff.append(" NOT NULL "); } } return buff.toString().trim(); } @Override public boolean equals(Object other){ if (other == null || !(other instanceof ColumnDescriptor)){ return false; } ColumnDescriptor othercd = (ColumnDescriptor)other; return toString().equalsIgnoreCase(othercd.toString()) ; } @Override public int hashCode() { return toString().hashCode(); } } public M getRefreshed(M partiallyFilledModel){ M fullModel = null; if (partiallyFilledModel.getId() > 0) { fullModel = Database.getTable(getModelClass()).get(partiallyFilledModel.getId()); }else { for (Expression where : getReflector().getUniqueKeyConditions(partiallyFilledModel,false)){ List<M> recordsMatchingUK = new Select().from(getModelClass()).where(where).execute(); if (recordsMatchingUK.size() == 1){ fullModel = recordsMatchingUK.get(0); break; } } } if (fullModel == null){ fullModel = partiallyFilledModel; }else { if (!fullModel.isAccessibleBy(Database.getInstance().getCurrentUser())){ throw new AccessDeniedException("Existing Record in " + getModelClass().getSimpleName() + " identified by "+ getReflector().get(fullModel,getReflector().getDescriptionField()) + " cannot be modified."); } Record rawPartiallyFilledRecord = partiallyFilledModel.getRawRecord(); Record rawFullRecord = fullModel.getRawRecord(); for (String field: rawPartiallyFilledRecord.getDirtyFields()){ rawFullRecord.put(field, partiallyFilledModel.getRawRecord().get(field)); } } return fullModel; } }