package io.advantageous.qbit.service.discovery;

import io.advantageous.boon.core.Lists;
import io.advantageous.boon.core.Sys;
import io.advantageous.qbit.service.health.HealthStatus;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.*;

import static io.advantageous.qbit.service.discovery.ServiceDiscovery.uniqueString;

/**
 * Service Definition
 * Contains a healthStatus, unique id, name, host, port and a timeToLive in seconds.
 * This describes all parts of a service as far as something like a ServiceDiscovery system like
 * [Consul](https://consul.io/) is concerned.
 *

 * The `timeToLive` field is for ttl checkins if the underlying system supports it.
 *

 * The `HealthStatus` represents the current state of this system as returned from the remote
 * service discovery system.
 *

 * created by rhightower on 3/23/15.
 */
public class EndpointDefinition {

    /**
     * Current health status.
     */
    private final HealthStatus healthStatus;

    /**
     * Unique id of the system.
     */
    private final String id;

    /**
     * Name of the service, i.e., EventBus, StatsEngine, etc.
     */
    private final String name;

    /**
     * Host name.
     */
    private final String host;

    /**
     * Port of the service.
     */
    private final int port;

    /**
     * Tags of the service.
     */
    private final List<String> tags;

    /**
     * Time to live: how long until this service has to check in with the remote service discovery
     * system if applicable. Whether this is used or needed depends on the underlying service discovery system.
     */
    private final long timeToLive;

    /**
     * Create a new one with default TTL of 20 seconds with tags.
     *
     * @param healthStatus healthStatus
     * @param id           id
     * @param name         name
     * @param host         post
     * @param port         port
     * @param tags         tags
     */
    public EndpointDefinition(
            final HealthStatus healthStatus,
            final String id,
            final String name,
            final String host,
            final int port,
            final long timeToLive,
            final List<String> tags) {
        this.healthStatus = healthStatus;
        this.id = id;
        this.name = name;
        this.host = host;
        this.port = port;
        this.timeToLive = timeToLive;
        this.tags = tags;
    }

    /**
     * Create a new one with default TTL of 20 seconds.
     *
     * @param healthStatus healthStatus
     * @param id           id
     * @param name         name
     * @param host         post
     * @param port         port
     * @param tags         tags
     */
    public EndpointDefinition(
            final HealthStatus healthStatus,
            final String id,
            final String name,
            final String host,
            final int port,
            final List<String> tags) {
        this(healthStatus, id, name, host, port, Sys.sysProp(EndpointDefinition.class.getName() + ".timeToLive", 20L), tags);
    }

    /**
     * Create a new one with default TTL of 20 seconds.
     *
     * @param healthStatus healthStatus
     * @param id           id
     * @param name         name
     * @param host         post
     * @param port         port
     */
    public EndpointDefinition(
            final HealthStatus healthStatus,
            final String id,
            final String name,
            final String host,
            final int port) {
        this(healthStatus, id, name, host, port, Sys.sysProp(EndpointDefinition.class.getName() + ".timeToLive", 20L), null);
    }

    /**
     * Create a new one with default TTL of 20 seconds.
     *
     * @param name name
     * @param host post
     * @param port port
     */
    public EndpointDefinition(
            final String name,
            final String host,
            final int port) {
        this(HealthStatus.PASS, name + "-" + port + "-" + host.replace('.', '-'), name, host, port);
    }

    /**
     * Create a new one with default TTL of 20 seconds.
     *
     * @param healthStatus healthStatus
     * @param id           id
     * @param name         name
     * @param host         post
     * @param port         port
     * @param timeToLive   time to live
     */
    public EndpointDefinition(
            final HealthStatus healthStatus,
            final String id,
            final String name,
            final String host,
            final int port,
            final long timeToLive) {
        this(healthStatus, id, name, host, port, timeToLive, null);
    }

    /**
     * Find host
     *
     * @return hostname
     */
    static String findHostName() {
        try {
            return InetAddress.getLocalHost().getHostName();
        } catch (UnknownHostException e) {
            throw new IllegalStateException("unable to find host name");
        }
    }

    /**
     * Creates a list of service definitions.
     *
     * @param endpointDefinitions vararg array of service definitions
     * @return list of service definitions
     */
    public static List<EndpointDefinition> serviceDefinitions(final EndpointDefinition... endpointDefinitions) {
        return Lists.list(endpointDefinitions);
    }

    /**
     * Creates a EndpointDefinition for a service, i.e., a serviceDefinition.
     *
     * @param name name
     * @return serviceDefinition
     */
    public static EndpointDefinition serviceDefinition(final String name) {

        return new EndpointDefinition(HealthStatus.PASS,
                name + "-" + uniqueString(0), name, findHostName(), 0);
    }

    /**
     * Creates a EndpointDefinition for a service, i.e., a serviceDefinition.
     *
     * @param name service name
     * @param port port
     * @return serviceDefinition
     */
    public static EndpointDefinition serviceDefinition(final String name, int port) {

        return new EndpointDefinition(HealthStatus.PASS,
                name + "-" + uniqueString(port), name, findHostName(), 0);
    }

    /**
     * Creates a EndpointDefinition for a service, i.e., a serviceDefinition.
     *
     * @param id   id
     * @param name name
     * @param host host
     * @param port port
     * @return EndpointDefinition
     */
    public static EndpointDefinition serviceDefinition(
            final String id,
            final String name,
            final String host,
            final int port) {

        return new EndpointDefinition(HealthStatus.PASS,
                id, name, host, port);
    }

    /**
     * Creates a EndpointDefinition for a service, i.e., a serviceDefinition.
     *
     * @param id   id
     * @param name name
     * @param host host
     * @param port port
     * @param tags tags
     * @return EndpointDefinition
     */
    public static EndpointDefinition serviceDefinition(
            final String id,
            final String name,
            final String host,
            final int port,
            List<String> tags) {

        return new EndpointDefinition(HealthStatus.PASS,
                id, name, host, port, tags);
    }

    /**
     * Creates a EndpointDefinition for a service, i.e., a serviceDefinition.
     *
     * @param name name
     * @param host host
     * @param port port
     * @return serviceDefinition
     */
    public static EndpointDefinition serviceDefinition(
            final String name,
            final String host,
            final int port) {

        return new EndpointDefinition(HealthStatus.PASS,
                name + "-" + uniqueString(port), name, host, port);
    }

    /**
     * Creates a EndpointDefinition for a service, i.e., a serviceDefinition.
     *
     * @param name name
     * @param host host
     * @return serviceDefinition
     */
    public static EndpointDefinition serviceDefinition(
            final String name,
            final String host
    ) {

        return new EndpointDefinition(HealthStatus.PASS,
                name + "-" + uniqueString(0), name, host, 0);
    }

    /**
     * Creates a EndpointDefinition for a service, i.e., a serviceDefinition.
     *
     * @param id   id
     * @param name name
     * @param host host
     * @return EndpointDefinition
     */
    public static EndpointDefinition serviceDefinitionWithId(
            final String name,
            final String host,
            final String id) {

        return new EndpointDefinition(HealthStatus.PASS,
                id, name, host, 0);
    }

    public HealthStatus getHealthStatus() {
        return healthStatus;
    }

    public String getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public String getHost() {
        return host;
    }

    public int getPort() {
        return port;
    }

    public List<String> getTags() {
        return tags;
    }

    @SuppressWarnings("SimplifiableIfStatement")
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof EndpointDefinition)) return false;

        EndpointDefinition that = (EndpointDefinition) o;

        if (port != that.port) return false;
        if (healthStatus != that.healthStatus) return false;
        if (host != null ? !host.equals(that.host) : that.host != null) return false;
        if (id != null ? !id.equals(that.id) : that.id != null) return false;
        return !(name != null ? !name.equals(that.name) : that.name != null);

    }

    @Override
    public int hashCode() {
        int result = healthStatus != null ? healthStatus.hashCode() : 0;
        result = 31 * result + (id != null ? id.hashCode() : 0);
        result = 31 * result + (name != null ? name.hashCode() : 0);
        result = 31 * result + (host != null ? host.hashCode() : 0);
        result = 31 * result + port;
        return result;
    }

    @Override
    public String toString() {
        return "ServiceDefinition{" +
                "status=" + healthStatus +
                ", id='" + id + '\'' +
                ", name='" + name + '\'' +
                ", host='" + host + '\'' +
                ", port=" + port +
                ", tags=" + tags +
                '}';
    }

    public long getTimeToLive() {
        return timeToLive;
    }

}


package io.advantageous.qbit.http;

import io.advantageous.qbit.http.request.HttpRequest;
import io.advantageous.qbit.message.Request;
import io.advantageous.qbit.service.RequestContext;

import java.util.Optional;

/**
 * Holds current information about the HttpRequest.
 */
public class HttpContext extends RequestContext {

    /**
     * Grab the current http request.
     *
     * @return Optional http request.
     */
    public Optional<HttpRequest> getHttpRequest() {
        final Optional<Request<Object>> request = this.getRequest();
        if (request.isPresent()) {
            return findHttpRequest(request.get());
        } else {
            return Optional.empty();
        }
    }

    private Optional<HttpRequest> findHttpRequest(Request<Object> request) {

        if (request.originatingRequest() instanceof HttpRequest) {
            return Optional.of(((HttpRequest) request.originatingRequest()));
        } else if (request.originatingRequest() != null) {
            return findHttpRequest(request.originatingRequest());
        } else {
            return Optional.empty();
        }
    }

}


package io.advantageous.qbit.events.impl;

import io.advantageous.boon.core.Str;
import io.advantageous.qbit.GlobalConstants;
import io.advantageous.qbit.client.ClientProxy;
import io.advantageous.qbit.client.RemoteTCPClientProxy;
import io.advantageous.qbit.events.spi.EventConnector;
import io.advantageous.qbit.events.spi.EventTransferObject;
import io.advantageous.qbit.service.ServiceProxyUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * Provides a collection of event connectors and makes them look like a single event connector.
 * It will also work with the RemoteTCPClientProxy to close connectors that are removed.
 * When you remove a remote connector that implements RemoteTCPClientProxy,
 * note it will be closed if removed from the EventConnectorHub.
 */
public class EventConnectorHub implements EventConnector, Iterable<EventConnector> {

    private final List<EventConnector> eventConnectors;
    private final Logger logger = LoggerFactory.getLogger(EventConnectorHub.class);
    private final boolean debug = GlobalConstants.DEBUG || logger.isDebugEnabled();

