1 package io.getunleash.util;
2
3 import static io.getunleash.DefaultUnleash.UNKNOWN_STRATEGY;
4
5 import io.getunleash.CustomHttpHeadersProvider;
6 import io.getunleash.DefaultCustomHttpHeadersProviderImpl;
7 import io.getunleash.UnleashContextProvider;
8 import io.getunleash.UnleashException;
9 import io.getunleash.event.NoOpSubscriber;
10 import io.getunleash.event.UnleashSubscriber;
11 import io.getunleash.lang.Nullable;
12 import io.getunleash.metric.DefaultHttpMetricsSender;
13 import io.getunleash.repository.HttpFeatureFetcher;
14 import io.getunleash.repository.ToggleBootstrapProvider;
15 import io.getunleash.strategy.Strategy;
16 import java.io.File;
17 import java.math.BigInteger;
18 import java.net.*;
19 import java.nio.charset.StandardCharsets;
20 import java.security.MessageDigest;
21 import java.security.NoSuchAlgorithmException;
22 import java.time.Duration;
23 import java.util.HashMap;
24 import java.util.Map;
25 import java.util.Optional;
26 import java.util.UUID;
27 import java.util.function.Consumer;
28
29 public class UnleashConfig {
30
31 public static final String LEGACY_UNLEASH_APP_NAME_HEADER = "UNLEASH-APPNAME";
32 public static final String UNLEASH_INSTANCE_ID_HEADER = "UNLEASH-INSTANCEID";
33 public static final String UNLEASH_CONNECTION_ID_HEADER = "X-UNLEASH-CONNECTION-ID";
34 public static final String UNLEASH_APP_NAME_HEADER = "X-UNLEASH-APPNAME";
35 public static final String UNLEASH_SDK_HEADER = "X-UNLEASH-SDK";
36
37 private final URI unleashAPI;
38 private final UnleashURLs unleashURLs;
39 private final Map<String, String> customHttpHeaders;
40 private final CustomHttpHeadersProvider customHttpHeadersProvider;
41 private final String appName;
42 private final String environment;
43 private final String instanceId;
44 private final String connectionId;
45 private final String sdkVersion;
46 private final String backupFile;
47
48 private final String clientSpecificationVersion;
49 @Nullable private final String projectName;
50 @Nullable private final String namePrefix;
51 private final long fetchTogglesInterval;
52
53 private final Duration fetchTogglesConnectTimeout;
54
55 private final Duration fetchTogglesReadTimeout;
56
57 private final boolean disablePolling;
58 private final long sendMetricsInterval;
59
60 private final Duration sendMetricsConnectTimeout;
61
62 private final Duration sendMetricsReadTimeout;
63 private final boolean disableMetrics;
64 private final boolean isProxyAuthenticationByJvmProperties;
65 private final UnleashFeatureFetcherFactory unleashFeatureFetcherFactory;
66
67 private final MetricSenderFactory metricSenderFactory;
68
69 private final UnleashContextProvider contextProvider;
70 private final boolean synchronousFetchOnInitialisation;
71 private final UnleashScheduledExecutor unleashScheduledExecutor;
72 private final UnleashSubscriber unleashSubscriber;
73 @Nullable private final Strategy fallbackStrategy;
74 @Nullable private final ToggleBootstrapProvider toggleBootstrapProvider;
75 @Nullable private final Proxy proxy;
76 @Nullable private final Consumer<UnleashException> startupExceptionHandler;
77
78 private UnleashConfig(
79 @Nullable URI unleashAPI,
80 Map<String, String> customHttpHeaders,
81 CustomHttpHeadersProvider customHttpHeadersProvider,
82 @Nullable String appName,
83 String environment,
84 @Nullable String instanceId,
85 String connectionId,
86 String sdkVersion,
87 String backupFile,
88 @Nullable String projectName,
89 @Nullable String namePrefix,
90 long fetchTogglesInterval,
91 Duration fetchTogglesConnectTimeout,
92 Duration fetchTogglesReadTimeout,
93 boolean disablePolling,
94 long sendMetricsInterval,
95 Duration sendMetricsConnectTimeout,
96 Duration sendMetricsReadTimeout,
97 boolean disableMetrics,
98 UnleashContextProvider contextProvider,
99 boolean isProxyAuthenticationByJvmProperties,
100 boolean synchronousFetchOnInitialisation,
101 UnleashFeatureFetcherFactory unleashFeatureFetcherFactory,
102 MetricSenderFactory metricSenderFactory,
103 @Nullable UnleashScheduledExecutor unleashScheduledExecutor,
104 @Nullable UnleashSubscriber unleashSubscriber,
105 @Nullable Strategy fallbackStrategy,
106 @Nullable ToggleBootstrapProvider unleashBootstrapProvider,
107 @Nullable Proxy proxy,
108 @Nullable Authenticator proxyAuthenticator,
109 @Nullable Consumer<UnleashException> startupExceptionHandler) {
110
111 if (appName == null) {
112 throw new IllegalStateException("You are required to specify the unleash appName");
113 }
114
115 if (instanceId == null) {
116 throw new IllegalStateException("You are required to specify the unleash instanceId");
117 }
118
119 if (unleashAPI == null) {
120 throw new IllegalStateException("You are required to specify the unleashAPI url");
121 }
122
123 if (unleashScheduledExecutor == null) {
124 throw new IllegalStateException("You are required to specify a scheduler");
125 }
126
127 if (unleashSubscriber == null) {
128 throw new IllegalStateException("You are required to specify a subscriber");
129 }
130
131 if (fallbackStrategy == null) {
132 this.fallbackStrategy = UNKNOWN_STRATEGY;
133 } else {
134 this.fallbackStrategy = fallbackStrategy;
135 }
136
137 if (isProxyAuthenticationByJvmProperties && proxyAuthenticator == null) {
138 enableProxyAuthentication();
139 } else if (proxyAuthenticator != null) {
140 Authenticator.setDefault(proxyAuthenticator);
141 }
142
143 this.unleashAPI = unleashAPI;
144 this.customHttpHeaders = customHttpHeaders;
145 this.customHttpHeadersProvider = customHttpHeadersProvider;
146 this.unleashURLs = new UnleashURLs(unleashAPI);
147 this.appName = appName;
148 this.environment = environment;
149 this.instanceId = instanceId;
150 this.connectionId = connectionId;
151 this.sdkVersion = sdkVersion;
152 this.backupFile = backupFile;
153 this.projectName = projectName;
154 this.namePrefix = namePrefix;
155 this.fetchTogglesInterval = fetchTogglesInterval;
156 this.fetchTogglesConnectTimeout = fetchTogglesConnectTimeout;
157 this.fetchTogglesReadTimeout = fetchTogglesReadTimeout;
158 this.disablePolling = disablePolling;
159 this.sendMetricsInterval = sendMetricsInterval;
160 this.sendMetricsConnectTimeout = sendMetricsConnectTimeout;
161 this.sendMetricsReadTimeout = sendMetricsReadTimeout;
162 this.disableMetrics = disableMetrics;
163 this.contextProvider = contextProvider;
164 this.isProxyAuthenticationByJvmProperties = isProxyAuthenticationByJvmProperties;
165 this.synchronousFetchOnInitialisation = synchronousFetchOnInitialisation;
166 this.unleashScheduledExecutor = unleashScheduledExecutor;
167 this.unleashSubscriber = unleashSubscriber;
168 this.toggleBootstrapProvider = unleashBootstrapProvider;
169 this.proxy = proxy;
170 this.unleashFeatureFetcherFactory = unleashFeatureFetcherFactory;
171 this.metricSenderFactory = metricSenderFactory;
172 this.clientSpecificationVersion =
173 UnleashProperties.getProperty("client.specification.version");
174 this.startupExceptionHandler = startupExceptionHandler;
175 }
176
177 public static Builder builder() {
178 return new Builder();
179 }
180
181 public static void setRequestProperties(HttpURLConnection connection, UnleashConfig config) {
182 connection.setRequestProperty(LEGACY_UNLEASH_APP_NAME_HEADER, config.getAppName());
183 connection.setRequestProperty(UNLEASH_APP_NAME_HEADER, config.getAppName());
184 connection.setRequestProperty(UNLEASH_INSTANCE_ID_HEADER, config.getInstanceId());
185 connection.setRequestProperty(UNLEASH_CONNECTION_ID_HEADER, config.getConnectionId());
186 connection.setRequestProperty(UNLEASH_SDK_HEADER, config.getSdkVersion());
187 connection.setRequestProperty("User-Agent", config.getAppName());
188 connection.setRequestProperty(
189 "Unleash-Client-Spec", config.getClientSpecificationVersion());
190 config.getCustomHttpHeaders().forEach(connection::setRequestProperty);
191 config.customHttpHeadersProvider.getCustomHeaders().forEach(connection::setRequestProperty);
192 }
193
194 private void enableProxyAuthentication() {
195
196
197 Authenticator.setDefault(new SystemProxyAuthenticator());
198 }
199
200 public URI getUnleashAPI() {
201 return unleashAPI;
202 }
203
204 public Map<String, String> getCustomHttpHeaders() {
205 return customHttpHeaders;
206 }
207
208 public CustomHttpHeadersProvider getCustomHttpHeadersProvider() {
209 return customHttpHeadersProvider;
210 }
211
212 public String getAppName() {
213 return appName;
214 }
215
216 public String getEnvironment() {
217 return environment;
218 }
219
220 public String getInstanceId() {
221 return instanceId;
222 }
223
224 String getConnectionId() {
225 return connectionId;
226 }
227
228 public String getSdkVersion() {
229 return sdkVersion;
230 }
231
232 public String getClientSpecificationVersion() {
233 return clientSpecificationVersion;
234 }
235
236 public @Nullable String getProjectName() {
237 return projectName;
238 }
239
240 public long getFetchTogglesInterval() {
241 return fetchTogglesInterval;
242 }
243
244 public Duration getFetchTogglesConnectTimeout() {
245 return fetchTogglesConnectTimeout;
246 }
247
248 public Duration getFetchTogglesReadTimeout() {
249 return fetchTogglesReadTimeout;
250 }
251
252 public boolean isDisablePolling() {
253 return disablePolling;
254 }
255
256 public long getSendMetricsInterval() {
257 return sendMetricsInterval;
258 }
259
260 public Duration getSendMetricsConnectTimeout() {
261 return sendMetricsConnectTimeout;
262 }
263
264 public Duration getSendMetricsReadTimeout() {
265 return sendMetricsReadTimeout;
266 }
267
268 public UnleashURLs getUnleashURLs() {
269 return unleashURLs;
270 }
271
272 public boolean isDisableMetrics() {
273 return disableMetrics;
274 }
275
276 public String getBackupFile() {
277 return this.backupFile;
278 }
279
280 @Nullable
281 public String getApiKey() {
282 String auth = this.customHttpHeadersProvider.getCustomHeaders().get("Authorization");
283 if (auth == null) {
284 auth = this.customHttpHeaders.get("Authorization");
285 }
286 return auth;
287 }
288
289 public String getClientIdentifier() {
290 try {
291 MessageDigest md = MessageDigest.getInstance("SHA-256");
292 if (getApiKey() != null) {
293 md.update(getApiKey().getBytes(StandardCharsets.UTF_8));
294 }
295 md.update(getAppName().getBytes(StandardCharsets.UTF_8));
296 md.update(getInstanceId().getBytes(StandardCharsets.UTF_8));
297 return new BigInteger(1, md.digest()).toString(16);
298 } catch (NoSuchAlgorithmException nse) {
299 throw new IllegalStateException("Could not build hash for client", nse);
300 }
301 }
302
303 public boolean isSynchronousFetchOnInitialisation() {
304 return synchronousFetchOnInitialisation;
305 }
306
307 public UnleashContextProvider getContextProvider() {
308 return contextProvider;
309 }
310
311 public UnleashScheduledExecutor getScheduledExecutor() {
312 return unleashScheduledExecutor;
313 }
314
315 public UnleashSubscriber getSubscriber() {
316 return unleashSubscriber;
317 }
318
319 public boolean isProxyAuthenticationByJvmProperties() {
320 return isProxyAuthenticationByJvmProperties;
321 }
322
323 @Nullable
324 public Strategy getFallbackStrategy() {
325 return fallbackStrategy;
326 }
327
328 @Nullable
329 public ToggleBootstrapProvider getToggleBootstrapProvider() {
330 return toggleBootstrapProvider;
331 }
332
333 @Nullable
334 public String getNamePrefix() {
335 return namePrefix;
336 }
337
338 @Nullable
339 public Proxy getProxy() {
340 return proxy;
341 }
342
343 public MetricSenderFactory getMetricSenderFactory() {
344 return this.metricSenderFactory;
345 }
346
347 public UnleashFeatureFetcherFactory getUnleashFeatureFetcherFactory() {
348 return this.unleashFeatureFetcherFactory;
349 }
350
351 @Nullable
352 public Consumer<UnleashException> getStartupExceptionHandler() {
353 return startupExceptionHandler;
354 }
355
356 static class SystemProxyAuthenticator extends Authenticator {
357 @Override
358 protected @Nullable PasswordAuthentication getPasswordAuthentication() {
359 if (getRequestorType() == RequestorType.PROXY) {
360 final String proto = getRequestingProtocol().toLowerCase();
361 final String proxyHost = System.getProperty(proto + ".proxyHost", "");
362 final String proxyPort = System.getProperty(proto + ".proxyPort", "");
363 final String proxyUser = System.getProperty(proto + ".proxyUser", "");
364 final String proxyPassword = System.getProperty(proto + ".proxyPassword", "");
365
366
367
368 if (getRequestingHost().equalsIgnoreCase(proxyHost)
369 && Integer.parseInt(proxyPort) == getRequestingPort()) {
370 return new PasswordAuthentication(proxyUser, proxyPassword.toCharArray());
371 }
372 }
373 return null;
374 }
375 }
376
377 static class CustomProxyAuthenticator extends Authenticator {
378
379 private final Proxy proxy;
380 private final String proxyUser;
381 private final String proxyPassword;
382
383 public CustomProxyAuthenticator(Proxy proxy, String proxyUser, String proxyPassword) {
384 this.proxy = proxy;
385 this.proxyUser = proxyUser;
386 this.proxyPassword = proxyPassword;
387 }
388
389 @Override
390 protected @Nullable PasswordAuthentication getPasswordAuthentication() {
391 if (getRequestorType() == RequestorType.PROXY
392 && proxy.type() == Proxy.Type.HTTP
393 && proxy.address() instanceof InetSocketAddress) {
394 final String proxyHost = ((InetSocketAddress) proxy.address()).getHostName();
395 final int proxyPort = ((InetSocketAddress) proxy.address()).getPort();
396
397
398
399
400 if (getRequestingHost().equalsIgnoreCase(proxyHost)
401 && proxyPort == getRequestingPort()) {
402 return new PasswordAuthentication(proxyUser, proxyPassword.toCharArray());
403 }
404 }
405 return null;
406 }
407 }
408
409 public static class Builder {
410
411 private @Nullable URI unleashAPI;
412 private Map<String, String> customHttpHeaders = new HashMap<>();
413 private CustomHttpHeadersProvider customHttpHeadersProvider =
414 new DefaultCustomHttpHeadersProviderImpl();
415 private @Nullable String appName;
416 private String environment = "default";
417 private String instanceId = getDefaultInstanceId();
418 private String connectionId = getDefaultConnectionId();
419 private final String sdkVersion = getDefaultSdkVersion();
420 private @Nullable String backupFile;
421 private @Nullable String projectName;
422 private @Nullable String namePrefix;
423 private long fetchTogglesInterval = 10;
424
425 private Duration fetchTogglesConnectTimeout = Duration.ofSeconds(10);
426
427 private Duration fetchTogglesReadTimeout = Duration.ofSeconds(10);
428
429 private boolean disablePolling = false;
430 private long sendMetricsInterval = 60;
431
432 private Duration sendMetricsConnectTimeout = Duration.ofSeconds(10);
433
434 private Duration sendMetricsReadTimeout = Duration.ofSeconds(10);
435 private boolean disableMetrics = false;
436 private UnleashFeatureFetcherFactory unleashFeatureFetcherFactory = HttpFeatureFetcher::new;
437
438 private MetricSenderFactory unleashMetricSenderFactory = DefaultHttpMetricsSender::new;
439 private UnleashContextProvider contextProvider =
440 UnleashContextProvider.getDefaultProvider();
441 private boolean synchronousFetchOnInitialisation = false;
442 private @Nullable UnleashScheduledExecutor scheduledExecutor;
443 private @Nullable UnleashSubscriber unleashSubscriber;
444 private boolean isProxyAuthenticationByJvmProperties;
445 private @Nullable Strategy fallbackStrategy;
446 private @Nullable ToggleBootstrapProvider toggleBootstrapProvider;
447 private @Nullable Proxy proxy;
448 private @Nullable Authenticator proxyAuthenticator;
449
450 private @Nullable Consumer<UnleashException> startupExceptionHandler;
451
452 private static String getHostname() {
453 String hostName = System.getProperty("hostname");
454 if (hostName == null || hostName.isEmpty()) {
455 try {
456 hostName = InetAddress.getLocalHost().getHostName();
457 } catch (UnknownHostException e) {
458 }
459 }
460 return hostName + "-";
461 }
462
463 static String getDefaultInstanceId() {
464 return getHostname() + "generated-" + Math.round(Math.random() * 1000000.0D);
465 }
466
467 static String getDefaultConnectionId() {
468 return UUID.randomUUID().toString();
469 }
470
471 public Builder unleashAPI(URI unleashAPI) {
472 this.unleashAPI = unleashAPI;
473 return this;
474 }
475
476 public Builder unleashAPI(String unleashAPI) {
477 this.unleashAPI = URI.create(unleashAPI);
478 return this;
479 }
480
481 public Builder customHttpHeader(String name, String value) {
482 this.customHttpHeaders.put(name, value);
483 return this;
484 }
485
486 public Builder customHttpHeadersProvider(CustomHttpHeadersProvider provider) {
487 this.customHttpHeadersProvider = provider;
488 return this;
489 }
490
491 public Builder appName(String appName) {
492 this.appName = appName;
493 return this;
494 }
495
496 public Builder environment(String environment) {
497 this.environment = environment;
498 return this;
499 }
500
501 public Builder instanceId(String instanceId) {
502 this.instanceId = instanceId;
503 return this;
504 }
505
506 public Builder projectName(String projectName) {
507 this.projectName = projectName;
508 return this;
509 }
510
511 public Builder namePrefix(String namePrefix) {
512 this.namePrefix = namePrefix;
513 return this;
514 }
515
516 public Builder unleashFeatureFetcherFactory(
517 UnleashFeatureFetcherFactory unleashFeatureFetcherFactory) {
518 this.unleashFeatureFetcherFactory = unleashFeatureFetcherFactory;
519 return this;
520 }
521
522 public Builder metricsSenderFactory(MetricSenderFactory metricSenderFactory) {
523 this.unleashMetricSenderFactory = metricSenderFactory;
524 return this;
525 }
526
527 public Builder fetchTogglesInterval(long fetchTogglesInterval) {
528 this.fetchTogglesInterval = fetchTogglesInterval;
529 return this;
530 }
531
532 public Builder fetchTogglesConnectTimeout(Duration connectTimeout) {
533 this.fetchTogglesConnectTimeout = connectTimeout;
534 return this;
535 }
536
537 public Builder fetchTogglesConnectTimeoutSeconds(long connectTimeoutSeconds) {
538 this.fetchTogglesConnectTimeout = Duration.ofSeconds(connectTimeoutSeconds);
539 return this;
540 }
541
542 public Builder fetchTogglesReadTimeout(Duration readTimeout) {
543 this.fetchTogglesReadTimeout = readTimeout;
544 return this;
545 }
546
547 public Builder fetchTogglesReadTimeoutSeconds(long readTimeoutSeconds) {
548 this.fetchTogglesReadTimeout = Duration.ofSeconds(readTimeoutSeconds);
549 return this;
550 }
551
552 public Builder sendMetricsInterval(long sendMetricsInterval) {
553 this.sendMetricsInterval = sendMetricsInterval;
554 return this;
555 }
556
557
558 public Builder disablePolling() {
559 this.disablePolling = true;
560 return this;
561 }
562
563 public Builder sendMetricsConnectTimeout(Duration connectTimeout) {
564 this.sendMetricsConnectTimeout = connectTimeout;
565 return this;
566 }
567
568 public Builder sendMetricsConnectTimeoutSeconds(long connectTimeoutSeconds) {
569 this.sendMetricsConnectTimeout = Duration.ofSeconds(connectTimeoutSeconds);
570 return this;
571 }
572
573 public Builder sendMetricsReadTimeout(Duration readTimeout) {
574 this.sendMetricsReadTimeout = readTimeout;
575 return this;
576 }
577
578 public Builder sendMetricsReadTimeoutSeconds(long readTimeoutSeconds) {
579 this.sendMetricsReadTimeout = Duration.ofSeconds(readTimeoutSeconds);
580 return this;
581 }
582
583
588 public Builder disableMetrics() {
589 this.disableMetrics = true;
590 return this;
591 }
592
593 public Builder backupFile(String backupFile) {
594 this.backupFile = backupFile;
595 return this;
596 }
597
598 public Builder enableProxyAuthenticationByJvmProperties() {
599 this.isProxyAuthenticationByJvmProperties = true;
600 return this;
601 }
602
603 public Builder unleashContextProvider(UnleashContextProvider contextProvider) {
604 this.contextProvider = contextProvider;
605 return this;
606 }
607
608 public Builder synchronousFetchOnInitialisation(boolean enable) {
609 this.synchronousFetchOnInitialisation = enable;
610 return this;
611 }
612
613 public Builder scheduledExecutor(UnleashScheduledExecutor scheduledExecutor) {
614 this.scheduledExecutor = scheduledExecutor;
615 return this;
616 }
617
618 public Builder subscriber(UnleashSubscriber unleashSubscriber) {
619 this.unleashSubscriber = unleashSubscriber;
620 return this;
621 }
622
623 public Builder fallbackStrategy(@Nullable Strategy fallbackStrategy) {
624 this.fallbackStrategy = fallbackStrategy;
625 return this;
626 }
627
628 public Builder toggleBootstrapProvider(
629 @Nullable ToggleBootstrapProvider toggleBootstrapProvider) {
630 this.toggleBootstrapProvider = toggleBootstrapProvider;
631 return this;
632 }
633
634 public Builder proxy(Proxy proxy) {
635 this.proxy = proxy;
636 return this;
637 }
638
639 public Builder proxy(
640 Proxy proxy, @Nullable String proxyUser, @Nullable String proxyPassword) {
641 this.proxy = proxy;
642
643 if (proxyUser != null && proxyPassword != null) {
644 this.proxyAuthenticator =
645 new CustomProxyAuthenticator(proxy, proxyUser, proxyPassword);
646 }
647 return this;
648 }
649
650 private String getBackupFile() {
651 if (backupFile != null) {
652 return backupFile;
653 } else {
654 String fileName = "unleash-" + sanitizedAppName(appName) + "-repo.json";
655 String tmpDir = System.getProperty("java.io.tmpdir");
656 if (tmpDir == null) {
657 throw new IllegalStateException(
658 "'java.io.tmpdir' must not be empty, cause we write backup files into it.");
659 }
660 tmpDir = !tmpDir.endsWith(File.separator) ? tmpDir + File.separatorChar : tmpDir;
661 return tmpDir + fileName;
662 }
663 }
664
665 private String sanitizedAppName(String appName) {
666 if (null == appName) {
667 return "default";
668 } else if (appName.contains("/") || appName.contains("\\")) {
669 return appName.replace("/", "-").replace("\\", "-");
670 } else {
671 return appName;
672 }
673 }
674
675
681 public Builder apiKey(String apiKey) {
682 this.customHttpHeaders.put("Authorization", apiKey);
683 return this;
684 }
685
686
693 public Builder startupExceptionHandler(
694 @Nullable Consumer<UnleashException> startupExceptionHandler) {
695 this.startupExceptionHandler = startupExceptionHandler;
696 return this;
697 }
698
699 public UnleashConfig build() {
700 return new UnleashConfig(
701 unleashAPI,
702 customHttpHeaders,
703 customHttpHeadersProvider,
704 appName,
705 environment,
706 instanceId,
707 connectionId,
708 sdkVersion,
709 getBackupFile(),
710 projectName,
711 namePrefix,
712 fetchTogglesInterval,
713 fetchTogglesConnectTimeout,
714 fetchTogglesReadTimeout,
715 disablePolling,
716 sendMetricsInterval,
717 sendMetricsConnectTimeout,
718 sendMetricsReadTimeout,
719 disableMetrics,
720 contextProvider,
721 isProxyAuthenticationByJvmProperties,
722 synchronousFetchOnInitialisation,
723 unleashFeatureFetcherFactory,
724 unleashMetricSenderFactory,
725 Optional.ofNullable(scheduledExecutor)
726 .orElseGet(UnleashScheduledExecutorImpl::getInstance),
727 Optional.ofNullable(unleashSubscriber).orElseGet(NoOpSubscriber::new),
728 fallbackStrategy,
729 toggleBootstrapProvider,
730 proxy,
731 proxyAuthenticator,
732 startupExceptionHandler);
733 }
734
735 public String getDefaultSdkVersion() {
736 String version =
737 Optional.ofNullable(getClass().getPackage().getImplementationVersion())
738 .orElse("development");
739 return "unleash-client-java:" + version;
740 }
741 }
742 }
743