/*
* Copyright (c) 2013, OpenCloudDB/MyCAT and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software;Designed and Developed mainly by many Chinese
* opensource volunteers. you can redistribute it and/or modify it under the
* terms of the GNU General Public License version 2 only, as published by the
* Free Software Foundation.
*
* This code 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 General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Any questions about this component can be directed to it's project Web address
* https://code.google.com/p/opencloudb/.
*
*/
package org.opencloudb.net;
import java.io.EOFException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.nio.channels.AsynchronousSocketChannel;
import java.util.Set;
import org.apache.log4j.Logger;
import org.opencloudb.config.Capabilities;
import org.opencloudb.config.ErrorCode;
import org.opencloudb.config.Versions;
import org.opencloudb.mysql.CharsetUtil;
import org.opencloudb.mysql.MySQLMessage;
import org.opencloudb.net.handler.FrontendAuthenticator;
import org.opencloudb.net.handler.FrontendPrepareHandler;
import org.opencloudb.net.handler.FrontendPrivileges;
import org.opencloudb.net.handler.FrontendQueryHandler;
import org.opencloudb.net.mysql.ErrorPacket;
import org.opencloudb.net.mysql.HandshakePacket;
import org.opencloudb.net.mysql.MySQLPacket;
import org.opencloudb.net.mysql.OkPacket;
import org.opencloudb.util.RandomUtil;
/**
* @author mycat
*/
public abstract class FrontendConnection extends AbstractConnection {
private static final Logger LOGGER = Logger
.getLogger(FrontendConnection.class);
protected long id;
protected String host;
protected int port;
protected int localPort;
protected long idleTimeout;
protected String charset;
protected int charsetIndex;
protected byte[] seed;
protected String user;
protected String schema;
protected FrontendPrivileges privileges;
protected FrontendQueryHandler queryHandler;
protected FrontendPrepareHandler prepareHandler;
protected boolean isAccepted;
protected boolean isAuthenticated;
public FrontendConnection(AsynchronousSocketChannel channel)
throws IOException {
super(channel);
InetSocketAddress localAddr = (InetSocketAddress) channel
.getLocalAddress();
InetSocketAddress remoteAddr = (InetSocketAddress) channel
.getRemoteAddress();
this.host = remoteAddr.getHostString();
this.port = localAddr.getPort();
this.localPort = remoteAddr.getPort();
this.handler = new FrontendAuthenticator(this);
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public int getLocalPort() {
return localPort;
}
public void setLocalPort(int localPort) {
this.localPort = localPort;
}
public void setAccepted(boolean isAccepted) {
this.isAccepted = isAccepted;
}
public void setProcessor(NIOProcessor processor) {
super.setProcessor(processor);
processor.addFrontend(this);
}
public void setQueryHandler(FrontendQueryHandler queryHandler) {
this.queryHandler = queryHandler;
}
public void setPrepareHandler(FrontendPrepareHandler prepareHandler) {
this.prepareHandler = prepareHandler;
}
public void setAuthenticated(boolean isAuthenticated) {
this.isAuthenticated = isAuthenticated;
}
public FrontendPrivileges getPrivileges() {
return privileges;
}
public void setPrivileges(FrontendPrivileges privileges) {
this.privileges = privileges;
}
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 byte[] getSeed() {
return seed;
}
public int getCharsetIndex() {
return charsetIndex;
}
public boolean setCharsetIndex(int ci) {
String charset = CharsetUtil.getCharset(ci);
if (charset != null) {
this.charset = charset;
this.charsetIndex = ci;
return true;
} else {
return false;
}
}
public String getCharset() {
return charset;
}
public boolean setCharset(String charset) {
int ci = CharsetUtil.getIndex(charset);
if (ci > 0) {
this.charset = charset;
this.charsetIndex = ci;
return true;
} else {
return false;
}
}
public void writeErrMessage(int errno, String msg) {
writeErrMessage((byte) 1, errno, msg);
}
public void writeErrMessage(byte id, int errno, String msg) {
ErrorPacket err = new ErrorPacket();
err.packetId = id;
err.errno = errno;
err.message = encodeString(msg, charset);
err.write(this);
}
public void initDB(byte[] data) {
MySQLMessage mm = new MySQLMessage(data);
mm.position(5);
String db = mm.readString();
// 检查schema的有效性
if (db == null || !privileges.schemaExists(db)) {
writeErrMessage(ErrorCode.ER_BAD_DB_ERROR, "Unknown database '"
+ db + "'");
return;
}
if (!privileges.userExists(user, host)) {
writeErrMessage(ErrorCode.ER_ACCESS_DENIED_ERROR,
"Access denied for user '" + user + "'");
return;
}
Set<String> schemas = privileges.getUserSchemas(user);
if (schemas == null || schemas.size() == 0 || schemas.contains(db)) {
this.schema = db;
write(writeToBuffer(OkPacket.OK, allocate()));
} else {
String s = "Access denied for user '" + user + "' to database '"
+ db + "'";
writeErrMessage(ErrorCode.ER_DBACCESS_DENIED_ERROR, s);
}
}
public void query(byte[] data) {
if (queryHandler != null) {
// 取得语句
MySQLMessage mm = new MySQLMessage(data);
mm.position(5);
String sql = null;
try {
sql = mm.readString(charset);
} catch (UnsupportedEncodingException e) {
writeErrMessage(ErrorCode.ER_UNKNOWN_CHARACTER_SET,
"Unknown charset '" + charset + "'");
return;
}
if (sql == null || sql.length() == 0) {
writeErrMessage(ErrorCode.ER_NOT_ALLOWED_COMMAND, "Empty SQL");
return;
}
// sql = StringUtil.replace(sql, "`", "");
// remove last ';'
if (sql.endsWith(";")) {
sql = sql.substring(0, sql.length() - 1);
}
// 执行查询
queryHandler.setReadOnly(privileges.isReadOnly(user));
queryHandler.query(sql);
} else {
writeErrMessage(ErrorCode.ER_UNKNOWN_COM_ERROR,
"Query unsupported!");
}
}
public void stmtPrepare(byte[] data) {
if (prepareHandler != null) {
// 取得语句
MySQLMessage mm = new MySQLMessage(data);
mm.position(5);
String sql = null;
try {
sql = mm.readString(charset);
} catch (UnsupportedEncodingException e) {
writeErrMessage(ErrorCode.ER_UNKNOWN_CHARACTER_SET,
"Unknown charset '" + charset + "'");
return;
}
if (sql == null || sql.length() == 0) {
writeErrMessage(ErrorCode.ER_NOT_ALLOWED_COMMAND, "Empty SQL");
return;
}
// 执行预处理
prepareHandler.prepare(sql);
} else {
writeErrMessage(ErrorCode.ER_UNKNOWN_COM_ERROR,
"Prepare unsupported!");
}
}
public void stmtExecute(byte[] data) {
if (prepareHandler != null) {
prepareHandler.execute(data);
} else {
writeErrMessage(ErrorCode.ER_UNKNOWN_COM_ERROR,
"Prepare unsupported!");
}
}
public void stmtClose(byte[] data) {
if (prepareHandler != null) {
prepareHandler.close();
} else {
writeErrMessage(ErrorCode.ER_UNKNOWN_COM_ERROR,
"Prepare unsupported!");
}
}
public void ping() {
write(writeToBuffer(OkPacket.OK, allocate()));
}
public void heartbeat(byte[] data) {
write(writeToBuffer(OkPacket.OK, allocate()));
}
public void kill(byte[] data) {
writeErrMessage(ErrorCode.ER_UNKNOWN_COM_ERROR, "Unknown command");
}
public void unknown(byte[] data) {
writeErrMessage(ErrorCode.ER_UNKNOWN_COM_ERROR, "Unknown command");
}
@Override
public void register() throws IOException {
if (!isClosed.get()) {
// 生成认证数据
byte[] rand1 = RandomUtil.randomBytes(8);
byte[] rand2 = RandomUtil.randomBytes(12);
// 保存认证数据
byte[] seed = new byte[rand1.length + rand2.length];
System.arraycopy(rand1, 0, seed, 0, rand1.length);
System.arraycopy(rand2, 0, seed, rand1.length, rand2.length);
this.seed = seed;
// 发送握手数据包
HandshakePacket hs = new HandshakePacket();
hs.packetId = 0;
hs.protocolVersion = Versions.PROTOCOL_VERSION;
hs.serverVersion = Versions.SERVER_VERSION;
hs.threadId = id;
hs.seed = rand1;
hs.serverCapabilities = getServerCapabilities();
hs.serverCharsetIndex = (byte) (charsetIndex & 0xff);
hs.serverStatus = 2;
hs.restOfScrambleBuff = rand2;
hs.write(this);
// asynread response
this.asynRead();
}
}
@Override
public void handle(final byte[] data) {
if (data[4] == MySQLPacket.COM_QUIT) {
this.getProcessor().getCommands().doQuit();
this.close("quit cmd");
return;
}
// 异步处理前端数据
// processor.getHandler().execute(new Runnable()
processor.getExecutor().execute(new Runnable() {
@Override
public void run() {
try {
handler.handle(data);
} catch (Throwable t) {
error(ErrorCode.ERR_HANDLE_DATA, t);
}
}
});
}
protected int getServerCapabilities() {
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 |= ServerDefs.CLIENT_RESERVED;
flag |= Capabilities.CLIENT_SECURE_CONNECTION;
return flag;
}
protected boolean isConnectionReset(Throwable t) {
if (t instanceof IOException) {
String msg = t.getMessage();
return (msg != null && msg.contains("Connection reset by peer"));
}
return false;
}
@Override
public String toString() {
return new StringBuilder().append("[thread=")
.append(Thread.currentThread().getName()).append(",class=")
.append(getClass().getSimpleName()).append(",id=").append(id)
.append(",host=").append(host).append(",port=").append(port)
.append(",schema=").append(schema).append(']').toString();
}
@Override
public void error(int errCode, Throwable t) {
if (isClosed()) {
return;
}
// 根据异常类型和信息,选择日志输出级别。
if (t instanceof EOFException) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(toString(), t);
}
} else if (isConnectionReset(t)) {
if (LOGGER.isInfoEnabled()) {
LOGGER.info(toString(), t);
}
} else {
LOGGER.warn(toString(), t);
}
String msg = t.getMessage();
writeErrMessage(ErrorCode.ER_YES, msg == null ? t.getClass()
.getSimpleName() : msg);
}
private final static byte[] encodeString(String src, String charset) {
if (src == null) {
return null;
}
if (charset == null) {
return src.getBytes();
}
try {
return src.getBytes(charset);
} catch (UnsupportedEncodingException e) {
return src.getBytes();
}
}
}