/*
 * Copyright (c) 2015. Rick Hightower, Geoff Chandler
 *
 * 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.
 *
 * QBit - The Microservice lib for Java : JSON, WebSocket, REST. Be The Web!
 *
 * Note this class was heavily influenced and inspired by the Orbitz Consul client.
 */
package io.advantageous.consul;

import io.advantageous.consul.domain.ConsulException;
import io.advantageous.consul.endpoints.*;
import io.advantageous.qbit.QBit;
import io.advantageous.qbit.json.JsonMapper;

import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;

/**
 * Consul HTTP API proxy
 *

 * Note: Used Orbitz consul client and others as a guides.
 *
 * @author Richard Hightower
 */
public class Consul {

    private final AgentEndpoint agent;
    private final HealthEndpoint health;
    private final KeyValueStoreEndpoint keyValueStore;
    private final CatalogEndpoint catalog;
    private final StatusEndpoint status;
    private final SessionEndpoint session;

    /**
     * Private constructor.
     *
     * @param url The full URL of a running Consul instance.
     */
    private Consul(String url) {

        URI uri = URI.create(url + "/v1");

        final String rootPath = uri.getPath();

        final JsonMapper mapper = QBit.factory().createJsonMapper();
        this.agent = new AgentEndpoint(uri, rootPath + "/agent", mapper);
        this.health = new HealthEndpoint(uri, rootPath + "/health", mapper);
        this.keyValueStore = new KeyValueStoreEndpoint(uri, rootPath + "/kv", mapper);
        this.catalog = new CatalogEndpoint(uri, rootPath + "/catalog", mapper);
        this.status = new StatusEndpoint(uri, rootPath + "/status", mapper);
        this.session = new SessionEndpoint(uri, rootPath + "/session", mapper);

    }

    /**
     * Creates a new client given a host and a port.
     *
     * @param host The Consul API hostname or IP.
     * @param port The Consul port.
     * @return A new client.
     */
    public static Consul consul(final String host, final int port) {
        try {
            return new Consul(new URL("http", host, port, "").toString());
        } catch (MalformedURLException e) {
            throw new ConsulException("Bad Consul URL", e);
        }
    }

    /**
     * Creates a new client given a host and a port.
     *
     * @return A new client.
     */
    public static Consul consul() {
        return consul("localhost", 8500);
    }

    /**
     * Catalog HTTP endpoint.
     *

     * /v1/catalog
     *
     * @return The Catalog HTTP endpoint.
     */
    public CatalogEndpoint catalog() {
        return catalog;
    }

    /**
     * Session endpoint.
     * @return The Session HTTP endpoint
     */
    public SessionEndpoint session() {
        return session;
    }

    /**
     * Health HTTP endpoint.
     *

     * /v1/health
     *
     * @return The Health HTTP endpoint.
     */
    public HealthEndpoint health() {
        return health;
    }

    /**
     * Status HTTP endpoint.
     *

     * /v1/status
     *
     * @return The Status HTTP endpoint.
     */
    public StatusEndpoint status() {
        return status;
    }

    /**
     * Agent HTTP endpoint.
     *

     * /v1/agent
     *
     * @return The Agent HTTP endpoint.
     */
    public AgentEndpoint agent() {
        return agent;
    }

    /**
     * Key/Value HTTP endpoint.
     *

     * /v1/kv
     *
     * @return The Key/Value HTTP endpoint.
     */
    public KeyValueStoreEndpoint keyValueStore() {
        return keyValueStore;
    }

}


package io.advantageous.qbit.service.discovery.dns;

import io.vertx.core.Vertx;
import io.vertx.core.dns.DnsClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.URI;
import java.util.List;

/**
 * If you don't supply a DNS port and host, then you get this class.
 * This class reads the information from /etc/resolv.conf.
 * If a host/port combo from /etc/resolv.conf fails, it uses the next one in the list.
 * If that one fails, it throws an exception.
 * Every try that fails will try the current DNS URI and the next one if the first fails.
 */
public class DnsClientFromResolveConfSupplier implements DnsClientSupplier {

    private final Logger logger = LoggerFactory.getLogger(DnsClientFromResolveConfSupplier.class);

    private final boolean debug = logger.isDebugEnabled();

    /**
     * Vertx instance. Vertx is used to build dns client.
     */
    private final Vertx vertx;

    /**
     * List of DNS addresses from /etc/resolv.conf.
     */
    private final List<URI> addressList;

    /**
     * Index of the current DNS address, this only increments if we have DNS failures.
     */
    private int index = 0;

    /**
     * Create DnsClientFromResolveConfSupplier
     *
     * @param vertx vertx
     */
    public DnsClientFromResolveConfSupplier(final Vertx vertx) {

        this.vertx = vertx;
        addressList = DnsUtil.readDnsConf();

        if (debug) logger.debug("DnsClientFromResolveConfSupplier {}", addressList);
    }

    /**
     * Get a DNS client.
     *
     * @return dns client.
     */
    @Override
    public DnsClient get() {
        final URI uri = addressList.get(index);
        try {
            if (debug) logger.debug("DnsClient.get port {} host {}", uri.getPort(), uri.getHost());
            return vertx.createDnsClient(uri.getPort(), uri.getHost());
        } catch (Exception ex) {
            logger.error("DnsClient.get EXCEPTION ", ex);
            logger.error("DnsClient.get EXCEPTION port {} host {}", uri.getPort(), uri.getHost());
            return getIfErrors();
        }
    }

    private void nextAddress() {
        if (index + 1 == addressList.size()) {
            index = 0;
        } else {
            index++;
        }
    }

    @Override
    public DnsClient getIfErrors() {
        nextAddress();
        final URI uri = addressList.get(index);
        if (debug) logger.debug("DnsClient.get FAIL OVER port {} host {}", uri.getPort(), uri.getHost());
        return vertx.createDnsClient(uri.getPort(), uri.getHost());
    }
}


/*
 * Copyright (c) 2015. Rick Hightower, Geoff Chandler
 *
 * 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.
 *
 * QBit - The Microservice lib for Java : JSON, WebSocket, REST. Be The Web!
 */

package io.advantageous.qbit.queue.impl;

import io.advantageous.boon.core.reflection.ClassMeta;
import io.advantageous.boon.core.reflection.ConstructorAccess;
import io.advantageous.qbit.queue.*;
import io.advantageous.qbit.queue.impl.sender.BasicBlockingQueueSender;
import io.advantageous.qbit.queue.impl.sender.BasicSendQueueWithTransferQueue;
import io.advantageous.qbit.queue.impl.sender.BasicSendQueueWithTryTransfer;
import io.advantageous.qbit.queue.impl.sender.NoBatchSendQueue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TransferQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;

/**
 * This is the base for all the queues we use.
 * <p>
 * created by Richard on 8/4/14.
 *
 * @param <T> type
 * @author rhightower
 */
public class BasicQueue<T> implements Queue<T> {

    private final BlockingQueue<Object> queue;
    private final int batchSize;
    private final Logger logger = LoggerFactory.getLogger(BasicQueue.class);
    private final boolean debug = logger.isDebugEnabled();
    private final int limit;
    private final String name;
    private final int pollTimeWait;
    private final TimeUnit pollTimeTimeUnit;
    private final AtomicBoolean stop = new AtomicBoolean(true);
    private final Supplier<SendQueue<T>> sendQueueSupplier;
    private ReceiveQueueManager<T> receiveQueueManager;

    public BasicQueue(final String name,
                      final int waitTime,
                      @SuppressWarnings("SameParameterValue") final TimeUnit timeUnit,
                      final int batchSize,
                      final Class<? extends BlockingQueue> queueClass,
                      final boolean checkIfBusy,
                      final int size,
                      final int checkEvery,
                      final boolean tryTransfer,
                      final UnableToEnqueueHandler unableToEnqueueHandler,
                      final int limit) {

        logger.debug("Queue created {} {} limit {} size {} checkEvery {} tryTransfer {} waitTime {} limit {}",
                name, queueClass, batchSize, size, checkEvery, tryTransfer, waitTime, limit);

        this.name = name;
        this.pollTimeWait = waitTime;
        this.pollTimeTimeUnit = timeUnit;
        this.batchSize = batchSize;
        this.limit = limit;

        if (size == -1) {

            //noinspection unchecked
            this.queue = ClassMeta.classMeta(queueClass).noArgConstructor().create();
        } else {

            final ClassMeta<? extends BlockingQueue> classMeta = ClassMeta.classMeta(queueClass);
            if (queueClass != LinkedTransferQueue.class) {
                if (debug) logger.debug("Not a LinkedTransfer queue");
                final ConstructorAccess<Object> constructor = classMeta.declaredConstructor(int.class);
                //noinspection unchecked
                this.queue = (BlockingQueue<Object>) constructor.create(size);
            } else {
                final ConstructorAccess<? extends BlockingQueue> constructorAccess = classMeta.noArgConstructor();
                //noinspection unchecked
                this.queue = (BlockingQueue<Object>) constructorAccess.create();
            }
        }

        if (this.batchSize == 1) {

            sendQueueSupplier = () -> new NoBatchSendQueue<>(queue, this, name);

        } else if (queue instanceof LinkedTransferQueue) {

            if (tryTransfer) {
                sendQueueSupplier = () -> new BasicSendQueueWithTryTransfer<>(name, batchSize, (TransferQueue<Object>) queue,
                        checkEvery, BasicQueue.this);
            } else {
                sendQueueSupplier = () -> new BasicSendQueueWithTransferQueue<>(name, batchSize, ((TransferQueue<Object>) queue),
                        checkEvery, BasicQueue.this);
            }
        } else {
            sendQueueSupplier = () -> new BasicBlockingQueueSender<>(name, batchSize, queue,
                    checkIfBusy, unableToEnqueueHandler, BasicQueue.this);
        }

        logger.info("Queue done creating {} limit {} checkEvery {} tryTransfer {}" +
                        "pollTimeWait/polltime {}",
                this.name, this.batchSize, checkEvery, tryTransfer,
                this.pollTimeWait);

    }

    /**
     * This returns a new instance of ReceiveQueue every time you call it
     * so call it only once per thread.
     *
     * @return received queue
     */
    @Override
    public ReceiveQueue<T> receiveQueue() {
        if (debug) logger.debug("ReceiveQueue requested for {}", name);
        return new BasicReceiveQueue<>(queue, pollTimeWait, pollTimeTimeUnit, limit);
    }