    public EventConnectorHub(final List<EventConnector> eventConnectors) {

        this.eventConnectors = new CopyOnWriteArrayList<>(eventConnectors);
    }

    public EventConnectorHub() {

        this.eventConnectors = new CopyOnWriteArrayList<>();
    }

    /**
     * Add an event connector
     *
     * @param eventConnector eventConnector
     */
    public void add(final EventConnector eventConnector) {
        this.eventConnectors.add(eventConnector);
    }

    /**
     * Add a bunch of event connectors.
     *
     * @param eventConnectors eventConnectors
     */
    public void addAll(final EventConnector... eventConnectors) {
        Collections.addAll(this.eventConnectors, eventConnectors);
    }

    /**
     * Remove an event connector
     *
     * @param eventConnector eventConnector
     */
    public void remove(final EventConnector eventConnector) {
        if (eventConnector != null) {
            try {

                if (eventConnector instanceof RemoteTCPClientProxy) {
                    final RemoteTCPClientProxy remoteTCPClientProxy = (RemoteTCPClientProxy) eventConnector;

                    logger.info(Str.sputs("Removing event connector host ",
                            remoteTCPClientProxy.host(), " port ", remoteTCPClientProxy.port(),
                            "connected ", remoteTCPClientProxy.connected()));
                    remoteTCPClientProxy.silentClose();
                }
                this.eventConnectors.remove(eventConnector);
            } catch (Exception ex) {
                logger.error("Unable to remove event connector", ex);
            }
        }
    }

    /**
     * Forwards en event to another event system.
     *
     * @param event event
     */
    @Override
    public void forwardEvent(final EventTransferObject<Object> event) {

        if (debug) logger.debug("forwardEvent " + event.channel() + " size " + eventConnectors.size());

        for (int index = 0; index < eventConnectors.size(); index++) {

            EventConnector eventConnector = null;
            try {
                eventConnector = eventConnectors.get(index);
                eventConnector.forwardEvent(event);
            } catch (Exception ex) {
                logger.error("problem sending event to event connector", ex);

                if (eventConnector instanceof RemoteTCPClientProxy) {
                    if (!((RemoteTCPClientProxy) eventConnector).connected()) {
                        eventConnectors.remove(eventConnector);
                    }
                }
            }
        }

        if (debug) logger.debug("forwardEvent done " + event.channel());
    }

    /**
     * Flushes the pool used to optimize flushing of IO operations.
     */
    @Override
    public void flush() {

        for (int index = 0; index < eventConnectors.size(); index++) {

            EventConnector eventConnector = null;
            try {
                eventConnector = eventConnectors.get(index);
                if (eventConnector instanceof ClientProxy) {
                    ServiceProxyUtils.flushServiceProxy(eventConnector);
                } else {
                    eventConnector.flush();
                }
            } catch (Exception ex) {
                logger.debug("problem sending event to event connector", ex);
                if (eventConnector instanceof RemoteTCPClientProxy) {
                    if (!((RemoteTCPClientProxy) eventConnector).connected()) {

                        eventConnectors.remove(eventConnector);
                    }
                }
            }
        }
    }

    @Override
    public Iterator<EventConnector> iterator() {
        return this.eventConnectors.listIterator();
    }

    public ListIterator<EventConnector> listIterator() {
        return this.eventConnectors.listIterator();
    }

    public int size() {
        return eventConnectors.size();
    }
}


package io.advantageous.qbit.admin;

import io.advantageous.boon.core.IO;
import io.advantageous.boon.core.Sys;
import io.advantageous.boon.json.JsonFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;

/**
 * Hold service config for a give service.
 * This is all of the stuff that will show up in Swagger file.
 * It is all informational, and will allow for generation of swagger clients.
 */
public class MicroserviceConfig {

    /**
     * Holds the key.
     */
    @SuppressWarnings("WeakerAccess")
    public static final String CONTEXT = "qbit.service.config.";
    /**
     * Contains the resource location of the configuration.
     */
    @SuppressWarnings("WeakerAccess")
    public static final String resourceLocation =
            Sys.sysProp(CONTEXT + "file", "/opt/qbit/conf/service.json");
    public static final String resourceLocationEnv = System.getenv("QBIT_CONF_FILE");
    /**
     * Title
     */
    private final String title;
    /**
     * Description
     */
    private final String description;
    /**
     * publicHost this is where the default API from swagger will make calls.
     */
    private final String publicHost;
    /**
     * publicPort this is the port where the default API from swagger will make calls.
     */
    private final int publicPort;
    /**
     * this is the port where the default API from swagger will make calls.
     */
    private final int port;
    /**
     * Contact name. This is Stephane's home phone number.
     */
    private final String contactName;
    /**
     * Contact email. This is Stephane's home email address.
     */
    private final String contactEmail;
    /**
     * Contact email. This is Stephane's website.
     */
    private final String contactURL;
    /**
     * Version of the API.
     */
    private final String version;
    /**
     * Root URI, this should be /v1, /v2, etc.
     */
    private final String rootURI;
    /**
     * License Name of the microservice.
     */
    private String licenseName;
    /**
     * License URL of the microservice.
     */
    private String licenseURL;
    /**
     * StatsD host where we put things.
     */
    private String statsDHost;
    /**
     * StatsD port. -1 means use default StatsD port.
     */
    private int statsDPort = -1;
    private int sampleStatFlushRate = 5;
    private int checkTimingEveryXCalls = 100;
    /**
     * Enables local stats collection.
     */
    private boolean enableLocalStats = true;
    /**
     * Enables sending stats to stats D.
     */
    private boolean enableStatsD = false;
    /**
     * Enables local health stats collection.
     */
    private boolean enableLocalHealth = true;
    /**
     * Enables the collection of stats.
     */
    private boolean enableStats = true;
    private boolean statsD;

    /**
     * @param title        title
     * @param description  description
     * @param publicHost   publicHost
     * @param publicPort   publicPort
     * @param port         port
     * @param contactName  contactName
     * @param contactEmail contactEmail
     * @param version      version
     * @param rootURI      rootURI
     * @param contactURL   contact URL
     */
    public MicroserviceConfig(String title, String description, String publicHost, int publicPort,
                              int port, String contactName,
                              String contactEmail, String version, String rootURI, String contactURL) {
        this.title = title;
        this.description = description;
        this.publicHost = publicHost;
        this.publicPort = publicPort;
        this.port = port;
        this.contactName = contactName;
        this.contactEmail = contactEmail;
        this.version = version;
        this.rootURI = rootURI;
        this.contactURL = contactURL;
    }

    /**
     * Reads the readConfig file, which can be a classpath or file system resource.
     *
     * @param serviceName the name of the service to load
     * @return service config
     */
    public static MicroserviceConfig readConfig(final String serviceName) {

        final Logger logger = LoggerFactory.getLogger(MicroserviceConfig.class);
        if (new File(resourceLocation).exists()) {
            final String json = IO.read(resourceLocation);

            return JsonFactory.fromJson(json, MicroserviceConfig.class);

        } else if (resourceLocationEnv != null && new File(resourceLocationEnv).exists()) {

            final String json = IO.read(resourceLocationEnv);
            return JsonFactory.fromJson(json, MicroserviceConfig.class);
        } else {
            logger.info("Reading config from classpath as it is not found on file system");

            final String qbitEnv = System.getenv("QBIT_ENV");

            final String resourceLocationOnClasspath =
                    qbitEnv != null && !qbitEnv.isEmpty() ? serviceName + "_" + qbitEnv + ".json" :
                            serviceName + ".json";
            final String json = IO.read(Thread.currentThread().getContextClassLoader()
                    .getResourceAsStream(resourceLocationOnClasspath));
            return JsonFactory.fromJson(json, MicroserviceConfig.class);
        }
    }

    public String getPublicHost() {
        return publicHost;
    }

    public int getPublicPort() {
        return publicPort;
    }

    public String getContactName() {
        return contactName;
    }

    public String getTitle() {
        return title;
    }

    public String getContactEmail() {
        return contactEmail;
    }

    public String getVersion() {
        return version;
    }

    public String getRootURI() {
        return rootURI;
    }

    public String getDescription() {
        return description;
    }

    public String getContactURL() {
        return contactURL;
    }

    @Override
    public String toString() {
        return "MicroserviceConfig{" +
                "title='" + title + '\'' +
                ", description='" + description + '\'' +
                ", publicHost='" + publicHost + '\'' +
                ", publicPort=" + publicPort +
                ", contactName='" + contactName + '\'' +
                ", contactEmail='" + contactEmail + '\'' +
                ", contactURL='" + contactURL + '\'' +
                ", version='" + version + '\'' +
                ", rootURI='" + rootURI + '\'' +
                '}';
    }

    public int getPort() {
        return port == 0 ? 8080 : port;
    }

    public String getLicenseName() {
        return licenseName;
    }

    public void setLicenseName(String licenseName) {
        this.licenseName = licenseName;
    }

    public String getLicenseURL() {
        return licenseURL;
    }

    public void setLicenseURL(String licenseURL) {
        this.licenseURL = licenseURL;
    }

    public boolean isStatsD() {
        return statsD;
    }

    public void setStatsD(boolean statsD) {
        this.statsD = statsD;
    }

    public String getStatsDHost() {
        return statsDHost;
    }

    public void setStatsDHost(String statsDHost) {
        this.statsDHost = statsDHost;
    }

    public int getStatsDPort() {
        return statsDPort <= 0 ? 8125 : statsDPort;
    }

    public void setStatsDPort(int statsDPort) {
        this.statsDPort = statsDPort;
    }

    public int getSampleStatFlushRate() {
        return sampleStatFlushRate;
    }

    public void setSampleStatFlushRate(int sampleStatFlushRate) {
        this.sampleStatFlushRate = sampleStatFlushRate;
    }

    public int getCheckTimingEveryXCalls() {
        return checkTimingEveryXCalls;
    }

    public void setCheckTimingEveryXCalls(int checkTimingEveryXCalls) {
        this.checkTimingEveryXCalls = checkTimingEveryXCalls;
    }

    public boolean isEnableLocalStats() {
        return enableLocalStats;
    }

    public void setEnableLocalStats(boolean enableLocalStats) {
        this.enableLocalStats = enableLocalStats;
    }

    public boolean isEnableStatsD() {
        return enableStatsD;
    }

    public void setEnableStatsD(boolean enableStatsD) {
        this.enableStatsD = enableStatsD;
    }

    public boolean isEnableLocalHealth() {
        return enableLocalHealth;
    }

