package org.adbcj.mysql.codec;
import java.util.EnumSet;
import java.util.Set;
import org.adbcj.Connection;
import org.adbcj.ConnectionManager;
import org.adbcj.DbException;
import org.adbcj.DbFuture;
import org.adbcj.DbSessionClosedException;
import org.adbcj.DbSessionFuture;
import org.adbcj.PreparedStatement;
import org.adbcj.Result;
import org.adbcj.ResultEventHandler;
import org.adbcj.support.AbstractDbSession;
import org.adbcj.support.DefaultDbFuture;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public abstract class AbstractMySqlConnection extends AbstractDbSession implements Connection {
private static final Logger logger = LoggerFactory.getLogger(AbstractMySqlConnection.class);
private final AbstractMySqlConnectionManager connectionManager;
private final int id;
private final LoginCredentials credentials;
private Request<Void> closeRequest; // Access must by synchronized on 'this'
private MysqlCharacterSet charset = MysqlCharacterSet.LATIN1_SWEDISH_CI;
protected AbstractMySqlConnection(AbstractMySqlConnectionManager connectionManager, LoginCredentials credentials) {
super(connectionManager.isPipeliningEnabled());
this.connectionManager = connectionManager;
this.credentials = credentials;
this.id = connectionManager.nextId();
connectionManager.addConnection(this);
}
public abstract void write(ClientRequest request);
protected abstract boolean isTransportClosing();
public abstract DefaultDbFuture<Connection> getConnectFuture();
public ConnectionManager getConnectionManager() {
return connectionManager;
}
public synchronized DbSessionFuture<Void> close(final boolean immediate) throws DbException {
// If the connection is already closed, return existing close future
logger.debug("Closing");
if (isClosed()) {
if (closeRequest == null) {
closeRequest = new Request<Void>() {
@Override
public void execute() throws Exception {
// Do nothing, close already occurred
}
@Override
public String toString() {
return "Close MySQL session";
}
};
closeRequest.setResult(null);
}
return closeRequest;
} else {
if (immediate) {
logger.debug("Executing immediate close");
// If the close is immediate, cancel pending requests and send request to server
cancelPendingRequests(true);
write(new CommandRequest(Command.QUIT));
closeRequest = new Request<Void>() {
@Override
protected boolean cancelRequest(boolean mayInterruptIfRunning) {
// Canceling is not possible when an immediate close
return false;
}
@Override
public void execute() throws Exception {
// Do nothing, close was already sent
}
@Override
public String toString() {
return "MySQl immediate close";
}
};
} else {
// If the close is NOT immediate, schedule the close
closeRequest = new Request<Void>() {
@Override
public boolean cancelRequest(boolean mayInterruptIfRunning) {
logger.debug("Cancelling close");
unclose();
return true;
}
public synchronized void execute() {
logger.debug("Sending QUIT to server");
write(new CommandRequest(Command.QUIT));
}
@Override
public String toString() {
return "MySQL deferred close";
}
};
enqueueRequest(closeRequest);
}
}
logger.trace("Exiting close()");
return closeRequest;
}
private synchronized void unclose() {
logger.debug("Unclosing");
this.closeRequest = null;
}
public synchronized boolean isClosed() {
return closeRequest != null || isTransportClosing();
}
public <T> DbSessionFuture<T> executeQuery(final String sql, ResultEventHandler<T> eventHandler, T accumulator) {
checkClosed();
return enqueueTransactionalRequest(new Request<T>(eventHandler, accumulator) {
@Override
public void execute() throws Exception {
logger.debug("Sending query '{}'", sql);
CommandRequest request = new CommandRequest(Command.QUERY, sql);
write(request);
}
@Override
public String toString() {
return "SELECT request: " + sql;
}
});
}
public DbSessionFuture<Result> executeUpdate(final String sql) {
checkClosed();
logger.debug("Scheduling update '{}'", sql);
return enqueueTransactionalRequest(new Request<Result>() {
public void execute() {
logger.debug("Sending update '{}'", sql);
CommandRequest request = new CommandRequest(Command.QUERY, sql);
write(request);
}
@Override
public String toString() {
return "MySQL update: " + sql;
}
});
}
public DbSessionFuture<PreparedStatement> prepareStatement(String sql) {
checkClosed();
// TODO Implement MySQL prepareStatement(String sql)
throw new IllegalStateException("Not yet implemented");
}
public DbSessionFuture<PreparedStatement> prepareStatement(Object key, String sql) {
checkClosed();
// TODO Implement MySQL prepareStatement(Object key, String sql)
throw new IllegalStateException("Not yet implemented");
}
public DbFuture<Void> ping() {
checkClosed();
// TODO Implement MySQL ping()
throw new IllegalStateException("Not yet implemented");
}
// ************* Transaction method implementations ******************************************
private static final CommandRequest BEGIN = new CommandRequest(Command.QUERY, "begin");
private static final CommandRequest COMMIT = new CommandRequest(Command.QUERY, "commit");
private static final CommandRequest ROLLBACK = new CommandRequest(Command.QUERY, "rollback");
@Override
protected void sendCommit() {
write(COMMIT);
}
@Override
protected void sendRollback() {
write(ROLLBACK);
}
@Override
protected void sendBegin() {
write(BEGIN);
}
// ************* Non-API methods *************************************************************
public LoginCredentials getCredentials() {
return credentials;
}
public MysqlCharacterSet getCharacterSet() {
return charset;
}
private static final Set<ClientCapabilities> CLIENT_CAPABILITIES = EnumSet.of(
ClientCapabilities.LONG_PASSWORD,
ClientCapabilities.FOUND_ROWS,
ClientCapabilities.LONG_COLUMN_FLAG,
ClientCapabilities.CONNECT_WITH_DB,
ClientCapabilities.LOCAL_FILES,
ClientCapabilities.PROTOCOL_4_1,
ClientCapabilities.TRANSACTIONS,
ClientCapabilities.SECURE_AUTHENTICATION);
public Set<ClientCapabilities> getClientCapabilities() {
return CLIENT_CAPABILITIES;
}
private static final Set<ExtendedClientCapabilities> EXTENDED_CLIENT_CAPABILITIES = EnumSet.of(
ExtendedClientCapabilities.MULTI_RESULTS
);
public Set<ExtendedClientCapabilities> getExtendedClientCapabilities() {
return EXTENDED_CLIENT_CAPABILITIES;
}
protected void checkClosed() {
if (isClosed()) {
throw new DbSessionClosedException(this, "This connection has been closed");
}
}
//
//
// Queuing methods
//
//
/*
* Make this method public.
*/
@Override
public <E> void enqueueRequest(Request<E> request) {
super.enqueueRequest(request);
}
/*
* Make this method public
*/
@Override
public <E> Request<E> getActiveRequest() {
return super.getActiveRequest();
}
@Override
public int hashCode() {
return id;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (this == obj) {
return true;
}
if (!(obj.getClass() == getClass())) {
return false;
}
return id == ((AbstractMySqlConnection)obj).id;
}
public void doClose() {
connectionManager.removeConnection(this);
// TODO Make a DbSessionClosedException and use here
DbException closedException = new DbException("Connection closed");
if (!getConnectFuture().isDone() ) {
getConnectFuture().setException(closedException);
}
errorPendingRequests(closedException);
synchronized (this) {
if (closeRequest != null) {
closeRequest.complete(null);
}
}
}
}