/* * Copyright 2012 The Solmix Project * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * http://www.gnu.org/licenses/ * or see the FSF site: http://www.fsf.org. */ package org.solmix.sql; import java.io.IOException; import java.io.PrintWriter; import java.sql.Connection; import java.sql.SQLException; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import javax.annotation.Resource; import javax.sql.DataSource; import org.apache.commons.dbcp.PoolableConnection; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.solmix.api.exception.SlxException; import org.solmix.api.pool.PoolManager; import org.solmix.api.pool.PoolManagerFactory; import org.solmix.api.types.Texception; import org.solmix.api.types.Tmodule; import org.solmix.commons.collections.DataTypeMap; import org.solmix.commons.util.Assert; import org.solmix.commons.util.DataUtils; import org.solmix.fmk.base.Reflection; import org.solmix.fmk.util.ServiceUtil; import org.solmix.runtime.SystemContext; import org.solmix.runtime.cm.ConfigureUnit; import org.solmix.runtime.cm.ConfigureUnitManager; import org.solmix.sql.internal.SqlCM; /** * * @author solmix.f@gmail.com * @version 110037 2011-3-20 */ public class ConnectionManagerImpl implements ConnectionManager { public PoolManager manager; private static final Logger log = LoggerFactory.getLogger(ConnectionManager.class.getName()); private DataTypeMap thisConfig = null; private static PoolManagerFactory poolManagerFactory; protected boolean monitorConnections; protected long recycleMillis; private boolean connectionMonitorIsOpen; protected static Map<Connection, Map<String, Object>> openConnections; private SystemContext sc; public ConnectionManagerImpl(final SystemContext sc) { setSystemContext(sc); } @Resource public void setSystemContext(final SystemContext sc) { this.sc = sc; if(sc!=null) sc.setExtension(this, ConnectionManager.class); } private synchronized DataTypeMap getSqlConfig() throws SlxException { if (thisConfig == null) { ConfigureUnitManager cum = sc.getExtension(ConfigureUnitManager.class); ConfigureUnit cu = null; try { cu = cum.getConfigureUnit(SQLDataSource.SERVICE_PID); } catch (IOException e) { throw new SlxException(Tmodule.SQL, Texception.IO_EXCEPTION, e); } if (cu != null) thisConfig = cu.getProperties(); else thisConfig = new DataTypeMap(); } return thisConfig; } public void init() { } /** * @return the manager */ public synchronized PoolManager getManager() { if (manager == null) { if (poolManagerFactory == null) { poolManagerFactory = sc.getExtension(PoolManagerFactory.class); } manager = poolManagerFactory.createPoolManager("sql", new PoolableSQLConnectionFactory(this,sc)); } return manager; } /** * @param manager the manager to set */ public void setManager(PoolManager manager) { this.manager = manager; } protected static DataSource getInternalDs(String dbName, DataTypeMap dbConfig) throws SlxException { Lock l = new ReentrantLock(); DataSource ds = null; l.lock(); try { if (ds == null) { String impl = dbConfig.getString("driver"); if (log.isDebugEnabled()) log.debug("Initializing sql config for [" + dbName + "] from system config -using DataSource: " + impl); ds = (DataSource) Reflection.newInstance(impl); DataTypeMap driverConfig = dbConfig.getSubtree("driver"); DataUtils.setProperties(driverConfig, ds); if (ds != null && dbConfig.getBoolean("log.enabled", false)) ds.setLogWriter(new PrintWriter(System.out)); } if (ds == null) { String __info = "Unable to instantiate JDBC DataSource for database [" + dbName + "] - check your config."; throw new SlxException(Tmodule.POOL, Texception.CAN_NOT_INSTANCE, __info); } else { return ds; } } catch (Exception e) { throw new SlxException(Tmodule.SQL, Texception.DEFAULT, "can not initial datasource"); } finally { l.unlock(); } } protected Connection getInternalConn(String dbName, DataTypeMap dbConfig) throws SlxException { DataSource ds= getDataSource(dbName, dbConfig); try{ if(ds!=null) return ds.getConnection(); else throw new SlxException(Tmodule.SQL, Texception.SQL_NO_CONNECTION, ""); } catch (SQLException e) { throw new SlxException(Tmodule.SQL, Texception.SQL_SQLEXCEPTION, e.getMessage(), e); } } @Override public Connection getConnection(String dbName) throws SlxException { if (dbName==null) dbName = getDefaultDbName(); Connection __return = null; DataTypeMap dbConfig = getSqlConfig().getSubtree(dbName); Boolean usedPool = dbConfig.getBoolean(SQLDataSource.USED_POOL); if (usedPool == null || usedPool.booleanValue()) __return = (Connection) getManager().borrowObject(dbName); else { __return = getInternalConn(dbName, dbConfig); } // markAsReferenced(dbName); writeOpenConnectionEntry(__return, "get"); return __return; } protected String getDefaultDbName() throws SlxException { return getSqlConfig().getString(SqlCM.P_DEFAULT_DATABASE, SqlCM.DEFAULT_DATABASE); } /** * for JNDI lookup datasource in the context. * * @return */ private static DataSource lookupDataSource(String filterName) throws SlxException { Object obj = ServiceUtil.getOsgiJndiService(DataSource.class.getName(), filterName); if (obj instanceof DataSource) { return (DataSource) obj; } else { throw new SlxException(Tmodule.SQL, Texception.OBJECT_TYPE_NOT_ADAPTED, "can not find a adapter for datasource"); } } /** * @param return1 * @param string * @throws SlxException */ private void writeOpenConnectionEntry(Connection conn, String type) throws SlxException { if (monitorConnections) { if (connectionMonitorIsOpen) startupMonitor(); Map<String, Object> openConnEntry = new HashMap<String, Object>(); openConnEntry.put("type", type); openConnEntry.put("time", Long.valueOf(System.currentTimeMillis())); openConnEntry.put("stacktrace", Thread.currentThread().getStackTrace()); openConnections.put(conn, openConnEntry); } } /** * @throws SlxException * */ private synchronized void startupMonitor() throws SlxException { connectionMonitorIsOpen = true; monitorConnections = getSqlConfig().getBoolean("monitorOpenConnections", false); recycleMillis = getSqlConfig().getLong("forceConnectionClosedPeriod", 30000); openConnections = new ConcurrentHashMap<Connection, Map<String, Object>>(); log.info("Startup Sql connection Monitor,the recycle check time is " + recycleMillis); Runnable monitor = new Runnable() { @Override public void run() { Map<Connection, Map<String, Object>> localCopy = null; do { localCopy = new ConcurrentHashMap<Connection, Map<String, Object>>(openConnections); long now = System.currentTimeMillis(); Iterator<Connection> i = localCopy.keySet().iterator(); do { if (!i.hasNext()) break; Connection conn = i.next(); Map<String, Object> info = localCopy.get(conn); long ms = ((Long) info.get("time")).longValue(); if (now - ms > recycleMillis) { String error = (new StringBuilder()).append("Connection '").append(conn.hashCode()).append("', of type '").append( info.get("type")).append("', borrowed by the ").append("following call stack, has been open for ").append( "more than ").append(recycleMillis).append("ms; it will ").append("now be forcibly closed").toString(); StackTraceElement elements[] = (StackTraceElement[]) info.get("stacktrace"); boolean start = false; for (int j = 0; j < elements.length; j++) { if (!start && elements[j].toString().indexOf("writeOpenConnectionEntry") != -1) { start = true; continue; } if (start) error = (new StringBuilder()).append(error).append("\n").append(elements[j].toString()).toString(); } log.warn(error); try { free(conn); } catch (Exception e) { e.printStackTrace(); } } } while (true); try { Thread.sleep(2000L); } catch (Exception ignored) { } } while (true); } }; (new Thread(monitor)).start(); } /** * @param connection * @throws SlxException */ @Override public void freeConnection(Connection conn) throws SlxException { try { if (conn == null) return; if (conn != null && !conn.isClosed() && !conn.getAutoCommit()) conn.commit(); if (conn instanceof PoolableConnection) { conn.close(); } else if (conn != null && !conn.isClosed()) conn.close(); if (monitorConnections) openConnections.remove(conn); } catch (SQLException e) { log.error("Error attempting to commit and close a connection"); throw new SlxException(Tmodule.SQL, Texception.SQL_SQLEXCEPTION, e); } } @Override public Connection getNewConnection(String dbName) throws SlxException { Connection __return; if (dbName == null) dbName = getSqlConfig().getString(SqlCM.P_DEFAULT_DATABASE, SqlCM.DEFAULT_DATABASE); try { DataTypeMap dbConfig = getSqlConfig().getSubtree(dbName); Boolean usedPool = dbConfig.getBoolean(SQLDataSource.USED_POOL); if (usedPool == null || usedPool.booleanValue()) __return = (Connection) getManager().borrowObject(dbName); else { __return = getInternalConn(dbName, dbConfig); } } catch (Exception e) { throw new SlxException(Tmodule.SQL, Texception.POOL_BORROW_OBJECT_FAILD, e); } writeOpenConnectionEntry(__return, "getNew"); return __return; } /** * {@inheritDoc} * * @see org.solmix.sql.ConnectionManager#getPoolManagerFactory() */ @Override public PoolManagerFactory getPoolManagerFactory() { return poolManagerFactory; } /** * {@inheritDoc} * * @see org.solmix.sql.ConnectionManager#freeConnection(java.sql.Connection) */ @Override public void free(Connection conn) throws SlxException { freeConnection(conn); } /** * {@inheritDoc} * * @see org.solmix.sql.ConnectionManager#getConnection() */ @Override public Connection get() throws SlxException { return getConnection(null); } /** * {@inheritDoc} * * @see org.solmix.sql.ConnectionManager#getConnection(java.lang.String) */ @Override public Connection get(String dbName) throws SlxException { return getConnection(dbName); } /** * {@inheritDoc} * * @see org.solmix.sql.ConnectionManager#getNewConnection(java.lang.String) */ @Override public Connection getNew(String dbName) throws SlxException { return getNewConnection(dbName); } /** * {@inheritDoc} * * @see org.solmix.sql.ConnectionManager#getNewConnection() */ @Override public Connection getNew() throws SlxException { return getNewConnection(null); } /** * {@inheritDoc} * * @see org.solmix.sql.ConnectionManager#getDataSource(java.lang.String) */ @Override public DataSource getDataSource(String dbName) throws SlxException { if (dbName==null) dbName = getDefaultDbName(); DataTypeMap dbConfig = getSqlConfig().getSubtree(dbName); if(dbConfig==null||dbConfig.isEmpty()) throw new SlxException(Tmodule.SQL,Texception.NO_FOUND,"No found configure for jdbc datasource name:"+dbName+ " config"); return getDataSource(dbName,dbConfig); } private DataSource getDataSource(String dbName, DataTypeMap dbConfig) throws SlxException { Assert.isNotNull(dbConfig); String _interfaceType = dbConfig.getString(SQLDataSource.INTERFACE_TYPE); EInterfaceType type = EInterfaceType.fromValue(_interfaceType); DataSource __return = null; switch (type) { case JNDIOSGI: { String filterName = dbConfig.getString("jndiosgi"); __return = lookupDataSource(filterName); break; } case OSGI: { String filter = dbConfig.getString("osgi"); List<DataSource> objs = ServiceUtil.getOSGIServices( DataSource.class, filter); if (objs != null && objs.size() == 1) __return = objs.get(0); if (objs.size() > 1) { log.warn("found more than one Jdbc DataSource for filter:" + filter + ",Just select first one"); __return = objs.get(0); } break; } case DATASOURCE: { __return = getInternalDs(dbName, dbConfig); break; } case DRIVERMANAGER: { boolean credentialsInURL = dbConfig.getBoolean( "interface.credentialsInURL", false); DataTypeMap driverConfig = dbConfig.getSubtree("driver"); String username = driverConfig.getString("username"); String password = driverConfig.getString("password"); String jdbcURL = driverConfig.getString("url"); if (jdbcURL == null) jdbcURL = new StringBuilder().append("jdbc:").append( driverConfig.getString("driverName")).append("://").append( driverConfig.getString("serverName")).append(":").append( driverConfig.get("portNumber")).append("/").append( driverConfig.get("databaseName")).append( credentialsInURL ? (new StringBuilder()).append( "?user=").append(username).append("&password=").append( password).toString() : "").toString(); Class<?> driver = null; String dmImplementer = dbConfig.getString("driver"); if (log.isDebugEnabled()) { log.debug((new StringBuilder()).append( "Initializing SQL config for '").append(dbName).append( "' from system config").append( " - using DriverManager: ").append(dmImplementer).toString()); } try { driver = Reflection.classForName(dmImplementer); } catch (Exception e) { throw new SlxException(Tmodule.SQL, Texception.REFLECTION_EXCEPTION, "can't find class :" + dmImplementer); } if (driver != null) { log.debug((new StringBuilder()).append(dmImplementer).append( " lookup successful").toString()); } __return= new DriverManagerDataSource(jdbcURL, username, password, credentialsInURL); break; } case JNDI: { String jndi = dbConfig.getString("jndi"); Object dataSource = ServiceUtil.getJNDIService(jndi); if (dataSource instanceof DataSource) { __return = (DataSource) dataSource; } else { throw new SlxException(Tmodule.SQL, Texception.OBJECT_TYPE_NOT_ADAPTED, "can not find a adapter for datasource"); } break; } default: { throw new SlxException(Tmodule.POOL, Texception.NO_SUPPORT, "Unsupported interface type " + type + " for database " + dbName + " - check your config."); } } return __return; } }