All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.split.client.SplitFactoryImpl Maven / Gradle / Ivy

The newest version!
package io.split.client;

import com.google.common.io.Files;
import io.split.client.dtos.Metadata;
import io.split.client.events.EventsSender;
import io.split.client.events.EventsStorage;
import io.split.client.events.EventsTask;
import io.split.client.events.InMemoryEventsStorage;
import io.split.client.events.NoopEventsStorageImp;
import io.split.client.impressions.AsynchronousImpressionListener;
import io.split.client.impressions.HttpImpressionsSender;
import io.split.client.impressions.ImpressionCounter;
import io.split.client.impressions.ImpressionListener;
import io.split.client.impressions.ImpressionObserver;
import io.split.client.impressions.ImpressionsManager;
import io.split.client.impressions.ImpressionsManagerImpl;
import io.split.client.impressions.ImpressionsSender;
import io.split.client.impressions.ImpressionsStorage;
import io.split.client.impressions.ImpressionsStorageConsumer;
import io.split.client.impressions.ImpressionsStorageProducer;
import io.split.client.impressions.InMemoryImpressionsStorage;
import io.split.client.impressions.PluggableImpressionSender;
import io.split.client.impressions.UniqueKeysTracker;
import io.split.client.impressions.UniqueKeysTrackerImp;
import io.split.client.impressions.strategy.ProcessImpressionDebug;
import io.split.client.impressions.strategy.ProcessImpressionNone;
import io.split.client.impressions.strategy.ProcessImpressionOptimized;
import io.split.client.impressions.strategy.ProcessImpressionStrategy;
import io.split.client.interceptors.ClientKeyInterceptorFilter;
import io.split.client.interceptors.FlagSetsFilter;
import io.split.client.interceptors.FlagSetsFilterImpl;
import io.split.client.interceptors.GzipDecoderResponseInterceptor;
import io.split.client.interceptors.GzipEncoderRequestInterceptor;
import io.split.client.interceptors.SdkMetadataInterceptorFilter;
import io.split.client.utils.FileInputStreamProvider;
import io.split.client.utils.FileTypeEnum;
import io.split.client.utils.InputStreamProvider;
import io.split.client.utils.SDKMetadata;
import io.split.client.utils.StaticContentInputStreamProvider;
import io.split.engine.SDKReadinessGates;
import io.split.engine.common.ConsumerSyncManager;
import io.split.engine.common.ConsumerSynchronizer;
import io.split.engine.common.LocalhostSyncManager;
import io.split.engine.common.LocalhostSynchronizer;
import io.split.engine.common.SplitAPI;
import io.split.engine.common.SplitTasks;
import io.split.engine.common.SyncManager;
import io.split.engine.common.SyncManagerImp;
import io.split.engine.common.Synchronizer;
import io.split.engine.evaluator.Evaluator;
import io.split.engine.evaluator.EvaluatorImp;
import io.split.engine.experiments.SplitChangeFetcher;
import io.split.engine.experiments.SplitFetcher;
import io.split.engine.experiments.SplitFetcherImp;
import io.split.engine.experiments.SplitParser;
import io.split.engine.experiments.SplitSynchronizationTask;
import io.split.engine.segments.SegmentChangeFetcher;
import io.split.engine.segments.SegmentSynchronizationTaskImp;
import io.split.integrations.IntegrationsConfig;
import io.split.service.SplitHttpClientImpl;
import io.split.service.SplitHttpClient;

import io.split.storages.SegmentCache;
import io.split.storages.SegmentCacheConsumer;
import io.split.storages.SegmentCacheProducer;
import io.split.storages.SplitCache;
import io.split.storages.SplitCacheConsumer;
import io.split.storages.SplitCacheProducer;
import io.split.storages.enums.OperationMode;
import io.split.storages.memory.InMemoryCacheImp;
import io.split.storages.memory.SegmentCacheInMemoryImpl;
import io.split.storages.pluggable.adapters.UserCustomEventAdapterProducer;
import io.split.storages.pluggable.adapters.UserCustomImpressionAdapterConsumer;
import io.split.storages.pluggable.adapters.UserCustomImpressionAdapterProducer;
import io.split.storages.pluggable.adapters.UserCustomSegmentAdapterConsumer;
import io.split.storages.pluggable.adapters.UserCustomSplitAdapterConsumer;
import io.split.storages.pluggable.adapters.UserCustomTelemetryAdapterProducer;
import io.split.storages.pluggable.domain.UserStorageWrapper;
import io.split.storages.pluggable.synchronizer.TelemetryConsumerSubmitter;
import io.split.telemetry.storage.InMemoryTelemetryStorage;
import io.split.telemetry.storage.NoopTelemetryStorage;
import io.split.telemetry.storage.TelemetryStorage;
import io.split.telemetry.storage.TelemetryStorageProducer;
import io.split.telemetry.synchronizer.TelemetryInMemorySubmitter;
import io.split.telemetry.synchronizer.TelemetrySyncTask;
import io.split.telemetry.synchronizer.TelemetrySynchronizer;

