com.couchbase.client.http.AsyncConnectionManager.java Source code

Java tutorial

Introduction

Here is the source code for com.couchbase.client.http.AsyncConnectionManager.java

Source

/**
 * Copyright (C) 2009-2013 Couchbase, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALING
 * IN THE SOFTWARE.
 */

package com.couchbase.client.http;

import com.couchbase.client.protocol.views.HttpOperation;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Set;

import net.spy.memcached.compat.SpyObject;

import org.apache.http.HttpException;
import org.apache.http.HttpHost;
import org.apache.http.impl.nio.DefaultClientIOEventDispatch;
import org.apache.http.impl.nio.reactor.DefaultConnectingIOReactor;
import org.apache.http.nio.ContentDecoder;
import org.apache.http.nio.ContentEncoder;
import org.apache.http.nio.NHttpClientConnection;
import org.apache.http.nio.NHttpClientHandler;
import org.apache.http.nio.reactor.ConnectingIOReactor;
import org.apache.http.nio.reactor.IOEventDispatch;
import org.apache.http.nio.reactor.IOReactorException;
import org.apache.http.nio.reactor.SessionRequest;
import org.apache.http.nio.reactor.SessionRequestCallback;
import org.apache.http.params.HttpParams;

/**
 * An asynchronous HTTP connection manager.
 */
public class AsyncConnectionManager extends SpyObject {

    private final HttpHost target;
    private final int maxConnections;
    private final NHttpClientHandler handler;
    private final HttpParams params;
    private final ConnectingIOReactor ioreactor;
    private final Object lock;
    private final Set<NHttpClientConnection> allConns;
    private final Queue<NHttpClientConnection> availableConns;
    private final Queue<AsyncConnectionRequest> pendingRequests;
    private final RequeueOpCallback requeueCallback;

    private volatile boolean shutdown;

    public AsyncConnectionManager(HttpHost target, int maxConnections, NHttpClientHandler handler,
            HttpParams params, RequeueOpCallback cb) throws IOReactorException {
        super();
        this.target = target;
        this.maxConnections = maxConnections;
        this.handler = handler;
        this.params = params;
        this.requeueCallback = cb;
        this.lock = new Object();
        this.allConns = new HashSet<NHttpClientConnection>();
        this.availableConns = new LinkedList<NHttpClientConnection>();
        this.pendingRequests = new LinkedList<AsyncConnectionRequest>();
        this.ioreactor = new DefaultConnectingIOReactor(2, params);
    }

    public void execute() throws IOException {
        IOEventDispatch dispatch = new DefaultClientIOEventDispatch(new ManagedClientHandler(this.handler, this),
                this.params);
        this.ioreactor.execute(dispatch);
    }

    public boolean hasPendingRequests() {
        return !pendingRequests.isEmpty();
    }

    public void shutdown(long waitMs) throws IOException {
        synchronized (this.lock) {
            if (!this.shutdown) {
                this.shutdown = true;
                while (!this.pendingRequests.isEmpty()) {
                    AsyncConnectionRequest request = this.pendingRequests.remove();
                    if (request != null) {
                        NHttpClientConnection connection = request.getConnection();
                        if (connection != null) {
                            HttpOperation op = (HttpOperation) connection.getContext().getAttribute("operation");
                            requeueCallback.invoke(op);
                        }
                        request.cancel();
                    }
                }
                this.availableConns.clear();
                this.allConns.clear();
            }
        }
        this.ioreactor.shutdown(waitMs);
    }

    void addConnection(NHttpClientConnection conn) {
        if (conn == null) {
            return;
        }
        if (this.shutdown) {
            return;
        }
        synchronized (this.lock) {
            this.allConns.add(conn);
        }
    }

    void removeConnection(NHttpClientConnection conn) {
        if (conn == null) {
            return;
        }
        if (this.shutdown) {
            return;
        }
        synchronized (this.lock) {
            if (this.allConns.remove(conn)) {
                this.availableConns.remove(conn);
            }
            processConnectionRequests();
        }
    }

