/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package com.venky.swf.db; import java.io.PrintWriter; import java.io.StringWriter; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import com.venky.cache.Cache; import com.venky.core.collections.IgnoreCaseMap; import com.venky.core.collections.SequenceSet; import com.venky.core.util.ObjectUtil; import com.venky.extension.Registry; import com.venky.swf.configuration.Installer; import com.venky.swf.db.annotations.model.CONFIGURATION; import com.venky.swf.db.jdbc.ConnectionManager; import com.venky.swf.db.jdbc.TransactionManager; import com.venky.swf.db.model.Model; import com.venky.swf.db.model.User; import com.venky.swf.db.model.reflection.ModelReflector; import com.venky.swf.db.model.reflection.TableReflector; import com.venky.swf.db.table.QueryCache; import com.venky.swf.db.table.Table; import com.venky.swf.routing.Config; /** * * @author venky */ public class Database implements _IDatabase{ private Database() { } private User currentUser ; public void open(Object currentUser) { if (connectionCache.get(ModelReflector.instance(User.class).getPool()) == null) { throw new RuntimeException("Failed to open connection to database: " + getCaller()); } this.currentUser = (User)currentUser; } public User getCurrentUser(){ return currentUser; } private TransactionManager tm = null; public TransactionManager getTransactionManager(){ if (tm == null){ tm = new TransactionManager(); } return tm; } public Transaction getCurrentTransaction() { return getTransactionManager().getCurrentTransaction(); } public boolean isActiveTransactionPresent(){ return getTransactionManager().isActiveTransactionPresent(); } public void close() { closeConnections(); currentUser = null; } private void closeConnections() { List<String> pools = new ArrayList<String>(connectionCache.keySet()); for (String pool : pools){ Connection connection = connectionCache.get(pool); try { if (!connection.isClosed()){ try { connection.rollback(); }catch (SQLException ex){ Config.instance().getLogger(Database.class.getName()).fine("Rollback Failed!! Closing anyway to release locks " + getCaller()); }finally { connection.close(); } Config.instance().getLogger(Database.class.getName()).fine("Connection closed : " + getCaller()); } } catch (SQLException ex) { throw new RuntimeException(ex); } finally { connectionCache.remove(pool); } } if (!pools.isEmpty()){ getTransactionManager().completeAllTransaction(); } } private Cache<String,Connection> connectionCache = new Cache<String,Connection>(){ /** * */ private static final long serialVersionUID = 8256289464354769723L; protected Connection getValue(String pool) { try { return createConnection(pool); } catch (SQLException ex) { throw new RuntimeException(ex); } catch (ClassNotFoundException ex) { throw new RuntimeException(ex); } } }; public Set<String> getActivePools(){ return connectionCache.keySet(); } private boolean registeringActivePool = false; // Prevent Recursion from createTransaction , setSavePoint calling getConnection. public Connection getConnection(String pool){ return getConnection(pool,true); } public Connection getConnection(String pool,boolean registerActivePool){ Connection conn = connectionCache.get(pool); try { if (conn.getTransactionIsolation() != getTransactionIsolationLevel() ){ if ( conn.getMetaData().supportsTransactionIsolationLevel(getTransactionIsolationLevel())) { conn.setTransactionIsolation(getTransactionIsolationLevel()); } } } catch (SQLException e) { throw new RuntimeException(e); } if (registerActivePool && !registeringActivePool){ registeringActivePool = true; try { getCurrentTransaction().registerActivePool(pool); }finally { registeringActivePool = false; } } return conn; } public void registerLockRelease(){ for (QueryCache cache: configQueryCacheMap.values()){ cache.registerLockRelease(); } } public <M extends Model> QueryCache getCache(ModelReflector<M> ref) { String tableName = ref.getTableName(); if (ref.isAnnotationPresent(CONFIGURATION.class)){ QueryCache cacheEntry = configQueryCacheMap.get(tableName); if (cacheEntry == null) { synchronized (configQueryCacheMap) { cacheEntry = configQueryCacheMap.get(tableName); if (cacheEntry == null) { cacheEntry = new QueryCache(tableName); configQueryCacheMap.put(tableName, cacheEntry); } } } return cacheEntry; } else { return getTransactionManager().getCurrentTransaction().getCache(ref); } } public PreparedStatement createStatement(String pool, String sql) throws SQLException{ return getConnection(pool).prepareStatement(sql); } public PreparedStatement createStatement(String pool, String sql,String[] columnNames) throws SQLException{ return getConnection(pool).prepareStatement(sql, columnNames ); } // Class level methods and variables. private static final Map<String, QueryCache> configQueryCacheMap = new HashMap<String, QueryCache>(); private static Map<String,Map<String, Table<? extends Model>>> tablesInPool = new HashMap<String,Map<String, Table<? extends Model>>>(); public static Map<String, Table<? extends Model>> getTables(String pool) { Map<String,Table<? extends Model>> map = tablesInPool.get(pool); if (map == null){ map = new IgnoreCaseMap<Table<? extends Model>>(); tablesInPool.put(pool,map); } return tablesInPool.get(pool); } @SuppressWarnings("unchecked") public static <M extends Model> Table<M> getTable(Class<M> modelClass) { return (Table<M>) getTable(ModelReflector.instance(modelClass).getPool(),Table.tableName(modelClass)); } public static <M extends Model> Table<M> getTable(String table){ SequenceSet<Table<M>> set = new SequenceSet<>(); for (String pool:tablesInPool.keySet()){ Table<M> t = getTable(pool, table); if (t != null){ set.add(t); if (t.getModelClass()!= null){ return t; } } } if (set.size() > 0){ return set.first(); } return null; } @SuppressWarnings("unchecked") public static <M extends Model> Table<M> getTable(String pool , String tableName) { return (Table<M>) tablesInPool.get(pool).get(tableName); } public static Set<String> getTableNames() { Set<String> tableNames = new HashSet<String>(); for (String pool :tablesInPool.keySet()){ tableNames.addAll(tablesInPool.get(pool).keySet()); } return tableNames; } public static void migrateTables() { boolean dbModified = false; loadTables(dbModified); for (String pool: tablesInPool.keySet()) { if (ConnectionManager.instance().isPoolReadOnly(pool)) { continue; } for (Table<?> table : tablesInPool.get(pool).values()) { if (table.isVirtual()){ continue; } Config.instance().getLogger(Database.class.getName()).info("Table " + table.getRealTableName() + " :" + pool + "Model " + table.getModelClass() + " :" + table.getPool()); if (!table.isExistingInDatabase() && ObjectUtil.equals(table.getReflector().getPool(),pool)) { table.createTable(); dbModified = true; } else if (table.getModelClass() == null || !ObjectUtil.equals(table.getReflector().getPool(),pool)) { table.dropTable(); dbModified = true; } else { dbModified = table.sync() || dbModified; } } } loadTables(dbModified); } public static void loadTables(boolean reload) { if (reload) { tablesInPool.clear(); } if (!tablesInPool.isEmpty()) { return; } loadTablesFromModel(); loadTablesFromDB(); } @SuppressWarnings({ "rawtypes", "unchecked" }) private static void loadTablesFromModel() { List<String> modelClasses = Config.instance().getModelClasses(); for (String className : modelClasses) { try { Class<? extends Model> modelClass = (Class<? extends Model>) Class.forName(className); if (!className.equals(Model.class.getName()) && modelClass.isInterface() && Model.class.isAssignableFrom(modelClass)){ Table table = new Table(modelClass); table.setExistingInDatabase(false); String tableName = table.getTableName(); String pool = ModelReflector.instance(modelClass).getPool(); if (table.getRealTableName() != null && !getTables(pool).containsKey(tableName)) { getTables(pool).put(table.getTableName(),table); } } } catch (ClassNotFoundException ex) { throw new RuntimeException(ex); } } } private static void loadTablesFromDB() { for (String pool : ConnectionManager.instance().getPools()){ loadTablesFromDB(pool); } } @SuppressWarnings({ "rawtypes", "unchecked" }) private static void loadTablesFromDB(String pool) { ResultSet tablesResultSet = null; try { Connection conn = getInstance().getConnection(pool); DatabaseMetaData meta = conn.getMetaData(); tablesResultSet = meta.getTables(null, getSchema(pool), "%", new String[]{"TABLE"}); while (tablesResultSet.next()) { String tableName = tablesResultSet.getString("TABLE_NAME"); Table table = getTables(pool).get(tableName); if (table == null){ // table = new Table(tableName,pool); getTables(pool).put(tableName, table); } table.setExistingInDatabase(true); ResultSet columnResultSet = null; try { columnResultSet = meta.getColumns(null,getSchema(pool), tableName, null); while (columnResultSet.next()) { String columnName = columnResultSet.getString("COLUMN_NAME"); table.getColumnDescriptor(columnName,true).load(columnResultSet); } }finally { if (columnResultSet != null){ columnResultSet.close(); } } } } catch (SQLException ex) { throw new RuntimeException(ex); }finally { if (tablesResultSet != null){ try { tablesResultSet.close(); } catch (SQLException ex) { throw new RuntimeException(ex); } } } } public void loadFactorySettings() { List<String> installerNames = Config.instance().getInstallers(); try { for (String installerName : installerNames){ Config.instance().getLogger(Database.class.getName()).info("Installing ... " + installerName ); try{ Installer installer = (Installer)Class.forName(installerName).newInstance(); installer.install(); }finally { Config.instance().getLogger(Database.class.getName()).info("done!"); } } Database.getInstance().getCurrentTransaction().commit(); } catch (Exception e) { throw new RuntimeException(e); } } private static ThreadLocal<Database> _instance = new ThreadLocal<Database>(); public static Database getInstance() { return getInstance(false); } public static Database getInstance(boolean migrate) { if (_instance.get() == null) { Database db = new Database(); _instance.set(db); } if (migrate) { migrateTables(); } else { loadTables(false); } return _instance.get(); } public static JdbcTypeHelper getJdbcTypeHelper(String pool) { return ConnectionManager.instance().getJdbcTypeHelper(pool); } private static String getSchema(String pool) { return Config.instance().getProperty(ConnectionManager.instance().getNormalizedPropertyName("swf.jdbc."+ pool + ".dbschema")); } private static boolean isSchemaToBeSetOnConnection(String pool) { return Boolean.valueOf(Config.instance().getProperty(ConnectionManager.instance().getNormalizedPropertyName("swf.jdbc."+pool+".dbschema.setonconnection"))); } private static Connection createConnection(String pool) throws SQLException, ClassNotFoundException{ Connection conn = ConnectionManager.instance().createConnection(pool); conn.setAutoCommit(false); if (isSchemaToBeSetOnConnection(pool)) { String schemaSettingCommand = Config.instance().getProperty(ConnectionManager.instance().getNormalizedPropertyName("swf.jdbc."+ pool +".set.dbschema.command"), "set schema ?"); PreparedStatement stmt = conn.prepareStatement(schemaSettingCommand); if (schemaSettingCommand.indexOf('?') >= 0) { stmt.setString(1, getSchema(pool)); } stmt.executeUpdate(); conn.commit(); } Config.instance().getLogger(Database.class.getName()).fine("Opened Connection:" + getCaller()); return conn; } public static void shutdown() { ConnectionManager.instance().close(); for (String pool:ConnectionManager.instance().getPools()){ String jdbcurl = Config.instance().getProperty(ConnectionManager.instance().getNormalizedPropertyName("swf.jdbc."+pool+".url")); String driver = Config.instance().getProperty(ConnectionManager.instance().getNormalizedPropertyName("swf.jdbc."+pool+ ".driver")); if (driver.equals("org.apache.derby.jdbc.EmbeddedDriver")) { try { jdbcurl = jdbcurl + ";shutdown=true"; String userid = Config.instance().getProperty(ConnectionManager.instance().getNormalizedPropertyName("swf.jdbc."+pool+".userid")); String password = Config.instance().getProperty(ConnectionManager.instance().getNormalizedPropertyName("swf.jdbc." + pool + ".password")); Properties info = new Properties(); info.setProperty("user", userid); info.setProperty("password", password); DriverManager.getConnection(jdbcurl, info); } catch (SQLException ex) { if (ex.getSQLState().equals("08006") && ex.getErrorCode() == 45000){ System.out.println("Derby db closed!"); } else { throw new RuntimeException(ex); } } } } } public static void dispose(){ Registry.instance().callExtensions("com.venky.swf.db.Database.beforeClose"); for (String pool : tablesInPool.keySet()){ tablesInPool.get(pool).clear(); } tablesInPool.clear(); for (String key : configQueryCacheMap.keySet()){ configQueryCacheMap.get(key).clear(); } configQueryCacheMap.clear(); ConnectionManager.instance().close(); ModelReflector.dispose(); TableReflector.dispose(); } public static String getCaller(){ StackTraceElement[] e = new Exception().getStackTrace(); for (StackTraceElement elem : e) { if (elem.getClassName().startsWith("com.venky.swf.db") || elem.getClassName().startsWith("com.venky.swf.sql") || elem.getClassName().startsWith("sun.") || elem.getClassName().startsWith("java.")) { continue; } return elem.toString(); } StringWriter w = new StringWriter(); new Exception().printStackTrace(new PrintWriter(w)); return w.toString(); } public void resetIdGeneration(){ for (String pool : ConnectionManager.instance().getPools()){ JdbcTypeHelper helper = getJdbcTypeHelper(pool); for (Table<? extends Model> table : Database.getTables(pool).values()){ if (table.isReal() && table.isExistingInDatabase()){ helper.updateSequence(table); } } } } int transactionIsolationLevel = Connection.TRANSACTION_READ_COMMITTED; public int getTransactionIsolationLevel(){ return transactionIsolationLevel; } public void setTransactionIsolationLevel(int newIsolationLevel) { if (transactionIsolationLevel == newIsolationLevel) { return; } if (isActiveTransactionPresent()){ throw new RuntimeException("Cannot Set Isolation Level when a Transaction is going on"); } transactionIsolationLevel = newIsolationLevel; Config.instance().getLogger(getClass().getName()).info ("Set transaction isolation level to " + newIsolationLevel); } public void resetTransactionIsolationLevel(){ setTransactionIsolationLevel(Connection.TRANSACTION_READ_COMMITTED); } }