import org.apache.hc.client5.http.auth.AuthScope;
import org.apache.hc.client5.http.auth.Credentials;
import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.cookie.StandardCookieSpec;
import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
import org.apache.hc.client5.http.impl.routing.DefaultProxyRoutePlanner;
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactoryBuilder;
import org.apache.hc.core5.http.io.SocketConfig;
import org.apache.hc.core5.http.ssl.TLS;
import org.apache.hc.core5.ssl.SSLContexts;
import org.apache.hc.core5.util.TimeValue;
import org.apache.hc.core5.util.Timeout;
import org.slf4j.LoggerFactory;
import pluggable.CustomStorageWrapper;

import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.concurrent.ExecutorService;
import java.util.stream.Collectors;
import java.util.HashSet;
import java.util.List;
import java.util.ArrayList;

import static io.split.client.utils.SplitExecutorFactory.buildExecutorService;

public class SplitFactoryImpl implements SplitFactory {
    private static final org.slf4j.Logger _log = LoggerFactory.getLogger(SplitFactoryImpl.class);
    private static final String LEGACY_LOG_MESSAGE = "The sdk initialize in localhost mode using Legacy file. The splitFile or "
            +
            "inputStream doesn't add it to the config.";
    private final static long SSE_CONNECT_TIMEOUT = 30000;
    private final static long SSE_SOCKET_TIMEOUT = 70000;

    private final SDKReadinessGates _gates;
    private final ImpressionsManager _impressionsManager;
    private final Evaluator _evaluator;
    private final String _apiToken;

    // Client and Manager
    private final SplitClient _client;
    private final SplitManager _manager;

    // Cache
    private final SplitCacheConsumer _splitCache;
    private final SegmentCacheConsumer _segmentCache;

    private boolean isTerminated = false;
    private final ApiKeyCounter _apiKeyCounter;
    private final TelemetryStorageProducer _telemetryStorageProducer;
    private final TelemetrySynchronizer _telemetrySynchronizer;
    private long _startTime;
    private final SDKMetadata _sdkMetadata;
    private OperationMode _operationMode;

    // Depending on mode are not mandatory
    private final TelemetrySyncTask _telemetrySyncTask;
    private final SegmentSynchronizationTaskImp _segmentSynchronizationTaskImp;
    private final SplitFetcher _splitFetcher;
    private final SplitSynchronizationTask _splitSynchronizationTask;
    private final EventsTask _eventsTask;
    private final SyncManager _syncManager;
    private SplitHttpClient _splitHttpClient;
    private final UserStorageWrapper _userStorageWrapper;
    private final ImpressionsSender _impressionsSender;
    private final URI _rootTarget;
    private final URI _eventsRootTarget;
    private final UniqueKeysTracker _uniqueKeysTracker;
    private RequestDecorator _requestDecorator;