    public void setEnableLocalHealth(boolean enableLocalHealth) {
        this.enableLocalHealth = enableLocalHealth;
    }

    public boolean isEnableStats() {
        return enableStats;
    }

    public void setEnableStats(boolean enableStats) {
        this.enableStats = enableStats;
    }
}


/*
 * 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.service.impl;

import io.advantageous.qbit.message.MethodCall;
import io.advantageous.qbit.message.Response;
import io.advantageous.qbit.queue.Queue;
import io.advantageous.qbit.reactive.Callback;
import io.advantageous.qbit.util.Timer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Maps incoming call returns to client callback objects.
 */
public class CallbackManagerWithTimeout implements CallbackManager {

    private final String name;
    private final boolean handleTimeouts;
    private final long timeOutMS;
    private final long checkInterval;
    private final Timer timer;
    private final Logger logger = LoggerFactory.getLogger(CallbackManagerWithTimeout.class);
    private final boolean debug = logger.isDebugEnabled();
    /**
     * Maps incoming calls with outgoing handlers (returns, async returns really).
     */
    private final Map<HandlerKey, Callback<Object>> handlers = new ConcurrentHashMap<>();
    private long lastCheckTime;
    private long now;

    public CallbackManagerWithTimeout(final Timer timer, final String name,
                                      boolean handleTimeouts,
                                      long timeOutMS, long checkInterval) {

        this.name = name;
        this.handleTimeouts = handleTimeouts;

        this.timeOutMS = timeOutMS;
        this.checkInterval = checkInterval > 0 ? checkInterval : 5_000;
        this.lastCheckTime = timer.now();
        this.now = lastCheckTime;
        this.timer = timer;
    }

    /**
     * Register a callbackWithTimeout handler
     *
     * @param methodCall method call
     * @param handler    call back handler to register
     */
    private void registerHandlerCallbackForClient(final MethodCall<Object> methodCall,
                                                  final Callback<Object> handler) {

        final HandlerKey handlerKey = new HandlerKey(methodCall.returnAddress(), methodCall.address(),
                methodCall.id(), methodCall.timestamp());

        if (debug) {
            if (handlers.containsKey(handlerKey)) {
                logger.debug("DUPLICATE HANDLERS {}", handlerKey);
            }
        }

        handlers.put(handlerKey, handler);

    }

    @Override
    public void registerCallbacks(final MethodCall<Object> methodCall) {
        registerHandlerCallbackForClient(methodCall, methodCall.callback());
    }

    /**
     * Handles responses coming back from services.
     *
     * @param responseQueue response queue
     */
    @Override
    public void startReturnHandlerProcessor(final Queue<Response<Object>> responseQueue) {

        //noinspection Convert2MethodRef
        responseQueue.startListener(response -> handleResponse(response));
    }

    @Override
    public void handleResponse(final Response<Object> response) {

        final HandlerKey handlerKey = new HandlerKey(
                response.returnAddress(),
                response.address(),
                response.id(),
                response.timestamp());

        final Callback<Object> handler = handlers.remove(handlerKey);

        if (handler == null) {
            if (response.request().hasCallback()) {
                logger.error("Could not find handler for key {}", handlerKey);
            }
            return;
        } else {

            if (debug)
                logger.info("FOUND HANDLER {}", handlerKey);
        }

        if (response.wasErrors()) {

            if (debug) {
                logger.debug("Service threw an exception address {} return address {} message id {} response error {}",
                        response.address(),
                        response.returnAddress(),
                        response.id(),
                        response.body());
            }

            if (response.body() instanceof Throwable) {
                handler.onError(((Throwable) response.body()));
            } else {
                handler.onError(new Exception(response.body().toString()));
            }
        } else {
            handler.accept(response.body());
        }

    }

    @Override
    public void process(long currentTime) {
        if (currentTime != 0) {
            this.now = currentTime;
        } else {
            this.now = timer.now();
        }

        long duration = this.now - lastCheckTime;

        if (duration > checkInterval) {
            lastCheckTime = this.now;
            if (handleTimeouts) {
                checkForTimeOuts(timeOutMS);
            } else {
                if (handlers.size() > 8_000) {
                    if (debug) {
                        logger.debug("Issue with handlers growing too large size {} " +
                                        "service name {}",
                                handlers.size(), this.name);
                    }
                }

                if (handlers.size() > 32_000) {
                    logger.error("Issue with handlers growing very large size {} " +
                                    "service name {}",
                            handlers.size(), this.name);
                    checkForTimeOuts(60_000);
                }

            }
        }

    }

    private void checkForTimeOuts(long timeOutMS) {

        if (debug) {
            logger.debug("checking for timeouts");
        }

        final ArrayList<Map.Entry<HandlerKey, Callback<Object>>> entries = new ArrayList<>(handlers.entrySet());

        for (Map.Entry<HandlerKey, Callback<Object>> entry : entries) {
            long duration = now - entry.getKey().timestamp;
            if (duration > timeOutMS) {

                if (debug) logger.debug("{} Call has timed out duration {} {} {}", name,
                        now - entry.getKey().timestamp,
                        entry.getKey().returnAddress,
                        entry.getKey().messageId,
                        new Date(entry.getKey().timestamp));

                handlers.remove(entry.getKey());
                entry.getValue().onTimeout();
            }
        }

    }

    public int outstandingCallbacksCount() {
        return handlers.size();
    }

}


package io.advantageous.qbit.logging;

import io.advantageous.boon.core.Str;
import io.advantageous.qbit.http.request.HttpRequest;
import io.advantageous.qbit.message.MethodCall;
import io.advantageous.qbit.message.Request;
import io.advantageous.qbit.message.Response;
import io.advantageous.qbit.service.AfterMethodCall;
import io.advantageous.qbit.service.BeforeMethodCall;
import io.advantageous.qbit.util.MultiMap;
import org.slf4j.MDC;

import java.util.Collections;
import java.util.Optional;
import java.util.Set;

/**
 * Provides MDC support for QBit REST services.
 * Intercepts method calls to a service.
 * Looks at the originatingRequest to see if HTTP request is the originating request.
 * If an HTTP request is the originating request then we decorate the Log with
 * MDC fields.
 *

 * [http://logback.qos.ch/manual/mdc.html](Mapped Diagnostic Context)
 *

 * You can specify the headers that you want extracted and placed inside
 * the Mapped Diagnostic Context as well.
 */
public class SetupMdcForHttpRequestInterceptor implements BeforeMethodCall, AfterMethodCall {

    public static final String REQUEST_URI = "requestUri";
    public static final String REQUEST_REMOTE_ADDRESS = "requestRemoteAddress";
    public static final String REQUEST_HTTP_METHOD = "requestHttpMethod";
    public static final String REQUEST_HEADER_PREFIX = "requestHeader.";
    public static final String REQUEST_ID = "requestId";

    /**
     * Holds the headers that we want to extract from the request.
     */
    private final Set<String> headersToAddToLoggingMappingDiagnosticsContext;

    /**
     * Construct a SetupMdcForHttpRequestInterceptor
     *
     * @param headersToAddToLoggingMappingDiagnosticsContext headers to add to the Logging Mapping Diagnostics Context.
     */
    public SetupMdcForHttpRequestInterceptor(Set<String> headersToAddToLoggingMappingDiagnosticsContext) {
        this.headersToAddToLoggingMappingDiagnosticsContext =
                Collections.unmodifiableSet(headersToAddToLoggingMappingDiagnosticsContext);
    }

    /**
     * Gets called before a method gets invoked on a service.
     * This adds request URI, remote address and request headers of the HttpRequest if found.
     *
     * @param methodCall methodCall
     * @return true to continue, always true.
     */
    @Override
    public boolean before(final MethodCall methodCall) {

        final Optional<HttpRequest> httpRequest = findHttpRequest(methodCall);
        if (httpRequest.isPresent()) {
            extractRequestInfoAndPutItIntoMappedDiagnosticContext(httpRequest.get());
        }
        return true;
    }

    private Optional<HttpRequest> findHttpRequest(Request<Object> request) {

        if (request.originatingRequest() instanceof HttpRequest) {
            return Optional.of(((HttpRequest) request.originatingRequest()));
        } else if (request.originatingRequest() != null) {
            return findHttpRequest(request.originatingRequest());
        } else {
            return Optional.empty();
        }
    }

    /**
     * Gets called after a method completes invocation on a service.
     * Used to clear the logging Mapped Diagnostic Context.
     *
     * @param call     method call
     * @param response response from method
     * @return always true
     */
    @Override
    public boolean after(final MethodCall call, final Response response) {
        MDC.clear();
        return true;
    }

    /**
     * Extract request data and put it into the logging Mapped Diagnostic Context.
     *
     * @param httpRequest httpRequest
     */
    private void extractRequestInfoAndPutItIntoMappedDiagnosticContext(final HttpRequest httpRequest) {
        MDC.put(REQUEST_URI, httpRequest.getUri());
        MDC.put(REQUEST_REMOTE_ADDRESS, httpRequest.getRemoteAddress());
        MDC.put(REQUEST_HTTP_METHOD, httpRequest.getMethod());
        MDC.put(REQUEST_ID, Long.toString(httpRequest.getMessageId()));

        extractHeaders(httpRequest);

    }

    /**
     * Extract headersToAddToLoggingMappingDiagnosticsContext data and put them into the logging mapping diagnostics context.
     *
     * @param httpRequest httpRequest
     */
    private void extractHeaders(final HttpRequest httpRequest) {
        if (headersToAddToLoggingMappingDiagnosticsContext.size() > 0) {
            final MultiMap<String, String> headers = httpRequest.getHeaders();
            headersToAddToLoggingMappingDiagnosticsContext.forEach(header -> {
                String value = headers.getFirst(header);
                if (!Str.isEmpty(value)) {
                    MDC.put(REQUEST_HEADER_PREFIX + header, value);
                }
            });
        }
    }

}


package io.advantageous.qbit.server;

