org.springframework.xd.dirt.zookeeper.ZooKeeperConnection.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.xd.dirt.zookeeper.ZooKeeperConnection.java

Source

/*
 * Copyright 2014 the original author or authors.
 *
 * 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 org.springframework.xd.dirt.zookeeper;

import java.util.concurrent.CopyOnWriteArraySet;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.state.ConnectionState;
import org.apache.curator.framework.state.ConnectionStateListener;
import org.apache.curator.retry.ExponentialBackoffRetry;

import org.springframework.context.SmartLifecycle;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

/**
 * A wrapper for a {@link CuratorFramework} instance whose lifecycle is managed as a Spring bean. Accepts
 * {@link ZooKeeperConnectionListener}s to be notified when connection or disconnection events are received.
 *
 * @author Mark Fisher
 * @author David Turanski
 * @author Patrick Peralta
 */
public class ZooKeeperConnection implements SmartLifecycle {

    /**
     * Logger.
     */
    private static final Log logger = LogFactory.getLog(ZooKeeperConnection.class);

    /**
     * The default client connect string. Port 2181 on localhost.
     */
    public static final String DEFAULT_CLIENT_CONNECT_STRING = "localhost:2181";

    /**
     * The default ZooKeeper session timeout in milliseconds.
     */
    public static final int DEFAULT_SESSION_TIMEOUT = 60000;

    /**
     * The default ZooKeeper connection timeout in milliseconds.
     */
    public static final int DEFAULT_CONNECTION_TIMEOUT = 30000;

    /**
     * The default initial number of milliseconds between connection retries.
     */
    public static final int DEFAULT_INITIAL_RETRY_WAIT = 1000;

    /**
     * The default number of connection attempts that will be made after
     * a failed connection attempt.
     */
    public static final int DEFAULT_MAX_RETRY_ATTEMPTS = 3;

    /**
     * The underlying {@link CuratorFramework} instance.
     */
    private volatile CuratorFramework curatorFramework;

    /**
     * Curator client retry policy.
     */
    private volatile RetryPolicy retryPolicy;

    /**
     * Connection listener for Curator {@link ConnectionState} events.
     */
    private final DelegatingConnectionStateListener connectionListener = new DelegatingConnectionStateListener();

    /**
     * The set of {@link ZooKeeperConnectionListener}s that should be notified for connection and disconnection events.
     */
    private final CopyOnWriteArraySet<ZooKeeperConnectionListener> listeners = new CopyOnWriteArraySet<ZooKeeperConnectionListener>();

    /**
     * Flag that indicates whether this connection is currently active within a context.
     */
    private volatile boolean running;

    /**
     * Flag that indicates whether this connection should be started automatically.
     */
    private volatile boolean autoStartup = true;

    /**
     * The current ZooKeeper ConnectionState.
     */
    private volatile ConnectionState currentState;

    /**
     * The ZooKeeper connect string.
     */
    private final String clientConnectString;

    /**
     * Namespace path within ZooKeeper. Default is {@link Paths#XD_NAMESPACE}.
     */
    private final String namespace;

    /**
     * ZooKeeper session timeout in milliseconds.
     */
    private final int sessionTimeout;

    /**
     * ZooKeeper connection timeout in milliseconds.
     */
    private final int connectionTimeout;

    /**
     * Establish a ZooKeeper connection with the default client connect string: {@value #DEFAULT_CLIENT_CONNECT_STRING}
     */
    public ZooKeeperConnection() {
        this(DEFAULT_CLIENT_CONNECT_STRING, null);
    }

    /**
     * Establish a ZooKeeper connection with the provided client connect string.
     *
     * @param clientConnectString one or more {@code host:port} strings, comma-delimited if more than one
     */
    public ZooKeeperConnection(String clientConnectString) {
        this(clientConnectString, null);
    }

    /**
     * Establish a ZooKeeper connection with the provided client connect string and namespace.
     *
     * @param clientConnectString one or more {@code host:port} strings, comma-delimited if more than one
     * @param namespace the root path namespace in ZooKeeper (or default namespace if null)
     */
    public ZooKeeperConnection(String clientConnectString, String namespace) {
        this(clientConnectString, namespace, DEFAULT_SESSION_TIMEOUT, DEFAULT_CONNECTION_TIMEOUT,
                DEFAULT_INITIAL_RETRY_WAIT, DEFAULT_MAX_RETRY_ATTEMPTS);
    }

    /**
     * Establish a ZooKeeper connection with the provided client connect string, namespace,
     * and timing values.
     *
     * <p>This class uses {@link ExponentialBackoffRetry} internally.</p>
     *
     * @param clientConnectString one or more {@code host:port} strings, comma-delimited if more than one
     * @param namespace the root path namespace in ZooKeeper (or default namespace if null)
     * @param sessionTimeout ZooKeeper session timeout in milliseconds
     * @param connectionTimeout ZooKeeper connection timeout in milliseconds
     * @param initialRetryWait milliseconds between connection retries (base value)
     * @param retryMaxAttempts number of connection attempts that will be made after a failed connection attempt
     */
    public ZooKeeperConnection(String clientConnectString, String namespace, int sessionTimeout,
            int connectionTimeout, int initialRetryWait, int retryMaxAttempts) {
        Assert.hasText(clientConnectString, "clientConnectString is required");
        this.clientConnectString = clientConnectString;
        this.namespace = StringUtils.hasText(namespace) ? namespace : Paths.XD_NAMESPACE;
        this.sessionTimeout = sessionTimeout;
        this.connectionTimeout = connectionTimeout;
        this.retryPolicy = new ExponentialBackoffRetry(initialRetryWait, retryMaxAttempts);
    }

    /**
     * Checks whether the underlying connection is established.
     *
     * @return true if connected
     */
    public boolean isConnected() {
        return (this.currentState == ConnectionState.CONNECTED || this.currentState == ConnectionState.RECONNECTED);
    }

    /**
     * Provides access to the underlying {@link CuratorFramework} instance.
     *
     * @return the {@link CuratorFramework} instance
     */
    public CuratorFramework getClient() {
        return this.curatorFramework;
    }

    /**
     * Add a {@link ZooKeeperConnectionListener}.
     *
     * @param listener the listener to add
     * @return true if the listener was added or false if it was already registered
     */
    public boolean addListener(ZooKeeperConnectionListener listener) {
        return this.listeners.add(listener);
    }

    /**
     * Remove a {@link ZooKeeperConnectionListener}.
     *
     * @param listener the listener to remove
     * @return true if the listener was removed or false if it was never registered
     */
    public boolean removeListener(ZooKeeperConnectionListener listener) {
        return this.listeners.remove(listener);
    }

    // Lifecycle Implementation

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isAutoStartup() {
        return this.autoStartup;
    }

    /**
     * Set the flag that indicates whether this connection
     * should be started automatically.
     *
     * @param autoStartup if true, indicates this connection should
     *                    be started automatically
     */
    public void setAutoStartup(boolean autoStartup) {
        this.autoStartup = autoStartup;
    }

    /**
     * Set the Curator retry policy.
     *
     * @param retryPolicy Curator client {@link RetryPolicy}
     */
    public void setRetryPolicy(RetryPolicy retryPolicy) {
        Assert.notNull(retryPolicy, "retryPolicy cannot be null");
        this.retryPolicy = retryPolicy;
    }

    /**
     * Return the Curator retry policy.
     *
     * @return the Curator retry policy
     */
    public RetryPolicy getRetryPolicy() {
        return this.retryPolicy;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int getPhase() {
        // start in the last possible phase
        return Integer.MAX_VALUE;
    }

    /**
     * Check whether this client is running.
     */
    @Override
    public boolean isRunning() {
        return this.running;
    }

    /**
     * Starts the underlying {@link CuratorFramework} instance.
     */
    @Override
    public synchronized void start() {
        if (!this.running) {
            this.curatorFramework = CuratorFrameworkFactory.builder().defaultData(new byte[0]).namespace(namespace)
                    .retryPolicy(this.retryPolicy).connectString(this.clientConnectString)
                    .sessionTimeoutMs(sessionTimeout).connectionTimeoutMs(connectionTimeout).build();
            this.curatorFramework.getConnectionStateListenable().addListener(connectionListener);
            curatorFramework.start();
            this.running = true;
        }
    }

    /**
     * Closes the underlying {@link CuratorFramework} instance.
     */
    @Override
    public synchronized void stop() {
        if (this.running) {
            if (this.currentState != null) {
                curatorFramework.close();
            }
            this.running = false;
        }
    }

    /**
     * Closes the underlying {@link CuratorFramework} instance and then invokes the callback.
     */
    @Override
    public void stop(Runnable callback) {
        this.stop();
        callback.run();
    }

    /**
     * Listener for Curator {@link ConnectionState} events that delegates to any registered ZooKeeperListeners.
     */
    private class DelegatingConnectionStateListener implements ConnectionStateListener {

        /**
         * {@inheritDoc}
         */
        @Override
        public void stateChanged(CuratorFramework client, ConnectionState newState) {
            currentState = newState;
            switch (newState) {
            case CONNECTED:
                logger.info(">>> Curator connected event: " + newState);
                for (ZooKeeperConnectionListener listener : listeners) {
                    listener.onConnect(client);
                }
                break;
            case RECONNECTED:
                logger.info(">>> Curator reconnected event: " + newState);
                for (ZooKeeperConnectionListener listener : listeners) {
                    listener.onResume(client);
                }
                break;
            case LOST:
                logger.info(">>> Curator disconnected event: " + newState);
                for (ZooKeeperConnectionListener listener : listeners) {
                    listener.onDisconnect(client);
                }
                break;
            case SUSPENDED:
                logger.info(">>> Curator suspended event: " + newState);
                for (ZooKeeperConnectionListener listener : listeners) {
                    listener.onSuspend(client);
                }
                break;
            case READ_ONLY:
                // todo: ?
                logger.info(">>> Curator read-only event: " + newState);
                break;
            }
        }
    }

}