    // Constructor for standalone mode
    public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyntaxException, IOException {
        _userStorageWrapper = null;
        _operationMode = config.operationMode();
        _startTime = System.currentTimeMillis();
        _apiToken = apiToken;
        _apiKeyCounter = ApiKeyCounter.getApiKeyCounterInstance();
        _apiKeyCounter.add(apiToken);
        _sdkMetadata = createSdkMetadata(config.ipAddressEnabled(), SplitClientConfig.splitSdkVersion);

        TelemetryStorage telemetryStorage = new InMemoryTelemetryStorage();
        _telemetryStorageProducer = telemetryStorage;

        if (config.blockUntilReady() == -1) {
            // BlockUntilReady not been set
            _log.warn(
                    "no setBlockUntilReadyTimeout parameter has been set - incorrect control treatments could be logged” "
                            +
                            "if no ready config has been set when building factory");
        }

        // SDKReadinessGates
        _gates = new SDKReadinessGates();

        _requestDecorator = new RequestDecorator(config.customHeaderDecorator());
        // HttpClient
        if (config.alternativeHTTPModule() == null) {
            _splitHttpClient = buildSplitHttpClient(apiToken, config, _sdkMetadata, _requestDecorator);
        } else {
            _splitHttpClient = config.alternativeHTTPModule().createClient(apiToken, _sdkMetadata, _requestDecorator);
        }

        // Roots
        _rootTarget = URI.create(config.endpoint());
        _eventsRootTarget = URI.create(config.eventsEndpoint());

        // Cache Initialisations
        SegmentCache segmentCache = new SegmentCacheInMemoryImpl();
        FlagSetsFilter flagSetsFilter = new FlagSetsFilterImpl(config.getSetsFilter());
        SplitCache splitCache = new InMemoryCacheImp(flagSetsFilter);
        ImpressionsStorage impressionsStorage = new InMemoryImpressionsStorage(config.impressionsQueueSize());
        _splitCache = splitCache;
        _segmentCache = segmentCache;
        _telemetrySynchronizer = new TelemetryInMemorySubmitter(_splitHttpClient, URI.create(config.telemetryURL()),
                telemetryStorage,
                splitCache, _segmentCache, telemetryStorage, _startTime);

        // Segments
        _segmentSynchronizationTaskImp = buildSegments(config, segmentCache, splitCache);

        SplitParser splitParser = new SplitParser();
        // SplitFetcher
        _splitFetcher = buildSplitFetcher(splitCache, splitParser, flagSetsFilter);

        // SplitSynchronizationTask
        _splitSynchronizationTask = new SplitSynchronizationTask(_splitFetcher,
                splitCache,
                config.featuresRefreshRate(),
                config.getThreadFactory());

        // ImpressionSender
        _impressionsSender = HttpImpressionsSender.create(_splitHttpClient, URI.create(config.eventsEndpoint()),
                config.impressionsMode(),
                _telemetryStorageProducer);

        // UniqueKeysTracker
        _uniqueKeysTracker = createUniqueKeysTracker(config);

        // Impressions
        _impressionsManager = buildImpressionsManager(config, impressionsStorage, impressionsStorage);

        // EventClient
        EventsStorage eventsStorage = new InMemoryEventsStorage(config.eventsQueueSize(), _telemetryStorageProducer);
        EventsSender eventsSender = EventsSender.create(_splitHttpClient, _eventsRootTarget, _telemetryStorageProducer);
        _eventsTask = EventsTask.create(config.eventSendIntervalInMillis(), eventsStorage, eventsSender,
                config.getThreadFactory());
        _telemetrySyncTask = new TelemetrySyncTask(config.getTelemetryRefreshRate(), _telemetrySynchronizer,
                config.getThreadFactory());

        // Evaluator
        _evaluator = new EvaluatorImp(splitCache, segmentCache);

        // SplitClient
        _client = new SplitClientImpl(this,
                splitCache,
                _impressionsManager,
                eventsStorage,
                config,
                _gates,
                _evaluator,
                _telemetryStorageProducer, // TelemetryEvaluation instance
                _telemetryStorageProducer, // TelemetryConfiguration instance
                flagSetsFilter);

        // SplitManager
        _manager = new SplitManagerImpl(splitCache, config, _gates, _telemetryStorageProducer);

        // SyncManager
        SplitTasks splitTasks = SplitTasks.build(_splitSynchronizationTask, _segmentSynchronizationTaskImp,
                _impressionsManager, _eventsTask, _telemetrySyncTask, _uniqueKeysTracker);
        SplitAPI splitAPI = SplitAPI.build(_splitHttpClient, buildSSEdHttpClient(apiToken, config, _sdkMetadata),
                _requestDecorator);

        _syncManager = SyncManagerImp.build(splitTasks, _splitFetcher, splitCache, splitAPI,
                segmentCache, _gates, _telemetryStorageProducer, _telemetrySynchronizer, config, splitParser,
                flagSetsFilter);
        _syncManager.start();

        // DestroyOnShutDown
        if (config.destroyOnShutDown()) {
            Thread shutdown = new Thread(() -> {
                // Using the full path to avoid conflicting with Thread.destroy()
                SplitFactoryImpl.this.destroy();
            });
            shutdown.setName("split-destroy-worker");
            Runtime.getRuntime().addShutdownHook(shutdown);
        }
    }