import io.advantageous.boon.core.Str;
import io.advantageous.boon.primitive.CharBuf;
import io.advantageous.qbit.GlobalConstants;
import io.advantageous.qbit.annotation.RequestMethod;
import io.advantageous.qbit.http.HttpStatus;
import io.advantageous.qbit.http.HttpStatusCodeException;
import io.advantageous.qbit.http.request.*;
import io.advantageous.qbit.json.JsonMapper;
import io.advantageous.qbit.message.MethodCall;
import io.advantageous.qbit.message.Request;
import io.advantageous.qbit.message.Response;
import io.advantageous.qbit.meta.RequestMetaData;
import io.advantageous.qbit.meta.ServiceMethodMeta;
import io.advantageous.qbit.meta.builder.ContextMetaBuilder;
import io.advantageous.qbit.meta.provider.StandardMetaDataProvider;
import io.advantageous.qbit.meta.transformer.StandardRequestTransformer;
import io.advantageous.qbit.queue.SendQueue;
import io.advantageous.qbit.service.ServiceBundle;
import io.advantageous.qbit.service.ServiceMethodNotFoundException;
import io.advantageous.qbit.util.MultiMap;
import io.advantageous.qbit.util.MultiMapImpl;
import io.advantageous.qbit.util.Timer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer;

import static io.advantageous.boon.core.Sets.set;
import static io.advantageous.boon.core.Str.startsWithItemInCollection;
import static io.advantageous.boon.primitive.Arry.array;

public class HttpRequestServiceServerHandlerUsingMetaImpl implements HttpRequestServiceServerHandler {

    private static final Set<String> ignorePackages = set("sun.", "com.sun.",
            "javax.java", "java.", "oracle.", "com.oracle.", "org.junit",
            "com.intellij", "io.advantageous.boon");

    private final int timeoutInSeconds;
    private final AtomicLong lastTimeoutCheckTime = new AtomicLong();
    private final int numberOfOutstandingRequests;
    private final SendQueue<MethodCall<Object>> methodCallSendQueue;
    private final int flushInterval;
    private final JsonMapper jsonMapper;
    private final Map<String, Request<Object>> outstandingRequestMap = new ConcurrentHashMap<>(100_000);
    private final Logger logger = LoggerFactory.getLogger(HttpRequestServiceServerHandlerUsingMetaImpl.class);

    private final boolean devMode = GlobalConstants.DEV_MODE;
    private final Lock lock = new ReentrantLock();
    private final Map<RequestMethod, StandardMetaDataProvider> metaDataProviderMap = new ConcurrentHashMap<>();
    private final Consumer<Throwable> errorHandler;
    private long lastFlushTime;
    private ContextMetaBuilder contextMetaBuilder = ContextMetaBuilder.contextMetaBuilder();
    private StandardRequestTransformer standardRequestTransformer;

    public HttpRequestServiceServerHandlerUsingMetaImpl(final int timeoutInSeconds,
                                                        final ServiceBundle serviceBundle,
                                                        final JsonMapper jsonMapper,
                                                        final int numberOfOutstandingRequests,
                                                        final int flushInterval,
                                                        final Consumer<Throwable> errorHandler) {
        this.timeoutInSeconds = timeoutInSeconds;
        lastTimeoutCheckTime.set(Timer.timer().now() + (timeoutInSeconds * 1000));
        this.numberOfOutstandingRequests = numberOfOutstandingRequests;
        this.jsonMapper = jsonMapper;
        this.errorHandler = errorHandler;

        this.methodCallSendQueue = serviceBundle.methodSendQueue();
        this.flushInterval = flushInterval;

        contextMetaBuilder = ContextMetaBuilder.contextMetaBuilder();

    }

    public static StackTraceElement[] getFilteredStackTrace(StackTraceElement[] stackTrace) {

        if (stackTrace == null || stackTrace.length == 0) {
            return new StackTraceElement[0];
        }
        List<StackTraceElement> list = new ArrayList<>();
        Set<String> seenThisBefore = new HashSet<>();

        for (StackTraceElement st : stackTrace) {
            if (startsWithItemInCollection(st.getClassName(), ignorePackages)) {

                continue;
            }

            String key = Str.sputs(st.getClassName(), st.getFileName(), st.getMethodName(), st.getLineNumber());
            if (seenThisBefore.contains(key)) {
                continue;
            } else {
                seenThisBefore.add(key);
            }

            list.add(st);
        }

        return array(StackTraceElement.class, list);

    }

    public static void stackTraceToJson(CharBuf buffer, StackTraceElement[] stackTrace) {

        if (stackTrace.length == 0) {
            buffer.addLine("[]");
            return;
        }

        buffer.multiply(' ', 16).addLine('[');

        for (int index = 0; index < stackTrace.length; index++) {
            StackTraceElement element = stackTrace[index];

            if (element.getClassName().contains("Exceptions")) {
                continue;
            }
            buffer.indent(17).add("[  ").asJsonString(element.getMethodName())
                    .add(',');

            buffer.indent(3).asJsonString(element.getClassName());

            if (element.getLineNumber() > 0) {
                buffer.add(",");
                buffer.indent(3).asJsonString("" + element.getLineNumber())
                        .addLine("   ],");
            } else {
                buffer.addLine(" ],");
            }

        }
        buffer.removeLastChar(); //trailing \n
        buffer.removeLastChar(); //trailing ,

        buffer.addLine().multiply(' ', 15).add(']');
    }

    /**
     * MOST IMPORTANT METHOD FOR DEBUGGING WHY SOMETHING IS NOT CALLED.
     */
    @Override
    public void handleRestCall(final HttpRequest request) {

        final List<String> errorList = new ArrayList<>(0);
        final MethodCall<Object> methodCall = standardRequestTransformer.transform(request, errorList);

        if (methodCall != null && errorList.size() == 0) {
            if (!addRequestToCheckForTimeouts(request)) {
                handleOverflow(request);
                return;
            }
            sendMethodToServiceBundle(methodCall);
        } else {
            if (!request.isHandled()) {
                handleErrorConverting(request, errorList, methodCall);
            }
            return;
        }

        final RequestMetaData requestMetaData = metaDataProviderMap
                .get(RequestMethod.valueOf(request.getMethod())).get(request.address());

        final ServiceMethodMeta serviceMethod = requestMetaData.getMethod();

        if (serviceMethod.getMethodAccess().returnType() == void.class
                && !serviceMethod.hasCallBack()) {

            request.handled();

            final int responseCode = serviceMethod.getResponseCode();
            writeResponse(request.getReceiver(), responseCode == -1 ? HttpStatus.ACCEPTED : responseCode,
                    serviceMethod.getContentType(), "\"success\"",
                    requestMetaData.getRequest().getResponseHeaders());

        }

    }

    /**
     * 2nd MOST IMPORTANT METHOD FOR DEBUGGING WHY SOMETHING IS NOT CALLED.
     */
    @Override
    public void handleResponseFromServiceToHttpResponse(final Response<Object> response, final HttpRequest originatingRequest) {

        final String key = Str.add("" + originatingRequest.id(), "|", originatingRequest.returnAddress());
        this.outstandingRequestMap.remove(key);

        if (response.wasErrors()) {
            handleError(response, originatingRequest);
        } else {
            if (response.body() instanceof HttpResponse) {
                writeHttpResponse(originatingRequest.getReceiver(), ((HttpResponse) response.body()));
            } else {
                final RequestMetaData requestMetaData = metaDataProviderMap
                        .get(RequestMethod.valueOf(originatingRequest.getMethod())).get(originatingRequest.address());

                final ServiceMethodMeta serviceMethodMeta = requestMetaData.getMethod();
                final int responseCode = serviceMethodMeta.getResponseCode();

                MultiMap<String, String> headers = response.headers();

                if (requestMetaData.getRequest().hasResponseHeaders()) {
                    if (response.headers() == MultiMap.EMPTY) {
                        headers = new MultiMapImpl<>();
                    } else {
                        headers = response.headers();
                    }
                    headers.putAllCopyLists(requestMetaData.getRequest().getResponseHeaders());
                }

                writeResponse(originatingRequest.getReceiver(),
                        responseCode == -1 ? HttpStatus.OK : responseCode,
                        serviceMethodMeta.getContentType(),
                        jsonMapper.toJson(response.body()),
                        headers);

            }
        }

    }

    @Override
    public void httpRequestQueueIdle(Void v) {
        long lastFlush = lastFlushTime;
        long now = Timer.timer().now();
        long duration = now - lastFlush;

        if (duration > flushInterval) {
            lastFlushTime = now;
            try {
                lock.lock();
                methodCallSendQueue.flushSends();
            } finally {
                lock.unlock();
            }
        }

    }

    public void start() {

        metaDataProviderMap.put(RequestMethod.GET, new StandardMetaDataProvider(contextMetaBuilder.build(), RequestMethod.GET));
        metaDataProviderMap.put(RequestMethod.POST, new StandardMetaDataProvider(contextMetaBuilder.build(), RequestMethod.POST));
        metaDataProviderMap.put(RequestMethod.PUT, new StandardMetaDataProvider(contextMetaBuilder.build(), RequestMethod.PUT));
        metaDataProviderMap.put(RequestMethod.DELETE, new StandardMetaDataProvider(contextMetaBuilder.build(), RequestMethod.DELETE));
        metaDataProviderMap.put(RequestMethod.HEAD, new StandardMetaDataProvider(contextMetaBuilder.build(), RequestMethod.HEAD));
        metaDataProviderMap.put(RequestMethod.OPTIONS, new StandardMetaDataProvider(contextMetaBuilder.build(), RequestMethod.OPTIONS));
        metaDataProviderMap.put(RequestMethod.TRACE, new StandardMetaDataProvider(contextMetaBuilder.build(), RequestMethod.TRACE));
        metaDataProviderMap.put(RequestMethod.CONNECT, new StandardMetaDataProvider(contextMetaBuilder.build(), RequestMethod.CONNECT));

        standardRequestTransformer = new StandardRequestTransformer(metaDataProviderMap, Optional.ofNullable(errorHandler));

    }

    private void handleOverflow(HttpRequest request) {
        writeResponse(request.getReceiver(), HttpStatus.TOO_MANY_REQUEST, "application/json",
                "\"too many outstanding requests\"", MultiMap.empty());
    }

    private void sendMethodToServiceBundle(MethodCall<Object> methodCall) {

        try {
            lock.lock();
            methodCallSendQueue.send(methodCall);
        } finally {
            lock.unlock();
        }
    }

    private void handleErrorConverting(HttpRequest request, List<String> errorList, MethodCall<Object> methodCall) {
        if (methodCall == null) {
            if (errorList.size() > 0) {
                request.getReceiver().response(HttpStatus.NOT_FOUND, "application/json", jsonMapper.toJson(errorList));
            } else {
                request.getReceiver().response(HttpStatus.NOT_FOUND, "application/json", "\"not found\"");
            }
        } else {
            if (errorList.size() > 0) {
                request.getReceiver().response(HttpStatus.BAD_REQUEST, "application/json", jsonMapper.toJson(errorList));
            } else {
                request.getReceiver().response(HttpStatus.ERROR, "application/json", "\"unable to make call\"");
            }
        }
    }

