Package com.datastax.driver.core

Source Code of com.datastax.driver.core.ResultSetFuture$ResponseCallback

/*
*      Copyright (C) 2012 DataStax Inc.
*
*   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.datastax.driver.core;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.TimeUnit;

import com.google.common.util.concurrent.Uninterruptibles;

import com.datastax.cassandra.transport.Message;
import com.datastax.cassandra.transport.messages.ErrorMessage;
import com.datastax.cassandra.transport.messages.ResultMessage;

import com.datastax.driver.core.exceptions.*;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* A future on a {@link ResultSet}.
*
* Note that this class implements <a href="http://code.google.com/p/guava-libraries/">Guava</a>'s {@code
* ListenableFuture} and can so be used with Guava's future utilities.
*/
public class ResultSetFuture extends SimpleFuture<ResultSet> {

    private static final Logger logger = LoggerFactory.getLogger(ResultSetFuture.class);

    private final Session.Manager session;
    final ResponseCallback callback;

    ResultSetFuture(Session.Manager session, Message.Request request) {
        this.session = session;
        this.callback = new ResponseCallback(request);
    }

    // The reason this exists is because we don't want to expose its method
    // publicly (otherwise Future could implement RequestHandler.Callback directly)
    class ResponseCallback implements RequestHandler.Callback {

        private final Message.Request request;
        private volatile RequestHandler handler;

        ResponseCallback(Message.Request request) {
            this.request = request;
        }

        @Override
        public void register(RequestHandler handler) {
            this.handler = handler;
        }

        @Override
        public Message.Request request() {
            return request;
        }

        @Override
        public void onSet(Connection connection, Message.Response response, ExecutionInfo info) {
            try {
                switch (response.type) {
                    case RESULT:
                        ResultMessage rm = (ResultMessage)response;
                        switch (rm.kind) {
                            case SET_KEYSPACE:
                                // propagate the keyspace change to other connections
                                session.poolsState.setKeyspace(((ResultMessage.SetKeyspace)rm).keyspace);
                                set(ResultSet.fromMessage(rm, session, info));
                                break;
                            case SCHEMA_CHANGE:
                                ResultMessage.SchemaChange scc = (ResultMessage.SchemaChange)rm;
                                ResultSet rs = ResultSet.fromMessage(rm, session, info);
                                switch (scc.change) {
                                    case CREATED:
                                        if (scc.columnFamily.isEmpty()) {
                                            session.cluster.manager.refreshSchema(connection, ResultSetFuture.this, rs, null, null);
                                        } else {
                                            session.cluster.manager.refreshSchema(connection, ResultSetFuture.this, rs, scc.keyspace, null);
                                        }
                                        break;
                                    case DROPPED:
                                        if (scc.columnFamily.isEmpty()) {
                                            // If that the one keyspace we are logged in, reset to null (it shouldn't really happen but ...)
                                            // Note: Actually, Cassandra doesn't do that so we don't either as this could confuse prepared statements.
                                            // We'll add it back if CASSANDRA-5358 changes that behavior
                                            //if (scc.keyspace.equals(session.poolsState.keyspace))
                                            //    session.poolsState.setKeyspace(null);
                                            session.cluster.manager.refreshSchema(connection, ResultSetFuture.this, rs, null, null);
                                        } else {
                                            session.cluster.manager.refreshSchema(connection, ResultSetFuture.this, rs, scc.keyspace, null);
                                        }
                                        break;
                                    case UPDATED:
                                        if (scc.columnFamily.isEmpty()) {
                                            session.cluster.manager.refreshSchema(connection, ResultSetFuture.this, rs, scc.keyspace, null);
                                        } else {
                                            session.cluster.manager.refreshSchema(connection, ResultSetFuture.this, rs, scc.keyspace, scc.columnFamily);
                                        }
                                        break;
                                    default:
                                        logger.info("Ignoring unknown schema change result");
                                        break;
                                }
                                break;
                            default:
                                set(ResultSet.fromMessage(rm, session, info));
                                break;
                        }
                        break;
                    case ERROR:
                        setException(convertException(((ErrorMessage)response).error));
                        break;
                    default:
                        // This mean we have probably have a bad node, so defunct the connection
                        connection.defunct(new ConnectionException(connection.address, String.format("Got unexpected %s response", response.type)));
                        setException(new DriverInternalError(String.format("Got unexpected %s response from %s", response.type, connection.address)));
                        break;
                }
            } catch (RuntimeException e) {
                // If we get a bug here, the client will not get it, so better forwarding the error
                setException(new DriverInternalError("Unexpected error while processing response from " + connection.address, e));
            }
        }