    // Constructor for consumer mode
    protected SplitFactoryImpl(String apiToken, SplitClientConfig config, CustomStorageWrapper customStorageWrapper)
            throws URISyntaxException {
        // Variables that are not used in Consumer mode.
        _segmentSynchronizationTaskImp = null;
        _splitFetcher = null;
        _splitSynchronizationTask = null;
        _eventsTask = null;
        _splitHttpClient = null;
        _rootTarget = null;
        _eventsRootTarget = null;

        Metadata metadata = new Metadata(config.ipAddressEnabled(), SplitClientConfig.splitSdkVersion);
        _userStorageWrapper = new UserStorageWrapper(customStorageWrapper);
        UserCustomSegmentAdapterConsumer userCustomSegmentAdapterConsumer = new UserCustomSegmentAdapterConsumer(
                customStorageWrapper);
        UserCustomSplitAdapterConsumer userCustomSplitAdapterConsumer = new UserCustomSplitAdapterConsumer(
                customStorageWrapper);
        // TODO migrate impressions sender to Task instead manager and not instantiate
        // Producer here.
        UserCustomImpressionAdapterConsumer userCustomImpressionAdapterConsumer = new UserCustomImpressionAdapterConsumer();
        UserCustomImpressionAdapterProducer userCustomImpressionAdapterProducer = new UserCustomImpressionAdapterProducer(
                customStorageWrapper,
                metadata);
        UserCustomEventAdapterProducer userCustomEventAdapterProducer = new UserCustomEventAdapterProducer(
                customStorageWrapper, metadata);

        _operationMode = config.operationMode();
        _sdkMetadata = createSdkMetadata(config.ipAddressEnabled(), SplitClientConfig.splitSdkVersion);
        _startTime = System.currentTimeMillis();
        _apiToken = apiToken;
        _apiKeyCounter = ApiKeyCounter.getApiKeyCounterInstance();
        _apiKeyCounter.add(apiToken);
        _telemetryStorageProducer = new UserCustomTelemetryAdapterProducer(customStorageWrapper, _sdkMetadata);

        _splitCache = userCustomSplitAdapterConsumer;
        _segmentCache = userCustomSegmentAdapterConsumer;

        if (config.blockUntilReady() == -1) {
            // BlockUntilReady not been set
            _log.warn(
                    "no setBlockUntilReadyTimeout parameter has been set - incorrect control treatments could be logged” "
                            +
                            "if no ready config has been set when building factory");
        }

        // SDKReadinessGates
        _gates = new SDKReadinessGates();

        _telemetrySynchronizer = new TelemetryConsumerSubmitter(customStorageWrapper, _sdkMetadata);
        _evaluator = new EvaluatorImp(userCustomSplitAdapterConsumer, userCustomSegmentAdapterConsumer);
        _impressionsSender = PluggableImpressionSender.create(customStorageWrapper);
        _uniqueKeysTracker = createUniqueKeysTracker(config);
        _impressionsManager = buildImpressionsManager(config, userCustomImpressionAdapterConsumer,
                userCustomImpressionAdapterProducer);
        _telemetrySyncTask = new TelemetrySyncTask(config.getTelemetryRefreshRate(), _telemetrySynchronizer,
                config.getThreadFactory());

        SplitTasks splitTasks = SplitTasks.build(null, null,
                _impressionsManager, null, _telemetrySyncTask, _uniqueKeysTracker);

        // Synchronizer
        Synchronizer synchronizer = new ConsumerSynchronizer(splitTasks);
        FlagSetsFilter flagSetsFilter = new FlagSetsFilterImpl(new HashSet<>());
        if (!config.getSetsFilter().isEmpty()) {
            _log.warn(
                    "FlagSets filter is not applicable for Consumer modes where the SDK does not keep rollout data in sync. FlagSet "
                            +
                            "filter was discarded");
        }
        _client = new SplitClientImpl(this,
                userCustomSplitAdapterConsumer,
                _impressionsManager,
                userCustomEventAdapterProducer,
                config,
                _gates,
                _evaluator,
                _telemetryStorageProducer, // TelemetryEvaluation instance
                _telemetryStorageProducer, // TelemetryConfiguration instance
                flagSetsFilter);

        // SyncManager
        _syncManager = new ConsumerSyncManager(synchronizer);
        _syncManager.start();

        _manager = new SplitManagerImpl(userCustomSplitAdapterConsumer, config, _gates, _telemetryStorageProducer);
        manageSdkReady(config);
    }