    @Override
    public void addRestSupportFor(Class cls, String baseURI) {

        contextMetaBuilder.setRootURI(baseURI).addService(cls);

    }

    @Override
    public void addRestSupportFor(String alias, Class<?> cls, String baseURI) {

        contextMetaBuilder.setRootURI(baseURI).addService(alias, cls);
    }

    @Override
    public void checkTimeoutsForRequests() {

//        if (outstandingRequestMap.size()==0) {
//            return;
//        }

        final long now = Timer.timer().now();
        final long durationSinceLastCheck = now - lastTimeoutCheckTime.get();
        final long timeoutInMS = timeoutInSeconds * 1000;
        final boolean timedOut = durationSinceLastCheck > timeoutInMS;

        if (!(timedOut)) {
            return;
        }

        lastTimeoutCheckTime.set(now);

        long duration;

        final Set<Map.Entry<String, Request<Object>>> entries = outstandingRequestMap.entrySet();

        for (Map.Entry<String, Request<Object>> requestEntry : entries) {

            final Request<Object> request = requestEntry.getValue();
            final String key = requestEntry.getKey();

            if (request.isHandled()) {
                request.handled();
                outstandingRequestMap.remove(key);
                continue;
            }

            duration = now - request.timestamp();

            if (duration > timeoutInMS) {
                final HttpResponseReceiver httpResponse = ((HttpRequest) request).getReceiver();
                try {
                    //noinspection unchecked
                    httpResponse.response(HttpStatus.TIMED_OUT, "application/json", "\"timed out\"");
                } catch (Exception ex) {
                    logger.debug("Response not marked handled and it timed out, but could not be written " + request, ex);
                }
            }
        }
    }

    private void handleError(Response<Object> response, HttpRequest httpRequest) {
        final Object obj = response.body();

        if (obj instanceof ServiceMethodNotFoundException) {
            writeResponse(httpRequest.getReceiver(), HttpStatus.NOT_FOUND, "application/json",
                    jsonMapper.toJson(response.body()), response.headers());

        } else if (obj instanceof HttpStatusCodeException) {
            final HttpStatusCodeException httpStatusCodeException = ((HttpStatusCodeException) obj);
            writeResponse(httpRequest.getReceiver(), httpStatusCodeException.code(), "application/json",
                    jsonMapper.toJson(httpStatusCodeException.getMessage()), response.headers());

        } else if (obj instanceof Throwable) {

            writeResponse(httpRequest.getReceiver(), HttpStatus.ERROR, "application/json", asJson(((Throwable) obj)), response.headers());

        } else {
            writeResponse(httpRequest.getReceiver(), HttpStatus.ERROR, "application/json",
                    jsonMapper.toJson(response.body()), response.headers());
        }
    }

    private void writeHttpResponse(HttpResponseReceiver<Object> receiver, HttpResponse httpResponse) {
        if (httpResponse instanceof HttpTextResponse) {
            HttpTextResponse httpTextResponse = (HttpTextResponse) httpResponse;
            writeResponse(receiver, httpResponse.code(),
                    httpResponse.contentType(), httpTextResponse.body(), httpTextResponse.headers());
        } else if (httpResponse instanceof HttpBinaryResponse) {
            HttpBinaryResponse httpBinaryResponse = ((HttpBinaryResponse) httpResponse);
            receiver.response(httpResponse.code(), httpResponse.contentType(), httpBinaryResponse.body(),
                    httpBinaryResponse.headers());
        }
    }

    private void writeResponse(HttpResponseReceiver response, int code, String mimeType, String responseString,
                               MultiMap<String, String> headers) {

        if (response.isText()) {
            //noinspection unchecked
            response.response(code, mimeType, responseString, headers);
        } else {
            //noinspection unchecked
            response.response(code, mimeType, responseString.getBytes(StandardCharsets.UTF_8), headers);
        }
    }

    /**
     * Add a request to the timeout queue. Server checks for timeouts when it is idle or when
     * the max outstanding outstandingRequests is met.
     *
     * @param request request.
     */
    private boolean addRequestToCheckForTimeouts(final Request<Object> request) {

        String key = Str.add("" + request.id(), "|", request.returnAddress());
        this.outstandingRequestMap.put(key, request);

        return outstandingRequestMap.size() < numberOfOutstandingRequests;
    }

    public String asJson(final Throwable ex) {
        final CharBuf buffer = CharBuf.create(255);

        buffer.add('{');

        buffer.addLine().indent(5).addJsonFieldName("message")
                .asJsonString(ex.getMessage()).addLine(',');

        buffer.addLine().indent(5).addJsonFieldName("exception")
                .asJsonString(ex.getClass().getSimpleName()).addLine(',');

        if (devMode) {

            if (ex.getCause() != null) {
                buffer.addLine().indent(5).addJsonFieldName("causeMessage")
                        .asJsonString(ex.getCause().getMessage()).addLine(',');

                if (ex.getCause().getCause() != null) {
                    buffer.addLine().indent(5).addJsonFieldName("cause2Message")
                            .asJsonString(ex.getCause().getCause().getMessage()).addLine(',');

                    if (ex.getCause().getCause().getCause() != null) {
                        buffer.addLine().indent(5).addJsonFieldName("cause3Message")
                                .asJsonString(ex.getCause().getCause().getCause().getMessage()).addLine(',');

                        if (ex.getCause().getCause().getCause().getCause() != null) {
                            buffer.addLine().indent(5).addJsonFieldName("cause4Message")
                                    .asJsonString(ex.getCause().getCause().getCause().getCause().getMessage()).addLine(',');

                        }

                    }

                }

            }

            final StackTraceElement[] stackTrace = getFilteredStackTrace(ex.getStackTrace());

            if (stackTrace != null && stackTrace.length > 0) {

                buffer.addLine().indent(5).addJsonFieldName("stackTrace").addLine();

                stackTraceToJson(buffer, stackTrace);

                buffer.add(',');
            }

            buffer.addLine().indent(5).addJsonFieldName("fullStackTrace").addLine();

            final StackTraceElement[] fullStackTrace = ex.getStackTrace();
            stackTraceToJson(buffer, fullStackTrace);
        }

        buffer.add('}');
        return buffer.toString();

    }

}


package io.advantageous.qbit.service.stats;

import io.advantageous.boon.primitive.Lng;

import java.util.AbstractList;
import java.util.Arrays;

import static io.advantageous.boon.primitive.Lng.varianceDouble;

/**
 * Holds primitive values in a list like object for ints.
 * <p>
 * Has sum, mean, median, standardDeviation, reduceBy,
 * variance.
 * </p>
 *
 * @author Rick Hightower
 */
public class StatList extends AbstractList<Long> {

    /**
     * Values in this list.
     */
    private long[] values;
    /**
     * Index of last value added.
     */
    private int end;

    /**
     * Create a new list with this many items in it.
     *
     * @param capacity capacity
     */
    public StatList(final int capacity) {
        this.values = new long[capacity];
    }

    /**
     * Create a new list with exactly 10 items in it.
     */
    public StatList() {
        this.values = new long[10];
    }

    /**
     * Create a new list with this many items in it.
     *
     * @param values values
     */
    public StatList(long values[]) {
        this.values = values;
        this.end = values.length;
    }

    private static double meanDouble(long[] values, final int start, final int length) {
        //noinspection UnnecessaryLocalVariable
        @SuppressWarnings("UnnecessaryLocalVariable") double mean = ((double) bigSum(values, start, length)) / ((double) length);
        return mean;
    }

    /**
     * Sum
     * Provides overflow protection.
     *
     * @param values values in int
     * @return sum
     */
    public static long bigSum(long[] values) {
        return bigSum(values, 0, values.length);
    }

    /**
     * Sum
     * Provides overflow protection.
     *
     * @param values values in int
     * @param length length
     * @return sum
     */
    public static long bigSum(long[] values, int length) {
        return bigSum(values, 0, length);
    }

    /**
     * Big Sum
     *
     * @param values values in int
     * @param length length
     * @param start  start
     * @return sum
     */
    public static long bigSum(long[] values, int start, int length) {
        long sum = 0;
        for (int index = start; index < length; index++) {
            sum += values[index];
        }

        return sum;

    }

    public void clear() {
        this.values = new long[10];
        this.end = 0;
    }

    /**
     * Get the value at index
     *
     * @param index index
     * @return value
     */
    @Override
    public Long get(int index) {
        return values[index];
    }

    /**
     * Get the value at index but don't use a wrapper
     *
     * @param index index
     * @return value
     */
    public final long getLong(int index) {
        return values[index];
    }

    /**
     * Add a new value to the list.
     *
     * @param integer new value
     * @return was able to add.
     */
    @Override
    public boolean add(Long integer) {
        if (end + 1 >= values.length) {
            values = Lng.grow(values);
        }
        values[end] = integer;
        end++;
        return true;
    }

    /**
     * Add a new value to the list but don't employ a wrapper.
     *
     * @param integer new value
     * @return was able to add.
     */
    public boolean addLong(long integer) {
        if (end + 1 >= values.length) {
            values = Lng.grow(values);
        }
        values[end] = integer;
        end++;
        return true;
    }

    /**
     * Add a new value to the list but don't employ a wrapper.
     *
     * @param integer new value
     * @return was able to add.
     */
    @SuppressWarnings("UnusedReturnValue")
    public StatList add(int integer) {
        if (end + 1 >= values.length) {
            values = Lng.grow(values);
        }
        values[end] = integer;
        end++;
        return this;
    }

    /**
     * Add a new array to the list.
     *
     * @param newValues new values
     * @return was able to add.
     */
    public boolean addArray(long... newValues) {
        if (end + newValues.length >= values.length) {
            values = Lng.grow(values, (values.length + newValues.length) * 2);
        }

        System.arraycopy(newValues, 0, values, end, newValues.length);
        end += newValues.length;
        return true;
    }

    /**
     * Set a value in the list.
     *
     * @param index   index
     * @param element new value
     * @return old value at this index
     */
    @Override
    public Long set(int index, Long element) {
        long oldValue = values[index];
        values[index] = element;
        return oldValue;
    }

    /**
     * Set in a new value no wrapper
     *
     * @param index   index
     * @param element new value
     * @return old value at this index
     */
    public long setLong(int index, long element) {
        long oldValue = values[index];
        values[index] = element;
        return oldValue;
    }

    /**
     * Return the current size.
     *
     * @return size
     */
    @Override
    public int size() {
        return end;
    }

    /**
     * Sums the values with bounds checking.
     *
     * @return sum
     */
    public long sum() {

        return Lng.sum(values, end);
    }

    /**
     * Get a copy of the array up to the end element.
     *
     * @return array
     */
    public long[] toValueArray() {

        return java.util.Arrays.copyOfRange(values, 0, end);
    }

    /**
     * This would be a good opportunity to reintroduce dynamic invoke
     *
     * @param function function
     * @return array
     */
    public long reduceBy(Object function) {
        return Lng.reduceBy(values, end, function);
    }

    /**
     * This would be a good opportunity to reintroduce dynamic invoke
     *
     * @param function function
     * @param name     name
     * @return result
     */
    public long reduceBy(Object function, String name) {
        return Lng.reduceBy(values, end, function, name);
    }

    /**
     * @param reduceBy reduceBy function
     * @return the reduction
     */
    public long reduceBy(Lng.ReduceBy reduceBy) {
        return Lng.reduceBy(values, end, reduceBy);
    }

    /**
     * Mean
     *
     * @return mean
     */
    public float mean() {
        return (float) meanDouble(values, 0, end);
    }

    /**
     * standardDeviation
     *
     * @return standardDeviation
     */
    public float standardDeviation() {
        double variance = varianceDouble(values, 0, end);
        return (float) Math.sqrt(variance);
    }

    /**
     * variance
     *
     * @return variance
     */
    public float variance() {
        return (float) varianceDouble(values, 0, end);
    }

    /**
     * max
     *
     * @return max
     */
    public long max() {
        return Lng.max(values, end);
    }

    /**
     * min
     *
     * @return min
     */
    public long min() {
        return Lng.min(values, end);
    }

    /**
     * median
     *
     * @return median
     */
    public long median() {
        return Lng.median(values, end);
    }

    /**
     * sort
     */
    public void sort() {
        Arrays.sort(values, 0, end);
    }

    @SuppressWarnings("SimplifiableIfStatement")
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        StatList integers = (StatList) o;

        //noinspection SimplifiableIfStatement
        if (end != integers.end) return false;
        return Lng.equals(0, end, values, integers.values);

    }

    @Override
    public int hashCode() {
        int result = 1;
        result = 31 * result + (values != null ? Lng.hashCode(0, end, values) : 0);
        result = 31 * result + end;
        return result;
    }

}


package io.advantageous.qbit.http.interceptor;

import io.advantageous.qbit.client.BeforeMethodSent;
import io.advantageous.qbit.message.MethodCallBuilder;
import io.advantageous.qbit.message.Request;
import io.advantageous.qbit.service.RequestContext;

import java.util.Optional;

/**
 * This is used by proxies to find the parent request and forward it
 * to the service that the parent calls.
 */
public class ForwardCallMethodInterceptor implements BeforeMethodSent {

    /**
     * Holds the request context, which holds the active request.
     */
    private final RequestContext requestContext;

    /**
     * @param requestContext request context
     */
    public ForwardCallMethodInterceptor(final RequestContext requestContext) {
        this.requestContext = requestContext;
    }

    /**
     * Intercept the call before it gets sent to the service queue.
     *
     * @param methodBuilder methodBuilder
     */
    @Override
    public void beforeMethodSent(final MethodCallBuilder methodBuilder) {

        if (methodBuilder.getOriginatingRequest() == null) {
            final Optional<Request<Object>> request = requestContext.getRequest();
            if (request.isPresent()) {
                methodBuilder.setOriginatingRequest(request.get());
            }
        }
    }
}


/*
 * Copyright (c) 2015 Spotify AB
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.spotify.dns;

import com.google.common.collect.ImmutableSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Collections;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantLock;

import static com.google.common.base.Preconditions.checkNotNull;

/**
 * A helper for implementing the {@link ChangeNotifier} interface.
 */
abstract class AbstractChangeNotifier<T> implements ChangeNotifier<T> {

  private static final Logger log = LoggerFactory.getLogger(AbstractChangeNotifier.class);

  private final AtomicReference<Listener<T>> listenerRef = new AtomicReference<Listener<T>>();

  private final AtomicBoolean listenerNotified = new AtomicBoolean(false);

  private final ReentrantLock lock = new ReentrantLock();

  @Override
  public void setListener(final Listener<T> listener, final boolean fire) {
    checkNotNull(listener, "listener");

    lock.lock();
    try {
          if (!listenerRef.compareAndSet(null, listener)) {
        throw new IllegalStateException("Listener already set!");
      }

      if (fire) {
        notifyListener(newChangeNotification(current(), Collections.<T>emptySet()), true);
      }
    } finally {
      lock.unlock();
    }
  }

  @Override
  public final void close() {
    listenerRef.set(null);
    closeImplementation();
  }

  protected abstract void closeImplementation();

  protected final void fireRecordsUpdated(ChangeNotification<T> changeNotification) {
    notifyListener(changeNotification, false);
  }

  /**
   * Notify the listener about a change. If this is due to adding a new listener rather than
   * being an update, only notify the listener if this is the first notification sent to it.
   *
   * @param changeNotification the change notification to send
   * @param newListener call is triggered by adding a listener rather than an update
   */
  private void notifyListener(ChangeNotification<T> changeNotification, boolean newListener) {
    lock.lock();
    try {
      checkNotNull(changeNotification, "changeNotification");

      final Listener<T> listener = listenerRef.get();
      if (listener != null) {
        try {
          final boolean notified = listenerNotified.getAndSet(true);
          if (!(newListener && notified)) {
            listener.onChange(changeNotification);
          }
        } catch (Throwable e) {
          log.error("Change notification listener threw exception", e);
        }
      }
    } finally {
      lock.unlock();
    }
  }

  protected final ChangeNotification<T> newChangeNotification(Set<T> current, Set<T> previous) {
    checkNotNull(current, "current");
    checkNotNull(previous, "previous");

    return new ChangeNotificationImpl<T>(current, previous);
  }

  private static class ChangeNotificationImpl<T> implements ChangeNotification<T> {

    private final Set<T> current;
    private final Set<T> previous;

    protected ChangeNotificationImpl(Set<T> current, Set<T> previous) {
      this.current = current;
      this.previous = previous;
    }

    @Override
    public Set<T> current() {
      return unmodifiable(current);
    }

    private Set<T> unmodifiable(Set<T> set) {
      if (ChangeNotifiers.isInitialEmptyData(set)) {
        return set;
      }
      if (set instanceof ImmutableSet) {
        return set;
      }
      return Collections.unmodifiableSet(set);
    }

    @Override
    public Set<T> previous() {
      return unmodifiable(previous);
    }
  }
}


/*
 * Copyright (c) 2015 Spotify AB
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.spotify.dns;

import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.ThreadFactoryBuilder;

import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static java.util.concurrent.TimeUnit.SECONDS;

/**
 * Provides builders for configuring and instantiating {@link DnsSrvWatcher}s.
 */
public final class DnsSrvWatchers {

  /**
   * Creates a {@link DnsSrvWatcherBuilder} using the given {@link DnsSrvResolver}. The builder
   * can be configured to have the desired behavior.
   *
   *
Exactly one of {@link DnsSrvWatcherBuilder#polling(long, TimeUnit)} or
   * {@link DnsSrvWatcherBuilder#customTrigger(DnsSrvWatcherFactory)} must be used.
   *
   * @param resolver The resolver to use for lookups
   * @return a builder for further configuring the watcher
   */
  public static DnsSrvWatcherBuilder<LookupResult> newBuilder(DnsSrvResolver resolver) {
    checkNotNull(resolver, "resolver");

    return new DnsSrvWatcherBuilder<LookupResult>(resolver, Functions.<LookupResult>identity());
  }

  /**
   * Creates a {@link DnsSrvWatcherBuilder} using the given {@link DnsSrvResolver}. The builder
   * can be configured to have the desired behavior.
   *
   *
This watcher will use a function that transforms the {@link LookupResult}s into an
   * arbitrary type that will be used throughout the {@link DnsSrvWatcher} api.
   *
   *
Exactly one of {@link DnsSrvWatcherBuilder#polling(long, TimeUnit)} or
   * {@link DnsSrvWatcherBuilder#customTrigger(DnsSrvWatcherFactory)} must be used.
   *
   * @param resolver          The resolver to use for lookups
   * @param resultTransformer The transformer function
   * @return a builder for further configuring the watcher
   */
  public static <T> DnsSrvWatcherBuilder<T> newBuilder(
      DnsSrvResolver resolver,
      Function<LookupResult, T> resultTransformer) {

    checkNotNull(resolver, "resolver");
    checkNotNull(resultTransformer, "resultTransformer");

    return new DnsSrvWatcherBuilder<T>(resolver, resultTransformer);
  }

  public static final class DnsSrvWatcherBuilder<T> {

    private final DnsSrvResolver resolver;
    private final Function<LookupResult, T> resultTransformer;

    private final boolean polling;
    private final long pollingInterval;
    private final TimeUnit pollingIntervalUnit;

    private final ErrorHandler errorHandler;

    private final DnsSrvWatcherFactory<T> dnsSrvWatcherFactory;

    private final ScheduledExecutorService scheduledExecutorService;

    private DnsSrvWatcherBuilder(
        DnsSrvResolver resolver,
        Function<LookupResult, T> resultTransformer) {
      this(resolver, resultTransformer, false, 0, null, null, null, null);
    }

    private DnsSrvWatcherBuilder(
        DnsSrvResolver resolver,
        Function<LookupResult, T> resultTransformer,
        boolean polling,
        long pollingInterval,
        TimeUnit pollingIntervalUnit,
        ErrorHandler errorHandler,
        DnsSrvWatcherFactory<T> dnsSrvWatcherFactory,
        ScheduledExecutorService scheduledExecutorService) {
      this.resolver = resolver;
      this.resultTransformer = resultTransformer;
      this.polling = polling;
      this.pollingInterval = pollingInterval;
      this.pollingIntervalUnit = pollingIntervalUnit;
      this.errorHandler = errorHandler;
      this.dnsSrvWatcherFactory = dnsSrvWatcherFactory;
      this.scheduledExecutorService = scheduledExecutorService;
    }