    /**
     * This returns a new instance of SendQueue every time you call it
     * so call it only once per thread.
     *
     * @return sendQueue.
     */
    @Override
    public SendQueue<T> sendQueue() {
        if (debug) logger.debug("SendQueue requested for {}", name);
        return sendQueueSupplier.get();
    }

    @Override
    public void startListener(final ReceiveQueueListener<T> listener) {
        this.receiveQueueManager = new BasicReceiveQueueManager<>(name);
        stop.set(false);
        logger.info("Starting queue listener for  {} {}", name, listener);
        this.receiveQueueManager.addQueueToManage(name, this.receiveQueue(), listener, limit);
        this.receiveQueueManager.start();
    }

    @Override
    public void stop() {
        logger.info("Stopping queue  {}", name);
        stop.set(true);
        if (receiveQueueManager != null) {
            receiveQueueManager.stop();
        }

        if (queue != null) {
            queue.clear();
        }
    }

    @Override
    public int size() {
        return queue.size();
    }

    @Override
    public boolean started() {
        return !stop.get();
    }

    @Override
    public String toString() {
        return "BasicQueue{" +
                "name='" + name + '\'' +
                '}';
    }

    @Override
    public String name() {
        return name;
    }
}


package io.advantageous.qbit.reakt;

import io.advantageous.qbit.reactive.Callback;
import io.advantageous.qbit.reactive.CallbackBuilder;
import io.advantageous.qbit.reactive.Reactor;
import io.advantageous.reakt.Result;
import io.advantageous.reakt.promise.Promise;
import io.advantageous.reakt.promise.Promises;

import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;

/**
 * Converts Reakt promises and callbacks into QBit callbacks.
 */
public class Reakt {

    /**
     * Converts a Reakt promise into a QBit callback.
     *
     * @param promise reakt promise
     * @param <T>     type of promise
     * @return new QBit callback that wraps promise.
     */
    public static <T> Callback<T> convertPromise(final Promise<T> promise) {
        return convertPromiseToCallback(promise, CallbackBuilder.callbackBuilder());
    }

    /**
     * Converts a Reakt promise into a QBit callback.
     * Reactor is used to manage timeouts and ensure callback happens on same thread as caller.
     *
     * @param reactor QBit reactor
     * @param promise reakt promise
     * @param <T>     type of promise
     * @return new QBit callback that wraps promise.
     */
    public static <T> Callback<T> convertPromise(final Reactor reactor, final Promise<T> promise) {
        return convertPromiseToCallback(promise, reactor.callbackBuilder());
    }

    /**
     * Converts a Reakt callback into a QBit callback.
     *
     * @param callback reakt callback
     * @param <T>      type of result
     * @return QBit callback
     */
    public static <T> Callback<T> convertCallback(final io.advantageous.reakt.CallbackHandler<T> callback) {
        return convertReaktCallbackToQBitCallback(callback, CallbackBuilder.callbackBuilder());
    }

    /**
     * Converts a Reakt callback into a QBit callback.
     *
     * @param callback reakt callback
     * @param <T>      type of result
     * @return QBit callback
     */
    public static <T> io.advantageous.reakt.CallbackHandler<T> convertQBitCallback(final Callback<T> callback) {
        return result -> {

            if (result.failure()) {
                callback.onError(result.cause());
            } else {
                callback.accept(result.get());
            }
        };
    }

    /**
     * Converts a Reakt callback into a QBit callback.
     * <p>
     * Reactor is used to manage timeouts and ensure callback happens on same thread as caller.
     *
     * @param reactor  QBit reactor
     * @param callback reakt callback
     * @param <T>      type of result
     * @return QBit callback
     */
    public static <T> Callback<T> convertCallback(final Reactor reactor,
                                                  final io.advantageous.reakt.CallbackHandler<T> callback) {
        return convertReaktCallbackToQBitCallback(callback, reactor.callbackBuilder());
    }

    private static <T> Callback<T> convertPromiseToCallback(final Promise<T> promise, CallbackBuilder callbackBuilder) {
        return convertReaktCallbackToQBitCallback(promise.asHandler(), callbackBuilder);
    }

    private static <T> Callback<T> convertReaktCallbackToQBitCallback(final io.advantageous.reakt.CallbackHandler<T> callback, CallbackBuilder callbackBuilder) {
        return callbackBuilder
                .withCallback(o -> {
                    callback.onResult(Result.result((T) o));
                })
                .withErrorHandler(throwable -> callback.onResult(Result.error(throwable)))
                .withTimeoutHandler(() -> callback.onResult(Result.error(new TimeoutException()))).build();

    }

    /**
     * Creates a QBit callback based on promise created.
     *
     * @param promiseConsumer promise consumer
     * @param <T>             T
     * @return QBit callback
     */
    public <T> Callback<T> callback(final Consumer<Promise<T>> promiseConsumer) {
        Promise<T> promise = Promises.promise();
        promiseConsumer.accept(promise);
        return Reakt.convertPromise(promise);
    }

}


/*
 * Copyright (c) 2015. Rick Hightower, Geoff Chandler
 *
 * 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.
 *
 * QBit - The Microservice lib for Java : JSON, WebSocket, REST. Be The Web!
 *
 * Note this class was heavily influenced and inspired by the Orbitz Consul client.
 */
package io.advantageous.consul.endpoints;

import io.advantageous.consul.domain.option.RequestOptions;
import io.advantageous.qbit.http.HTTP;
import io.advantageous.qbit.http.request.HttpRequestBuilder;
import io.advantageous.qbit.json.JsonMapper;

import java.net.URI;
import java.util.List;

import static io.advantageous.consul.domain.ConsulException.die;

public class StatusEndpoint extends Endpoint {

    public StatusEndpoint(String scheme, String host, String port, String rootPath, final JsonMapper mapper) {
        super(scheme, host, port, rootPath, mapper);
    }

    public StatusEndpoint(URI rootURI, String rootPath, final JsonMapper mapper) {
        super(rootURI, rootPath, mapper);
    }

    /**
     * Retrieves the host/port of the Consul leader.
     *

     * GET /v1/status/leader
     *
     * @return The host/port of the leader.
     */
    public String getLeader() {

        final URI uri = createURI("/leader");

        final HttpRequestBuilder httpRequestBuilder = RequestUtils
                .getHttpRequestBuilder(null, null, RequestOptions.BLANK, "");

        final HTTP.Response httpResponse = HTTP.getResponse(uri + "?" + httpRequestBuilder.paramString());

        if (httpResponse.code() != 200) {
            die("Unable to retrieve the leader", uri, httpResponse.code(), httpResponse.body());
        }

        return fromJson(httpResponse.body(), String.class).replace("\"", "").trim();
    }

    /**
     * Retrieves a list of host/ports for raft peers.
     *

     * GET /v1/status/peers
     *
     * @return List of host/ports for raft peers.
     */
    public List<String> getPeers() {

        final URI uri = createURI("/peers");

        final HttpRequestBuilder httpRequestBuilder = RequestUtils
                .getHttpRequestBuilder(null, null, RequestOptions.BLANK, "");

        final HTTP.Response httpResponse = HTTP.getResponse(uri + "?" + httpRequestBuilder.paramString());

        if (httpResponse.code() != 200) {
            die("Unable to get the peers", uri, httpResponse.code(), httpResponse.body());
        }

        return fromJsonArray(httpResponse.body(), String.class);
    }

}


package io.advantageous.qbit.service.discovery.impl;

import io.advantageous.boon.core.Sys;
import io.advantageous.qbit.GlobalConstants;
import io.advantageous.qbit.QBit;
import io.advantageous.qbit.concurrent.PeriodicScheduler;
import io.advantageous.qbit.reactive.Callback;
import io.advantageous.qbit.service.discovery.*;
import io.advantageous.qbit.service.discovery.ServiceDiscovery;
import io.advantageous.qbit.service.discovery.spi.ServiceDiscoveryProvider;
import io.advantageous.qbit.service.health.HealthStatus;
import io.advantageous.qbit.util.ConcurrentHashSet;
import io.advantageous.qbit.util.Timer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;

/**
 * Service Discovery. This is a generic service discovery class.
 * It has two providers. If the primary provider fails, it uses the secondary provider.
 * <p>
 * created by rhightower on 3/23/15.
 */
public class ServiceDiscoveryImpl implements ServiceDiscovery {

    private final PeriodicScheduler periodicScheduler;
    private final BlockingQueue<String> doneQueue = new LinkedTransferQueue<>();
    private final BlockingQueue<ServiceHealthCheckIn> checkInsQueue = new LinkedTransferQueue<>();
    private final BlockingQueue<EndpointDefinition> registerQueue = new LinkedTransferQueue<>();
    private final ServiceChangedEventChannel serviceChangedEventChannel;
    private final ServicePoolListener servicePoolListener;
    private final ExecutorService executorService;
    private final ConcurrentHashMap<String, ServicePool> servicePoolMap = new ConcurrentHashMap<>();
    private final ConcurrentHashSet<EndpointDefinition> endpointDefinitions = new ConcurrentHashSet<>();
    private final ServiceDiscoveryProvider provider;
    private final Logger logger = LoggerFactory.getLogger(ServiceDiscoveryImpl.class);
    private final boolean debug = GlobalConstants.DEBUG || logger.isDebugEnabled();
    private final boolean trace = logger.isTraceEnabled();
    private final int pollForServicesIntervalMS;
    private final int checkInIntervalInMS;

    private final ServiceDiscoveryProvider backupProvider;
    private final ConcurrentHashSet<String> serviceNamesBeingLoaded = new ConcurrentHashSet<>();
    private final AtomicBoolean stop = new AtomicBoolean();
    private final Set<String> serviceNames = new TreeSet<>();
    private final ConcurrentHashMap<String, BlockingQueue<Callback<List<EndpointDefinition>>>>
            callbackMap = new ConcurrentHashMap<>();
    private long lastCheckIn;