        @Override
        public void onSet(Connection connection, Message.Response response) {
            // This is only called for internal calls (i.e, when the callback is not wrapped in ResponseHandler),
            // so don't bother with ExecutionInfo.
            onSet(connection, response, null);
        }

        @Override
        public void onException(Connection connection, Exception exception) {
            setException(exception);
        }

        @Override
        public void onTimeout(Connection connection) {
            // This is only called for internal calls (i.e, when the callback is not wrapped in ResponseHandler).
            // So just set an exception for the final result, which should be handled correctly by said internal call.
            setException(new ConnectionException(connection.address, "Operation Timeouted"));
        }
    }

    /**
     * Waits for the query to return and return its result.
     *
     * This method is usually more convenient than {@link #get} because it:
     * <ul>
     *   <li>Waits for the result uninterruptibly, and so doesn't throw
     *   {@link InterruptedException}.</li>
     *   <li>Returns meaningful exceptions, instead of having to deal
     *   with ExecutionException.</li>
     * </ul>
     * As such, it is the preferred way to get the future result.
     *
     * @throws NoHostAvailableException if no host in the cluster can be
     * contacted successfully to execute this query.
     * @throws QueryExecutionException if the query triggered an execution
     * exception, that is an exception thrown by Cassandra when it cannot execute
     * the query with the requested consistency level successfully.
     * @throws QueryValidationException if the query is invalid (syntax error,
     * unauthorized or any other validation problem).
     */
    public ResultSet getUninterruptibly() {
        try {
            return Uninterruptibles.getUninterruptibly(this);
        } catch (ExecutionException e) {
            extractCauseFromExecutionException(e);
            throw new AssertionError();
        }
    }

    /**
     * Waits for the provided time for the query to return and return its
     * result if available.
     *
     * This method is usually more convenient than {@link #get} because it:
     * <ul>
     *   <li>Waits for the result uninterruptibly, and so doesn't throw
     *   {@link InterruptedException}.</li>
     *   <li>Returns meaningful exceptions, instead of having to deal
     *   with ExecutionException.</li>
     * </ul>
     * As such, it is the preferred way to get the future result.
     *
     * @throws NoHostAvailableException if no host in the cluster can be
     * contacted successfully to execute this query.
     * @throws QueryExecutionException if the query triggered an execution
     * exception, that is an exception thrown by Cassandra when it cannot execute
     * the query with the requested consistency level successfully.
     * @throws QueryValidationException if the query if invalid (syntax error,
     * unauthorized or any other validation problem).
     * @throws TimeoutException if the wait timed out (Note that this is
     * different from a Cassandra timeout, which is a {@code
     * QueryExecutionException}).
     */
    public ResultSet getUninterruptibly(long timeout, TimeUnit unit) throws TimeoutException {
        try {
            return Uninterruptibles.getUninterruptibly(this, timeout, unit);
        } catch (ExecutionException e) {
            extractCauseFromExecutionException(e);
            throw new AssertionError();
        }
    }