    // Localhost
    protected SplitFactoryImpl(SplitClientConfig config) {
        _userStorageWrapper = null;
        _apiToken = "localhost";
        _apiKeyCounter = ApiKeyCounter.getApiKeyCounterInstance();
        _apiKeyCounter.add("localhost");
        _sdkMetadata = createSdkMetadata(config.ipAddressEnabled(), SplitClientConfig.splitSdkVersion);
        _telemetrySynchronizer = null;
        _telemetrySyncTask = null;
        _eventsTask = null;
        _splitHttpClient = null;
        _impressionsSender = null;
        _rootTarget = null;
        _eventsRootTarget = null;
        _uniqueKeysTracker = null;
        _telemetryStorageProducer = new NoopTelemetryStorage();

        SegmentCache segmentCache = new SegmentCacheInMemoryImpl();
        FlagSetsFilter flagSetsFilter = new FlagSetsFilterImpl(config.getSetsFilter());
        SplitCache splitCache = new InMemoryCacheImp(flagSetsFilter);
        _splitCache = splitCache;
        _gates = new SDKReadinessGates();
        _segmentCache = segmentCache;

        // SegmentFetcher

        SegmentChangeFetcher segmentChangeFetcher = new LocalhostSegmentFetcherNoop();
        if (config.segmentDirectory() != null) {
            segmentChangeFetcher = new LocalhostSegmentChangeFetcher(config.segmentDirectory());
        }

        _segmentSynchronizationTaskImp = new SegmentSynchronizationTaskImp(segmentChangeFetcher,
                config.segmentsRefreshRate(),
                config.numThreadsForSegmentFetch(),
                segmentCache,
                _telemetryStorageProducer,
                _splitCache,
                config.getThreadFactory());

        // SplitFetcher
        SplitChangeFetcher splitChangeFetcher = createSplitChangeFetcher(config);
        SplitParser splitParser = new SplitParser();

        _splitFetcher = new SplitFetcherImp(splitChangeFetcher, splitParser, splitCache, _telemetryStorageProducer,
                flagSetsFilter);

        // SplitSynchronizationTask
        _splitSynchronizationTask = new SplitSynchronizationTask(_splitFetcher, splitCache,
                config.featuresRefreshRate(), config.getThreadFactory());

        _impressionsManager = new ImpressionsManager.NoOpImpressionsManager();

        SplitTasks splitTasks = SplitTasks.build(_splitSynchronizationTask, _segmentSynchronizationTaskImp,
                _impressionsManager, null, null, null);

        // Evaluator
        _evaluator = new EvaluatorImp(splitCache, segmentCache);

        EventsStorage eventsStorage = new NoopEventsStorageImp();

        // SplitClient
        _client = new SplitClientImpl(this,
                splitCache,
                _impressionsManager,
                eventsStorage,
                config,
                _gates,
                _evaluator,
                _telemetryStorageProducer, // TelemetryEvaluation instance
                _telemetryStorageProducer, // TelemetryConfiguration instance
                flagSetsFilter);

        // Synchronizer
        Synchronizer synchronizer = new LocalhostSynchronizer(splitTasks, _splitFetcher,
                config.localhostRefreshEnabled());

        // SplitManager
        _manager = new SplitManagerImpl(splitCache, config, _gates, _telemetryStorageProducer);
        // SyncManager
        _syncManager = new LocalhostSyncManager(synchronizer, _gates);
        _syncManager.start();

        // DestroyOnShutDown
        if (config.destroyOnShutDown()) {
            Thread shutdown = new Thread(() -> {
                // Using the full path to avoid conflicting with Thread.destroy()
                SplitFactoryImpl.this.destroy();
            });
            shutdown.setName("split-destroy-worker");
            Runtime.getRuntime().addShutdownHook(shutdown);
        }
    }

