/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 org.apache.ignite.internal.processors.odbc; import org.apache.ignite.IgniteLogger; import org.apache.ignite.internal.GridKernalContext; import org.apache.ignite.internal.binary.BinaryReaderExImpl; import org.apache.ignite.internal.binary.BinaryWriterExImpl; import org.apache.ignite.internal.binary.streams.BinaryHeapInputStream; import org.apache.ignite.internal.binary.streams.BinaryHeapOutputStream; import org.apache.ignite.internal.binary.streams.BinaryInputStream; import org.apache.ignite.internal.processors.odbc.odbc.OdbcMessageParser; import org.apache.ignite.internal.processors.odbc.odbc.OdbcRequestHandler; import org.apache.ignite.internal.util.GridSpinBusyLock; import org.apache.ignite.internal.util.nio.GridNioServerListenerAdapter; import org.apache.ignite.internal.util.nio.GridNioSession; import org.apache.ignite.internal.util.nio.GridNioSessionMetaKey; import org.jetbrains.annotations.Nullable; import java.util.HashSet; import java.util.Set; import java.util.concurrent.atomic.AtomicLong; /** * ODBC message listener. */ public class OdbcNioListener extends GridNioServerListenerAdapter<byte[]> { /** Current version. */ private static final SqlListenerProtocolVersion CURRENT_VER = SqlListenerProtocolVersion.create(2, 1, 0); /** Supported versions. */ private static final Set<SqlListenerProtocolVersion> SUPPORTED_VERS = new HashSet<>(); /** Connection-related metadata key. */ private static final int CONN_CTX_META_KEY = GridNioSessionMetaKey.nextUniqueKey(); /** Request ID generator. */ private static final AtomicLong REQ_ID_GEN = new AtomicLong(); /** Busy lock. */ private final GridSpinBusyLock busyLock; /** Kernal context. */ private final GridKernalContext ctx; /** Maximum allowed cursors. */ private final int maxCursors; /** Logger. */ private final IgniteLogger log; static { SUPPORTED_VERS.add(CURRENT_VER); } /** * Constructor. * * @param ctx Context. * @param busyLock Shutdown busy lock. * @param maxCursors Maximum allowed cursors. */ public OdbcNioListener(GridKernalContext ctx, GridSpinBusyLock busyLock, int maxCursors) { this.ctx = ctx; this.busyLock = busyLock; this.maxCursors = maxCursors; log = ctx.log(getClass()); } /** {@inheritDoc} */ @Override public void onConnected(GridNioSession ses) { if (log.isDebugEnabled()) log.debug("SQL client connected: " + ses.remoteAddress()); } /** {@inheritDoc} */ @Override public void onDisconnected(GridNioSession ses, @Nullable Exception e) { if (log.isDebugEnabled()) { if (e == null) log.debug("SQL client disconnected: " + ses.remoteAddress()); else log.debug("SQL client disconnected due to an error [addr=" + ses.remoteAddress() + ", err=" + e + ']'); } } /** {@inheritDoc} */ @Override public void onMessage(GridNioSession ses, byte[] msg) { assert msg != null; SqlListenerConnectionContext connCtx = ses.meta(CONN_CTX_META_KEY); if (connCtx == null) { onHandshake(ses, msg); return; } SqlListenerMessageParser parser = connCtx.parser(); SqlListenerRequest req; try { req = parser.decode(msg); } catch (Exception e) { log.error("Failed to parse SQL client request [err=" + e + ']'); ses.close(); return; } assert req != null; req.requestId(REQ_ID_GEN.incrementAndGet()); try { long startTime = 0; if (log.isDebugEnabled()) { startTime = System.nanoTime(); log.debug("SQL client request received [reqId=" + req.requestId() + ", addr=" + ses.remoteAddress() + ", req=" + req + ']'); } SqlListenerRequestHandler handler = connCtx.handler(); SqlListenerResponse resp = handler.handle(req); if (log.isDebugEnabled()) { long dur = (System.nanoTime() - startTime) / 1000; log.debug("SQL client request processed [reqId=" + req.requestId() + ", dur(mcs)=" + dur + ", resp=" + resp.status() + ']'); } byte[] outMsg = parser.encode(resp); ses.send(outMsg); } catch (Exception e) { log.error("Failed to process SQL client request [reqId=" + req.requestId() + ", err=" + e + ']'); ses.send(parser.encode(new SqlListenerResponse(SqlListenerResponse.STATUS_FAILED, e.toString()))); } } /** * Perform handshake. * * @param ses Session. * @param msg Message bytes. */ private void onHandshake(GridNioSession ses, byte[] msg) { BinaryInputStream stream = new BinaryHeapInputStream(msg); BinaryReaderExImpl reader = new BinaryReaderExImpl(null, stream, null, true); byte cmd = reader.readByte(); if (cmd != SqlListenerRequest.HANDSHAKE) { log.error("Unexpected SQL client request (will close session): " + ses.remoteAddress()); ses.close(); return; } short verMajor = reader.readShort(); short verMinor = reader.readShort(); short verMaintenance = reader.readShort(); SqlListenerProtocolVersion ver = SqlListenerProtocolVersion.create(verMajor, verMinor, verMaintenance); String errMsg = null; if (SUPPORTED_VERS.contains(ver)) { // Prepare context. SqlListenerConnectionContext connCtx = prepareContext(ver, reader); ses.addMeta(CONN_CTX_META_KEY, connCtx); } else { log.warning("Unsupported version: " + ver.toString()); errMsg = "Unsupported version."; } // Send response. BinaryWriterExImpl writer = new BinaryWriterExImpl(null, new BinaryHeapOutputStream(8), null, null); if (errMsg == null) { writer.writeBoolean(true); } else { writer.writeBoolean(false); writer.writeShort(CURRENT_VER.major()); writer.writeShort(CURRENT_VER.minor()); writer.writeShort(CURRENT_VER.maintenance()); writer.doWriteString(errMsg); } ses.send(writer.array()); } /** * Prepare context. * * @param ver Version. * @param reader Reader. * @return Context. */ private SqlListenerConnectionContext prepareContext(SqlListenerProtocolVersion ver, BinaryReaderExImpl reader) { // TODO: Switch between ODBC and JDBC. boolean distributedJoins = reader.readBoolean(); boolean enforceJoinOrder = reader.readBoolean(); OdbcRequestHandler handler = new OdbcRequestHandler(ctx, busyLock, maxCursors, distributedJoins, enforceJoinOrder); OdbcMessageParser parser = new OdbcMessageParser(ctx); return new SqlListenerConnectionContext(handler, parser); } }