com.outerspacecat.cassandra.StaticSeededCluster.java Source code

Java tutorial

Introduction

Here is the source code for com.outerspacecat.cassandra.StaticSeededCluster.java

Source

/**
 * Copyright 2011 Caleb Richardson
 * 
 * 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.outerspacecat.cassandra;

import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Queues;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.outerspacecat.connection.ConfiguredConnectionPoolDataSource;
import com.outerspacecat.connection.ConnectionPool;
import com.outerspacecat.connection.DataSource;
import com.outerspacecat.connection.DataSources;
import com.outerspacecat.connection.HasConnectionPool;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import org.apache.cassandra.thrift.Cassandra;
import org.apache.cassandra.thrift.InvalidRequestException;
import org.apache.cassandra.thrift.TokenRange;
import org.apache.thrift.TException;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.transport.TFramedTransport;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;
import org.apache.thrift.transport.TTransportException;

/**
 * A Cassandra {@link DataSource} that delegates to an underlying
 * {@link ConnectionPool} and periodically issues Thrift calls to auto-discover
 * nodes.
 * 
 * @author Caleb Richardson
 */
@ThreadSafe
public final class StaticSeededCluster implements HasConnectionPool<CassandraConnection> {
    // seconds
    private final static int DEFAULT_REFRESH_PERIOD = 5;
    // seconds
    private final static int DEFAULT_WAIT = 5;

    private final static ScheduledExecutorService exec = Executors
            .newSingleThreadScheduledExecutor(new ThreadFactoryBuilder().setDaemon(true).build());

    private final Lock poolLock = new ReentrantLock();
    private final Condition poolInitialized = poolLock.newCondition();
    @GuardedBy("poolLock")
    private ConnectionPool<CassandraConnection> pool;

    private final Lock listenersLock = new ReentrantLock();
    @GuardedBy("listenersLock")
    private final Deque<HasConnectionPool.PoolChangedListener<CassandraConnection>> listeners = new ArrayDeque<>();

    /**
     * Creates a new cluster.
     * 
     * @param name the name of the cluster. Must be non {@code null}.
     * @param keyspace the name of the keyspace to use for auto-discovering nodes.
     *        Must be non {@code null}.
     * @param timeout the timeout in to use in milliseconds for both establishing
     *        a connection and for reading and writing data. Will be used for both
     *        contacting seeds and obtaining connections from discovered nodes.
     *        Must be &gt;= 0. 0 indicates an infinite timeout.
     * @param maxConnectionsPerNode the maximum number of connections allowed to
     *        each node. Must be &gt;= 0.
     * @param idleTimeout the amount of time in seconds before an unused
     *        connection is considered idle. Must be &gt;= 0.
     * @param seeds the seeds to use. Must be non {@code null}, contain at least
     *        one element, and all elements must be non {@code null}.
     * @param port the port to use for both contacting seeds and obtaining
     *        connections to nodes. Must be &gt;= 1 and &lt;= 65535.
     */
    public StaticSeededCluster(final CharSequence name, final CharSequence keyspace, final int timeout,
            final int maxConnectionsPerNode, final int idleTimeout, final Iterable<InetAddress> seeds,
            final int port) {
        Preconditions.checkNotNull(name, "name required");
        Preconditions.checkNotNull(keyspace, "keyspace required");
        Preconditions.checkArgument(timeout >= 0, "timeout must be >= 0");
        Preconditions.checkArgument(maxConnectionsPerNode >= 0, "maxConnectionsPerNode must be >= 0");
        Preconditions.checkArgument(idleTimeout >= 0, "idleTimeout must be >= 0");
        Preconditions.checkNotNull(seeds, "seeds required");
        Preconditions.checkArgument(seeds.iterator().hasNext(), "seeds must be non empty");
        Preconditions.checkArgument(port >= 1 && port <= 65535, "port must be >=1 and <= 65535");

        final AtomicReference<ImmutableSet<String>> oldHosts = new AtomicReference<>();

        exec.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                for (InetAddress addr : seeds) {
                    ImmutableSet.Builder<String> hostsBuilder = ImmutableSet.builder();

                    TSocket sock = new TSocket(addr.getHostAddress(), port, timeout);
                    TTransport tr = new TFramedTransport(sock);

                    try {
                        tr.open();
                    } catch (TTransportException e) {
                        continue;
                    }

                    Cassandra.Client client = new Cassandra.Client(new TBinaryProtocol(tr));

                    try {
                        for (TokenRange range : client.describe_ring(keyspace.toString()))
                            hostsBuilder.addAll(range.getEndpoints());
                    } catch (TException | InvalidRequestException e) {
                        continue;
                    }

                    List<ConfiguredConnectionPoolDataSource<CassandraConnection>> dataSources = new ArrayList<>();

                    ImmutableSet<String> hosts = hostsBuilder.build();
                    if (hosts.equals(oldHosts.get()))
                        return;
                    oldHosts.set(hosts);

                    for (String host : hosts)
                        dataSources
                                .add(DataSources.configure(host,
                                        new ThriftCassandraConnectionPoolDataSource(
                                                new InetSocketAddress(host, port), timeout),
                                        maxConnectionsPerNode, idleTimeout, 1));

                    ConnectionPool<CassandraConnection> pool = new ConnectionPool<>(name, dataSources);

                    poolLock.lock();
                    try {
                        StaticSeededCluster.this.pool = pool;
                        poolInitialized.signalAll();
                    } finally {
                        poolLock.unlock();
                    }

                    Deque<HasConnectionPool.PoolChangedListener<CassandraConnection>> listeners = null;
                    listenersLock.lock();
                    try {
                        listeners = Queues.newArrayDeque(StaticSeededCluster.this.listeners);
                    } finally {
                        listenersLock.unlock();
                    }
                    for (HasConnectionPool.PoolChangedListener<CassandraConnection> l : listeners)
                        l.poolChanged(StaticSeededCluster.this);
                }
            }
        }, 0L, DEFAULT_REFRESH_PERIOD, TimeUnit.SECONDS);
    }

    @Override
    public CassandraConnection getConnection() throws IOException {
        poolLock.lock();
        try {
            while (pool == null) {
                if (!poolInitialized.await(DEFAULT_WAIT, TimeUnit.SECONDS))
                    throw new IOException("timed out waiting for connection");
            }

            return pool.getConnection();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new IOException("thread interrupted", e);
        } finally {
            poolLock.unlock();
        }
    }

    @Override
    public Optional<ConnectionPool<CassandraConnection>> getConnectionPool() {
        poolLock.lock();
        try {
            return Optional.fromNullable(pool);
        } finally {
            poolLock.unlock();
        }
    }

    @Override
    public void addPoolChangedListener(final HasConnectionPool.PoolChangedListener<CassandraConnection> l) {
        Preconditions.checkNotNull(l, "l required");

        listenersLock.lock();
        try {
            listeners.add(l);
        } finally {
            listenersLock.unlock();
        }
    }

    @Override
    public void removePoolChangedListener(final HasConnectionPool.PoolChangedListener<CassandraConnection> l) {
        Preconditions.checkNotNull(l, "l required");

        listenersLock.lock();
        try {
            listeners.remove(l);
        } finally {
            listenersLock.unlock();
        }
    }
}