    @Override
    public SplitClient client() {
        return _client;
    }

    @Override
    public SplitManager manager() {
        return _manager;
    }

    @Override
    public synchronized void destroy() {
        if (isTerminated) {
            return;
        }
        try {
            _log.info("Shutdown called for split");
            _syncManager.shutdown();
            _log.info("Successful shutdown of syncManager");
            if (OperationMode.STANDALONE.equals(_operationMode)) {
                _telemetryStorageProducer.recordSessionLength(System.currentTimeMillis() - _startTime);
            } else if (OperationMode.CONSUMER.equals(_operationMode)) {
                _userStorageWrapper.disconnect();
            }
        } catch (IOException e) {
            _log.error("We could not shutdown split", e);
        }
        _apiKeyCounter.remove(_apiToken);
        isTerminated = true;
    }

    @Override
    public boolean isDestroyed() {
        return isTerminated;
    }

    protected static SplitHttpClient buildSplitHttpClient(String apiToken, SplitClientConfig config,
            SDKMetadata sdkMetadata, RequestDecorator requestDecorator)
            throws URISyntaxException {
        SSLConnectionSocketFactory sslSocketFactory = SSLConnectionSocketFactoryBuilder.create()
                .setSslContext(SSLContexts.createSystemDefault())
                .setTlsVersions(TLS.V_1_1, TLS.V_1_2)
                .build();

        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectTimeout(Timeout.ofMilliseconds(config.connectionTimeout()))
                .setCookieSpec(StandardCookieSpec.STRICT)
                .build();

        PoolingHttpClientConnectionManager cm = PoolingHttpClientConnectionManagerBuilder.create()
                .setSSLSocketFactory(sslSocketFactory)
                .setDefaultSocketConfig(SocketConfig.custom()
                        .setSoTimeout(Timeout.ofMilliseconds(config.readTimeout()))
                        .build())
                .setValidateAfterInactivity(TimeValue.ofMilliseconds(config.validateAfterInactivityInMillis()))
                .build();
        cm.setMaxTotal(20);
        cm.setDefaultMaxPerRoute(20);

        HttpClientBuilder httpClientbuilder = HttpClients.custom()
                .setConnectionManager(cm)
                .setDefaultRequestConfig(requestConfig)
                .addRequestInterceptorLast(new GzipEncoderRequestInterceptor())
                .addResponseInterceptorLast((new GzipDecoderResponseInterceptor()));

        // Set up proxy is it exists
        if (config.proxy() != null) {
            httpClientbuilder = setupProxy(httpClientbuilder, config);
        }

        return SplitHttpClientImpl.create(httpClientbuilder.build(),
                requestDecorator,
                apiToken,
                sdkMetadata);
    }

    private static CloseableHttpClient buildSSEdHttpClient(String apiToken, SplitClientConfig config,
            SDKMetadata sdkMetadata) {
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectTimeout(Timeout.ofMilliseconds(SSE_CONNECT_TIMEOUT))
                .build();

        SSLConnectionSocketFactory sslSocketFactory = SSLConnectionSocketFactoryBuilder.create()
                .setSslContext(SSLContexts.createSystemDefault())
                .setTlsVersions(TLS.V_1_1, TLS.V_1_2)
                .build();

        PoolingHttpClientConnectionManager cm = PoolingHttpClientConnectionManagerBuilder.create()
                .setSSLSocketFactory(sslSocketFactory)
                .setDefaultSocketConfig(SocketConfig.custom()
                        .setSoTimeout(Timeout.ofMilliseconds(SSE_SOCKET_TIMEOUT))
                        .build())
                .build();
        cm.setMaxTotal(1);
        cm.setDefaultMaxPerRoute(1);

        HttpClientBuilder httpClientbuilder = HttpClients.custom()
                .setConnectionManager(cm)
                .setDefaultRequestConfig(requestConfig)
                .addRequestInterceptorLast(SdkMetadataInterceptorFilter.instance(sdkMetadata))
                .addRequestInterceptorLast(ClientKeyInterceptorFilter.instance(apiToken));

        // Set up proxy is it exists
        if (config.proxy() != null) {
            httpClientbuilder = setupProxy(httpClientbuilder, config);
        }

        return httpClientbuilder.build();
    }

