/*
* JEF - Copyright 2009-2010 Jiyi (mr.jiyi@gmail.com)
*
* Licensed 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 jef.database.innerpool;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Logger;
import javax.persistence.PersistenceException;
import javax.sql.DataSource;
import jef.common.Callback;
import jef.common.log.LogUtil;
import jef.common.pool.PoolStatus;
import jef.database.ConnectInfo;
import jef.database.DbCfg;
import jef.database.DbMetaData;
import jef.database.dialect.DatabaseDialect;
import jef.database.meta.Feature;
import jef.tools.Assert;
import jef.tools.JefConfiguration;
import org.easyframe.enterprise.spring.TransactionMode;
import com.google.common.collect.MapMaker;
/**
* 第三版本的连接池 设计简化
*
* 该版本连接池实现的特性:
* <ul>
* <li>1、根据IUserManagedPool接口要求,对同一个线程或者事务会返回相同的连接。</li>
* <li>2、连接池大小控制,会在指定的范围内,自动管理连接数量。</li>
* <li>3、定时检查连接有效性</li>
* </ul>
*/
final class SingleManagedConnectionPool implements IManagedConnectionPool, DataSource, CheckablePool {
private DataSource ds;
private int max;
private int min;
private final DbMetaData metadata;
// TODO there's nowhrere to set this value;
private String testSQL;
/**
* 本来是使用usedConnections.size()来计算目前使用中的连接数的, 但是发现Google
* map在数量统计时不太靠谱,只好自行记录使用
*/
private final AtomicInteger used = new AtomicInteger();
final Map<Object, ReentrantConnection> usedConnections = new MapMaker().concurrencyLevel(12).weakKeys().makeMap();
/**
* 空闲连接数
*/
private final BlockingQueue<ReentrantConnection> freeConns;
// 统计信息,统计拿取和设置的全不知
private final AtomicLong pollCount = new AtomicLong();
private final AtomicLong offerCount = new AtomicLong();
SingleManagedConnectionPool(DataSource ds, int min, int max) {
if (min > max)
min = max;
this.ds = ds;
this.min = min;
this.max = max;
freeConns = new LinkedBlockingQueue<ReentrantConnection>(max);
this.metadata = new DbMetaData(ds, this, null);
metadata.getProfile().accept(metadata);
PoolReleaseThread.getInstance().addPool(this);
PoolCheckThread.getInstance().addPool(this);
}
public String toString() {
return ds.toString() + getStatus().toString();
}
public DataSource getDatasource() {
return ds;
}
public PoolStatus getStatus() {
int used = usedConnections.size();
int free = freeConns.size();
PoolStatus ps = new PoolStatus(max, min, used + free, used, free);
ps.setOfferCount(offerCount.get());
ps.setPollCount(pollCount.get());
return ps;
}
@SuppressWarnings("unchecked")
public Collection<String> getAllDatasourceNames() {
return Collections.EMPTY_SET;
}
public ReentrantConnection getConnection(Object transaction) throws SQLException {
pollCount.incrementAndGet();
try {
ReentrantConnection conn = usedConnections.get(transaction);
if (conn == null) {
if (used.get() < max && freeConns.isEmpty()) {// 尝试用新连接
used.getAndIncrement(); // 提前计数
conn = new SingleConnection(ds.getConnection(), this);
conn.setUsedByObject(transaction);
} else {
used.getAndIncrement(); // 提前计数,并发下为了严格阻止连接池超出上限,必须这样做
conn = freeConns.poll(5000000000L, TimeUnit.NANOSECONDS);// 5秒
if (conn == null) {
used.decrementAndGet();
throw new SQLException("No connection avaliable now." + getStatus());
}
conn.ensureOpen();
conn.setUsedByObject(transaction);
}
usedConnections.put(transaction, conn);
} else {
conn.addUsedByObject();
}
// log(transaction,conn,"get");
return conn;
} catch (InterruptedException e) {
throw new SQLException(e);
}
}
// @SuppressWarnings("unused")
// private void log(Object transaction, ReentrantConnection conn, String action) {
// StackTraceElement[] eles = new Throwable().getStackTrace();
// System.out.println(action + " " + conn);
// System.out.println(eles[4]);
// System.out.println(eles[5]);
// }
public ReentrantConnection poll() throws SQLException {
return getConnection(Thread.currentThread());
}
public void offer(ReentrantConnection conn) {
offerCount.incrementAndGet();
if (conn != null) {
Object o = conn.popUsedByObject();
// log(o,conn,"return");
if (o == null) {
return;// 不是真正的归还
}
ReentrantConnection conn1 = usedConnections.remove(o);
boolean success = freeConns.offer(conn);
if (!success) {
conn.closePhysical();
}
// 归还成功,才减低连接池大小
used.decrementAndGet();
if (conn1 != conn) {
throw new IllegalStateException("The connection returned not match." + conn + "\t" + conn1);
}
}
}
public void close() throws SQLException {
max = 0;
min = 0;
closeConnectionTillMin();
PoolReleaseThread.getInstance().removePool(this);
PoolService.logPoolStatic(getClass().getSimpleName(), pollCount.get(), offerCount.get());
}
public void closeConnectionTillMin() {
if (freeConns.size() > min) {
ReentrantConnection conn;
// 注意下面两个条件顺序必须确保poll操作在后,因为poll操作会变更集合的Size
while (freeConns.size() > min && (conn = freeConns.poll()) != null) {
conn.closePhysical();
}
}
}
public DbMetaData getMetadata(String dbkey) {
return metadata;
}
public DatabaseDialect getProfile(String dbkey) {
return getProfile();
}
public ConnectInfo getInfo(String dbkey) {
return getMetadata(dbkey).getInfo();
}
public DatabaseDialect getProfile() {
return metadata.getProfile();
}
public boolean hasRemarkFeature(String dbkey) {
if (JefConfiguration.getBoolean(DbCfg.DB_NO_REMARK_CONNECTION, false) || this.min > 5) {
return false;
}
DatabaseDialect profile = getProfile();
return profile.has(Feature.REMARK_META_FETCH);
}
public void registeDbInitCallback(Callback<String, SQLException> callback) {
if (callback != null) {
try {
callback.call(null);
} catch (SQLException e) {
throw new PersistenceException(e);
}
}
}
public PrintWriter getLogWriter() throws SQLException {
throw new UnsupportedOperationException("getLogWriter");
}
public void setLogWriter(PrintWriter out) throws SQLException {
throw new UnsupportedOperationException("setLogWriter");
}
public void setLoginTimeout(int seconds) throws SQLException {
throw new UnsupportedOperationException("setLoginTimeout");
}
public int getLoginTimeout() throws SQLException {
return 0;
}
@SuppressWarnings("unchecked")
public <T> T unwrap(Class<T> iface) throws SQLException {
Assert.notNull(iface, "Interface argument must not be null");
if (!DataSource.class.equals(iface)) {
throw new SQLException("DataSource of type [" + getClass().getName() + "] can only be unwrapped as [javax.sql.DataSource], not as [" + iface.getName());
}
return (T) this;
}
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
public Connection getConnection() throws SQLException {
return getConnection(Thread.currentThread());
}
public Connection getConnection(String username, String password) throws SQLException {
return getConnection(Thread.currentThread());
}
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
public void notifyDbDisconnect() {
if (LogUtil.isDebugEnabled()) {
LogUtil.debug("Disconnected connection found, notify Checker thread.");
}
this.doCheck();
}
public boolean isRouting() {
return false;
}
public boolean isDummy() {
return false;
}
public synchronized void doCheck() {
int total = freeConns.size();
int invalid = PoolService.doCheck(this.testSQL, freeConns.iterator());
LogUtil.debug("Checked [{}]. total:{}, invalid:{}", this, total, invalid);
}
public boolean isMultipleRdbms() {
return false;
}
private TransactionMode txMode;
@Override
public IUserManagedPool setTransactionMode(TransactionMode txMode) {
this.txMode=txMode;
return this;
}
@Override
public TransactionMode getTransactionMode() {
return txMode;
}
}