    /**
     * Attempts to cancel the execution of the request corresponding to this
     * future. This attempt will fail if the request has already returned.
     * <p>
     * Please note that this only cancle the request driver side, but nothing
     * is done to interrupt the execution of the request Cassandra side (and that even
     * if {@code mayInterruptIfRunning} is true) since  Cassandra does not
     * support such interruption.
     * <p>
     * This method can be used to ensure no more work is performed driver side
     * (which, while it doesn't include stopping a request already submitted
     * to a Cassandra node, may include not retrying another Cassandra host on
     * failure/timeout) if the ResultSet is not going to be retried. Typically,
     * the code to wait for a request result for a maximum of 1 second could
     * look like:
     * <pre>
     *   ResultSetFuture future = session.executeAsync(...some query...);
     *   try {
     *       ResultSet result = future.get(1, TimeUnit.SECONDS);
     *       ... process result ...
     *   } catch (TimeoutException e) {
     *       future.cancel(true); // Ensure any ressource used by this query driver
     *                            // side is released immediately
     *       ... handle timeout ...
     *   }
     * <pre>
     *
     * @param mayInterruptIfRunning the value of this parameter is currently
     * ignored.
     * @return {@code false} if the future could not be cancelled (it has already
     * completed normally); {@code true} otherwise.
     */
    @Override
    public boolean cancel(boolean mayInterruptIfRunning) {
        if (!super.cancel(mayInterruptIfRunning))
            return false;

        callback.handler.cancel();
        return true;
    }

    static void extractCauseFromExecutionException(ExecutionException e) {
        // We could just rethrow e.getCause(). However, the cause of the ExecutionException has likely been
        // created on the I/O thread receiving the response. Which means that the stacktrace associated
        // with said cause will make no mention of the current thread. This is painful for say, finding
        // out which execute() statement actually raised the exception. So instead, we re-create the
        // exception.
        if (e.getCause() instanceof DriverException)
            throw ((DriverException)e.getCause()).copy();
        else
            throw new DriverInternalError("Unexpected exception thrown", e.getCause());
    }

    static void extractCause(Throwable cause) {
        // Same as above
        if (cause instanceof DriverException)
            throw ((DriverException)cause).copy();
        throw new DriverInternalError("Unexpected exception thrown", cause);
    }

    static Exception convertException(org.apache.cassandra.exceptions.TransportException te) {
        switch (te.code()) {
            case SERVER_ERROR:
                return new DriverInternalError("An unexpected error occured server side: " + te.getMessage());
            case PROTOCOL_ERROR:
                return new DriverInternalError("An unexpected protocol error occured. This is a bug in this library, please report: " + te.getMessage());
            case UNAVAILABLE:
                org.apache.cassandra.exceptions.UnavailableException ue = (org.apache.cassandra.exceptions.UnavailableException)te;
                return new UnavailableException(ConsistencyLevel.from(ue.consistency), ue.required, ue.alive);
            case OVERLOADED:
                return new DriverInternalError("Queried host was overloaded; this shouldn't happen, another node should have been tried");
            case IS_BOOTSTRAPPING:
                return new DriverInternalError("Queried host was boostrapping; this shouldn't happen, another node should have been tried");
            case TRUNCATE_ERROR:
                return new TruncateException(te.getMessage());
            case WRITE_TIMEOUT:
                org.apache.cassandra.exceptions.WriteTimeoutException wte = (org.apache.cassandra.exceptions.WriteTimeoutException)te;
                return new WriteTimeoutException(ConsistencyLevel.from(wte.consistency), WriteType.from(wte.writeType), wte.received, wte.blockFor);
            case READ_TIMEOUT:
                org.apache.cassandra.exceptions.ReadTimeoutException rte = (org.apache.cassandra.exceptions.ReadTimeoutException)te;
                return new ReadTimeoutException(ConsistencyLevel.from(rte.consistency), rte.received, rte.blockFor, rte.dataPresent);
            case SYNTAX_ERROR:
                return new SyntaxError(te.getMessage());
            case UNAUTHORIZED:
                return new UnauthorizedException(te.getMessage());
            case INVALID:
                return new InvalidQueryException(te.getMessage());
            case CONFIG_ERROR:
                return new InvalidConfigurationInQueryException(te.getMessage());
            case ALREADY_EXISTS:
                org.apache.cassandra.exceptions.AlreadyExistsException aee = (org.apache.cassandra.exceptions.AlreadyExistsException)te;
                return new AlreadyExistsException(aee.ksName, aee.cfName);
            default:
                return new DriverInternalError("Unknown error return code: " + te.code());
        }
    }
}
TOP

Related Classes of com.datastax.driver.core.ResultSetFuture$ResponseCallback

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.