// =================================================================================================
// Copyright 2013 Twitter, Inc.
// -------------------------------------------------------------------------------------------------
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this work except in compliance with the License.
// You may obtain a copy of the License in the LICENSE file, or 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.twitter.common.zookeeper.guice.client;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.logging.Logger;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.base.Throwables;
import com.google.inject.Inject;
import com.google.inject.Key;
import com.google.inject.PrivateModule;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import com.twitter.common.application.ShutdownRegistry;
import com.twitter.common.inject.Bindings.KeyFactory;
import com.twitter.common.quantity.Amount;
import com.twitter.common.quantity.Time;
import com.twitter.common.zookeeper.ZooKeeperClient;
import com.twitter.common.zookeeper.ZooKeeperClient.Credentials;
import com.twitter.common.zookeeper.ZooKeeperUtils;
import com.twitter.common.zookeeper.testing.ZooKeeperTestServer;
/**
* A guice binding module that configures and binds a {@link ZooKeeperClient} instance.
*/
public class ZooKeeperClientModule extends PrivateModule {
private final KeyFactory keyFactory;
private final ClientConfig config;
/**
* Creates a new ZK client module from the provided configuration.
*
* @param config Configuration parameters for the client.
*/
public ZooKeeperClientModule(ClientConfig config) {
this(KeyFactory.PLAIN, config);
}
/**
* Creates a new ZK client module from the provided configuration, using a key factory to
* qualify any bindings.
*
* @param keyFactory Factory to use when creating any exposed bindings.
* @param config Configuration parameters for the client.
*/
public ZooKeeperClientModule(KeyFactory keyFactory, ClientConfig config) {
this.keyFactory = Preconditions.checkNotNull(keyFactory);
this.config = Preconditions.checkNotNull(config);
}
@Override
protected void configure() {
Key<ZooKeeperClient> clientKey = keyFactory.create(ZooKeeperClient.class);
if (config.inProcess) {
requireBinding(ShutdownRegistry.class);
// Bound privately to give the local provider access to configuration settings.
bind(ClientConfig.class).toInstance(config);
bind(clientKey).toProvider(LocalClientProvider.class).in(Singleton.class);
} else {
ZooKeeperClient client =
new ZooKeeperClient(config.sessionTimeout, config.credentials, config.chrootPath, config.servers);
bind(clientKey).toInstance(client);
}
expose(clientKey);
}
private static class LocalClientProvider implements Provider<ZooKeeperClient> {
private static final Logger LOG = Logger.getLogger(LocalClientProvider.class.getName());
private final ClientConfig config;
private final ShutdownRegistry shutdownRegistry;
@Inject
LocalClientProvider(ClientConfig config, ShutdownRegistry shutdownRegistry) {
this.config = Preconditions.checkNotNull(config);
this.shutdownRegistry = Preconditions.checkNotNull(shutdownRegistry);
}
@Override
public ZooKeeperClient get() {
ZooKeeperTestServer zooKeeperServer;
try {
zooKeeperServer = new ZooKeeperTestServer(0, shutdownRegistry);
zooKeeperServer.startNetwork();
} catch (IOException e) {
throw Throwables.propagate(e);
} catch (InterruptedException e) {
throw Throwables.propagate(e);
}
LOG.info("Embedded zookeeper cluster started on port " + zooKeeperServer.getPort());
return zooKeeperServer.createClient(config.sessionTimeout, config.credentials);
}
}
/**
* Composite type that contains configuration parameters used when creating a client.
* <p>
* Instances of this class are immutable, but builder-style chained calls are supported.
*/
public static class ClientConfig {
public final Iterable<InetSocketAddress> servers;
public final boolean inProcess;
public final Amount<Integer, Time> sessionTimeout;
public final Optional<String> chrootPath;
public final Credentials credentials;
/**
* Creates a new client configuration.
*
* @param servers ZooKeeper server addresses.
* @param inProcess Whether to run and create clients for an in-process ZooKeeper server.
* @param sessionTimeout Timeout duration for established sessions.
* @param credentials ZooKeeper authentication credentials.
*/
public ClientConfig(
Iterable<InetSocketAddress> servers,
boolean inProcess,
Amount<Integer, Time> sessionTimeout,
Credentials credentials) {
this(servers, Optional.<String>absent(), inProcess, sessionTimeout, credentials);
}
/**
* Creates a new client configuration.
*
* @param servers ZooKeeper server addresses.
* @param inProcess Whether to run and create clients for an in-process ZooKeeper server.
* @param chrootPath an optional chroot path
* @param sessionTimeout Timeout duration for established sessions.
* @param credentials ZooKeeper authentication credentials.
*/
public ClientConfig(
Iterable<InetSocketAddress> servers,
Optional<String> chrootPath,
boolean inProcess,
Amount<Integer, Time> sessionTimeout,
Credentials credentials) {
this.servers = servers;
this.chrootPath = chrootPath;
this.inProcess = inProcess;
this.sessionTimeout = sessionTimeout;
this.credentials = credentials;
}
/**
* Creates a new client configuration with defaults for the session timeout and credentials.
*
* @param servers ZooKeeper server addresses.
* @return A new configuration.
*/
public static ClientConfig create(Iterable<InetSocketAddress> servers) {
return new ClientConfig(
servers,
Optional.<String> absent(),
false,
ZooKeeperUtils.DEFAULT_ZK_SESSION_TIMEOUT,
Credentials.NONE);
}
/**
* Creates a new configuration identical to this configuration, but with the provided
* session timeout.
*
* @param sessionTimeout Timeout duration for established sessions.
* @return A modified clone of this configuration.
*/
public ClientConfig withSessionTimeout(Amount<Integer, Time> sessionTimeout) {
return new ClientConfig(servers, chrootPath, inProcess, sessionTimeout, credentials);
}
/**
* Creates a new configuration identical to this configuration, but with the provided
* credentials.
*
* @param credentials ZooKeeper authentication credentials.
* @return A modified clone of this configuration.
*/
public ClientConfig withCredentials(Credentials credentials) {
return new ClientConfig(servers, chrootPath, inProcess, sessionTimeout, credentials);
}
/**
* Convenience method for calling {@link #withCredentials(Credentials)} with digest credentials.
*
* @param username Digest authentication user.
* @param password Digest authentication raw password.
* @return A modified clone of this configuration.
*/
public ClientConfig withDigestCredentials(String username, String password) {
return withCredentials(ZooKeeperClient.digestCredentials(username, password));
}
/**
* Creates a new configuration identical to this configuration, but with the provided
* in-process setting.
*
* @param inProcess If {@code true}, an in-process ZooKeeper server server will be used,
* and all clients will connect to it.
* @return A modified clone of this configuration.
*/
public ClientConfig inProcess(boolean inProcess) {
return new ClientConfig(servers, chrootPath, inProcess, sessionTimeout, credentials);
}
/**
* Creates a new configuration identical to this configuration, but with the provided
* chroot path setting.
*
* @param chrootPath a valid ZooKeeper path used as a chroot for ZooKeeper connections.
* @return A modified clone of this configuration.
*/
public ClientConfig withChrootPath(String chrootPath) {
return new ClientConfig(servers, Optional.of(chrootPath), inProcess, sessionTimeout, credentials);
}
}
}