Java tutorial
/** * 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 >= 0. 0 indicates an infinite timeout. * @param maxConnectionsPerNode the maximum number of connections allowed to * each node. Must be >= 0. * @param idleTimeout the amount of time in seconds before an unused * connection is considered idle. Must be >= 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 >= 1 and <= 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(); } } }