    public DnsSrvWatcher<T> build() {
      checkState(polling ^ dnsSrvWatcherFactory != null, "specify either polling or custom trigger");

      final ChangeNotifierFactory<T> changeNotifierFactory =
          new ChangeNotifierFactory<T>() {
            @Override
            public RunnableChangeNotifier<T> create(String fqdn) {
              return new ServiceResolvingChangeNotifier<T>(resolver, fqdn, resultTransformer,
                                                           errorHandler);
            }
          };

      DnsSrvWatcherFactory<T> watcherFactory;
      if (polling) {
        final ScheduledExecutorService executor =
            scheduledExecutorService != null
            ? scheduledExecutorService
            : MoreExecutors.getExitingScheduledExecutorService(
                new ScheduledThreadPoolExecutor(
                    1, new ThreadFactoryBuilder().setNameFormat("dns-lookup-%d").build()),
                0, SECONDS);

        watcherFactory = new DnsSrvWatcherFactory<T>() {
          @Override
          public DnsSrvWatcher<T> create(ChangeNotifierFactory<T> changeNotifierFactory) {
            return new PollingDnsSrvWatcher<T>(changeNotifierFactory, executor, pollingInterval,
                                               pollingIntervalUnit);
          }
        };
      } else {
        watcherFactory = checkNotNull(dnsSrvWatcherFactory);
      }

      return watcherFactory.create(changeNotifierFactory);
    }

    public DnsSrvWatcherBuilder<T> polling(long pollingInterval, TimeUnit pollingIntervalUnit) {
      checkArgument(pollingInterval > 0);
      checkNotNull(pollingIntervalUnit, "pollingIntervalUnit");

      return new DnsSrvWatcherBuilder<T>(resolver, resultTransformer, true, pollingInterval,
                                         pollingIntervalUnit, errorHandler, dnsSrvWatcherFactory,
                                         scheduledExecutorService);
    }

    public DnsSrvWatcherBuilder<T> usingExecutor(ScheduledExecutorService scheduledExecutorService) {
      return new DnsSrvWatcherBuilder<T>(resolver, resultTransformer, polling, pollingInterval,
                                         pollingIntervalUnit, errorHandler, dnsSrvWatcherFactory,
                                         scheduledExecutorService);
    }

    public DnsSrvWatcherBuilder<T> customTrigger(DnsSrvWatcherFactory<T> watcherFactory) {
      checkNotNull(watcherFactory, "watcherFactory");

      return new DnsSrvWatcherBuilder<T>(resolver, resultTransformer, true, pollingInterval,
                                         pollingIntervalUnit, errorHandler, watcherFactory,
                                         scheduledExecutorService);
    }

    public DnsSrvWatcherBuilder<T> withErrorHandler(ErrorHandler errorHandler) {
      checkNotNull(errorHandler, "errorHandler");

      return new DnsSrvWatcherBuilder<T>(resolver, resultTransformer, true, pollingInterval,
                                         pollingIntervalUnit, errorHandler, dnsSrvWatcherFactory,
                                         scheduledExecutorService);
    }
  }

  private DnsSrvWatchers() {
    // prevent instantiation
  }
}


/*
 * Copyright (c) 2015 Spotify AB
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.spotify.dns;

import com.google.common.base.Supplier;
import com.google.common.collect.Sets;

import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.spotify.dns.ChangeNotifierFactory.RunnableChangeNotifier;

public final class ChangeNotifiers {

  /**
   * Ensure that we get a unique empty set that you can't create any other way
   * (i.e. ImmutableSet.of() always returns the same instance).
   *
   * This is needed to distinguishing the initial state of change notifiers from
   * when they have gotten proper data.
   */
  private static final Set INITIAL_EMPTY_DATA = Collections.unmodifiableSet(new HashSet());

  private ChangeNotifiers() {
  }

  /**
   * Use this to determine if the data you get back from a notifier is the initial result of the result of a proper
   * DNS lookup. This is useful for distinguishing a proper but empty DNS result from the case
   * where a lookup has not completed yet.
   * @param set
   * @return true if the input is an initially empty set.
   */
  public static <T> boolean isInitialEmptyData(Set<T> set) {
    return set == INITIAL_EMPTY_DATA;
  }

  static <T> Set<T> initialEmptyDataInstance() {
    return INITIAL_EMPTY_DATA;
  }

  static <T> boolean isNoLongerInitial(Set<T> current, Set<T> previous) {
    return isInitialEmptyData(previous) && !isInitialEmptyData(current);
  }

  /**
   * Creates a {@link ChangeNotifier} that aggregates the records provided by a list of notifiers.
   *
   *
A change event on any of the input notifiers will propagate up the the returned notifier.
   * The set of previous and current records contained in the event will be the union of all
   * records in the input notifiers, before and after the change event.
   *
   * @param notifiers  A list of notifiers to aggregate
   * @param <T>        The record type
   * @return A notifier with the described behaviour
   */
  public static <T> ChangeNotifier<T> aggregate(ChangeNotifier<T>... notifiers) {
    return aggregate(Arrays.asList(notifiers));
  }

  public static <T> ChangeNotifier<T> aggregate(Iterable<ChangeNotifier<T>> notifiers) {
    return new AggregatingChangeNotifier<T>(notifiers);
  }

  /**
   * Create a {@link ChangeNotifier} with a static set of records.
   *
   *
This notifier will never generate any change events. Thus any attached
   * {@link ChangeNotifier.Listener} will at most get one initial call to
   * {@link ChangeNotifier.Listener#onChange(ChangeNotifier.ChangeNotification)}
   * if they are attached with the {@code fire} argument set to {@code true}.
   *
   * @param records  The records that the notifier will contain
   * @param <T>      The record type
   * @return A notifier with a static set of records
   */
  public static <T> ChangeNotifier<T> staticRecords(T... records) {
    return staticRecords(Sets.newHashSet(records));
  }

  public static <T> ChangeNotifier<T> staticRecords(Set<T> records) {
    return new StaticChangeNotifier<T>(records);
  }

  /**
   * Create a {@link RunnableChangeNotifier} that directly wraps a set of records given by a
   * {@link Supplier}.
   *
   *
Each call to {@link Runnable#run()} will cause the supplier to be polled and regular
   * change notifications to be triggered.
   *
   *
This implementation is useful for testing components that depend on a
   * {@link ChangeNotifier}.
   *
   * @param recordsSupplier  The supplier of records
   * @param <T>              The record type
   * @return A runnable notifier
   */
  public static <T> RunnableChangeNotifier<T> direct(Supplier<Set<T>> recordsSupplier) {
    return new DirectChangeNotifier<T>(recordsSupplier);
  }

  public static <T> RunnableChangeNotifier<T> direct(AtomicReference<Set<T>> recordsHolder) {
    return new DirectChangeNotifier<T>(supplierFromRef(recordsHolder));
  }

  private static <T> Supplier<Set<T>> supplierFromRef(final AtomicReference<Set<T>> ref) {
    checkNotNull(ref, "ref");
    return new Supplier<Set<T>>() {
      @Override
      public Set<T> get() {
        return ref.get();
      }
    };
  }
}


/*
 * Copyright 2010 Martin Grotzke
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
package de.javakaffee.kryoserializers;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;

import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.Serializer;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;

/**
 * A kryo {@link Serializer} for synchronized {@link Collection}s and {@link Map}s
 * created via {@link Collections}.
 *
 * @author Martin Grotzke
 */
public class SynchronizedCollectionsSerializer extends Serializer<Object> {

    private static final Field SOURCE_COLLECTION_FIELD;
    private static final Field SOURCE_MAP_FIELD;

    static {
        try {
            SOURCE_COLLECTION_FIELD = Class.forName("java.util.Collections$SynchronizedCollection" )
                .getDeclaredField( "c" );
            SOURCE_COLLECTION_FIELD.setAccessible( true );

            SOURCE_MAP_FIELD = Class.forName("java.util.Collections$SynchronizedMap" )
                .getDeclaredField( "m" );
            SOURCE_MAP_FIELD.setAccessible( true );
        } catch ( final Exception e ) {
            throw new RuntimeException( "Could not access source collection" +
                    " field in java.util.Collections$SynchronizedCollection.", e );
        }
    }

    @Override
    public Object read(final Kryo kryo, final Input input, final Class<? extends Object> clazz) {
        final int ordinal = input.readInt( true );
        final SynchronizedCollection collection = SynchronizedCollection.values()[ordinal];
        final Object sourceCollection = kryo.readClassAndObject( input );
        return collection.create( sourceCollection );
    }

    @Override
    public void write(final Kryo kryo, final Output output, final Object object) {
        try {
            final SynchronizedCollection collection = SynchronizedCollection.valueOfType( object.getClass() );
            // the ordinal could be replaced by s.th. else (e.g. a explicitely managed "id")
            output.writeInt( collection.ordinal(), true );
            synchronized (object) {
                kryo.writeClassAndObject( output, collection.sourceCollectionField.get( object ) );
            }
        } catch ( final RuntimeException e ) {
            // Don't eat and wrap RuntimeExceptions because the ObjectBuffer.write...
            // handles SerializationException specifically (resizing the buffer)...
            throw e;
        } catch ( final Exception e ) {
            throw new RuntimeException( e );
        }
    }

    @Override
    public Object copy(Kryo kryo, Object original) {
      try {
          final SynchronizedCollection collection = SynchronizedCollection.valueOfType( original.getClass() );
          Object sourceCollectionCopy = kryo.copy(collection.sourceCollectionField.get(original));
          return collection.create( sourceCollectionCopy );
      } catch ( final RuntimeException e ) {
          // Don't eat and wrap RuntimeExceptions
          throw e;
      } catch ( final Exception e ) {
          throw new RuntimeException( e );
      }
    }