    private static HttpClientBuilder setupProxy(HttpClientBuilder httpClientbuilder, SplitClientConfig config) {
        _log.info("Initializing Split SDK with proxy settings");
        DefaultProxyRoutePlanner routePlanner = new DefaultProxyRoutePlanner(config.proxy());
        httpClientbuilder.setRoutePlanner(routePlanner);

        if (config.proxyUsername() != null && config.proxyPassword() != null) {
            _log.debug("Proxy setup using credentials");
            BasicCredentialsProvider credsProvider = new BasicCredentialsProvider();
            AuthScope siteScope = new AuthScope(config.proxy().getHostName(), config.proxy().getPort());
            Credentials siteCreds = new UsernamePasswordCredentials(config.proxyUsername(),
                    config.proxyPassword().toCharArray());
            credsProvider.setCredentials(siteScope, siteCreds);
            httpClientbuilder.setDefaultCredentialsProvider(credsProvider);
        }

        return httpClientbuilder;
    }

    private SegmentSynchronizationTaskImp buildSegments(SplitClientConfig config,
            SegmentCacheProducer segmentCacheProducer,
            SplitCacheConsumer splitCacheConsumer) throws URISyntaxException {
        SegmentChangeFetcher segmentChangeFetcher = HttpSegmentChangeFetcher.create(_splitHttpClient, _rootTarget,
                _telemetryStorageProducer);

        return new SegmentSynchronizationTaskImp(segmentChangeFetcher,
                config.segmentsRefreshRate(),
                config.numThreadsForSegmentFetch(),
                segmentCacheProducer,
                _telemetryStorageProducer,
                splitCacheConsumer,
                config.getThreadFactory());
    }

    private SplitFetcher buildSplitFetcher(SplitCacheProducer splitCacheProducer, SplitParser splitParser,
            FlagSetsFilter flagSetsFilter) throws URISyntaxException {
        SplitChangeFetcher splitChangeFetcher = HttpSplitChangeFetcher.create(_splitHttpClient, _rootTarget,
                _telemetryStorageProducer);
        return new SplitFetcherImp(splitChangeFetcher, splitParser, splitCacheProducer, _telemetryStorageProducer,
                flagSetsFilter);
    }

    private ImpressionsManagerImpl buildImpressionsManager(SplitClientConfig config,
            ImpressionsStorageConsumer impressionsStorageConsumer,
            ImpressionsStorageProducer impressionsStorageProducer) throws URISyntaxException {
        List impressionListeners = new ArrayList<>();
        if (config.integrationsConfig() != null) {
            config.integrationsConfig().getImpressionsListeners(IntegrationsConfig.Execution.ASYNC).stream()
                    .map(l -> AsynchronousImpressionListener.build(l.listener(), l.queueSize()))
                    .collect(Collectors.toCollection(() -> impressionListeners));

            config.integrationsConfig().getImpressionsListeners(IntegrationsConfig.Execution.SYNC).stream()
                    .map(IntegrationsConfig.ImpressionListenerWithMeta::listener)
                    .collect(Collectors.toCollection(() -> impressionListeners));
        }
        ProcessImpressionStrategy processImpressionStrategy = null;
        ImpressionCounter counter = null;
        ImpressionListener listener = !impressionListeners.isEmpty()
                ? new ImpressionListener.FederatedImpressionListener(impressionListeners)
                : null;
        switch (config.impressionsMode()) {
            case OPTIMIZED:
                counter = new ImpressionCounter();
                ImpressionObserver impressionObserver = new ImpressionObserver(config.getLastSeenCacheSize());
                processImpressionStrategy = new ProcessImpressionOptimized(listener != null, impressionObserver,
                        counter, _telemetryStorageProducer);
                break;
            case DEBUG:
                impressionObserver = new ImpressionObserver(config.getLastSeenCacheSize());
                processImpressionStrategy = new ProcessImpressionDebug(listener != null, impressionObserver);
                break;
            case NONE:
                counter = new ImpressionCounter();
                processImpressionStrategy = new ProcessImpressionNone(listener != null, _uniqueKeysTracker, counter);
                break;
        }
        return ImpressionsManagerImpl.instance(config, _telemetryStorageProducer, impressionsStorageConsumer,
                impressionsStorageProducer,
                _impressionsSender, processImpressionStrategy, counter, listener);
    }

