/*
* Copyright 1999-2012 Alibaba Group.
*
* 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 com.alibaba.cobar.mysql.nio;
import java.io.UnsupportedEncodingException;
import java.nio.channels.SocketChannel;
import java.security.NoSuchAlgorithmException;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.log4j.Logger;
import com.alibaba.cobar.config.Capabilities;
import com.alibaba.cobar.config.ErrorCode;
import com.alibaba.cobar.config.Isolations;
import com.alibaba.cobar.exception.UnknownTxIsolationException;
import com.alibaba.cobar.mysql.CharsetUtil;
import com.alibaba.cobar.mysql.SecurityUtil;
import com.alibaba.cobar.mysql.nio.handler.ResponseHandler;
import com.alibaba.cobar.net.BackendConnection;
import com.alibaba.cobar.net.mysql.AuthPacket;
import com.alibaba.cobar.net.mysql.CommandPacket;
import com.alibaba.cobar.net.mysql.HandshakePacket;
import com.alibaba.cobar.net.mysql.MySQLPacket;
import com.alibaba.cobar.net.mysql.QuitPacket;
import com.alibaba.cobar.route.RouteResultsetNode;
import com.alibaba.cobar.server.ServerConnection;
import com.alibaba.cobar.statistic.SQLRecord;
import com.alibaba.cobar.statistic.SQLRecorder;
import com.alibaba.cobar.util.TimeUtil;
/**
* @author xianmao.hexm
*/
public class MySQLConnection extends BackendConnection {
private static final Logger LOGGER = Logger.getLogger(MySQLConnection.class);
private static final long CLIENT_FLAGS = initClientFlags();
private static long initClientFlags() {
int flag = 0;
flag |= Capabilities.CLIENT_LONG_PASSWORD;
flag |= Capabilities.CLIENT_FOUND_ROWS;
flag |= Capabilities.CLIENT_LONG_FLAG;
flag |= Capabilities.CLIENT_CONNECT_WITH_DB;
// flag |= Capabilities.CLIENT_NO_SCHEMA;
// flag |= Capabilities.CLIENT_COMPRESS;
flag |= Capabilities.CLIENT_ODBC;
// flag |= Capabilities.CLIENT_LOCAL_FILES;
flag |= Capabilities.CLIENT_IGNORE_SPACE;
flag |= Capabilities.CLIENT_PROTOCOL_41;
flag |= Capabilities.CLIENT_INTERACTIVE;
// flag |= Capabilities.CLIENT_SSL;
flag |= Capabilities.CLIENT_IGNORE_SIGPIPE;
flag |= Capabilities.CLIENT_TRANSACTIONS;
// flag |= Capabilities.CLIENT_RESERVED;
flag |= Capabilities.CLIENT_SECURE_CONNECTION;
// client extension
// flag |= Capabilities.CLIENT_MULTI_STATEMENTS;
// flag |= Capabilities.CLIENT_MULTI_RESULTS;
return flag;
}
private static final CommandPacket _READ_UNCOMMITTED = new CommandPacket();
private static final CommandPacket _READ_COMMITTED = new CommandPacket();
private static final CommandPacket _REPEATED_READ = new CommandPacket();
private static final CommandPacket _SERIALIZABLE = new CommandPacket();
private static final CommandPacket _AUTOCOMMIT_ON = new CommandPacket();
private static final CommandPacket _AUTOCOMMIT_OFF = new CommandPacket();
private static final CommandPacket _COMMIT = new CommandPacket();
private static final CommandPacket _ROLLBACK = new CommandPacket();
static {
_READ_UNCOMMITTED.packetId = 0;
_READ_UNCOMMITTED.command = MySQLPacket.COM_QUERY;
_READ_UNCOMMITTED.arg = "SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED".getBytes();
_READ_COMMITTED.packetId = 0;
_READ_COMMITTED.command = MySQLPacket.COM_QUERY;
_READ_COMMITTED.arg = "SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED".getBytes();
_REPEATED_READ.packetId = 0;
_REPEATED_READ.command = MySQLPacket.COM_QUERY;
_REPEATED_READ.arg = "SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ".getBytes();
_SERIALIZABLE.packetId = 0;
_SERIALIZABLE.command = MySQLPacket.COM_QUERY;
_SERIALIZABLE.arg = "SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE".getBytes();
_AUTOCOMMIT_ON.packetId = 0;
_AUTOCOMMIT_ON.command = MySQLPacket.COM_QUERY;
_AUTOCOMMIT_ON.arg = "SET autocommit=1".getBytes();
_AUTOCOMMIT_OFF.packetId = 0;
_AUTOCOMMIT_OFF.command = MySQLPacket.COM_QUERY;
_AUTOCOMMIT_OFF.arg = "SET autocommit=0".getBytes();
_COMMIT.packetId = 0;
_COMMIT.command = MySQLPacket.COM_QUERY;
_COMMIT.arg = "commit".getBytes();
_ROLLBACK.packetId = 0;
_ROLLBACK.command = MySQLPacket.COM_QUERY;
_ROLLBACK.arg = "rollback".getBytes();
}
private MySQLConnectionPool pool;
private long threadId;
private HandshakePacket handshake;
private int charsetIndex;
private String charset;
private String dbCharset;
private volatile int txIsolation;
private volatile boolean autocommit;
private long clientFlags;
private boolean isAuthenticated;
private String user;
private String password;
private String schema;
private Object attachment;
private final AtomicBoolean isRunning;
private long lastTime; // QS_TODO
private final AtomicBoolean isQuit;
private volatile StatusSync statusSync;
public MySQLConnection(SocketChannel channel) {
super(channel);
this.clientFlags = CLIENT_FLAGS;
this.lastTime = TimeUtil.currentTimeMillis();
this.isRunning = new AtomicBoolean(false);
this.isQuit = new AtomicBoolean(false);
this.autocommit = true;
}
public MySQLConnectionPool getPool() {
return pool;
}
public void setPool(MySQLConnectionPool pool) {
this.pool = pool;
}
public String getUser() {
return user;
}
public void setUser(String user) {
this.user = user;
}
public String getSchema() {
return schema;
}
public void setSchema(String schema) {
this.schema = schema;
}
public void setPassword(String password) {
this.password = password;
}
public HandshakePacket getHandshake() {
return handshake;
}
public void setHandshake(HandshakePacket handshake) {
this.handshake = handshake;
}
public void setCharsetIndex(int charsetIndex) {
this.charsetIndex = charsetIndex;
}
public long getThreadId() {
return threadId;
}
public void setThreadId(long threadId) {
this.threadId = threadId;
}
public String getCharset() {
return charset;
}
public void setCharset(String charset) {
this.charset = charset;
}
public boolean isAuthenticated() {
return isAuthenticated;
}
public void setAuthenticated(boolean isAuthenticated) {
this.isAuthenticated = isAuthenticated;
}
public String getPassword() {
return password;
}
public void authenticate() {
AuthPacket packet = new AuthPacket();
packet.packetId = 1;
packet.clientFlags = clientFlags;
packet.maxPacketSize = maxPacketSize;
packet.charsetIndex = charsetIndex;
packet.user = user;
try {
packet.password = passwd(password, handshake);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e.getMessage());
}
packet.database = schema;
packet.write(this);
}
public long getLastTime() {
return lastTime;
}
public void setLastTime(long lastTime) {
this.lastTime = lastTime;
}
public void setRunning(boolean running) {
isRunning.set(running);
}
public boolean isRunning() {
return isRunning.get();
}
public boolean isAutocommit() {
return autocommit;
}
public Object getAttachment() {
return attachment;
}
public void setAttachment(Object attachment) {
this.attachment = attachment;
}
public boolean isClosedOrQuit() {
return isClosed() || isQuit.get();
}
private static class StatusSync {
private final RouteResultsetNode rrn;
private final MySQLConnection conn;
private CommandPacket charCmd;
private CommandPacket isoCmd;
private CommandPacket acCmd;
private final int charIndex;
private final int txIsolation;
private final boolean autocommit;
private volatile boolean executed;
public StatusSync(MySQLConnection conn, RouteResultsetNode rrn, ServerConnection sc, boolean autocommit) {
this.conn = conn;
this.rrn = rrn;
this.charIndex = sc.getCharsetIndex();
this.charCmd = conn.charsetIndex != charIndex ? getCharsetCommand(charIndex) : null;
this.txIsolation = sc.getTxIsolation();
this.isoCmd = conn.txIsolation != txIsolation ? getTxIsolationCommand(txIsolation) : null;
this.autocommit = autocommit;
this.acCmd = conn.autocommit != autocommit ? (autocommit ? _AUTOCOMMIT_ON : _AUTOCOMMIT_OFF) : null;
}
private Runnable updater;
public boolean isExecuted() {
return executed;
}
public boolean isSync() {
return charCmd == null && isoCmd == null && acCmd == null;
}
public void update() {
Runnable updater = this.updater;
if (updater != null) {
updater.run();
}
}
/**
* @return false if sync complete
*/
public boolean sync() {
CommandPacket cmd;
if (charCmd != null) {
updater = new Runnable() {
@Override
public void run() {
int ci = StatusSync.this.charIndex;
conn.charsetIndex = ci;
conn.charset = CharsetUtil.getCharset(ci);
}
};
cmd = charCmd;
charCmd = null;
cmd.write(conn);
return true;
}
if (isoCmd != null) {
updater = new Runnable() {
@Override
public void run() {
conn.txIsolation = StatusSync.this.txIsolation;
}
};
cmd = isoCmd;
isoCmd = null;
cmd.write(conn);
return true;
}
if (acCmd != null) {
updater = new Runnable() {
@Override
public void run() {
conn.autocommit = StatusSync.this.autocommit;
}
};
cmd = acCmd;
acCmd = null;
cmd.write(conn);
return true;
}
return false;
}
public void execute() throws UnsupportedEncodingException {
executed = true;
CommandPacket packet = new CommandPacket();
packet.packetId = 0;
packet.command = MySQLPacket.COM_QUERY;
packet.arg = rrn.getStatement().getBytes(conn.getCharset());
conn.lastTime = TimeUtil.currentTimeMillis();
packet.write(conn);
}
private static CommandPacket getTxIsolationCommand(int txIsolation) {
switch (txIsolation) {
case Isolations.READ_UNCOMMITTED:
return _READ_UNCOMMITTED;
case Isolations.READ_COMMITTED:
return _READ_COMMITTED;
case Isolations.REPEATED_READ:
return _REPEATED_READ;
case Isolations.SERIALIZABLE:
return _SERIALIZABLE;
default:
throw new UnknownTxIsolationException("txIsolation:" + txIsolation);
}
}
private static CommandPacket getCharsetCommand(int ci) {
String charset = CharsetUtil.getCharset(ci);
StringBuilder s = new StringBuilder();
s.append("SET names ").append(charset);
CommandPacket cmd = new CommandPacket();
cmd.packetId = 0;
cmd.command = MySQLPacket.COM_QUERY;
cmd.arg = s.toString().getBytes();
return cmd;
}
}
/**
* @return if synchronization finished and execute-sql has already been sent
* before
*/
public boolean syncAndExcute() throws UnsupportedEncodingException {
StatusSync sync = statusSync;
if (sync.isExecuted()) {
return true;
}
if (sync.isSync()) {
sync.update();
sync.execute();
} else {
sync.update();
sync.sync();
}
return false;
}
public void execute(RouteResultsetNode rrn, ServerConnection sc, boolean autocommit)
throws UnsupportedEncodingException {
StatusSync sync = new StatusSync(this, rrn, sc, autocommit);
statusSync = sync;
if (sync.isSync() || !sync.sync()) {
sync.execute();
}
}
public void quit() {
if (isQuit.compareAndSet(false, true) && !isClosed()) {
if (isAuthenticated) {
// QS_TODO check
write(writeToBuffer(QuitPacket.QUIT, allocate()));
write(processor.getBufferPool().allocate());
} else {
close();
}
}
}
@Override
public boolean close() {
isQuit.set(true);
boolean closed = super.close();
if (closed) {
pool.deActive();
}
return closed;
}
public void commit() {
_COMMIT.write(this);
}
public void rollback() {
_ROLLBACK.write(this);
}
public void release() {
attachment = null;
statusSync = null;
setResponseHandler(null);
pool.releaseChannel(this);
}
@Override
public void error(int errCode, Throwable t) {
LOGGER.warn(toString(), t);
switch (errCode) {
case ErrorCode.ERR_HANDLE_DATA:
// handle error ..
break;
case ErrorCode.ERR_PUT_WRITE_QUEUE:
// QS_TODO
break;
default:
close();
if (handler instanceof MySQLConnectionHandler) {
((MySQLConnectionHandler) handler).connectionError(t);
}
}
}
public boolean setResponseHandler(ResponseHandler queryHandler) {
if (handler instanceof MySQLConnectionHandler) {
((MySQLConnectionHandler) handler).setResponseHandler(queryHandler);
return true;
}
return false;
}
/**
* 记录sql执行信息
*/
public void recordSql(String host, String schema, String stmt) {
final long now = TimeUtil.currentTimeMillis();
if (now > this.lastTime) {
long time = now - this.lastTime;
SQLRecorder sqlRecorder = this.pool.getSqlRecorder();
if (sqlRecorder.check(time)) {
SQLRecord recorder = new SQLRecord();
recorder.host = host;
recorder.schema = schema;
recorder.statement = stmt;
recorder.startTime = lastTime;
recorder.executeTime = time;
recorder.dataNode = pool.getName();
recorder.dataNodeIndex = pool.getIndex();
sqlRecorder.add(recorder);
}
}
this.lastTime = now;
}
private static byte[] passwd(String pass, HandshakePacket hs) throws NoSuchAlgorithmException {
if (pass == null || pass.length() == 0) {
return null;
}
byte[] passwd = pass.getBytes();
int sl1 = hs.seed.length;
int sl2 = hs.restOfScrambleBuff.length;
byte[] seed = new byte[sl1 + sl2];
System.arraycopy(hs.seed, 0, seed, 0, sl1);
System.arraycopy(hs.restOfScrambleBuff, 0, seed, sl1, sl2);
return SecurityUtil.scramble411(passwd, seed);
}
public String getDbCharset() {
return dbCharset;
}
public void setDbCharset(String dbCharset) {
this.dbCharset = dbCharset;
}
}