    private static enum SynchronizedCollection {
        COLLECTION( Collections.synchronizedCollection( Arrays.asList( "" ) ).getClass(), SOURCE_COLLECTION_FIELD ){
            @Override
            public Object create( final Object sourceCollection ) {
                return Collections.synchronizedCollection( (Collection<?>) sourceCollection );
            }
        },
        RANDOM_ACCESS_LIST( Collections.synchronizedList( new ArrayList<Void>() ).getClass(), SOURCE_COLLECTION_FIELD ){
            @Override
            public Object create( final Object sourceCollection ) {
                return Collections.synchronizedList( (List<?>) sourceCollection );
            }
        },
        LIST( Collections.synchronizedList( new LinkedList<Void>() ).getClass(), SOURCE_COLLECTION_FIELD ){
            @Override
            public Object create( final Object sourceCollection ) {
                return Collections.synchronizedList( (List<?>) sourceCollection );
            }
        },
        SET( Collections.synchronizedSet( new HashSet<Void>() ).getClass(), SOURCE_COLLECTION_FIELD ){
            @Override
            public Object create( final Object sourceCollection ) {
                return Collections.synchronizedSet( (Set<?>) sourceCollection );
            }
        },
        SORTED_SET( Collections.synchronizedSortedSet( new TreeSet<Void>() ).getClass(), SOURCE_COLLECTION_FIELD ){
            @Override
            public Object create( final Object sourceCollection ) {
                return Collections.synchronizedSortedSet( (SortedSet<?>) sourceCollection );
            }
        },
        MAP( Collections.synchronizedMap( new HashMap<Void, Void>() ).getClass(), SOURCE_MAP_FIELD ) {

            @Override
            public Object create( final Object sourceCollection ) {
                return Collections.synchronizedMap( (Map<?, ?>) sourceCollection );
            }

        },
        SORTED_MAP( Collections.synchronizedSortedMap( new TreeMap<Void, Void>() ).getClass(), SOURCE_MAP_FIELD ) {
            @Override
            public Object create( final Object sourceCollection ) {
                return Collections.synchronizedSortedMap( (SortedMap<?, ?>) sourceCollection );
            }
        };

        private final Class<?> type;
        private final Field sourceCollectionField;

        private SynchronizedCollection( final Class<?> type, final Field sourceCollectionField ) {
            this.type = type;
            this.sourceCollectionField = sourceCollectionField;
        }

        /**
         * @param sourceCollection
         */
        public abstract Object create( Object sourceCollection );

        static SynchronizedCollection valueOfType( final Class<?> type ) {
            for( final SynchronizedCollection item : values() ) {
                if ( item.type.equals( type ) ) {
                    return item;
                }
            }
            throw new IllegalArgumentException( "The type " + type + " is not supported." );
        }

    }

    /**
     * Creates a new {@link SynchronizedCollectionsSerializer} and registers its serializer
     * for the several synchronized Collections that can be created via {@link Collections},
     * including {@link Map}s.
     *
     * @param kryo the {@link Kryo} instance to set the serializer on.
     *
     * @see Collections#synchronizedCollection(Collection)
     * @see Collections#synchronizedList(List)
     * @see Collections#synchronizedSet(Set)
     * @see Collections#synchronizedSortedSet(SortedSet)
     * @see Collections#synchronizedMap(Map)
     * @see Collections#synchronizedSortedMap(SortedMap)
     */
    public static void registerSerializers( final Kryo kryo ) {
        final SynchronizedCollectionsSerializer serializer = new SynchronizedCollectionsSerializer();
        SynchronizedCollection.values();
        for ( final SynchronizedCollection item : SynchronizedCollection.values() ) {
            kryo.register( item.type, serializer );
        }
    }

}


package de.javakaffee.kryoserializers.guava;

import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.Registration;
import com.esotericsoftware.kryo.Serializer;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSetMultimap;

import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * A kryo {@link Serializer} for guava-libraries {@link ImmutableMultimap}.
 */
public class ImmutableMultimapSerializer extends Serializer<ImmutableMultimap<Object, Object>> {

    private static final boolean DOES_NOT_ACCEPT_NULL = true;
    private static final boolean IMMUTABLE = true;

    public ImmutableMultimapSerializer() {
        super(DOES_NOT_ACCEPT_NULL, IMMUTABLE);
    }

    @Override
    public void write(Kryo kryo, Output output, ImmutableMultimap<Object, Object> immutableMultiMap) {
        kryo.writeObject(output, ImmutableMap.copyOf(immutableMultiMap.asMap()));
    }

    @Override
    public ImmutableMultimap<Object, Object> read(Kryo kryo, Input input, Class<? extends ImmutableMultimap<Object, Object>> type) {
        final ImmutableMultimap.Builder builder;
        if (type.equals (ImmutableListMultimap.class)) {
            builder = ImmutableMultimap.builder();
        }
        else if (type.equals (ImmutableSetMultimap.class)) {
            builder = ImmutableSetMultimap.builder();
        }
        else {
            builder = ImmutableMultimap.builder();
        }

        final Map map = kryo.readObject(input, ImmutableMap.class);
        final Set<Map.Entry<Object, List<? extends Object>>> entries = map.entrySet();

        for (Map.Entry<Object, List<? extends Object>> entry : entries) {
            builder.putAll(entry.getKey(), entry.getValue());
        }

        return builder.build();
    }

    /* kryo.getSerializer (invoking kryo.getRegistration) throws an exception if registration is required,
     * therefore we're getting a potentially existing serializer on another way.
     */
    private static Serializer<?> getSerializer(Kryo kryo, Class<?> type) {
        Registration registration = kryo.getClassResolver().getRegistration(type);
        return registration != null ? registration.getSerializer() : null;
    }

    /**
     * Creates a new {@link ImmutableMultimapSerializer} and registers its serializer
     * for the several ImmutableMultimap related classes.
     *
     * @param kryo the {@link Kryo} instance to set the serializer on
     */
    public static void registerSerializers(final Kryo kryo) {

        // ImmutableMap is used by ImmutableMultimap. However,
        // we already have a separate serializer class for ImmutableMap,
        // ImmutableMapSerializer. If it is not already being used, register it.
        Serializer immutableMapSerializer = getSerializer(kryo, ImmutableMap.class);
        if (!(immutableMapSerializer instanceof ImmutableMapSerializer)) {
            ImmutableMapSerializer.registerSerializers(kryo);
        }

        // ImmutableList is used by ImmutableListMultimap. However,
        // we already have a separate serializer class for ImmutableList,
        // ImmutableListSerializer. If it is not already being used, register it.
        Serializer immutableListSerializer = getSerializer(kryo, ImmutableList.class);
        if (!(immutableListSerializer instanceof ImmutableListSerializer)) {
            ImmutableListSerializer.registerSerializers(kryo);
        }

        // ImmutableSet is used by ImmutableSetMultimap. However,
        // we already have a separate serializer class for ImmutableSet,
        // ImmutableSetSerializer. If it is not already being used, register it.
        Serializer immutableSetSerializer = getSerializer(kryo, ImmutableSet.class);
        if (!(immutableSetSerializer instanceof ImmutableSetSerializer)) {
            ImmutableSetSerializer.registerSerializers(kryo);
        }

        final ImmutableMultimapSerializer serializer = new ImmutableMultimapSerializer();

        // ImmutableMultimap (abstract class)
        //  +- EmptyImmutableListMultimap
        //  +- ImmutableListMultimap
        //  +- EmptyImmutableSetMultimap
        //  +- ImmutableSetMultimap

        kryo.register(ImmutableMultimap.class, serializer);
        kryo.register(ImmutableListMultimap.of().getClass(), serializer);
        kryo.register(ImmutableListMultimap.of("A", "B").getClass(), serializer);
        kryo.register(ImmutableSetMultimap.of().getClass(), serializer);
        kryo.register(ImmutableSetMultimap.of("A", "B").getClass(), serializer);
    }
}


package de.javakaffee.kryoserializers;

import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.Serializer;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;

import java.lang.Character.UnicodeBlock;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.IdentityHashMap;

/**
 * A kryo {@link Serializer} for fields of type {@link UnicodeBlock}, which is effectively but not
 * actually an enum.
 *
 * @author Chris Hennick
 */
public class UnicodeBlockSerializer extends Serializer<UnicodeBlock> {
    private static final IdentityHashMap<UnicodeBlock, String> BLOCK_NAMES
            = new IdentityHashMap<UnicodeBlock, String>();
    static {
        // Reflectively look up the instances and their names, which are in UnicodeBlock's static
        // fields (necessary since UnicodeBlock isn't an actual enum)
        for (Field field : UnicodeBlock.class.getDeclaredFields()) {
            if (Modifier.isStatic(field.getModifiers())) {
                try {
                    // For some reason, UnicodeBlock constants aren't already accessible, even
                    // though they're public! WTF?
                    field.setAccessible(true);
                    Object value = field.get(null);
                    if (value instanceof UnicodeBlock) {
                        BLOCK_NAMES.put((UnicodeBlock) value, field.getName());
                    }
                } catch (IllegalAccessException e) {
                    // Should never happen
                    throw new InternalError();
                }
            }
        }
    }

    public UnicodeBlockSerializer() {
        super();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void write(final Kryo kryo, final Output output, final UnicodeBlock obj) {
        output.writeAscii(BLOCK_NAMES.get(obj));
    }

    /**
     * Returns {@code original}; see {@link com.esotericsoftware.kryo.serialize.EnumSerializer#copy}
     * for why we behave this way.
     */
    @Override
    public UnicodeBlock copy(final Kryo kryo, final UnicodeBlock original) {
        return original;
    }

    @Override
    public UnicodeBlock read(final Kryo kryo, final Input input,
                             final Class<? extends UnicodeBlock> unicodeBlockClass) {
        String name = input.readString();
        return (name == null) ? null : UnicodeBlock.forName(name);
    }
}


package de.javakaffee.kryoserializers.guava;

import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.Serializer;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import com.google.common.collect.HashBasedTable;

/**
 * A kryo {@link Serializer} for guava-libraries {@link HashBasedTable}.
 */
public class HashBasedTableSerializer<R, C, V> extends TableSerializerBase<R, C, V, HashBasedTable<R, C, V>> {

    private static final boolean HANDLES_NULL = false;
    private static final boolean IMMUTABLE = false;

    public HashBasedTableSerializer() {
        super(HANDLES_NULL, IMMUTABLE);
    }

    @Override
    public void write(Kryo kryo, Output output, HashBasedTable<R, C, V> table) {
        super.writeTable(kryo, output, table);
    }

    @Override
    public HashBasedTable<R, C, V> read(Kryo kryo, Input input, Class<? extends HashBasedTable<R, C, V>> type) {
        HashBasedTable<R, C, V> table = HashBasedTable.create();
        super.readTable(kryo, input, table);
        return table;
    }

    @Override
    public HashBasedTable<R, C, V> copy(final Kryo kryo, final HashBasedTable<R, C, V> original) {
        return HashBasedTable.create(original);
    }

    /**
     * Creates a new {@link HashBasedTableSerializer} and registers its serializer.
     *
     * @param kryo the {@link Kryo} instance to set the serializer on
     */
    public static void registerSerializers(final Kryo kryo) {
        final HashBasedTableSerializer serializer = new HashBasedTableSerializer();
        kryo.register(HashBasedTable.class, serializer);
    }
}