    private SDKMetadata createSdkMetadata(boolean ipAddressEnabled, String splitSdkVersion) {
        String machineName = "";
        String ip = "";

        if (ipAddressEnabled) {
            try {
                InetAddress localHost = InetAddress.getLocalHost();
                machineName = localHost.getHostName();
                ip = localHost.getHostAddress();
            } catch (Exception e) {
                _log.error("Could not resolve InetAddress", e);
            }
        }
        return new SDKMetadata(splitSdkVersion, ip, machineName);
    }

    private void manageSdkReady(SplitClientConfig config) {
        ExecutorService executorService = buildExecutorService(config.getThreadFactory(),
                "SPLIT-SDKReadyForConsumer-%d");
        executorService.submit(() -> {
            while (!_userStorageWrapper.connect()) {
                try {
                    Thread.currentThread().sleep(1000);
                } catch (InterruptedException e) {
                    _log.warn("Sdk Initializer thread interrupted");
                    Thread.currentThread().interrupt();
                }
            }
            _gates.sdkInternalReady();
            _telemetrySynchronizer.synchronizeConfig(config, System.currentTimeMillis(),
                    ApiKeyCounter.getApiKeyCounterInstance().getFactoryInstances(), new ArrayList<>());
        });
    }

    private UniqueKeysTracker createUniqueKeysTracker(SplitClientConfig config) {
        if (config.impressionsMode().equals(ImpressionsManager.Mode.NONE)) {
            int uniqueKeysRefreshRate = config.operationMode().equals(OperationMode.STANDALONE)
                    ? config.uniqueKeysRefreshRateInMemory()
                    : config.uniqueKeysRefreshRateRedis();
            return new UniqueKeysTrackerImp(_telemetrySynchronizer, uniqueKeysRefreshRate,
                    config.filterUniqueKeysRefreshRate(),
                    config.getThreadFactory());
        }
        return null;
    }

    private SplitChangeFetcher createSplitChangeFetcher(SplitClientConfig splitClientConfig) {
        String splitFile = splitClientConfig.splitFile();
        InputStream inputStream = splitClientConfig.inputStream();
        FileTypeEnum fileType = splitClientConfig.fileType();
        InputStreamProvider inputStreamProvider;
        if (splitFile != null || !isInputStreamConfigValid(inputStream, fileType)) {
            if (splitFile == null) {
                _log.warn("The InputStream config is invalid");
            }
            fileType = getFileTypeFromFileName(splitFile);
            inputStreamProvider = new FileInputStreamProvider(splitFile);
        } else {
            inputStreamProvider = new StaticContentInputStreamProvider(inputStream);
        }
        try {
            switch (fileType) {
                case JSON:
                    return new JsonLocalhostSplitChangeFetcher(inputStreamProvider);
                case YAML:
                case YML:
                    return new YamlLocalhostSplitChangeFetcher(inputStreamProvider);
                default:
                    _log.warn(LEGACY_LOG_MESSAGE);
                    return new LegacyLocalhostSplitChangeFetcher(splitFile);
            }
        } catch (Exception e) {
            _log.warn(String.format("There was no file named %s found. " +
                    "We created a split client that returns default treatments for all feature flags for all of your users. "
                    +
                    "If you wish to return a specific treatment for a feature flag, enter the name of that feature flag name and "
                    +
                    "treatment name separated by whitespace in %s; one pair per line. Empty lines or lines starting with '#' are "
                    +
                    "considered comments",
                    splitFile, splitFile), e);
        }
        _log.warn(LEGACY_LOG_MESSAGE);
        return new LegacyLocalhostSplitChangeFetcher(splitFile);
    }

    private Boolean isInputStreamConfigValid(InputStream inputStream, FileTypeEnum fileType) {
        return inputStream != null && fileType != null;
    }

    private FileTypeEnum getFileTypeFromFileName(String fileName) {
        try {
            return FileTypeEnum.valueOf(Files.getFileExtension(fileName).toUpperCase());
        } catch (Exception e) {
            return FileTypeEnum.LEGACY;
        }

    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy