Package com.bazaarvoice.ostrich.pool

Source Code of com.bazaarvoice.ostrich.pool.ServicePoolBuilder$ClosingHostDiscoverySource

package com.bazaarvoice.ostrich.pool;

import com.bazaarvoice.ostrich.HostDiscovery;
import com.bazaarvoice.ostrich.HostDiscoverySource;
import com.bazaarvoice.ostrich.LoadBalanceAlgorithm;
import com.bazaarvoice.ostrich.RetryPolicy;
import com.bazaarvoice.ostrich.ServiceFactory;
import com.bazaarvoice.ostrich.healthcheck.ExponentialBackoffHealthCheckRetryDelay;
import com.bazaarvoice.ostrich.healthcheck.HealthCheckRetryDelay;
import com.bazaarvoice.ostrich.loadbalance.RandomAlgorithm;
import com.bazaarvoice.ostrich.partition.IdentityPartitionFilter;
import com.bazaarvoice.ostrich.partition.PartitionFilter;
import com.bazaarvoice.ostrich.partition.PartitionKey;
import com.codahale.metrics.MetricRegistry;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import com.google.common.base.Throwables;
import com.google.common.base.Ticker;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.ThreadFactoryBuilder;

import java.io.IOException;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static java.lang.String.format;

public class ServicePoolBuilder<S> {
    private static final int DEFAULT_NUM_HEALTH_CHECK_THREADS = 1;
    private static final HealthCheckRetryDelay DEFAULT_HEALTH_CHECK_RETRY_POLICY = new ExponentialBackoffHealthCheckRetryDelay(100, 10_000, TimeUnit.MILLISECONDS);

    private final Class<S> _serviceType;
    private final List<HostDiscoverySource> _hostDiscoverySources = Lists.newArrayList();
    private boolean _closeHostDiscovery;
    private ServiceFactory<S> _serviceFactory;
    private String _serviceName;
    private ScheduledExecutorService _healthCheckExecutor;
    private ServiceCachingPolicy _cachingPolicy;
    private PartitionFilter _partitionFilter = new IdentityPartitionFilter();
    private PartitionContextSupplier _partitionContextSupplier = new EmptyPartitionContextSupplier();
    private LoadBalanceAlgorithm _loadBalanceAlgorithm = new RandomAlgorithm();
    private MetricRegistry _metrics;
    private ExecutorService _asyncExecutor;
    private HealthCheckRetryDelay _healthCheckRetryDelay = DEFAULT_HEALTH_CHECK_RETRY_POLICY;

    public static <S> ServicePoolBuilder<S> create(Class<S> serviceType) {
        return new ServicePoolBuilder<>(serviceType);
    }

    private ServicePoolBuilder(Class<S> serviceType) {
        _serviceType = checkNotNull(serviceType);
    }

    /**
     * Adds a {@link HostDiscoverySource} instance to the builder.  Multiple instances of {@code HostDiscoverySource}
     * may be specified.  The service pool will query the sources in the order they were registered and use the first
     * non-null {@link HostDiscovery} returned for the service name provided by the
     * {@link ServiceFactory#getServiceName()} method of the factory configured by {@link #withServiceFactory}.
     * <p>
     * Note that using this method will cause the ServicePoolBuilder to call
     * {@link HostDiscoverySource#forService(String serviceName)} when {@link #build()} is called and pass the returned
     * {@link HostDiscovery} to the new {@code ServicePool}.  Subsequently calling {@link ServicePool#close()} will in
     * turn call {@link HostDiscovery#close()} on the passed instance.
     *
     * @param hostDiscoverySource a host discovery source to use to find the {@link HostDiscovery} when constructing
     * the {@link ServicePool}
     * @return this
     */
    public ServicePoolBuilder<S> withHostDiscoverySource(HostDiscoverySource hostDiscoverySource) {
        checkNotNull(hostDiscoverySource);
        return withHostDiscovery(hostDiscoverySource, true);
    }

    /**
     * Adds a {@link HostDiscovery} instance to the builder.  The service pool will use this {@code HostDiscovery}
     * instance unless a preceding {@link HostDiscoverySource} provides a non-null instance of {@code HostDiscovery} for
     * a given service name.
     * <p>
     * Once this method is called, any subsequent calls to host discovery-related methods on this builder instance are
     * ignored (because this non-null discovery will always be returned).
     * <p>
     * Note that callers of this method are responsible for calling {@link HostDiscovery#close} on the passed instance.
     *
     * @param hostDiscovery the host discovery instance to use in the built {@link ServicePool}
     * @return this
     */
    public ServicePoolBuilder<S> withHostDiscovery(final HostDiscovery hostDiscovery) {
        checkNotNull(hostDiscovery);
        HostDiscoverySource hostDiscoverySource = new HostDiscoverySource() {
            @Override
            public HostDiscovery forService(String serviceName) {
                return hostDiscovery;
            }
        };
        return withHostDiscovery(hostDiscoverySource, false);
    }

    private ServicePoolBuilder<S> withHostDiscovery(HostDiscoverySource source, boolean close) {
        _hostDiscoverySources.add(close
                ? new ClosingHostDiscoverySource(source)
                : source);
        return this;
    }

    /**
     * Adds a {@code ServiceFactory} instance to the builder.  The {@code ServiceFactory#configure} method will be
     * called at this time to allow the {@code ServiceFactory} to set service pool settings on the builder.
     * <p>
     * @param serviceFactory the ServiceFactory to use
     * @return this
     */
    public ServicePoolBuilder<S> withServiceFactory(ServiceFactory<S> serviceFactory) {
        checkNotNull(serviceFactory);
        checkArgument(!Strings.isNullOrEmpty(serviceFactory.getServiceName()), "Service name must be configured");

        _serviceFactory = serviceFactory;
        _serviceName = serviceFactory.getServiceName();
        _serviceFactory.configure(this);
        return this;
    }

    /**
     * Adds a {@code ScheduledExecutorService} instance to the builder for use in executing health checks.
     * <p/>
     * Adding an executor is optional.  If one isn't specified then one will be created and used automatically.
     *
     * @param executor The {@code ScheduledExecutorService} to use
     * @return this
     */
    public ServicePoolBuilder<S> withHealthCheckExecutor(ScheduledExecutorService executor) {
        _healthCheckExecutor = checkNotNull(executor);
        return this;
    }

    /**
     * Adds an {@code ExecutorService} instance to the builder for use in executing asynchronous requests. The executor
     * is not used unless an asynchronous pool is built with the {@link #buildAsync} method.
     * <p/>
     * Adding an executor is optional.  If one isn't specified then one will be created and used automatically when
     * {@code buildAsync} is called.
     *
     * @param executor The {@code ExecutorService} to use
     * @return this
     */
    public ServicePoolBuilder<S> withAsyncExecutor(ExecutorService executor) {
        _asyncExecutor = checkNotNull(executor);
        return this;
    }

    /**
     * Enables caching of service instances in the built {@link ServicePool}.
     * <p/>
     * Specifying a caching policy is optional.  If one isn't specified then a default one that doesn't cache service
     * instances will be created and used automatically.
     *
     * @param policy The {@link ServiceCachingPolicy} to use
     * @return this
     */
    public ServicePoolBuilder<S> withCachingPolicy(ServiceCachingPolicy policy) {
        _cachingPolicy = checkNotNull(policy);
        return this;
    }

    /**
     * Uses the specified partition filter on every service pool operation to narrow down the set of end points that
     * may be used to service a particular request.
     *
     * @param partitionFilter The {@link PartitionFilter} to use
     * @return  this
     */
    public ServicePoolBuilder<S> withPartitionFilter(PartitionFilter partitionFilter) {
        _partitionFilter = checkNotNull(partitionFilter);
        return this;
    }

