com.netflix.curator.framework.imps.CuratorFrameworkImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.netflix.curator.framework.imps.CuratorFrameworkImpl.java

Source

/*
 *
 *  Copyright 2011 Netflix, Inc.
 *
 *     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.netflix.curator.framework.imps;

import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.netflix.curator.CuratorZookeeperClient;
import com.netflix.curator.RetryLoop;
import com.netflix.curator.TimeTrace;
import com.netflix.curator.framework.CuratorFramework;
import com.netflix.curator.framework.CuratorFrameworkFactory;
import com.netflix.curator.framework.api.*;
import com.netflix.curator.framework.api.transaction.CuratorTransaction;
import com.netflix.curator.framework.listen.Listenable;
import com.netflix.curator.framework.listen.ListenerContainer;
import com.netflix.curator.framework.state.ConnectionState;
import com.netflix.curator.framework.state.ConnectionStateListener;
import com.netflix.curator.framework.state.ConnectionStateManager;
import com.netflix.curator.utils.DebugUtils;
import com.netflix.curator.utils.EnsurePath;
import com.netflix.curator.utils.ThreadUtils;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Arrays;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicReference;

public class CuratorFrameworkImpl implements CuratorFramework {
    private final Logger log = LoggerFactory.getLogger(getClass());
    private final CuratorZookeeperClient client;
    private final ListenerContainer<CuratorListener> listeners;
    private final ListenerContainer<UnhandledErrorListener> unhandledErrorListeners;
    private final ThreadFactory threadFactory;
    private final BlockingQueue<OperationAndData<?>> backgroundOperations;
    private final NamespaceImpl namespace;
    private final ConnectionStateManager connectionStateManager;
    private final AtomicReference<AuthInfo> authInfo = new AtomicReference<AuthInfo>();
    private final byte[] defaultData;
    private final FailedDeleteManager failedDeleteManager;
    private final CompressionProvider compressionProvider;
    private final ACLProvider aclProvider;
    private final NamespaceFacadeCache namespaceFacadeCache;

    private volatile ExecutorService executorService;

    interface DebugBackgroundListener {
        void listen(OperationAndData<?> data);
    }

    volatile DebugBackgroundListener debugListener = null;

    private enum State {
        LATENT, STARTED, STOPPED
    }

    private final AtomicReference<State> state = new AtomicReference<State>(State.LATENT);

    private static class AuthInfo {
        final String scheme;
        final byte[] auth;

        private AuthInfo(String scheme, byte[] auth) {
            this.scheme = scheme;
            this.auth = auth;
        }
    }

    public CuratorFrameworkImpl(CuratorFrameworkFactory.Builder builder) {
        this.client = new CuratorZookeeperClient(builder.getZookeeperFactory(), builder.getEnsembleProvider(),
                builder.getSessionTimeoutMs(), builder.getConnectionTimeoutMs(), new Watcher() {
                    @Override
                    public void process(WatchedEvent watchedEvent) {
                        CuratorEvent event = new CuratorEventImpl(CuratorFrameworkImpl.this,
                                CuratorEventType.WATCHED, watchedEvent.getState().getIntValue(),
                                unfixForNamespace(watchedEvent.getPath()), null, null, null, null, null,
                                watchedEvent, null);
                        processEvent(event);
                    }
                }, builder.getRetryPolicy());

        listeners = new ListenerContainer<CuratorListener>();
        unhandledErrorListeners = new ListenerContainer<UnhandledErrorListener>();
        backgroundOperations = new DelayQueue<OperationAndData<?>>();
        namespace = new NamespaceImpl(this, builder.getNamespace());
        threadFactory = getThreadFactory(builder);
        connectionStateManager = new ConnectionStateManager(this, builder.getThreadFactory());
        compressionProvider = builder.getCompressionProvider();
        aclProvider = builder.getAclProvider();
        namespaceFacadeCache = new NamespaceFacadeCache(this);

        byte[] builderDefaultData = builder.getDefaultData();
        defaultData = (builderDefaultData != null) ? Arrays.copyOf(builderDefaultData, builderDefaultData.length)
                : new byte[0];

        if (builder.getAuthScheme() != null) {
            authInfo.set(new AuthInfo(builder.getAuthScheme(), builder.getAuthValue()));
        }

        failedDeleteManager = new FailedDeleteManager(this);
    }

    private ThreadFactory getThreadFactory(CuratorFrameworkFactory.Builder builder) {
        ThreadFactory threadFactory = builder.getThreadFactory();
        if (threadFactory == null) {
            threadFactory = ThreadUtils.newThreadFactory("CuratorFramework");
        }
        return threadFactory;
    }

    protected CuratorFrameworkImpl(CuratorFrameworkImpl parent) {
        client = parent.client;
        listeners = parent.listeners;
        unhandledErrorListeners = parent.unhandledErrorListeners;
        threadFactory = parent.threadFactory;
        backgroundOperations = parent.backgroundOperations;
        connectionStateManager = parent.connectionStateManager;
        defaultData = parent.defaultData;
        failedDeleteManager = parent.failedDeleteManager;
        compressionProvider = parent.compressionProvider;
        aclProvider = parent.aclProvider;
        namespaceFacadeCache = parent.namespaceFacadeCache;
        namespace = new NamespaceImpl(this, null);
    }

    @Override
    public boolean isStarted() {
        return state.get() == State.STARTED;
    }

    @Override
    public void start() {
        log.info("Starting");
        if (!state.compareAndSet(State.LATENT, State.STARTED)) {
            IllegalStateException error = new IllegalStateException();
            log.error("Already started", error);
            throw error;
        }

        try {
            client.start();
            connectionStateManager.start();
            executorService = Executors.newFixedThreadPool(2, threadFactory); // 1 for listeners, 1 for background ops

            executorService.submit(new Callable<Object>() {
                @Override
                public Object call() throws Exception {
                    backgroundOperationsLoop();
                    return null;
                }
            });
        } catch (Exception e) {
            handleBackgroundOperationException(null, e);
        }
    }

    @Override
    public void close() {
        log.debug("Closing");
        if (state.compareAndSet(State.STARTED, State.STOPPED)) {
            listeners.forEach(new Function<CuratorListener, Void>() {
                @Override
                public Void apply(CuratorListener listener) {
                    CuratorEvent event = new CuratorEventImpl(CuratorFrameworkImpl.this, CuratorEventType.CLOSING,
                            0, null, null, null, null, null, null, null, null);
                    try {
                        listener.eventReceived(CuratorFrameworkImpl.this, event);
                    } catch (Exception e) {
                        log.error("Exception while sending Closing event", e);
                    }
                    return null;
                }
            });

            listeners.clear();
            unhandledErrorListeners.clear();
            connectionStateManager.close();
            client.close();
            executorService.shutdownNow();
        }
    }

    @Override
    public CuratorFramework nonNamespaceView() {
        return usingNamespace(null);
    }

    @Override
    public String getNamespace() {
        String str = namespace.getNamespace();
        return (str != null) ? str : "";
    }

    @Override
    public CuratorFramework usingNamespace(String newNamespace) {
        Preconditions.checkState(state.get() == State.STARTED,
                "instance must be started before calling this method");

        return namespaceFacadeCache.get(newNamespace);
    }

    @Override
    public CreateBuilder create() {
        Preconditions.checkState(state.get() == State.STARTED,
                "instance must be started before calling this method");

        return new CreateBuilderImpl(this);
    }

    @Override
    public DeleteBuilder delete() {
        Preconditions.checkState(state.get() == State.STARTED,
                "instance must be started before calling this method");

        return new DeleteBuilderImpl(this);
    }

    @Override
    public ExistsBuilder checkExists() {
        Preconditions.checkState(state.get() == State.STARTED,
                "instance must be started before calling this method");

        return new ExistsBuilderImpl(this);
    }

    @Override
    public GetDataBuilder getData() {
        Preconditions.checkState(state.get() == State.STARTED,
                "instance must be started before calling this method");

        return new GetDataBuilderImpl(this);
    }

    @Override
    public SetDataBuilder setData() {
        Preconditions.checkState(state.get() == State.STARTED,
                "instance must be started before calling this method");

        return new SetDataBuilderImpl(this);
    }

    @Override
    public GetChildrenBuilder getChildren() {
        Preconditions.checkState(state.get() == State.STARTED,
                "instance must be started before calling this method");

        return new GetChildrenBuilderImpl(this);
    }

    @Override
    public GetACLBuilder getACL() {
        Preconditions.checkState(state.get() == State.STARTED,
                "instance must be started before calling this method");

        return new GetACLBuilderImpl(this);
    }

    @Override
    public SetACLBuilder setACL() {
        Preconditions.checkState(state.get() == State.STARTED,
                "instance must be started before calling this method");

        return new SetACLBuilderImpl(this);
    }

    @Override
    public CuratorTransaction inTransaction() {
        Preconditions.checkState(state.get() == State.STARTED,
                "instance must be started before calling this method");

        return new CuratorTransactionImpl(this);
    }

    @Override
    public Listenable<ConnectionStateListener> getConnectionStateListenable() {
        return connectionStateManager.getListenable();
    }

    @Override
    public Listenable<CuratorListener> getCuratorListenable() {
        return listeners;
    }

    @Override
    public Listenable<UnhandledErrorListener> getUnhandledErrorListenable() {
        return unhandledErrorListeners;
    }

    @Override
    public void sync(String path, Object context) {
        Preconditions.checkState(state.get() == State.STARTED,
                "instance must be started before calling this method");

        path = fixForNamespace(path);

        internalSync(this, path, context);
    }

    protected void internalSync(CuratorFrameworkImpl impl, String path, Object context) {
        BackgroundOperation<String> operation = new BackgroundSyncImpl(impl, context);
        backgroundOperations.offer(new OperationAndData<String>(operation, path, null, null));
    }

    @Override
    public CuratorZookeeperClient getZookeeperClient() {
        return client;
    }

    @Override
    public EnsurePath newNamespaceAwareEnsurePath(String path) {
        return namespace.newNamespaceAwareEnsurePath(path);
    }

    ACLProvider getAclProvider() {
        return aclProvider;
    }

    FailedDeleteManager getFailedDeleteManager() {
        return failedDeleteManager;
    }

    RetryLoop newRetryLoop() {
        return client.newRetryLoop();
    }

    ZooKeeper getZooKeeper() throws Exception {
        return client.getZooKeeper();
    }

    CompressionProvider getCompressionProvider() {
        return compressionProvider;
    }

    @SuppressWarnings({ "ThrowableResultOfMethodCallIgnored" })
    <DATA_TYPE> void processBackgroundOperation(OperationAndData<DATA_TYPE> operationAndData, CuratorEvent event) {
        boolean isInitialExecution = (event == null);
        if (isInitialExecution) {
            performBackgroundOperation(operationAndData);
            return;
        }

        boolean queueOperation = false;
        do {
            if (RetryLoop.shouldRetry(event.getResultCode())) {
                if (client.getRetryPolicy().allowRetry(operationAndData.getThenIncrementRetryCount(),
                        operationAndData.getElapsedTimeMs(), operationAndData)) {
                    queueOperation = true;
                } else {
                    if (operationAndData.getErrorCallback() != null) {
                        operationAndData.getErrorCallback().retriesExhausted(operationAndData);
                    }

                    KeeperException.Code code = KeeperException.Code.get(event.getResultCode());
                    Exception e = null;
                    try {
                        e = (code != null) ? KeeperException.create(code) : null;
                    } catch (Throwable ignore) {
                    }
                    if (e == null) {
                        e = new Exception("Unknown result code: " + event.getResultCode());
                    }
                    logError("Background operation retry gave up", e);
                }
                break;
            }

            if (operationAndData.getCallback() != null) {
                sendToBackgroundCallback(operationAndData, event);
                break;
            }

            processEvent(event);
        } while (false);

        if (queueOperation) {
            backgroundOperations.offer(operationAndData);
        }
    }

    void logError(String reason, final Throwable e) {
        if ((reason == null) || (reason.length() == 0)) {
            reason = "n/a";
        }

        if (!Boolean.getBoolean(DebugUtils.PROPERTY_DONT_LOG_CONNECTION_ISSUES)
                || !(e instanceof KeeperException)) {
            log.error(reason, e);
        }

        if (e instanceof KeeperException.ConnectionLossException) {
            connectionStateManager.addStateChange(ConnectionState.LOST);
        }

        final String localReason = reason;
        unhandledErrorListeners.forEach(new Function<UnhandledErrorListener, Void>() {
            @Override
            public Void apply(UnhandledErrorListener listener) {
                listener.unhandledError(localReason, e);
                return null;
            }
        });
    }

    String unfixForNamespace(String path) {
        return namespace.unfixForNamespace(path);
    }

    String fixForNamespace(String path) {
        return namespace.fixForNamespace(path);
    }

    byte[] getDefaultData() {
        return defaultData;
    }

    NamespaceFacadeCache getNamespaceFacadeCache() {
        return namespaceFacadeCache;
    }

    private <DATA_TYPE> void sendToBackgroundCallback(OperationAndData<DATA_TYPE> operationAndData,
            CuratorEvent event) {
        try {
            operationAndData.getCallback().processResult(this, event);
        } catch (Exception e) {
            handleBackgroundOperationException(operationAndData, e);
        }
    }

    private <DATA_TYPE> void handleBackgroundOperationException(OperationAndData<DATA_TYPE> operationAndData,
            Throwable e) {
        do {
            if ((operationAndData != null) && RetryLoop.isRetryException(e)) {
                if (!Boolean.getBoolean(DebugUtils.PROPERTY_DONT_LOG_CONNECTION_ISSUES)) {
                    log.debug("Retry-able exception received", e);
                }
                if (client.getRetryPolicy().allowRetry(operationAndData.getThenIncrementRetryCount(),
                        operationAndData.getElapsedTimeMs(), operationAndData)) {
                    if (!Boolean.getBoolean(DebugUtils.PROPERTY_DONT_LOG_CONNECTION_ISSUES)) {
                        log.debug("Retrying operation");
                    }
                    backgroundOperations.offer(operationAndData);
                    break;
                } else {
                    if (!Boolean.getBoolean(DebugUtils.PROPERTY_DONT_LOG_CONNECTION_ISSUES)) {
                        log.debug("Retry policy did not allow retry");
                    }
                    if (operationAndData.getErrorCallback() != null) {
                        operationAndData.getErrorCallback().retriesExhausted(operationAndData);
                    }
                }
            }

            logError("Background exception was not retry-able or retry gave up", e);
        } while (false);
    }

    private void backgroundOperationsLoop() {
        AuthInfo auth = authInfo.getAndSet(null);
        if (auth != null) {
            try {
                client.getZooKeeper().addAuthInfo(auth.scheme, auth.auth);
            } catch (Exception e) {
                logError("addAuthInfo for background operation threw exception", e);
                return;
            }
        }

        while (!Thread.interrupted()) {
            OperationAndData<?> operationAndData;
            try {
                operationAndData = backgroundOperations.take();
                if (debugListener != null) {
                    debugListener.listen(operationAndData);
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                break;
            }

            performBackgroundOperation(operationAndData);
        }
    }

    private void performBackgroundOperation(OperationAndData<?> operationAndData) {
        try {
            operationAndData.callPerformBackgroundOperation();
        } catch (Throwable e) {
            handleBackgroundOperationException(operationAndData, e);
        }
    }

    private void processEvent(final CuratorEvent curatorEvent) {
        validateConnection(curatorEvent);

        listeners.forEach(new Function<CuratorListener, Void>() {
            @Override
            public Void apply(CuratorListener listener) {
                try {
                    TimeTrace trace = client.startTracer("EventListener");
                    listener.eventReceived(CuratorFrameworkImpl.this, curatorEvent);
                    trace.commit();
                } catch (Exception e) {
                    logError("Event listener threw exception", e);
                }
                return null;
            }
        });
    }

    private void validateConnection(CuratorEvent curatorEvent) {
        if (curatorEvent.getType() == CuratorEventType.WATCHED) {
            if (curatorEvent.getWatchedEvent().getState() == Watcher.Event.KeeperState.Disconnected) {
                connectionStateManager.addStateChange(ConnectionState.SUSPENDED);
                internalSync(this, "/", null); // we appear to have disconnected, force a new ZK event and see if we can connect to another server
            } else if (curatorEvent.getWatchedEvent().getState() == Watcher.Event.KeeperState.Expired) {
                connectionStateManager.addStateChange(ConnectionState.LOST);
            } else if (curatorEvent.getWatchedEvent().getState() == Watcher.Event.KeeperState.SyncConnected) {
                connectionStateManager.addStateChange(ConnectionState.RECONNECTED);
            }
        }
    }
}