    public AsyncConnectionRequest requestConnection() {
        if (this.shutdown) {
            throw new IllegalStateException("Connection manager has been shut down");
        }
        AsyncConnectionRequest request = new AsyncConnectionRequest();
        synchronized (this.lock) {
            while (!this.availableConns.isEmpty()) {
                NHttpClientConnection conn = this.availableConns.remove();
                if (conn.isOpen()) {
                    getLogger().debug("Re-using persistent connection");
                    request.setConnection(conn);
                    break;
                } else {
                    this.allConns.remove(conn);
                }
            }
            if (!request.isCompleted()) {
                this.pendingRequests.add(request);
                processConnectionRequests();
            }
        }
        return request;
    }

    public void releaseConnection(NHttpClientConnection conn) {
        if (conn == null) {
            return;
        }
        if (this.shutdown) {
            return;
        }
        synchronized (this.lock) {
            if (this.allConns.contains(conn)) {
                if (conn.isOpen()) {
                    conn.setSocketTimeout(0);
                    AsyncConnectionRequest request = this.pendingRequests.poll();
                    if (request != null) {
                        getLogger().debug("Re-using persistent connection");
                        request.setConnection(conn);
                    } else {
                        this.availableConns.add(conn);
                    }
                } else {
                    this.allConns.remove(conn);
                    processConnectionRequests();
                }
            }
        }
    }

    private void processConnectionRequests() {
        while (this.allConns.size() < this.maxConnections) {
            AsyncConnectionRequest request = this.pendingRequests.poll();
            if (request == null) {
                break;
            }
            InetSocketAddress address = new InetSocketAddress(this.target.getHostName(), this.target.getPort());
            ConnRequestCallback callback = new ConnRequestCallback(request);
            getLogger().info("Opening new Couchbase HTTP connection");
            this.ioreactor.connect(address, null, request, callback);
        }
    }

    static class ManagedClientHandler implements NHttpClientHandler {

        private final NHttpClientHandler handler;
        private final AsyncConnectionManager connMgr;

        public ManagedClientHandler(NHttpClientHandler handler, AsyncConnectionManager connMgr) {
            super();
            this.handler = handler;
            this.connMgr = connMgr;
        }

        public void connected(NHttpClientConnection conn, Object attachment) {
            AsyncConnectionRequest request = (AsyncConnectionRequest) attachment;
            this.handler.connected(conn, attachment);
            this.connMgr.addConnection(conn);
            request.setConnection(conn);
        }

        public void closed(NHttpClientConnection conn) {
            this.connMgr.removeConnection(conn);
            this.handler.closed(conn);
        }

        public void requestReady(NHttpClientConnection conn) {
            this.handler.requestReady(conn);
        }

        public void outputReady(NHttpClientConnection conn, ContentEncoder encoder) {
            this.handler.outputReady(conn, encoder);
        }

        public void responseReceived(NHttpClientConnection conn) {
            this.handler.responseReceived(conn);
        }

        public void inputReady(NHttpClientConnection conn, ContentDecoder decoder) {
            this.handler.inputReady(conn, decoder);
        }

        public void exception(NHttpClientConnection conn, HttpException ex) {
            this.handler.exception(conn, ex);
        }

        public void exception(NHttpClientConnection conn, IOException ex) {
            this.handler.exception(conn, ex);
        }

        public void timeout(NHttpClientConnection conn) {
            this.handler.timeout(conn);
        }
    }

    static class ConnRequestCallback extends SpyObject implements SessionRequestCallback {

        private final AsyncConnectionRequest connRequest;

        public ConnRequestCallback(AsyncConnectionRequest request) {
            super();
            connRequest = request;
        }

        public void completed(SessionRequest request) {
            getLogger().info(request.getRemoteAddress() + " - Session request successful");
        }

        public void cancelled(SessionRequest request) {
            getLogger().info(request.getRemoteAddress() + " - Session request cancelled");
            connRequest.cancel();
        }

        public void failed(SessionRequest request) {
            getLogger().error(request.getRemoteAddress() + " - Session request failed");
            IOException ex = request.getException();
            if (ex != null) {
                ex.printStackTrace();
            }
            connRequest.cancel();
        }

        public void timeout(SessionRequest request) {
            getLogger().info(request.getRemoteAddress() + " - Session request timed out");
            connRequest.cancel();
        }
    }
}