    /**
     * Makes the built proxy generate partition context based on the {@link PartitionKey} annotation
     * on method arguments in class {@code S}.
     * <p>
     * If {@code S} is not annotated, or annotated differently than desired, consider using
     * {@link #withPartitionContextAnnotationsFrom(Class)} instead.
     * <p>
     * NOTE: This is only useful if building a proxy with {@link #buildProxy(com.bazaarvoice.ostrich.RetryPolicy)}.  If
     * partition context is necessary with a normal service pool, then can be provided directly by calling
     * {@link com.bazaarvoice.ostrich.ServicePool#execute(com.bazaarvoice.ostrich.PartitionContext,
     * com.bazaarvoice.ostrich.RetryPolicy, com.bazaarvoice.ostrich.ServiceCallback)}.
     *
     * @return this
     */
    public ServicePoolBuilder<S> withPartitionContextAnnotations() {
        return withPartitionContextAnnotationsFrom(_serviceType);
    }

    /**
     * Uses {@link PartitionKey} annotations from the specified class to generate partition context in the built proxy.
     * <p>
     * NOTE: This is only useful if building a proxy with {@link #buildProxy(com.bazaarvoice.ostrich.RetryPolicy)}.  If
     * partition context is necessary with a normal service pool, then can be provided directly by calling
     * {@link com.bazaarvoice.ostrich.ServicePool#execute(com.bazaarvoice.ostrich.PartitionContext,
     * com.bazaarvoice.ostrich.RetryPolicy, com.bazaarvoice.ostrich.ServiceCallback)}.
     *
     * @param annotatedServiceClass A service class with {@link PartitionKey} annotations.
     * @return this
     */
    public ServicePoolBuilder<S> withPartitionContextAnnotationsFrom(Class<? extends S> annotatedServiceClass) {
        checkNotNull(annotatedServiceClass);
        _partitionContextSupplier = new AnnotationPartitionContextSupplier(_serviceType, annotatedServiceClass);
        return this;
    }

    /**
     * Sets the {@code LoadBalanceAlgorithm} that should be used for this service.
     *
     * @param algorithm A load balance algorithm to choose between available end points for the service.
     * @return this
     */
    public ServicePoolBuilder<S> withLoadBalanceAlgorithm(LoadBalanceAlgorithm algorithm) {
        _loadBalanceAlgorithm = checkNotNull(algorithm);
        return this;
    }

    /** Sets the {@code MetricRegistry} that should be used for this service.
     *
     * @param metrics The metric registry to use.
     * @return this
     */
    public ServicePoolBuilder<S> withMetricRegistry(MetricRegistry metrics) {
        _metrics = checkNotNull(metrics);
        return this;
    }

    /**
     * Sets the {@code HealthCheckRetryPolicy} that should be used for this service.
     *
     * @param healthCheckRetryDelay retry policy to use
     * @return this
     */
    public ServicePoolBuilder<S> withHealthCheckRetryPolicy(HealthCheckRetryDelay healthCheckRetryDelay) {
        _healthCheckRetryDelay = checkNotNull(healthCheckRetryDelay);
        return this;
    }

    /**
     * Builds a {@code com.bazaarvoice.ostrich.ServicePool}.
     *
     * @return The {@code com.bazaarvoice.ostrich.ServicePool} that was constructed.
     */
    public com.bazaarvoice.ostrich.ServicePool<S> build() {
        return buildInternal();
    }

    /**
     * Builds a {@code com.bazaarvoice.ostrich.AsyncServicePool}.
     *
     * @return The {@code com.bazaarvoice.ostrich.AsyncServicePool} that was constructed.
     */
    public com.bazaarvoice.ostrich.AsyncServicePool<S> buildAsync() {
        ServicePool<S> pool = buildInternal();

        boolean shutdownAsyncExecutorOnClose = (_asyncExecutor == null);
        if (_asyncExecutor == null) {
            ThreadFactory threadFactory = new ThreadFactoryBuilder()
                    .setNameFormat(_serviceName + "-AsyncExecutorThread-%d")
                    .setDaemon(true)
                    .build();
            _asyncExecutor = Executors.newCachedThreadPool(threadFactory);
        }

        return new AsyncServicePool<>(Ticker.systemTicker(), pool, true, _asyncExecutor,
                shutdownAsyncExecutorOnClose, _metrics);
    }