    public ServiceDiscoveryImpl(
            final PeriodicScheduler periodicScheduler,
            final ServiceChangedEventChannel serviceChangedEventChannel,
            final ServiceDiscoveryProvider provider,
            final ServiceDiscoveryProvider backupProvider,
            final ServicePoolListener servicePoolListener,
            final ExecutorService executorService,
            final int pollForServicesIntervalSeconds,
            final int checkInIntervalInSeconds) {

        this.backupProvider = backupProvider;
        this.checkInIntervalInMS = checkInIntervalInSeconds * 1000;
        this.provider = provider;
        this.pollForServicesIntervalMS = pollForServicesIntervalSeconds * 1000;

        this.periodicScheduler =
                periodicScheduler == null ? QBit.factory().periodicScheduler() : periodicScheduler;

        this.serviceChangedEventChannel = serviceChangedEventChannel == null ?
                serviceName -> {

                } : serviceChangedEventChannel;

        this.servicePoolListener = servicePoolListener == null ? serviceName -> {
        } : servicePoolListener;

        this.executorService = executorService == null ?
                Executors.newCachedThreadPool(runnable -> new Thread(runnable, "ServiceDiscovery")) :
                executorService;//Mostly sleeping threads doing long polls

        if (trace) {
            logger.trace(
                    "ServiceDiscoveryImpl created" + provider
            );
        }

        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            if (!stop.get()) stop();
        }));

    }

    @Override
    public EndpointDefinition registerWithTTL(
            final String serviceName,
            final String host,
            final int port,
            final int timeToLiveSeconds) {

        if (trace) {
            logger.trace(
                    "ServiceDiscoveryImpl::registerWithTTL() " + serviceName + " " + port
            );
        }

        watch(serviceName);

        EndpointDefinition endpointDefinition = new EndpointDefinition(HealthStatus.PASS,
                serviceName + "-" + ServiceDiscovery.uniqueString(port),
                serviceName, host, port, timeToLiveSeconds);

        return doRegister(endpointDefinition);
    }

    @Override
    public EndpointDefinition registerWithIdAndTimeToLive(
            final String serviceName, final String serviceId, String host, final int port, final int timeToLiveSeconds) {

        if (trace) {
            logger.trace(
                    "ServiceDiscoveryImpl::registerWithIdAndTimeToLive() " + serviceName + " " + port
            );
        }

        watch(serviceName);
        EndpointDefinition endpointDefinition = new EndpointDefinition(HealthStatus.PASS,
                serviceId,
                serviceName, host, port, timeToLiveSeconds);

        return doRegister(endpointDefinition);
    }

    @Override
    public EndpointDefinition registerWithIdAndTTLAndTags(
            final String serviceName, final String serviceId, String host, final int port, final int timeToLiveSeconds, List<String> tags) {

        if (trace) {
            logger.trace(
                    "ServiceDiscoveryImpl::registerWithIdAndTimeToLive() " + serviceName + " " + port
            );
        }

        watch(serviceName);
        EndpointDefinition endpointDefinition = new EndpointDefinition(HealthStatus.PASS,
                serviceId,
                serviceName, host, port, timeToLiveSeconds, tags);

        return doRegister(endpointDefinition);
    }

    private EndpointDefinition doRegister(EndpointDefinition endpointDefinition) {

        endpointDefinitions.add(endpointDefinition);
        registerQueue.offer(endpointDefinition);

        return endpointDefinition;
    }

    @Override
    public EndpointDefinition register(final String serviceName, String host, final int port) {

        if (trace) {
            logger.trace(
                    "ServiceDiscoveryImpl::register()" + serviceName + " " + host + ":" + port
            );
        }

        watch(serviceName);

        EndpointDefinition endpointDefinition = new EndpointDefinition(HealthStatus.PASS,
                serviceName + "-" + ServiceDiscovery.uniqueString(port),
                serviceName, host, port);

        return doRegister(endpointDefinition);

    }

    @Override
    public EndpointDefinition registerWithId(final String serviceName, final String serviceId, String host, final int port) {

        if (trace) {
            logger.trace(
                    "ServiceDiscoveryImpl::registerWithId()" + serviceName + " " + host + ":" + port
            );
        }

        watch(serviceName);

        EndpointDefinition endpointDefinition = new EndpointDefinition(HealthStatus.PASS,
                serviceId,
                serviceName, host, port);

        return doRegister(endpointDefinition);

    }

    @Override
    public void watch(String serviceName) {
        if (trace) {
            logger.trace(
                    "ServiceDiscoveryImpl::watch()" + serviceName
            );
        }
        if (!serviceNames.contains(serviceName)) {
            serviceNames.add(serviceName);
            doneQueue.offer(serviceName);
        }

    }

    @Override
    public void checkIn(final String serviceId, final HealthStatus healthStatus) {

        if (trace) {
            logger.trace(
                    "ServiceDiscoveryImpl::checkIn()" + serviceId, healthStatus
            );
        }

        checkInsQueue.offer(new ServiceHealthCheckIn(serviceId, healthStatus));

    }

    @Override
    public void checkInOk(final String serviceId) {

        if (trace) {
            logger.trace(
                    "ServiceDiscoveryImpl::checkInOk()" + serviceId
            );
        }

        checkInsQueue.offer(new ServiceHealthCheckIn(serviceId, HealthStatus.PASS));

    }

    public ServicePool servicePool(final String serviceName) {
        ServicePool servicePool = servicePoolMap.get(serviceName);
        if (servicePool == null) {
            servicePool = new ServicePool(serviceName, this.servicePoolListener);
            servicePoolMap.put(serviceName, servicePool);
        }
        return servicePool;
    }

    public void loadServicesAsync(Callback<List<EndpointDefinition>> callback, final String serviceName) {
        final List<EndpointDefinition> endpointDefinitions = loadServices(serviceName);
        if (endpointDefinitions.size() > 0) {
            callback.accept(endpointDefinitions);
            return;
        }

        BlockingQueue<Callback<List<EndpointDefinition>>> callbacks
                = callbackMap.get(serviceName);

        if (callbacks == null) {
            callbacks = new ArrayBlockingQueue<>(200);
            try {
                callbacks.put(callback);
            } catch (InterruptedException e) {
                throw new IllegalStateException(e);
            }
            callbackMap.put(serviceName, callbacks);
        } else {
            try {
                if (callbacks.size() == 0) {
                    callbacks.put(callback);
                    serviceNamesBeingLoaded.remove(serviceName);
                }
            } catch (InterruptedException e) {
                throw new IllegalStateException(e);
            }
        }
    }

    @Override
    public List<EndpointDefinition> loadServices(final String serviceName) {

        if (trace) {
            logger.trace(
                    "ServiceDiscoveryImpl::loadServices()" + serviceName
            );
        }

        ServicePool servicePool = servicePoolMap.get(serviceName);
        if (servicePool == null) {
            servicePool = new ServicePool(serviceName, this.servicePoolListener);
            servicePoolMap.put(serviceName, servicePool);
            watch(serviceName);
            return Collections.emptyList();
        }
        return servicePool.services();
    }

    public List<EndpointDefinition> loadServicesNow(final String serviceName) {

        if (debug) {
            logger.debug("ServiceDiscoveryImpl::loadServicesNow {}", serviceName);
        }

        ServicePool servicePool = servicePoolMap.get(serviceName);
        if (servicePool == null || servicePool.services() == null || servicePool.services().size() == 0) {
            servicePool = new ServicePool(serviceName, this.servicePoolListener);
            servicePoolMap.put(serviceName, servicePool);
            try {
                final List<EndpointDefinition> healthyServices = provider.loadServices(serviceName);
                if (debug) {
                    logger.debug("ServiceDiscoveryImpl::loadServicesNow {} healthyServices {}", serviceName, healthyServices);
                }
                servicePool.setHealthyNodes(healthyServices, this.servicePoolListener);
            } catch (Exception ex) {
                logger.warn("Unable to load healthy nodes from primary service discovery provider", ex);
                final List<EndpointDefinition> healthyServices = backupProvider.loadServices(serviceName);
                servicePool.setHealthyNodes(healthyServices, this.servicePoolListener);

            }
            watch(serviceName);
        }
        return servicePool.services();

    }

    @Override
    public void start() {

        if (debug) {
            logger.debug("Starting Service Discovery " + provider);
        }

        this.periodicScheduler.repeat(() -> {
            try {
                logger.info("Starting Service Discovery monitor");
                monitor();
            } catch (Exception e) {
                logger.error("ServiceDiscoveryImpl::" +
                        "Error while running monitor", e);
            }
        }, pollForServicesIntervalMS, TimeUnit.MILLISECONDS);
    }

    public void monitor() throws Exception {

        while (!stop.get()) {

            if (registerQueue.size() > 0) {
                provider.registerServices(registerQueue);
            }

            if (doneQueue.size() > 0) {
                executorService.submit(() -> {

                    /* There is no rush, we are periodically checking in.
                    * Protect the service registry from too aggressive config. */
                    loadHealthyServices();
                });
            }

            if (checkInsQueue.size() > 0) {
                provider.checkIn(checkInsQueue);
            }

            if (registerQueue.size() == 0) {
                Sys.sleep(pollForServicesIntervalMS);
            }

            if (doneQueue.size() == 0) {
                long now = Timer.timer().now();
                long duration = now - lastCheckIn;
                if (duration > checkInIntervalInMS) {
                    lastCheckIn = now;
                    doneQueue.addAll(serviceNames);
                }
            }
        }
    }

    /**
     * Iterate through the health service queue and load the services.
     */
    private void loadHealthyServices() {

        try {
            String serviceName = doneQueue.poll();

            while (serviceName != null) {

                final String serviceNameToFetch = serviceName;

                /* Don't load the service if it is already being loaded. */
                if (!serviceNamesBeingLoaded.contains(serviceNameToFetch)) {
                    serviceNamesBeingLoaded.add(serviceNameToFetch);
                    executorService.submit(() -> {
                     /*
                       Loading a service pool might take a while so
                       the actual load operation happens in its own thread.
                      */
                        doLoadHealthServices(serviceNameToFetch);
                    });
                }
                serviceName = doneQueue.poll();
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    /**
     * Loads the service from the remote service registry (i.e., consul).
     *
     * @param serviceNameToFetch service that we are loading a pool for.
     */
    private void doLoadHealthServices(final String serviceNameToFetch) {

        try {
            final List<EndpointDefinition> healthyServices = provider.loadServices(serviceNameToFetch);
            populateServiceMap(serviceNameToFetch, healthyServices);
        } catch (Exception ex) {
            doFailOverHealthServicesLoad(serviceNameToFetch, ex);
        } finally {
            /*  Remove the service from the serviceNamesBeingLoaded
                SET and add it back to the work pool
                to get loaded again.
                 We are constantly loading services through long polling for changes.
             */
            serviceNamesBeingLoaded.remove(serviceNameToFetch);
        }

    }

    /**
     * If the primary load failed, we could have a backup provider registered.
     *
     * @param serviceNameToFetch service pool to fetch
     * @param ex
     */
    private void doFailOverHealthServicesLoad(final String serviceNameToFetch, Exception ex) {

        /* If there is a backup provider, load from there. */
        if (backupProvider != null) {

            if (debug) logger.debug("ServiceDiscoveryImpl::loadHealthyServices " +
                    "Error while loading healthy" +
                    " services for " + serviceNameToFetch, ex);

            final List<EndpointDefinition> healthyServices = backupProvider.loadServices(serviceNameToFetch);
            populateServiceMap(serviceNameToFetch, healthyServices);
            serviceNamesBeingLoaded.remove(serviceNameToFetch);

        } else {

            logger.error("ServiceDiscoveryImpl::loadHealthyServices " +
                    "Error while loading healthy" +
                    " services for " + serviceNameToFetch, ex);
        }

        Sys.sleep(10_000); //primary is down so slow it down so we don't flow the system with updates of service pools.

    }

    /**
     * Populate the service map.
     * Look up the service pool.
     * Apply the healthy services so the pool can see if there were changes (additions, removal, etc.)
     *
     * @param serviceName     service name
     * @param healthyServices list of healthy services that we just loaded.
     */
    private void populateServiceMap(final String serviceName,
                                    final List<EndpointDefinition> healthyServices) {

        final ServicePool servicePool = servicePool(serviceName);

        /* If there were changes then send a service pool change event on the event channel. */
        if (servicePool.setHealthyNodes(healthyServices)) {
            serviceChangedEventChannel.servicePoolChanged(serviceName);
            serviceChangedEventChannel.flushEvents();
        }

        final BlockingQueue<Callback<List<EndpointDefinition>>> callbacks = callbackMap.get(serviceName);

        if (callbacks != null) {
            Callback<List<EndpointDefinition>> callback = callbacks.poll();
            while (callback != null) {
                callback.resolve(healthyServices);
                callback = callbacks.poll();
            }
        }
    }

    @Override
    public void stop() {

        if (debug) {
            logger.debug("Stopping Service Discovery");
        }

        provider.unregisterServices(endpointDefinitions);
        this.periodicScheduler.stop();
        this.stop.set(true);

    }

    public Set<EndpointDefinition> localDefinitions() {
        return endpointDefinitions;
    }
}


package io.advantageous.qbit.service.discovery.dns;

import io.vertx.core.Vertx;
import io.vertx.core.dns.DnsClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Provider abstracts how the DNS Client is created so we can unit test it.
 */
public class DnsSingleClientSupplier implements DnsClientSupplier {

    /**
     * Vertx instance. Vertx is used to build dns client.
     */
    private final Vertx vertx;

    /**
     * port of DNS server.
     */
    private final int port;

    /**
     * Host of DNS server.
     */
    private final String host;

    private final Logger logger = LoggerFactory.getLogger(DnsSingleClientSupplier.class);

    private final boolean debug = logger.isDebugEnabled();

    /**
     * DnsSingleClientSupplier constructor.
     *
     * @param vertx vertx
     * @param host  host
     * @param port  port
     */
    public DnsSingleClientSupplier(final Vertx vertx, final String host, final int port) {

        this.vertx = vertx;
        this.port = port;
        this.host = host;

        if (debug) logger.debug("DnsSingleClientSupplier( host = {}, port = {})", host, port);
    }

    /**
     * Supply an instance of DnsClient.
     *
     * @return DnsClient.
     */
    @Override
    public DnsClient get() {

        if (debug) logger.debug("DnsSingleClientSupplier.get()::host = {}, port = {}", host, port);
        return vertx.createDnsClient(port, host);
    }
}


package io.advantageous.qbit.reactive;

import io.advantageous.qbit.reactive.async.AsyncFutureBlockOnGetCallbackImpl;
import io.advantageous.qbit.reactive.async.AsyncFutureCallbackImpl;
import io.advantageous.qbit.service.ServiceProxyUtils;
import io.advantageous.qbit.time.Duration;
import io.advantageous.qbit.util.Timer;
import org.slf4j.Logger;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.stream.Collectors;

/**
 * You could use a reactor per service.
 * Reactor ensures that callbacks execute on thread of caller not callee for thread safety.
 *

 * Right now we don't use QBit queues, but we could. We need a way to flush the queues async.
 * Reactor. We could get a lot perf from refactoring this to use QBit queues instead of BlockingQueue.
 * created by rhightower on 3/22/15.
 */
@SuppressWarnings("UnusedReturnValue")
public class Reactor {

    /* The queues could benefit from being QBit queues. */

    /* Future can come back on another thread so it has to be handled by thread safe queue. */
    private final BlockingQueue<AsyncFutureCallback<?>> futureQueue = new LinkedTransferQueue<>();

    /* Future can come back on another thread so it has to be handled by thread safe queue. */
    private final BlockingQueue<AsyncFutureCallback<?>> removeFutureQueue = new LinkedTransferQueue<>();

    /* Coordinator can come back on another thread so it has to be handled by thread safe queue. */
    private final BlockingQueue<CallbackCoordinator> coordinatorQueue = new LinkedTransferQueue<>();

    /* Coordinator can come back on another thread so it has to be handled by thread safe queue. */
    private final BlockingQueue<CallbackCoordinator> removeCoordinatorQueue = new LinkedTransferQueue<>();

    /**
     * List of futures that we are managing.
     */
    private final Set<AsyncFutureCallback<?>> futureList = new HashSet<>();

    /**
     * List of coordinators that we are managing.
     */
    private final Set<CallbackCoordinator> coordinatorList = new HashSet<>();

    /**
     * Timer to keep track of current time.
     */
    private final Timer timer;

    /**
     * Time out to use if a timeout is not specified.
     */
    private final long defaultTimeOut;

    /**
     * Current time.
     */
    private long currentTime;

    /**
     * Keeps list of repeating tasks.
     */
    private List<RepeatingTask> repeatingTasks = new ArrayList<>(1);

    private List<FireOnceTask> fireOnceAfterTasks = new ArrayList<>(1);

    /**
     * Keeps list of collaborating services to flush.
     */
    private List<Object> collaboratingServices = new ArrayList<>(1);

    /**
     * Reactor
     *
     * @param timer          timer
     * @param defaultTimeOut defaultTimeOut
     * @param timeUnit       time unit for default time out
     */
    public Reactor(final Timer timer, long defaultTimeOut, TimeUnit timeUnit) {
        this.timer = timer;
        currentTime = timer.now();
        this.defaultTimeOut = timeUnit.toMillis(defaultTimeOut);
    }

    /**
     * Add an object that is auto flushed.
     *
     * @param serviceObject as service object that will be auto-flushed.
     */
    public void addServiceToFlush(final Object serviceObject) {
        collaboratingServices.add(serviceObject);
    }

    /**
     * Add a task that gets repeated.
     *
     * @param repeatEvery repeat Every time period
     * @param timeUnit    unit for repeatEvery
     * @param task        task to perform
     */
    public void addRepeatingTask(final long repeatEvery, final TimeUnit timeUnit, final Runnable task) {

        repeatingTasks.add(new RepeatingTask(task, timeUnit, repeatEvery));
    }

    /**
     * Add a task that gets executed once.
     *
     * @param fireAfter run task after time period
     * @param timeUnit  unit for fireAfter
     * @param task      task to perform
     */
    public void addOneShotAfterTask(final long fireAfter, final TimeUnit timeUnit, final Runnable task) {

        fireOnceAfterTasks.add(new FireOnceTask(task, timeUnit, fireAfter));
    }

    /**
     * Add a task that gets executed once.
     *
     * @param task task to perform
     */
    public void addOneShotTask(final Runnable task) {

        fireOnceAfterTasks.add(new FireOnceTask(task, TimeUnit.MILLISECONDS, 0));
    }

    /**
     * Add a task that gets repeated.
     *
     * @param repeatEvery repeat Every time period
     * @param task        task to perform
     */
    public void addRepeatingTask(final Duration repeatEvery, final Runnable task) {

        repeatingTasks.add(new RepeatingTask(task, repeatEvery.getTimeUnit(), repeatEvery.getDuration()));
    }

    /**
     * Process items in reactor.
     */
    public void process() {

        /** Manages lists of coordinators, and callbacks which can come back async. */
        drainQueues();

        currentTime = timer.now();

        /* Check to see if callbacks completed or timed out. */
        monitorCallBacks();

        /* Check to see if coordinators completed or timed out. */
        monitorCallbackCoordinators();

        /* flush services. */
        collaboratingServices.forEach(ServiceProxyUtils::flushServiceProxy);

        processRepeatingTasks();
        processFireOnceTasks();
    }

    public void processRepeatingTasks() {

        /* Run repeating tasks if needed. */
        repeatingTasks.forEach(repeatingTask -> {
            if (currentTime - repeatingTask.lastTimeInvoked > repeatingTask.repeatEveryMS) {
                repeatingTask.lastTimeInvoked = currentTime;
                repeatingTask.task.run();
            }
        });
    }

    public void processFireOnceTasks() {

        /* Run repeating tasks if needed. */
        final List<FireOnceTask> fireOnceTasks = fireOnceAfterTasks.stream()
                .filter(fireOnceTask -> currentTime - fireOnceTask.created > fireOnceTask.fireAfterMS)
                .collect(Collectors.toList());

        fireOnceTasks.forEach(fireOnceTask -> fireOnceTask.task.run());

        fireOnceAfterTasks.removeAll(fireOnceTasks);

    }

    @SuppressWarnings({"UnusedReturnValue", "SameReturnValue"})
    private boolean drainQueues() {

        CallbackCoordinator callable = coordinatorQueue.poll();
        while (callable != null) {
            coordinatorList.add(callable);
            callable = coordinatorQueue.poll();
        }

        callable = removeCoordinatorQueue.poll();
        while (callable != null) {
            coordinatorList.remove(callable);
            callable = coordinatorQueue.poll();
        }

        AsyncFutureCallback<?> futureCallback = futureQueue.poll();
        while (futureCallback != null) {
            futureList.add(futureCallback);
            futureCallback = futureQueue.poll();
        }

        futureCallback = removeFutureQueue.poll();
        while (futureCallback != null) {
            futureList.remove(futureCallback);
            futureCallback = futureQueue.poll();
        }

        return false;
    }

    public CallbackBuilder callbackBuilder() {
        return CallbackBuilder.newCallbackBuilderWithReactor(this);
    }

    public CoordinatorBuilder coordinatorBuilder() {
        return CoordinatorBuilder.coordinatorBuilder(this);
    }

    public <T> AsyncFutureCallback<T> callback(final Callback<T> callback) {

        return callbackWithTimeout(callback, defaultTimeOut, TimeUnit.MILLISECONDS);
    }

    public <T> AsyncFutureCallback<T> callbackWithLatch(final Callback<T> callback) {

        return callbackWithTimeoutAndLatch(callback, defaultTimeOut, TimeUnit.MILLISECONDS);
    }

    public <T> AsyncFutureCallback<T> callback(Class<T> cls, final Callback<T> callback) {

        return callbackWithTimeout(callback, defaultTimeOut, TimeUnit.MILLISECONDS);
    }

    public <T> AsyncFutureCallback<T> callbackWithTimeout(final Callback<T> callback,
                                                          final long timeoutDuration,
                                                          final TimeUnit timeUnit) {

        return callbackWithTimeoutAndErrorHandler(callback, timeoutDuration, timeUnit, null);

    }

    public <T> AsyncFutureCallback<T> callbackWithTimeoutAndLatch(final Callback<T> callback,
                                                                  final long timeoutDuration,
                                                                  final TimeUnit timeUnit) {

        return callbackWithTimeoutAndErrorHandlerAndLatch(callback, timeoutDuration, timeUnit, null);

    }

    public <T> AsyncFutureCallback<T> callbackWithTimeoutAndErrorHandler(
            final Callback<T> callback,
            final long timeoutDuration,
            final TimeUnit timeUnit,
            final Consumer<Throwable> onError) {

        return callbackWithTimeoutAndErrorHandlerAndOnTimeout(callback, timeoutDuration, timeUnit, null, onError);

    }

    public <T> AsyncFutureCallback<T> callbackWithTimeoutAndErrorHandlerAndLatch(
            final Callback<T> callback,
            final long timeoutDuration,
            final TimeUnit timeUnit,
            final Consumer<Throwable> onError) {

        return callbackWithTimeoutAndErrorHandlerAndOnTimeoutWithLatch(callback, timeoutDuration, timeUnit, null, onError);

    }

    /**
     * Create a callback
     *
     * @param callback        callback
     * @param timeoutDuration timeout duration
     * @param timeUnit        time unit of timeout
     * @param onTimeout       onTimeout handler
     * @param onError         on error handler
     * @param <T>             T
     * @return callback
     */
    public <T> AsyncFutureCallback<T> callbackWithTimeoutAndErrorHandlerAndOnTimeout(
            final Callback<T> callback,
            final long timeoutDuration,
            final TimeUnit timeUnit,
            final Runnable onTimeout,
            final Consumer<Throwable> onError) {

        final AtomicReference<AsyncFutureCallback<T>> ref = new AtomicReference<>();

        final AsyncFutureCallbackImpl<T> asyncFutureCallback =
                AsyncFutureCallbackImpl.callback(callback, currentTime,
                        timeUnit.toMillis(timeoutDuration),
                        createOnFinished(ref)
                        , onTimeout, onError);

        ref.set(asyncFutureCallback);
        addCallback(asyncFutureCallback);
        return asyncFutureCallback;

    }

    /**
     * Create a callback
     *
     * @param callback        callback
     * @param timeoutDuration timeout duration
     * @param timeUnit        time unit of timeout
     * @param onTimeout       onTimeout handler
     * @param onError         on error handler
     * @param <T>             T
     * @return callback
     */
    public <T> AsyncFutureCallback<T> callbackWithTimeoutAndErrorHandlerAndOnTimeoutWithLatch(
            final Callback<T> callback,
            final long timeoutDuration,
            final TimeUnit timeUnit,
            final Runnable onTimeout,
            final Consumer<Throwable> onError) {

        final AtomicReference<AsyncFutureCallback<T>> ref = new AtomicReference<>();

        final AsyncFutureBlockOnGetCallbackImpl<T> asyncFutureCallback =
                AsyncFutureBlockOnGetCallbackImpl.callback(callback, currentTime,
                        timeUnit.toMillis(timeoutDuration),
                        createOnFinished(ref)
                        , onTimeout, onError);

        ref.set(asyncFutureCallback);
        addCallback(asyncFutureCallback);
        return asyncFutureCallback;

    }

    /**
     * Add a callback to manage
     *
     * @param asyncFutureCallback callback
     * @param <T>                 T
     */
    public <T> void addCallback(final AsyncFutureCallback<T> asyncFutureCallback) {
        futureQueue.add(asyncFutureCallback);
    }

    private <T> Runnable createOnFinished(final AtomicReference<AsyncFutureCallback<T>> ref) {
        return () -> Reactor.this.removeFuture(ref.get());
    }

    /**
     * Create a callback
     *
     * @param cls             cls
     * @param callback        callback
     * @param timeoutDuration timeout duration
     * @param timeUnit        timeUnit for timeout
     * @param <T>             T
     * @return callback
     */
    public <T> AsyncFutureCallback<T> callbackWithTimeout(Class<T> cls, final Callback<T> callback,
                                                          final long timeoutDuration,
                                                          final TimeUnit timeUnit) {

        return callbackWithTimeout(callback, timeoutDuration, timeUnit);
    }

    /**
     * Create a callback
     *
     * @param cls             cls
     * @param callback        callback
     * @param timeoutDuration timeout duration
     * @param timeUnit        timeUnit for timeout
     * @param onError         onError handler
     * @param <T>             T
     * @return callback
     */
    public <T> AsyncFutureCallback<T> callbackWithTimeoutAndErrorHandler(Class<T> cls,
                                                                         final Callback<T> callback,
                                                                         final long timeoutDuration,
                                                                         final TimeUnit timeUnit,
                                                                         final Consumer<Throwable> onError) {

        return callbackWithTimeoutAndErrorHandler(callback, timeoutDuration, timeUnit, onError);
    }

    /**
     * Add the coordinator to our list of coordinators.
     *
     * @param coordinator coordinator
     * @return coordinator
     */
    public CallbackCoordinator coordinate(final CallbackCoordinator coordinator) {

        this.coordinatorQueue.add(coordinator);
        return coordinator;
    }

    /**
     * Remove a coordinator from the list of coordinators that we are managing.
     *
     * @param coordinator coordinator
     * @return coordinator
     */
    public CallbackCoordinator removeCoordinator(final CallbackCoordinator coordinator) {

        this.removeCoordinatorQueue.add(coordinator);
        return coordinator;
    }

    /**
     * Remove a callback from the list of callbacks that we are managing.
     *
     * @param asyncFutureCallback asyncFutureCallback
     * @param <T>                 T
     * @return the callback that we removed.
     */
    public <T> AsyncFutureCallback<T> removeFuture(AsyncFutureCallback<T> asyncFutureCallback) {

        this.removeFutureQueue.offer(asyncFutureCallback);
        return asyncFutureCallback;
    }

    /**
     * Utility method to create a coordinator.
     *
     * @param coordinator     coordinator
     * @param startTime       startTime
     * @param timeoutDuration timeoutDuration
     * @param timeUnit        timeUnit
     * @param timeOutHandler  timeOutHandler
     * @param finishedHandler finishedHandler
     * @return callback coordinator
     */
    public CallbackCoordinator coordinateWithTimeout(final CallbackCoordinator coordinator,
                                                     final long startTime,
                                                     final long timeoutDuration,
                                                     final TimeUnit timeUnit,
                                                     final Runnable timeOutHandler,
                                                     final Runnable finishedHandler) {

        final long timeoutDurationMS = timeUnit.toMillis(timeoutDuration);

        final long theStartTime = startTime == -1 ? currentTime : startTime;

        final CallbackCoordinator wrapper = new CallbackCoordinator() {

            AtomicBoolean done = new AtomicBoolean();

            @Override
            public boolean checkComplete() {
                if (done.get()) {
                    return true;
                }

                if (coordinator.checkComplete()) {
                    done.set(true);
                }

                return done.get();

            }

            public boolean timedOut(long now) {

                if (startTime() == -1 || timeOutDuration() == -1) {
                    return false;
                }
                if ((now - startTime()) > timeOutDuration()) {

                    if (!done.get()) {
                        timeOutHandler.run();
                        done.set(true);
                    }
                    return true;
                } else {
                    return false;
                }
            }

            @Override
            public long timeOutDuration() {
                return coordinator.timeOutDuration() == -1 ? timeoutDurationMS : coordinator.timeOutDuration();
            }

            @Override
            public long startTime() {
                return coordinator.startTime() == -1 ? theStartTime : coordinator.startTime();
            }

            public void finished() {
                if (checkComplete()) {
                    removeCoordinator(this);
                }
                if (finishedHandler != null) {
                    finishedHandler.run();
                }
                coordinator.finished();
            }

            public void cancel() {
                done.set(true);
                removeCoordinator(this);
                coordinator.cancel();
            }
        };

        this.coordinatorQueue.add(wrapper);
        return wrapper;
    }

    /**
     * Monitors timeouts. If the callback timed-out trigger it, and then remove callback from the list.
     */
    private void monitorCallBacks() {

        if (futureList.size() > 0) {

            final List<AsyncFutureCallback<?>> removeList = new ArrayList<>(futureList.size());
            long now = currentTime;
            for (AsyncFutureCallback<?> callback : futureList) {
                if (callback.isDone()) {
                    callback.run();
                    removeList.add(callback);
                } else {
                    if (callback.checkTimeOut(now)) {
                        removeList.add(callback);
                    }
                }
            }
            futureList.removeAll(removeList);
        }
    }

    /**
     * Monitors Callback Coordinators.
     * Trigger timeouts if needed and remove coordinators from list that timed out.
     */
    private void monitorCallbackCoordinators() {

        if (coordinatorList.size() > 0) {
            final List<CallbackCoordinator> removeList = new ArrayList<>(coordinatorList.size());

            for (CallbackCoordinator callable : coordinatorList) {
                if (callable.checkComplete()) {
                    callable.finished();
                    removeList.add(callable);
                } else if (callable.timedOut(currentTime)) {
                    removeList.add(callable);
                }
            }
            coordinatorList.removeAll(removeList);
        }

    }

    /**
     * Used for quickly delegating one callback to another.
     *

     * This allows one liners so you don't have to create a builder for this common case.
     *
     * @param operationDescription Describe the operation for logging
     * @param callback             callback to delegate error and timeouts too.
     * @param logger               logger to log errors and timeouts.
     * @param <T>                  Generic type
     * @return wrapped callback that is tied to this reactor.
     */
    public <T> Callback<T> wrapCallback(final String operationDescription,
                                        final Callback<T> callback,
                                        final Logger logger) {

        /* Set the callback to delegate to this callback. */
        Callback<T> reactiveCallback = callbackBuilder().withCallback(new Callback<T>() {
            @Override
            public void accept(T t) {
                if (logger.isDebugEnabled()) {
                    logger.debug("{} returned {}", operationDescription, t);
                }
                callback.resolve(t);
            }

        /* Provide some boiler plate error handling. */
        }).withErrorHandler(error -> {
            logger.error(String.format("ERROR calling %s", operationDescription), error);
            callback.onError(error);

        /* Provide some boiler timeout handling. */
        }).withTimeoutHandler(() -> {
            logger.error("TIMEOUT calling {}", operationDescription);
            callback.onTimeout();
        })
                .build();

        return reactiveCallback;
    }

    /**
     * Used for quickly delegating one callback to another when the return types are different.
     * This is usually the case if you want to do some transformation of the object and not just return it.
     *

     * This allows one liners so you don't have to create a builder for this common case.
     *
     * @param operationDescription Describe the operation for logging
     * @param logger               logger to log errors and timeouts.
     * @param errorHandler         Callback that does the error handling if the delegated call fails.
     * @param callback             callback to delegate error and timeouts too.
     * @param <T>                  Generic type
     * @return wrapped callback that is tied to this reactor.
     */
    public <T> Callback<T> wrapCallbackErrors(final String operationDescription,
                                              final Callback<T> callback,
                                              final Callback<?> errorHandler,
                                              final Logger logger) {
        return callbackBuilder().setCallback(new Callback<T>() {
            @Override
            public void accept(T t) {
                if (logger.isDebugEnabled()) {
                    logger.debug("{} returned {}", operationDescription, t);
                }
                callback.resolve(t);
            }

        /* Provide some boiler plate error handling. */
        }).setOnError(error -> {
            logger.error(String.format("ERROR calling %s", operationDescription), error);
            errorHandler.onError(error);
        }).setOnTimeout(() -> {
            logger.error("TIMEOUT calling {}", operationDescription);
            errorHandler.onTimeout();
        })
                .build();
    }

    /**
     * Used for quickly delegating one callback to another.
     * This allows one liners so you don't have to create a builder for this common case.
     *
     * @param operationDescription Describe the operation for logging
     * @param callback             callback to delegate error and timeouts too.
     * @param logger               logger to log errors and timeouts.
     * @param <T>                  Generic type
     * @param timeoutDuration      time out duration
     * @param timeUnit             Time Unit
     * @return wrapped callback that is tied to this reactor.
     */
    public <T> Callback<T> wrapCallbackWithTimeout(
            final String operationDescription,
            final Callback<T> callback,
            final Logger logger,
            final TimeUnit timeUnit,
            final long timeoutDuration) {
        return callbackBuilder().setCallback(new Callback<T>() {
            @Override
            public void accept(T t) {
                if (logger.isDebugEnabled()) {
                    logger.debug("{} returned {}", operationDescription, t);
                }
                callback.resolve(t);
            }
        }).setOnError(error -> {
            logger.error(String.format("ERROR calling %s", operationDescription), error);
            callback.onError(error);

        }).setOnTimeout(() -> {
            logger.error("TIMEOUT calling {}", operationDescription);
            callback.onTimeout();
        }).setTimeoutTimeUnit(timeUnit).setTimeoutDuration(timeoutDuration)
                .build();
    }

    /**
     * Used for quickly delegating one callback to another when the return types are different.
     * This is usually the case if you want to do some transformation of the object and not just return it.
     *
     * @param operationDescription Describe the operation for logging
     * @param callback             callback to delegate error and timeouts too.
     * @param logger               logger to log errors and timeouts.
     * @param <T>                  Generic type
     * @param timeoutDuration      time out duration
     * @param timeUnit             Time Unit
     * @param errorHandler         error handler
     * @return wrapped callback that is tied to this reactor.
     */
    public <T> Callback<T> wrapCallbackErrorWithTimeout(
            final String operationDescription,
            final Callback<T> callback,
            final Callback<?> errorHandler,
            final Logger logger,
            final TimeUnit timeUnit,
            final long timeoutDuration) {
        return callbackBuilder().setCallback(new Callback<T>() {
            @Override
            public void accept(T t) {
                if (logger.isDebugEnabled()) {
                    logger.debug("{} returned {}", operationDescription, t);
                }
                callback.resolve(t);
            }
        }).setOnError(error -> {
            logger.error(String.format("ERROR calling %s", operationDescription), error);
            errorHandler.onError(error);

        }).setOnTimeout(() -> {
            logger.error("TIMEOUT calling {}", operationDescription);
            errorHandler.onTimeout();
        }).withTimeoutTimeUnit(timeUnit).setTimeoutDuration(timeoutDuration)
                .build();
    }

    /**
     * A repeating task.
     */
    class RepeatingTask {
        private final Runnable task;
        private final long repeatEveryMS;
        private long lastTimeInvoked;

        public RepeatingTask(Runnable task, TimeUnit timeUnit, long repeatEvery) {
            this.task = task;
            this.repeatEveryMS = timeUnit.toMillis(repeatEvery);
        }
    }

    /**
     * Fire once task.
     */
    class FireOnceTask {
        private final Runnable task;
        private final long fireAfterMS;
        private final long created;

        public FireOnceTask(Runnable task, TimeUnit timeUnit, long fireAfter) {
            this.task = task;
            this.created = currentTime;
            this.fireAfterMS = timeUnit.toMillis(fireAfter);
        }
    }
}


/*
 * Copyright (c) 2015. Rick Hightower, Geoff Chandler
 *
 * 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.
 *
 * QBit - The Microservice lib for Java : JSON, WebSocket, REST. Be The Web!
 *
 * Note this class was heavily influenced and inspired by the Orbitz Consul client.
 */
package io.advantageous.consul.endpoints;

import io.advantageous.consul.domain.CatalogNode;
import io.advantageous.consul.domain.CatalogService;
import io.advantageous.consul.domain.ConsulResponse;
import io.advantageous.consul.domain.Node;
import io.advantageous.consul.domain.option.RequestOptions;
import io.advantageous.qbit.http.HTTP;
import io.advantageous.qbit.http.request.HttpRequestBuilder;
import io.advantageous.qbit.json.JsonMapper;

import java.net.URI;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import static io.advantageous.consul.domain.ConsulException.die;

/**
 * HTTP Client for /v1/catalog/ endpoints.
 *

 * Note this class was heavily influenced and inspired by the Orbitz Consul client.
 */
public class CatalogEndpoint extends Endpoint {

    public CatalogEndpoint(String scheme, String host, String port, String rootPath, final JsonMapper mapper) {
        super(scheme, host, port, rootPath, mapper);
    }

    public CatalogEndpoint(URI rootURI, String rootPath, final JsonMapper mapper) {
        super(rootURI, rootPath, mapper);
    }

    /**
     * Retrieves all datacenters.
     *

     * GET /v1/catalog/datacenters
     *
     * @return A list of datacenter names.
     */
    public List<String> getDatacenters() {

        URI uri = createURI("/datacenters");

        HTTP.Response httpResponse = HTTP.getResponse(uri.toString());

        if (httpResponse.code() == 200) {
            return fromJsonArray(httpResponse.body(), String.class);
        }
        die("Unable to retrieve the datacenters", uri, httpResponse.code(), httpResponse.body());
        return Collections.emptyList();
    }

    /**
     * Retrieves all services for a given datacenter with {@link io.advantageous.consul.domain.option.RequestOptions}.
     *

     * GET /v1/catalog/services?dc={datacenter}
     *
     * @param requestOptions The Query Options to use.
     * @return A {@link io.advantageous.consul.domain.ConsulResponse} containing a map of service name to list of tags.
     */
    public ConsulResponse<Map<String, List<String>>> getServices(RequestOptions requestOptions) {
        return getServices(null, null, requestOptions);
    }

    public ConsulResponse<Map<String, List<String>>> getServices() {
        return getServices(null, null, RequestOptions.BLANK);
    }

    /**
     * Retrieves all services for a given datacenter with {@link io.advantageous.consul.domain.option.RequestOptions}.
     *

     * GET /v1/catalog/services?dc={datacenter}
     *
     * @param datacenter     datacenter
     * @param tag            tag
     * @param requestOptions The Query Options to use.
     * @return A {@link io.advantageous.consul.domain.ConsulResponse} containing a map of service name to list of tags.
     */
    public ConsulResponse<Map<String, List<String>>> getServices(
            @SuppressWarnings("SameParameterValue") final String datacenter, @SuppressWarnings("SameParameterValue") final String tag,
            final RequestOptions requestOptions) {

        final URI uri = createURI("/services");

        final HttpRequestBuilder httpRequestBuilder = RequestUtils.getHttpRequestBuilder(datacenter, tag, requestOptions, "/");

        HTTP.Response httpResponse = HTTP.getResponse(uri.toString() + "?" + httpRequestBuilder.paramString());

        if (httpResponse.code() != 200) {
            die("Unable to retrieve the datacenters", uri, httpResponse.code(), httpResponse.body());
        }

        //noinspection unchecked
        return (ConsulResponse<Map<String, List<String>>>) (Object) RequestUtils.consulResponse(Map.class, httpResponse);

    }

    /**
     * Retrieves a single service.
     *

     * GET /v1/catalog/service/{service}
     *
     * @param serviceName service name
     * @return A {@link io.advantageous.consul.domain.ConsulResponse} containing
     * {@link io.advantageous.consul.domain.CatalogService} objects.
     */
    public ConsulResponse<List<CatalogService>> getService(@SuppressWarnings("SameParameterValue") final String serviceName) {
        return getService(serviceName, null, null, RequestOptions.BLANK);
    }

    /**
     * Retrieves a single service for a given datacenter.
     *

     * GET /v1/catalog/service/{service}?dc={datacenter}
     *
     * @param serviceName service name
     * @param datacenter  datacenter
     * @param tag         tag
     * @return A {@link io.advantageous.consul.domain.ConsulResponse} containing
     * {@link io.advantageous.consul.domain.CatalogService} objects.
     */
    public ConsulResponse<List<CatalogService>> getService(final String serviceName,
                                                           final String datacenter, final String tag
    ) {
        return getService(serviceName, datacenter, tag, RequestOptions.BLANK);
    }

    /**
     * Retrieves a single service with {@link io.advantageous.consul.domain.option.RequestOptions}.
     *

     * GET /v1/catalog/service/{service}
     *
     * @param serviceName    service name
     * @param requestOptions The Query Options to use.
     * @return A {@link io.advantageous.consul.domain.ConsulResponse} containing
     * {@link io.advantageous.consul.domain.CatalogService} objects.
     */
    public ConsulResponse<List<CatalogService>> getService(String serviceName, RequestOptions requestOptions) {
        return getService(serviceName, null, null, requestOptions);
    }

    /**
     * Retrieves a single service for a given datacenter with {@link io.advantageous.consul.domain.option.RequestOptions}.
     *

     * GET /v1/catalog/service/{service}?dc={datacenter}
     *
     * @param serviceName    service name
     * @param datacenter     datacenter
     * @param tag            tag
     * @param requestOptions The Query Options to use.
     * @return A {@link io.advantageous.consul.domain.ConsulResponse} containing
     * {@link io.advantageous.consul.domain.CatalogService} objects.
     */
    public ConsulResponse<List<CatalogService>> getService(final String serviceName, final String datacenter, final String tag,
                                                           RequestOptions requestOptions) {

        final URI uri = createURI("/service/" + serviceName);

        final HttpRequestBuilder httpRequestBuilder = RequestUtils
                .getHttpRequestBuilder(datacenter, tag, requestOptions, "/");

        HTTP.Response httpResponse = HTTP.getResponse(uri.toString() + "?" + httpRequestBuilder.paramString());

        if (httpResponse.code() != 200) {
            die("Unable to retrieve the service", uri, httpResponse.code(), httpResponse.body());
        }

        return RequestUtils.consulResponseList(CatalogService.class, httpResponse);

    }

    /**
     * Retrieves a single node.
     *

     * GET /v1/catalog/node/{node}
     *
     * @param node node
     * @return A list of matching {@link io.advantageous.consul.domain.CatalogService} objects.
     */
    public ConsulResponse<CatalogNode> getNode(final String node) {
        return getNode(node, null, null, RequestOptions.BLANK);
    }

    /**
     * Retrieves a single node for a given datacenter.
     *

     * GET /v1/catalog/node/{node}?dc={datacenter}
     *
     * @param node       node
     * @param datacenter dc
     * @param tag        tag
     * @return A list of matching {@link io.advantageous.consul.domain.CatalogService} objects.
     */
    public ConsulResponse<CatalogNode> getNode(final String node, final String datacenter, final String tag
    ) {
        return getNode(node, datacenter, tag, RequestOptions.BLANK);
    }

    /**
     * Retrieves a single node with {@link io.advantageous.consul.domain.option.RequestOptions}.
     *

     * GET /v1/catalog/node/{node}
     *
     * @param node           node
     * @param requestOptions The Query Options to use.
     * @return A list of matching {@link io.advantageous.consul.domain.CatalogService} objects.
     */
    public ConsulResponse<CatalogNode> getNode(final String node,
                                               final RequestOptions requestOptions) {
        return getNode(node, null, null, requestOptions);
    }

    /**
     * Retrieves a single node for a given datacenter with {@link io.advantageous.consul.domain.option.RequestOptions}.
     *

     * GET /v1/catalog/node/{node}?dc={datacenter}
     *
     * @param node           node
     * @param datacenter     dc
     * @param tag            tag
     * @param requestOptions The Query Options to use.
     * @return A list of matching {@link io.advantageous.consul.domain.CatalogService} objects.
     */
    public ConsulResponse<CatalogNode> getNode(final String node,
                                               final String datacenter,
                                               final String tag,
                                               final RequestOptions requestOptions) {

        final URI uri = createURI("/node/" + node);

        final HttpRequestBuilder httpRequestBuilder = RequestUtils
                .getHttpRequestBuilder(datacenter, tag, requestOptions, "");

        final HTTP.Response httpResponse = HTTP.getResponse(uri + "?" + httpRequestBuilder.paramString());
        if (httpResponse.code() != 200) {
            die("Unable to retrieve the node", uri, httpResponse.code(), httpResponse.body());
        }
        return RequestUtils.consulResponse(CatalogNode.class, httpResponse);
    }

    public ConsulResponse<List<Node>> getNodes(final String datacenter,
                                               final String tag,
                                               final RequestOptions requestOptions) {

        final HttpRequestBuilder httpRequestBuilder = RequestUtils
                .getHttpRequestBuilder(datacenter, tag, requestOptions, "");

        final URI uri = createURI("/nodes");

        final HTTP.Response httpResponse = HTTP.getResponse(uri + "?" + httpRequestBuilder.paramString());

        if (httpResponse.code() != 200) {
            die("Unable to retrieve the nodes", uri, httpResponse.code(), httpResponse.body());
        }
        return RequestUtils.consulResponseList(Node.class, httpResponse);

    }

    public ConsulResponse<List<Node>> getNodes(final String datacenter) {
        return getNodes(datacenter, null, RequestOptions.BLANK);
    }

    public ConsulResponse<List<Node>> getNodes(@SuppressWarnings("SameParameterValue") final String datacenter, @SuppressWarnings("SameParameterValue") final String tag) {
        return getNodes(datacenter, tag, RequestOptions.BLANK);
    }

    public ConsulResponse<List<Node>> getNodes(RequestOptions requestOptions) {
        return getNodes(null, null, RequestOptions.BLANK);
    }

    public ConsulResponse<List<Node>> getNodes() {
        return getNodes(null, null, RequestOptions.BLANK);
    }
}


package io.advantageous.qbit.proxy;

import io.advantageous.qbit.http.client.HttpClientBuilder;
import io.advantageous.qbit.http.request.HttpRequest;
import io.advantageous.qbit.http.request.HttpRequestBuilder;
import io.advantageous.qbit.reactive.Reactor;
import io.advantageous.qbit.reactive.ReactorBuilder;
import io.advantageous.qbit.service.ServiceBuilder;
import io.advantageous.qbit.time.Duration;
import io.advantageous.qbit.util.Timer;

import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Predicate;

/**
 * Used to construct a proxy service to proxy call to a backend.
 */
public class ProxyBuilder {

    /**
     * Reactor used to manage the periodic jobs.
     */
    private Reactor reactor;

    /**
     * Timer used to get the current time in a cost effective manner.
     */
    private Timer timer;

    /**
     * HttpClientBuilder used to construct httpClients to talk to backend services.
     */
    private HttpClientBuilder httpClientBuilder;

    /**
     * Used to intercept calls to do things like populate additional headers.
     * This happens after the incoming request is copied into the HttpRequestBuilder.
     */
    private Consumer<HttpRequestBuilder> beforeSend;

    /**
     * Used if you want to do additional error handling.
     */
    private Consumer<Exception> errorHandler;

    /**
     * Used to determine if this request should be forwarded to the back end.
     * By default there is a predicate that always returns true.
     */
    private Predicate<HttpRequest> httpClientRequestPredicate;

    /**
     * How often we should check to see if the backend connection is healthy.
     */
    private Duration checkClientDuration = Duration.MINUTES.units(10);

    /**
     * Used to construct a ping request to the backend.
     * The ping request will be sent to backend every `checkClientDuration`.
     */
    private HttpRequestBuilder pingBuilder;

    /**
     * Used to determine if we want to track timeouts to backend services.
     */
    private boolean trackTimeOuts;

    /**
     * Sets the backend timeout. Requests that take longer than this are aborted.
     */
    private Duration timeOutInterval = Duration.SECONDS.units(180);

    /**
     * Used to construct a proxy service to the ProxyServiceImpl
     */
    private ServiceBuilder serviceBuilder;

    public static ProxyBuilder proxyBuilder() {
        return new ProxyBuilder();
    }

    public ServiceBuilder getServiceBuilder() {

        if (serviceBuilder == null) {
            serviceBuilder = ServiceBuilder.serviceBuilder();
            return serviceBuilder;
        }

        return serviceBuilder.copy();
    }

    public Reactor getReactor() {
        if (reactor == null) {
            reactor = ReactorBuilder.reactorBuilder().build();
        }
        return reactor;
    }

    public ProxyBuilder setReactor(Reactor reactor) {
        this.reactor = reactor;
        return this;
    }

    public Timer getTimer() {
        if (timer == null) {
            timer = Timer.timer();
        }
        return timer;
    }

    public ProxyBuilder setTimer(Timer timer) {
        this.timer = timer;
        return this;
    }

    public HttpClientBuilder getHttpClientBuilder() {
        if (httpClientBuilder == null) {
            httpClientBuilder = HttpClientBuilder.httpClientBuilder().setPipeline(false)
                    .setKeepAlive(false).setPoolSize(100);
        }
        return httpClientBuilder;
    }

    public ProxyBuilder setHttpClientBuilder(HttpClientBuilder httpClientBuilder) {
        this.httpClientBuilder = httpClientBuilder;
        return this;
    }

    public Consumer<HttpRequestBuilder> getBeforeSend() {
        if (beforeSend == null) {
            beforeSend = httpRequestBuilder -> {

            };
        }
        return beforeSend;
    }

    public ProxyBuilder setBeforeSend(Consumer<HttpRequestBuilder> beforeSend) {
        this.beforeSend = beforeSend;
        return this;
    }

    public Consumer<Exception> getErrorHandler() {
        if (errorHandler == null) {
            errorHandler = e -> {
            };
        }
        return errorHandler;
    }

    public ProxyBuilder setErrorHandler(Consumer<Exception> errorHandler) {
        this.errorHandler = errorHandler;
        return this;
    }

    public Predicate<HttpRequest> getHttpClientRequestPredicate() {
        if (httpClientRequestPredicate == null) {
            httpClientRequestPredicate = request -> true;
        }
        return httpClientRequestPredicate;
    }

    public ProxyBuilder setHttpClientRequestPredicate(Predicate<HttpRequest> httpClientRequestPredicate) {
        this.httpClientRequestPredicate = httpClientRequestPredicate;
        return this;
    }

    public Duration getCheckClientDuration() {
        return checkClientDuration;
    }

    public ProxyBuilder setCheckClientDuration(Duration checkClientDuration) {
        this.checkClientDuration = checkClientDuration;
        return this;
    }

    public HttpRequestBuilder getPingBuilder() {
        return pingBuilder;
    }

    public ProxyBuilder setPingBuilder(HttpRequestBuilder pingBuilder) {
        this.pingBuilder = pingBuilder;
        return this;
    }

    public boolean isTrackTimeOuts() {
        return trackTimeOuts;
    }

    public ProxyBuilder setTrackTimeOuts(boolean trackTimeOuts) {
        this.trackTimeOuts = trackTimeOuts;
        return this;
    }

    public Duration getTimeOutInterval() {
        return timeOutInterval;
    }

    public ProxyBuilder setTimeOutInterval(Duration timeOutInterval) {
        this.timeOutInterval = timeOutInterval;
        return this;
    }

    /**
     * Build the impl.
     *
     * @return returns an instance of the impl.
     */
    public ProxyService build() {
        return new ProxyServiceImpl(getReactor(), getTimer(), getHttpClientBuilder(), getBeforeSend(),
                getErrorHandler(), getHttpClientRequestPredicate(), getCheckClientDuration(),
                pingBuilder == null ? Optional.<HttpRequestBuilder>empty() : Optional.of(pingBuilder),
                isTrackTimeOuts(), getTimeOutInterval());
    }

    /**
     * Builds a proxy queue service to the impl.
     *
     * @return proxy queue service interface to impl.
     */
    public ProxyService buildProxy() {
        return getServiceBuilder().setServiceObject(build()).buildAndStart().createProxyWithAutoFlush(ProxyService.class,
                Duration.HUNDRED_MILLIS);
    }
}


/*
 * Copyright (c) 2015. Rick Hightower, Geoff Chandler
 *
 * 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.
 *
 * QBit - The Microservice lib for Java : JSON, WebSocket, REST. Be The Web!
 */

package io.advantageous.qbit.server;

import io.advantageous.boon.core.Lists;
import io.advantageous.qbit.GlobalConstants;
import io.advantageous.qbit.QBit;
import io.advantageous.qbit.http.server.websocket.WebSocketMessage;
import io.advantageous.qbit.http.websocket.WebSocketSender;
import io.advantageous.qbit.message.MethodCall;
import io.advantageous.qbit.message.Request;
import io.advantageous.qbit.message.Response;
import io.advantageous.qbit.message.impl.MethodCallImpl;
import io.advantageous.qbit.message.impl.ResponseImpl;
import io.advantageous.qbit.queue.SendQueue;
import io.advantageous.qbit.service.ServiceBundle;
import io.advantageous.qbit.spi.ProtocolEncoder;
import io.advantageous.qbit.spi.ProtocolParser;
import io.advantageous.qbit.util.Timer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * created by rhightower on 1/27/15.
 */
public class WebSocketServiceServerHandler {

    protected final int protocolBatchSize;
    protected final long flushResponseInterval;

    protected final ThreadLocal<ProtocolEncoder> encoderRef = new ThreadLocal<ProtocolEncoder>() {
        @Override
        protected ProtocolEncoder initialValue() {
            return QBit.factory().createEncoder();
        }
    };
    protected final ThreadLocal<ProtocolParser> parserRef = new ThreadLocal<ProtocolParser>() {
        @Override
        protected ProtocolParser initialValue() {

            return QBit.factory().createProtocolParser();
        }
    };

    private final Logger logger = LoggerFactory.getLogger(WebSocketServiceServerHandler.class);
    private final boolean debug = GlobalConstants.DEBUG || logger.isDebugEnabled();
    private final SendQueue<MethodCall<Object>> methodCallSendQueue;
    private final Map<String, WebSocketDelegate> webSocketDelegateMap = new ConcurrentHashMap<>(100);
    private final ExecutorService protocolParserThreadPool;
    private final ExecutorService protocolEncoderThreadPool;
    protected volatile long flushResponseLastTimestamp = 0;

    public WebSocketServiceServerHandler(
            final int protocolBatchSize,
            final ServiceBundle serviceBundle,
            final int parseWorkersCount,
            final int encodeWorkersCount,
            final long flushResponseInterval) {
        this.protocolBatchSize = protocolBatchSize;
        this.flushResponseInterval = flushResponseInterval;

        this.methodCallSendQueue = serviceBundle.methodSendQueue();

        final AtomicInteger threadId = new AtomicInteger();
        protocolParserThreadPool = Executors.newFixedThreadPool(parseWorkersCount, r -> {
            Thread thread = new Thread(r);
            thread.setName("WebSocketProtocolParser-" + threadId.incrementAndGet());
            thread.setDaemon(true);
            return thread;
        });

        protocolEncoderThreadPool = Executors.newFixedThreadPool(encodeWorkersCount, r -> {
            Thread thread = new Thread(r);
            thread.setName("WebSocketProtocolEncoder-" + threadId.incrementAndGet());
            thread.setDaemon(true);
            return thread;
        });

    }

    public void handleWebSocketClose(final WebSocketMessage webSocketMessage) {
        webSocketDelegateMap.remove(webSocketMessage.getRemoteAddress());
    }

    public void webSocketQueueIdle(Void v) {
        methodCallSendQueue.flushSends();
    }

    /**
     * All WebSocket calls come through here.
     *
     * @param webSocketMessage websocket message
     */
    public void handleWebSocketCall(final WebSocketMessage webSocketMessage) {

        if (debug) logger.debug("WebSocket message: " + webSocketMessage);

        WebSocketDelegate webSocketDelegate = webSocketDelegateMap.get(webSocketMessage.getRemoteAddress());

        if (webSocketDelegate == null) {
            webSocketDelegate = new WebSocketDelegate(protocolBatchSize, webSocketMessage);
            webSocketDelegateMap.put(webSocketMessage.getRemoteAddress(), webSocketDelegate);
        }

        protocolParserThreadPool.execute(() -> {

            try {
                final List<MethodCall<Object>> methodCallListToBeParsedFromBody =
                        createMethodCallListToBeParsedFromBody(webSocketMessage.getRemoteAddress(),
                                webSocketMessage.getMessage(), webSocketMessage);

                if (methodCallListToBeParsedFromBody.size() > 0) {
                    methodCallSendQueue.sendBatch(methodCallListToBeParsedFromBody);
                }
            } catch (Exception ex) {
                logger.error("", ex);
            }

        });

    }

    public void handleResponseFromServiceBundleToWebSocketSender(
            final Response<Object> response, final WebSocketMessage originatingRequest) {

        //noinspection UnnecessaryLocalVariable
        @SuppressWarnings("UnnecessaryLocalVariable") final WebSocketMessage webSocketMessage = originatingRequest;
        try {

            //uts("handle WebSocket response", webSocketMessage.getRemoteAddress());
            final WebSocketDelegate webSocketDelegate = this.webSocketDelegateMap.get(webSocketMessage.getRemoteAddress());

            if (webSocketDelegate != null) {
                webSocketDelegate.send(response);
            }
        } catch (Exception ex) {
            logger.warn("websocket unable to sendText response", ex);
        }
    }

    public List<MethodCall<Object>> createMethodCallListToBeParsedFromBody(
            final String addressPrefix,
            final Object body,
            final Request<Object> originatingRequest) {

        List<MethodCall<Object>> methodCalls;

        if (body != null) {

            methodCalls = parserRef.get().parseMethodCalls(addressPrefix, body.toString());

        } else {
            methodCalls = Collections.emptyList();

        }

        if (methodCalls == null || methodCalls.size() == 0) {

            if (originatingRequest instanceof WebSocketMessage) {
                WebSocketMessage webSocketMessage = ((WebSocketMessage) originatingRequest);

                final Response<Object> response = ResponseImpl.response(-1, Timer.timer().now(), "SYSTEM", "ERROR",
                        "CAN'T HANDLE CALL", originatingRequest, true);
                final WebSocketSender sender = webSocketMessage.getSender();
                sender.sendText(encoderRef.get().encodeResponses("SYSTEM", Lists.list(response)));

            }

            return Collections.emptyList();
        }

        //noinspection Convert2streamapi
        for (MethodCall<Object> methodCall : methodCalls) {
            if (methodCall instanceof MethodCallImpl) {

                MethodCallImpl method = ((MethodCallImpl) methodCall);

                method.originatingRequest(originatingRequest);
            }
        }

        return methodCalls;

    }

    public void checkResponseBatchSend() {

        final long now = Timer.timer().now();

        long duration = now - flushResponseLastTimestamp;

        if (duration > flushResponseInterval) {
            flushResponseLastTimestamp = now;

            final Collection<WebSocketDelegate> values = this.webSocketDelegateMap.values();
            for (WebSocketDelegate ws : values) {

                long dur = now - ws.lastSend;

                if (dur > flushResponseInterval) {
                    ws.buildAndSendResponse(null, now);
                }
            }
        }

    }

    class WebSocketDelegate {
        final int requestBatchSize;

        final BlockingQueue<Response<Object>> outputMessages;

        final WebSocketMessage serverWebSocket;

        volatile long lastSend;

        private WebSocketDelegate(int requestBatchSize, WebSocketMessage serverWebSocket) {
            this.requestBatchSize = requestBatchSize;
            outputMessages = new ArrayBlockingQueue<>(requestBatchSize);
            this.serverWebSocket = serverWebSocket;
        }

        public void send(final Response<Object> message) {

            if (!outputMessages.offer(message)) {
                buildAndSendResponse(message, Timer.timer().now());
            }
        }

        private void buildAndSendResponse(final Response<Object> response, long now) {

            if (outputMessages.size() == 0 && response == null) {
                return;
            }

            String returnAddress = response != null ? response.returnAddress() : null;

            List<Response<Object>> messages = new ArrayList<>(outputMessages.size() + 1);

            Response<Object> currentMessage = outputMessages.poll();

            while (currentMessage != null) {

                returnAddress = currentMessage.returnAddress();
                messages.add(currentMessage);
                currentMessage = outputMessages.poll();

            }

            if (response != null) {
                messages.add(response);
            }

            if (returnAddress != null) {
                final String returnAddr = returnAddress;
                protocolEncoderThreadPool.execute(() -> {
                    @SuppressWarnings("unchecked") final String textMessage = encoderRef.get().encodeResponses(returnAddr, messages);
                    serverWebSocket.getSender().sendText(textMessage);
                });
            }

            lastSend = now;

        }

    }

}


package io.advantageous.qbit.proxy;

import io.advantageous.qbit.http.server.HttpServer;
import io.advantageous.qbit.service.Startable;
import io.advantageous.qbit.service.Stoppable;

import static io.advantageous.qbit.service.ServiceProxyUtils.flushServiceProxy;

/**
 * Marries a ProxyService to an HTTP endpoint and is a good example of how to use a ProxyService.
 */
public class HttpProxy implements Startable, Stoppable {

    /**
     * Http Server we are using this to listen to requests.
     */
    private final HttpServer server;

    /**
     * Proxy service used to forward requests to a backend.
     */
    private final ProxyService proxyService;

    /**
     * Constructor for a new HttpProxy.
     *
     * @param server       server
     * @param proxyService proxy service
     */
    public HttpProxy(final HttpServer server, ProxyService proxyService) {
        this.server = server;
        this.proxyService = proxyService;
    }

    /**
     * Start this.
     */
    public void start() {
        server.setHttpRequestConsumer(proxyService::handleRequest);
        server.setHttpRequestsIdleConsumer(aVoid -> flushServiceProxy(proxyService));
        server.startServer();
    }

    /**
     * Stop this.
     */
    @Override
    public void stop() {
        server.stop();
    }
}