/*
* Copyright 2014 Alexey Plotnik
*
* 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 org.stem.client;
import com.codahale.metrics.Timer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.InetSocketAddress;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
public class RequestHandler implements Connection.ResponseCallback {
private static final Logger logger = LoggerFactory.getLogger(RequestHandler.class);
private final Session session;
private final Callback callback;
private final Message.Request request;
private volatile Host current;
private volatile ConnectionPool currentPool;
private volatile Map<InetSocketAddress, Throwable> errors;
private volatile boolean isCanceled;
private volatile Connection.ResponseHandler connectionHandler;
private final Timer.Context timerContext;
private final long startTime;
public RequestHandler(Session session, Callback callback, Message.Request request) {
this.session = session;
this.callback = callback;
this.request = request;
callback.register(this);
this.timerContext = null; // TODO: metrics support
this.startTime = System.nanoTime();
}
private void logError(InetSocketAddress address, Throwable exception) {
logger.debug("Error querying {}, trying next host (error is: {})", address, exception.toString());
if (errors == null)
errors = new HashMap<InetSocketAddress, Throwable>();
errors.put(address, exception);
}
@Override
public Message.Request request() {
Message.Request request = callback.request();
return request;
}
@Override
public void onSet(Connection connection, Message.Response response, long latency) {
try {
if (connection instanceof PooledConnection)
((PooledConnection) connection).release();
switch (response.type) {
case RESULT:
setFinalResult(connection, response);
break;
case ERROR:
Responses.Error err = (Responses.Error) response;
switch (err.code) {
case SERVER_ERROR:
logger.warn("{} replied with server error ({}), trying next host.", connection.address, err.message);
ClientException exception = new ClientException(
String.format("Host %s replied with server error: %s", connection.address, err.message));
logError(connection.address, exception);
connection.defunct(exception);
setFinalException(connection, exception);
return;
default:
break;
}
setFinalResult(connection, response);
break;
default:
setFinalResult(connection, response);
break;
}
} catch (Exception e) {
setFinalException(connection, e);
}
}
private void setFinalResult(Connection connection, Message.Response response) {
try {
if (timerContext != null)
timerContext.stop();
ExecutionInfo info = current.defaultExecutionInfo;
callback.onSet(connection, response, info, System.nanoTime() - startTime);
} catch (Exception e) {
callback.onException(connection, new ClientInternalError("Unexpected exception while setting final result from " + response, e), System.nanoTime() - startTime);
}
}
@Override
public void onException(Connection connection, Exception exception, long latency) {
try {
if (connection instanceof PooledConnection)
((PooledConnection) connection).release();
if (exception instanceof ConnectionException) {
ConnectionException ce = (ConnectionException) exception;
logError(ce.address, ce);
return;
}
setFinalException(connection, exception);
} catch (Exception e) {
setFinalException(null, new ClientInternalError("An unexpected error happened while handling exception " + exception, e));
}
}
@Override
public void onTimeout(Connection connection, long latency) {
try {
ClientException timeoutException = new ClientException("Timed out waiting for server response");
connection.defunct(timeoutException); // TODO: and what?
logError(connection.address, timeoutException);
setFinalException(connection, timeoutException);
} catch (Exception e) {
setFinalException(null, new ClientInternalError("An unexpected error happened while handling timeout", e));
}
}
public void sendRequest() {
try {
Host host = session.router.getHost(request);
if (query(host))
return;
setFinalException(null, new NoHostAvailableException("No hosts available"));
} catch (Exception e) {
setFinalException(null, new ClientInternalError("An unexpected error occurred while sending request", e));
}
}
private boolean query(Host host) {
if (null == host)
return false;
logger.trace("Querying storage node {}", host);
currentPool = session.pools.get(host);
if (currentPool == null || currentPool.isClosed())
return false;
PooledConnection connection = null;
try {
connection = currentPool.borrowConnection(session.configuration().getSocketOpts().getConnectTimeoutMs(), TimeUnit.MILLISECONDS);
current = host;
connectionHandler = connection.write(this);
return true;
} catch (ConnectionException e) {
if (null != connection)
connection.release();
logError(host.getSocketAddress(), e);
return false;
} catch (ConnectionBusyException e) {
if (connection != null)
connection.release();
logError(host.getSocketAddress(), e);
return false;
} catch (TimeoutException e) {
logError(host.getSocketAddress(), new ClientException("Timeout while trying to acquire available connection (you may want to increase the number of per-host connections)"));
return false;
} catch (RuntimeException e) {
if (connection != null)
connection.release();
logger.error("Unexpected error while querying " + host.getAddress(), e);
logError(host.getSocketAddress(), e);
return false;
}
}
public void cancel() {
isCanceled = true;
if (connectionHandler != null)
connectionHandler.cancelHandler();
}
private void setFinalException(Connection connection, Exception exception) {
try {
if (timerContext != null)
timerContext.stop();
}
finally {
callback.onException(connection, exception, System.nanoTime() - startTime);
}
}
interface Callback extends Connection.ResponseCallback {
public void onSet(Connection connection, Message.Response response, ExecutionInfo info, long latency);
public void register(RequestHandler handler);
}
}