    /**
     * Builds a dynamic proxy that wraps a {@code ServicePool} and implements the service interface directly.  This is
     * appropriate for stateless services where it's sensible for the same retry policy to apply to every method.
     * <p/>
     * It is the caller's responsibility to shutdown the service pool when they're done with it by casting the proxy
     * to {@link java.io.Closeable} and calling the {@link java.io.Closeable#close()} method.
     *
     * @param retryPolicy The retry policy to apply for every service call.
     * @return The dynamic proxy instance that implements the service interface {@code S} and the
     *         {@link java.io.Closeable} interface.
     */
    public S buildProxy(RetryPolicy retryPolicy) {
        return ServicePoolProxy.create(_serviceType, retryPolicy, build(), _partitionContextSupplier, true);
    }

    @VisibleForTesting
    ServicePool<S> buildInternal() {
        checkNotNull(_serviceFactory);
        checkNotNull(_metrics);

        HostDiscovery hostDiscovery = findHostDiscovery(_serviceName);

        boolean shutdownHealthCheckExecutorOnClose = (_healthCheckExecutor == null);

        try {
            if (_cachingPolicy == null) {
                _cachingPolicy = ServiceCachingPolicyBuilder.NO_CACHING;
            }

            if (_healthCheckExecutor == null) {
                _healthCheckExecutor = Executors.newScheduledThreadPool(DEFAULT_NUM_HEALTH_CHECK_THREADS,
                        new ThreadFactoryBuilder()
                                .setNameFormat(_serviceName + "-HealthCheckThread-%d")
                                .setDaemon(true)
                                .build());
            }

            ServicePool<S> servicePool = new ServicePool<>(Ticker.systemTicker(), hostDiscovery, _closeHostDiscovery,
                    _serviceFactory, _cachingPolicy, _partitionFilter, _loadBalanceAlgorithm, _healthCheckExecutor,
                    shutdownHealthCheckExecutorOnClose, _healthCheckRetryDelay, _metrics);

            _closeHostDiscovery = false;

            return servicePool;
        } catch (Throwable t) {
            if (shutdownHealthCheckExecutorOnClose && _healthCheckExecutor != null) {
                _healthCheckExecutor.shutdownNow();
                _healthCheckExecutor = null;
            }

            try {
                if (_closeHostDiscovery) {
                    hostDiscovery.close();
                }
            } catch (IOException e) {
                // NOP
            } finally {
                _closeHostDiscovery = false;
            }

            throw Throwables.propagate(t);
        }
    }

    private HostDiscovery findHostDiscovery(String serviceName) {
        for (HostDiscoverySource source : _hostDiscoverySources) {
            HostDiscovery hostDiscovery = source.forService(serviceName);
            if (hostDiscovery != null) {
                return hostDiscovery;
            }
        }
        throw new IllegalStateException(format("No HostDiscovery found for service: %s", serviceName));
    }

    private class ClosingHostDiscoverySource implements HostDiscoverySource {
        private HostDiscoverySource _wrappedSource;

        public ClosingHostDiscoverySource(HostDiscoverySource wrappedSource) {
            _wrappedSource = wrappedSource;
        }

        @Override
        public HostDiscovery forService(String serviceName) {
            HostDiscovery hostDiscovery = _wrappedSource.forService(serviceName);
            if (hostDiscovery != null) {
                _closeHostDiscovery = true;
            }
            return hostDiscovery;
        }
    }
}
TOP

Related Classes of com.bazaarvoice.ostrich.pool.ServicePoolBuilder$ClosingHostDiscoverySource

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.