├── .circleci └── config.yml ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yml │ └── feature_request.md ├── pull_request_template.md └── workflows │ └── stale.yml ├── .gitignore ├── .ldrelease ├── config.yml └── publish.sh ├── .sdk_metadata.json ├── CHANGELOG.md ├── CODEOWNERS ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── SECURITY.md ├── benchmarks ├── Makefile ├── build.gradle ├── settings.gradle └── src │ └── jmh │ └── java │ └── com │ └── launchdarkly │ └── sdk │ └── server │ ├── LDClientEvaluationBenchmarks.java │ └── TestValues.java ├── build.gradle ├── config └── checkstyle │ ├── checkstyle.xml │ └── suppressions.xml ├── contract-tests ├── README.md ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── service │ ├── build.gradle │ ├── settings.gradle │ └── src │ │ └── main │ │ ├── java │ │ └── sdktest │ │ │ ├── BigSegmentCallbackRepresentation.java │ │ │ ├── BigSegmentCallbackService.java │ │ │ ├── BigSegmentStoreFixture.java │ │ │ ├── HookCallbackService.java │ │ │ ├── MigrationCallbackService.java │ │ │ ├── Representations.java │ │ │ ├── SdkClientEntity.java │ │ │ ├── TestHook.java │ │ │ └── TestService.java │ │ └── resources │ │ └── logback.xml └── settings.gradle ├── gradle.properties ├── gradle.properties.example ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── packaging-test ├── Makefile ├── run-non-osgi-test.sh ├── run-osgi-test.sh └── test-app │ ├── build.gradle │ ├── settings.gradle │ └── src │ └── main │ └── java │ └── testapp │ ├── JsonSerializationTestData.java │ ├── TestApp.java │ ├── TestAppGsonTests.java │ ├── TestAppJacksonTests.java │ └── TestAppOsgiEntryPoint.java ├── scripts ├── release.sh └── update-version.sh ├── settings.gradle └── src ├── main └── java │ └── com │ └── launchdarkly │ └── sdk │ ├── json │ └── SdkSerializationExtensions.java │ └── server │ ├── BigSegmentStoreStatusProviderImpl.java │ ├── BigSegmentStoreWrapper.java │ ├── ClientContextImpl.java │ ├── Components.java │ ├── ComponentsImpl.java │ ├── DataModel.java │ ├── DataModelDependencies.java │ ├── DataModelPreprocessing.java │ ├── DataModelSerialization.java │ ├── DataSourceStatusProviderImpl.java │ ├── DataSourceUpdatesImpl.java │ ├── DataStoreStatusProviderImpl.java │ ├── DataStoreUpdatesImpl.java │ ├── DefaultEventProcessorWrapper.java │ ├── DefaultFeatureRequestor.java │ ├── EvalResult.java │ ├── EvalResultAndFlag.java │ ├── EvaluationOptions.java │ ├── EvaluationRecorder.java │ ├── Evaluator.java │ ├── EvaluatorBucketing.java │ ├── EvaluatorHelpers.java │ ├── EvaluatorInterface.java │ ├── EvaluatorOperators.java │ ├── EvaluatorTypeConversion.java │ ├── EvaluatorWithHooks.java │ ├── EventBroadcasterImpl.java │ ├── FeatureFlagsState.java │ ├── FeatureRequestor.java │ ├── FlagTrackerImpl.java │ ├── FlagsStateOption.java │ ├── InMemoryDataStore.java │ ├── InputValidatingEvaluator.java │ ├── JsonHelpers.java │ ├── LDClient.java │ ├── LDConfig.java │ ├── Loggers.java │ ├── MigrationOp.java │ ├── MigrationOpTracker.java │ ├── MigrationOrigin.java │ ├── MigrationStage.java │ ├── MigrationStageEnforcingEvaluator.java │ ├── MigrationVariation.java │ ├── NoOpEventProcessor.java │ ├── PersistentDataStoreStatusManager.java │ ├── PersistentDataStoreWrapper.java │ ├── PollingProcessor.java │ ├── SemanticVersion.java │ ├── ServerSideDiagnosticEvents.java │ ├── ServerSideEventContextDeduplicator.java │ ├── SimpleLRUCache.java │ ├── StandardEndpoints.java │ ├── StreamProcessor.java │ ├── StreamProcessorEvents.java │ ├── Util.java │ ├── Version.java │ ├── integrations │ ├── ApplicationInfoBuilder.java │ ├── BigSegmentsConfigurationBuilder.java │ ├── EvaluationSeriesContext.java │ ├── EventProcessorBuilder.java │ ├── FileData.java │ ├── FileDataSourceBuilder.java │ ├── FileDataSourceImpl.java │ ├── FileDataSourceParsing.java │ ├── Hook.java │ ├── HookMetadata.java │ ├── HooksConfigurationBuilder.java │ ├── HttpConfigurationBuilder.java │ ├── LoggingConfigurationBuilder.java │ ├── PersistentDataStoreBuilder.java │ ├── PollingDataSourceBuilder.java │ ├── ServiceEndpointsBuilder.java │ ├── StreamingDataSourceBuilder.java │ ├── TestData.java │ ├── WrapperInfoBuilder.java │ ├── package-info.java │ └── reactor │ │ ├── LDReactorClient.java │ │ ├── LDReactorClientInterface.java │ │ └── package-info.java │ ├── interfaces │ ├── ApplicationInfo.java │ ├── BigSegmentStoreStatusProvider.java │ ├── BigSegmentsConfiguration.java │ ├── ConsistencyCheck.java │ ├── DataSourceStatusProvider.java │ ├── DataStoreStatusProvider.java │ ├── FlagChangeEvent.java │ ├── FlagChangeListener.java │ ├── FlagTracker.java │ ├── FlagValueChangeEvent.java │ ├── FlagValueChangeListener.java │ ├── HttpAuthentication.java │ ├── LDClientInterface.java │ ├── ServiceEndpoints.java │ ├── WrapperInfo.java │ └── package-info.java │ ├── migrations │ ├── Migration.java │ ├── MigrationBuilder.java │ ├── MigrationExecution.java │ ├── MigrationExecutionMode.java │ ├── MigrationMethodResult.java │ ├── MigrationSerialOrder.java │ └── package-info.java │ ├── package-info.java │ └── subsystems │ ├── BigSegmentStore.java │ ├── BigSegmentStoreTypes.java │ ├── ClientContext.java │ ├── ComponentConfigurer.java │ ├── DataSource.java │ ├── DataSourceUpdateSink.java │ ├── DataStore.java │ ├── DataStoreTypes.java │ ├── DataStoreUpdateSink.java │ ├── DiagnosticDescription.java │ ├── EventProcessor.java │ ├── EventSender.java │ ├── HookConfiguration.java │ ├── HttpConfiguration.java │ ├── LoggingConfiguration.java │ ├── PersistentDataStore.java │ ├── SerializationException.java │ └── package-info.java ├── templates └── java │ └── com │ └── launchdarkly │ └── sdk │ └── server │ └── Version.java └── test ├── java └── com │ └── launchdarkly │ └── sdk │ └── server │ ├── BaseTest.java │ ├── BigSegmentStoreStatusProviderImplTest.java │ ├── BigSegmentStoreWrapperTest.java │ ├── ClientContextImplTest.java │ ├── DataModelDependenciesTest.java │ ├── DataModelPreprocessingTest.java │ ├── DataModelSerializationTest.java │ ├── DataModelTest.java │ ├── DataSourceStatusProviderImplTest.java │ ├── DataSourceUpdatesImplTest.java │ ├── DataStoreStatusProviderImplTest.java │ ├── DataStoreTestBase.java │ ├── DataStoreTestTypes.java │ ├── DataStoreUpdatesImplTest.java │ ├── DefaultFeatureRequestorTest.java │ ├── EvalResultTest.java │ ├── EvaluatorBigSegmentTest.java │ ├── EvaluatorBucketingTest.java │ ├── EvaluatorClauseTest.java │ ├── EvaluatorOperatorsParameterizedTest.java │ ├── EvaluatorPrerequisiteTest.java │ ├── EvaluatorRuleTest.java │ ├── EvaluatorSegmentMatchTest.java │ ├── EvaluatorTargetTest.java │ ├── EvaluatorTest.java │ ├── EvaluatorTestBase.java │ ├── EvaluatorTestUtil.java │ ├── EvaluatorWithHookTest.java │ ├── EventBroadcasterImplTest.java │ ├── FeatureFlagsStateTest.java │ ├── FlagModelDeserializationTest.java │ ├── FlagTrackerImplTest.java │ ├── InMemoryDataStoreTest.java │ ├── JsonHelpersTest.java │ ├── LDClientBigSegmentsTest.java │ ├── LDClientEndToEndTest.java │ ├── LDClientEvaluationTest.java │ ├── LDClientEventTest.java │ ├── LDClientExternalUpdatesOnlyTest.java │ ├── LDClientListenersTest.java │ ├── LDClientOfflineTest.java │ ├── LDClientTest.java │ ├── LDConfigTest.java │ ├── MigrationBuilderTests.java │ ├── MigrationConsistencyCheckTest.java │ ├── MigrationExecutionFixture.java │ ├── MigrationOpTrackerTests.java │ ├── MigrationStageTests.java │ ├── MigrationStagesExpectedExecutionTests.java │ ├── MigrationTests.java │ ├── MigrationVariationTests.java │ ├── ModelBuilders.java │ ├── PersistentDataStoreWrapperOtherTest.java │ ├── PersistentDataStoreWrapperTest.java │ ├── PollingProcessorTest.java │ ├── RolloutRandomizationConsistencyTest.java │ ├── SemanticVersionTest.java │ ├── ServerSideDiagnosticEventsTest.java │ ├── ServerSideEventContextDeduplicatorTest.java │ ├── SimpleLRUCacheTest.java │ ├── StreamProcessorEventsTest.java │ ├── StreamProcessorTest.java │ ├── TestComponents.java │ ├── TestUtil.java │ ├── UtilTest.java │ ├── integrations │ ├── ApplicationInfoBuilderTest.java │ ├── BigSegmentStoreTestBase.java │ ├── BigSegmentStoreTestBaseTest.java │ ├── BigSegmentsConfigurationBuilderTest.java │ ├── ClientWithFileDataSourceTest.java │ ├── DataLoaderTest.java │ ├── EventProcessorBuilderTest.java │ ├── FileDataSourceAutoUpdateTest.java │ ├── FileDataSourceTest.java │ ├── FileDataSourceTestData.java │ ├── FlagFileParserJsonTest.java │ ├── FlagFileParserTestBase.java │ ├── FlagFileParserYamlTest.java │ ├── HookConfigurationBuilderTest.java │ ├── HttpConfigurationBuilderTest.java │ ├── LoggingConfigurationBuilderTest.java │ ├── MockPersistentDataStore.java │ ├── PersistentDataStoreBuilderTest.java │ ├── PersistentDataStoreGenericTest.java │ ├── PersistentDataStoreTestBase.java │ ├── PollingDataSourceBuilderTest.java │ ├── ServiceEndpointsBuilderTest.java │ ├── StreamingDataSourceBuilderTest.java │ ├── TestDataTest.java │ ├── TestDataWithClientTest.java │ └── WrapperInfoBuilderTest.java │ └── interfaces │ ├── BigSegmentMembershipBuilderTest.java │ ├── DataSourceStatusProviderTypesTest.java │ ├── DataStoreStatusProviderTypesTest.java │ ├── DataStoreTypesTest.java │ └── HttpAuthenticationTypesTest.java └── resources ├── filesource ├── all-properties.json ├── all-properties.yml ├── flag-only.json ├── flag-only.yml ├── flag-with-duplicate-key.json ├── malformed.json ├── malformed.yml ├── no-data.json ├── segment-only.json ├── segment-only.yml ├── segment-with-duplicate-key.json ├── value-only.json ├── value-only.yml └── value-with-duplicate-key.json └── logback.xml /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is this a support request?** 11 | This issue tracker is maintained by LaunchDarkly SDK developers and is intended for feedback on the SDK code. If you're not sure whether the problem you are having is specifically related to the SDK, or to the LaunchDarkly service overall, it may be more appropriate to contact the LaunchDarkly support team; they can help to investigate the problem and will consult the SDK team if necessary. You can submit a support request by going [here](https://support.launchdarkly.com/hc/en-us/requests/new) or by emailing support@launchdarkly.com. 12 | 13 | Note that issues filed on this issue tracker are publicly accessible. Do not provide any private account information on your issues. If your problem is specific to your account, you should submit a support request as described above. 14 | 15 | **Describe the bug** 16 | A clear and concise description of what the bug is. 17 | 18 | **To reproduce** 19 | Steps to reproduce the behavior. 20 | 21 | **Expected behavior** 22 | A clear and concise description of what you expected to happen. 23 | 24 | **Logs** 25 | If applicable, add any log output related to your problem. 26 | 27 | **SDK version** 28 | The version of this SDK that you are using. 29 | 30 | **Language version, developer tools** 31 | For instance, Go 1.11 or Ruby 2.5.3. If you are using a language that requires a separate compiler, such as C, please include the name and version of the compiler too. 32 | 33 | **OS/platform** 34 | For instance, Ubuntu 16.04, Windows 10, or Android 4.0.3. If your code is running in a browser, please also include the browser type and version. 35 | 36 | **Additional context** 37 | Add any other context about the problem here. 38 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Support request 4 | url: https://support.launchdarkly.com/hc/en-us/requests/new 5 | about: File your support requests with LaunchDarkly's support team 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I would love to see the SDK [...does something new...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | **Requirements** 2 | 3 | - [ ] I have added test coverage for new or changed functionality 4 | - [ ] I have followed the repository's [pull request submission guidelines](../blob/master/CONTRIBUTING.md#submitting-pull-requests) 5 | - [ ] I have validated my changes against all supported platform versions 6 | 7 | **Related issues** 8 | 9 | Provide links to any issues in this repository or elsewhere relating to this pull request. 10 | 11 | **Describe the solution you've provided** 12 | 13 | Provide a clear and concise description of what you expect to happen. 14 | 15 | **Describe alternatives you've considered** 16 | 17 | Provide a clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | 21 | Add any other context about the pull request here. 22 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: 'Close stale issues and PRs' 2 | on: 3 | workflow_dispatch: 4 | schedule: 5 | # Happen once per day at 1:30 AM 6 | - cron: '30 1 * * *' 7 | 8 | jobs: 9 | sdk-close-stale: 10 | uses: launchdarkly/gh-actions/.github/workflows/sdk-stale.yml@main 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Eclipse project files 2 | .classpath 3 | .project 4 | .settings 5 | 6 | # Intellij project files 7 | *.iml 8 | *.ipr 9 | *.iws 10 | .idea/ 11 | 12 | #Gradle 13 | .gradletasknamecache 14 | .gradle/ 15 | build/ 16 | bin/ 17 | out/ 18 | classes/ 19 | 20 | packaging-test/temp/ 21 | benchmarks/lib/ 22 | -------------------------------------------------------------------------------- /.ldrelease/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | repo: 4 | public: java-server-sdk 5 | private: java-server-sdk-private 6 | 7 | publications: 8 | - url: https://oss.sonatype.org/content/groups/public/com/launchdarkly/launchdarkly-java-server-sdk/ 9 | description: Sonatype 10 | - url: https://javadoc.io/doc/com.launchdarkly/launchdarkly-java-server-sdk 11 | description: documentation (javadoc.io) 12 | 13 | jobs: 14 | - docker: 15 | image: gradle:7.6-jdk11 16 | template: 17 | name: gradle 18 | 19 | branches: 20 | - name: main 21 | description: 7.x 22 | - name: 6.x 23 | - name: 5.x 24 | - name: 4.x 25 | 26 | documentation: 27 | gitHubPages: true 28 | 29 | sdk: 30 | displayName: "Java" 31 | -------------------------------------------------------------------------------- /.ldrelease/publish.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -ue 4 | 5 | # Publish to Sonatype 6 | echo "Publishing to Sonatype" 7 | if [[ -n "${LD_RELEASE_IS_PRERELEASE}" ]]; then 8 | ./gradlew publishToSonatype || { echo "Gradle publish/release failed" >&2; exit 1; } 9 | else 10 | ./gradlew publishToSonatype closeAndReleaseRepository || { echo "Gradle publish/release failed" >&2; exit 1; } 11 | fi 12 | 13 | -------------------------------------------------------------------------------- /.sdk_metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "sdks": { 4 | "java-server-sdk": { 5 | "name": "Java Server SDK", 6 | "type": "server-side", 7 | "languages": [ 8 | "Java" 9 | ] 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Repository Maintainers 2 | * @launchdarkly/team-sdk-java 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2016 Catamorphic, Co. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | build: 3 | ./gradlew jar 4 | 5 | clean: 6 | ./gradlew clean 7 | 8 | test: 9 | ./gradlew test 10 | 11 | TEMP_TEST_OUTPUT=/tmp/sdk-test-service.log 12 | 13 | # Add any extra sdk-test-harness parameters here, such as -skip for tests that are 14 | # temporarily not working. 15 | TEST_HARNESS_PARAMS= 16 | 17 | build-contract-tests: 18 | @cd contract-tests && ../gradlew installDist 19 | 20 | start-contract-test-service: 21 | @contract-tests/service/build/install/service/bin/service 22 | 23 | start-contract-test-service-bg: 24 | @echo "Test service output will be captured in $(TEMP_TEST_OUTPUT)" 25 | @make start-contract-test-service >$(TEMP_TEST_OUTPUT) 2>&1 & 26 | 27 | run-contract-tests: 28 | @curl -s https://raw.githubusercontent.com/launchdarkly/sdk-test-harness/v2/downloader/run.sh \ 29 | | VERSION=v2 PARAMS="-url http://localhost:8000 -debug -stop-service-at-end $(TEST_HARNESS_PARAMS)" sh 30 | 31 | contract-tests: build-contract-tests start-contract-test-service-bg run-contract-tests 32 | 33 | .PHONY: build-contract-tests start-contract-test-service start-contract-test-service-bg run-contract-tests contract-tests 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # This code has a new home! 2 | 3 | You can now find it here in our [java-core repository](https://github.com/launchdarkly/java-core). 4 | 5 | ## About LaunchDarkly 6 | 7 | * LaunchDarkly is a continuous delivery platform that provides feature flags as a service and allows developers to iterate quickly and safely. We allow you to easily flag your features and manage them from the LaunchDarkly dashboard. With LaunchDarkly, you can: 8 | * Roll out a new feature to a subset of your users (like a group of users who opt-in to a beta tester group), gathering feedback and bug reports from real-world use cases. 9 | * Gradually roll out a feature to an increasing percentage of users, and track the effect that the feature has on key metrics (for instance, how likely is a user to complete a purchase if they have feature A versus feature B?). 10 | * Turn off a feature that you realize is causing performance problems in production, without needing to re-deploy, or even restart the application with a changed configuration file. 11 | * Grant access to certain features based on user attributes, like payment plan (eg: users on the ‘gold’ plan get access to more features than users in the ‘silver’ plan). Disable parts of your application to facilitate maintenance, without taking everything offline. 12 | * LaunchDarkly provides feature flag SDKs for a wide variety of languages and technologies. Read [our documentation](https://docs.launchdarkly.com/sdk) for a complete list. 13 | * Explore LaunchDarkly 14 | * [launchdarkly.com](https://www.launchdarkly.com/ "LaunchDarkly Main Website") for more information 15 | * [docs.launchdarkly.com](https://docs.launchdarkly.com/ "LaunchDarkly Documentation") for our documentation and SDK reference guides 16 | * [apidocs.launchdarkly.com](https://apidocs.launchdarkly.com/ "LaunchDarkly API Documentation") for our API documentation 17 | * [blog.launchdarkly.com](https://blog.launchdarkly.com/ "LaunchDarkly Blog Documentation") for the latest product updates 18 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Reporting and Fixing Security Issues 2 | 3 | Please report all security issues to the LaunchDarkly security team by submitting a bug bounty report to our [HackerOne program](https://hackerone.com/launchdarkly?type=team). LaunchDarkly will triage and address all valid security issues following the response targets defined in our program policy. Valid security issues may be eligible for a bounty. 4 | 5 | Please do not open issues or pull requests for security issues. This makes the problem immediately visible to everyone, including potentially malicious actors. 6 | -------------------------------------------------------------------------------- /benchmarks/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: benchmark clean sdk 2 | 3 | BASE_DIR:=$(shell pwd) 4 | PROJECT_DIR=$(shell cd .. && pwd) 5 | SDK_VERSION=$(shell grep "version=" $(PROJECT_DIR)/gradle.properties | cut -d '=' -f 2) 6 | 7 | BENCHMARK_SDK_JAR=lib/launchdarkly-java-server-sdk.jar 8 | BENCHMARK_TEST_JAR=lib/launchdarkly-java-server-sdk-test.jar 9 | SDK_JARS_DIR=$(PROJECT_DIR)/build/libs 10 | SDK_JAR=$(SDK_JARS_DIR)/launchdarkly-java-server-sdk-$(SDK_VERSION).jar 11 | SDK_TEST_JAR=$(SDK_JARS_DIR)/launchdarkly-java-server-sdk-$(SDK_VERSION)-test.jar 12 | 13 | benchmark: $(BENCHMARK_SDK_JAR) $(BENCHMARK_TEST_JAR) 14 | rm -rf build/tmp 15 | ../gradlew jmh 16 | cat build/reports/jmh/human.txt 17 | ../gradlew jmhReport 18 | 19 | clean: 20 | rm -rf build lib 21 | 22 | sdk: $(BENCHMARK_ALL_JAR) $(BENCHMARK_TEST_JAR) 23 | 24 | $(BENCHMARK_SDK_JAR): $(SDK_JAR) 25 | mkdir -p lib 26 | cp $< $@ 27 | 28 | $(BENCHMARK_TEST_JAR): $(SDK_TEST_JAR) 29 | mkdir -p lib 30 | cp $< $@ 31 | 32 | $(SDK_JAR): 33 | cd .. && ./gradlew shadowJar 34 | 35 | $(SDK_TEST_JAR): 36 | cd .. && ./gradlew testJar 37 | -------------------------------------------------------------------------------- /benchmarks/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | buildscript { 3 | repositories { 4 | mavenCentral() 5 | } 6 | } 7 | 8 | plugins { 9 | id "me.champeau.gradle.jmh" version "0.5.0" 10 | id "io.morethan.jmhreport" version "0.9.0" 11 | } 12 | 13 | repositories { 14 | mavenCentral() 15 | } 16 | 17 | ext.versions = [ 18 | "jmh": "1.21", 19 | "guava": "19.0" 20 | ] 21 | 22 | dependencies { 23 | implementation files("lib/launchdarkly-java-server-sdk.jar") 24 | implementation files("lib/launchdarkly-java-server-sdk-test.jar") 25 | implementation "com.google.code.gson:gson:2.8.9" 26 | implementation "com.google.guava:guava:${versions.guava}" // required by SDK test code 27 | implementation "com.squareup.okhttp3:mockwebserver:3.12.10" 28 | implementation "org.openjdk.jmh:jmh-core:1.21" 29 | implementation "org.openjdk.jmh:jmh-generator-annprocess:${versions.jmh}" 30 | } 31 | 32 | // need to set duplicatesStrategy because otherwise some non-class files with 33 | // duplicate names in our dependencies will cause an error 34 | tasks.getByName('jmhJar').doFirst() {duplicatesStrategy(DuplicatesStrategy.EXCLUDE)} 35 | 36 | jmh { 37 | iterations = 10 // Number of measurement iterations to do. 38 | benchmarkMode = ['avgt'] // "average time" - reports execution time as ns/op and allocations as B/op. 39 | // batchSize = 1 // Batch size: number of benchmark method calls per operation. (some benchmark modes can ignore this setting) 40 | fork = 1 // How many times to forks a single benchmark. Use 0 to disable forking altogether 41 | // failOnError = false // Should JMH fail immediately if any benchmark had experienced the unrecoverable error? 42 | forceGC = true // Should JMH force GC between iterations? 43 | humanOutputFile = project.file("${project.buildDir}/reports/jmh/human.txt") // human-readable output file 44 | // resultsFile = project.file("${project.buildDir}/reports/jmh/results.txt") // results file 45 | operationsPerInvocation = 3 // Operations per invocation. 46 | // benchmarkParameters = [:] // Benchmark parameters. 47 | profilers = [ 'gc' ] // Use profilers to collect additional data. Supported profilers: [cl, comp, gc, stack, perf, perfnorm, perfasm, xperf, xperfasm, hs_cl, hs_comp, hs_gc, hs_rt, hs_thr] 48 | timeOnIteration = '1s' // Time to spend at each measurement iteration. 49 | resultFormat = 'JSON' // Result format type (one of CSV, JSON, NONE, SCSV, TEXT) 50 | // synchronizeIterations = false // Synchronize iterations? 51 | // threads = 4 // Number of worker threads to run with. 52 | // timeout = '1s' // Timeout for benchmark iteration. 53 | timeUnit = 'ns' // Output time unit. Available time units are: [m, s, ms, us, ns]. 54 | verbosity = 'NORMAL' // Verbosity mode. Available modes are: [SILENT, NORMAL, EXTRA] 55 | warmup = '1s' // Time to spend at each warmup iteration. 56 | warmupBatchSize = 2 // Warmup batch size: number of benchmark method calls per operation. 57 | warmupIterations = 1 // Number of warmup iterations to do. 58 | // warmupForks = 0 // How many warmup forks to make for a single benchmark. 0 to disable warmup forks. 59 | // warmupMode = 'INDI' // Warmup mode for warming up selected benchmarks. Warmup modes are: [INDI, BULK, BULK_INDI]. 60 | 61 | jmhVersion = versions.jmh 62 | } 63 | 64 | jmhReport { 65 | jmhResultPath = project.file('build/reports/jmh/results.json') 66 | jmhReportOutput = project.file('build/reports/jmh') 67 | } 68 | -------------------------------------------------------------------------------- /benchmarks/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'launchdarkly-java-server-sdk-benchmarks' 2 | -------------------------------------------------------------------------------- /config/checkstyle/checkstyle.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /config/checkstyle/suppressions.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /contract-tests/README.md: -------------------------------------------------------------------------------- 1 | # SDK contract test service 2 | 3 | This directory contains an implementation of the cross-platform SDK testing protocol defined by https://github.com/launchdarkly/sdk-test-harness. See that project's `README` for details of this protocol, and the kinds of SDK capabilities that are relevant to the contract tests. This code should not need to be updated unless the SDK has added or removed such capabilities. 4 | 5 | To run these tests locally, run `make contract-tests` from the SDK project root directory. This downloads the correct version of the test harness tool automatically. 6 | 7 | Or, to test against an in-progress local version of the test harness, run `make start-contract-test-service` from the SDK project root directory; then, in the root directory of the `sdk-test-harness` project, build the test harness and run it from the command line. 8 | -------------------------------------------------------------------------------- /contract-tests/gradle.properties: -------------------------------------------------------------------------------- 1 | gnsp.disableApplyOnlyOnRootProjectEnforcement=true 2 | -------------------------------------------------------------------------------- /contract-tests/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/launchdarkly/java-server-sdk/16b474bff9151adb5f9f5dd52350a53402f2f697/contract-tests/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /contract-tests/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /contract-tests/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /contract-tests/service/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | plugins { 3 | id "java" 4 | id "application" 5 | } 6 | 7 | repositories { 8 | mavenCentral() 9 | mavenLocal() 10 | maven { url "https://oss.sonatype.org/content/groups/public/" } 11 | } 12 | 13 | configurations.all { 14 | // check for updates every build for dependencies with: 'changing: true' 15 | resolutionStrategy.cacheChangingModulesFor 0, 'seconds' 16 | } 17 | 18 | allprojects { 19 | sourceCompatibility = 1.8 20 | targetCompatibility = 1.8 21 | } 22 | 23 | archivesBaseName = "java-sdk-test-service" 24 | 25 | application { 26 | mainClassName = "sdktest.TestService" 27 | } 28 | 29 | ext.versions = [ 30 | "gson": "2.7", 31 | "logback": "1.1.3", 32 | "okhttp": "4.5.0", 33 | "testHelpers": "2.0.1", 34 | "launchdarklyJavaSdkCommon": project(":sdk").versions["launchdarklyJavaSdkCommon"] 35 | ] 36 | 37 | configurations { 38 | deps.extendsFrom(implementation) 39 | } 40 | 41 | dependencies { 42 | implementation project(":sdk") 43 | implementation "com.launchdarkly:launchdarkly-java-sdk-common:${versions.launchdarklyJavaSdkCommon}" 44 | implementation "ch.qos.logback:logback-classic:${versions.logback}" 45 | implementation "com.google.code.gson:gson:${versions.gson}" 46 | implementation "com.squareup.okhttp3:okhttp:${versions.okhttp}" 47 | implementation "com.launchdarkly:test-helpers:${versions.testHelpers}" 48 | } 49 | -------------------------------------------------------------------------------- /contract-tests/service/settings.gradle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/launchdarkly/java-server-sdk/16b474bff9151adb5f9f5dd52350a53402f2f697/contract-tests/service/settings.gradle -------------------------------------------------------------------------------- /contract-tests/service/src/main/java/sdktest/BigSegmentCallbackRepresentation.java: -------------------------------------------------------------------------------- 1 | package sdktest; 2 | 3 | import java.util.Map; 4 | 5 | public abstract class BigSegmentCallbackRepresentation { 6 | public static class BigSegmentStoreGetMetadataResponse { 7 | Long lastUpToDate; 8 | } 9 | 10 | public static class BigSegmentStoreGetMembershipParams { 11 | String contextHash; 12 | } 13 | 14 | public static class BigSegmentStoreGetMembershipResponse { 15 | Map values; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /contract-tests/service/src/main/java/sdktest/BigSegmentCallbackService.java: -------------------------------------------------------------------------------- 1 | package sdktest; 2 | 3 | import java.net.URI; 4 | 5 | import okhttp3.MediaType; 6 | import okhttp3.Request; 7 | import okhttp3.RequestBody; 8 | import okhttp3.Response; 9 | 10 | public class BigSegmentCallbackService { 11 | private final URI baseUri; 12 | 13 | public BigSegmentCallbackService(URI baseUri) { 14 | this.baseUri = baseUri; 15 | } 16 | 17 | public void close() { 18 | try { 19 | Request request = new Request.Builder().url(baseUri.toURL()).method("DELETE", null).build(); 20 | Response response = TestService.client.newCall(request).execute(); 21 | assertOk(response, ""); 22 | } catch (Exception e) { 23 | throw new RuntimeException(e); // all errors are unexpected here 24 | } 25 | } 26 | 27 | public T post(String path, Object params, Class responseClass) { 28 | try { 29 | String uri = baseUri.toString() + path; 30 | RequestBody body = RequestBody.create( 31 | TestService.gson.toJson(params == null ? "{}" : params), 32 | MediaType.parse("application/json")); 33 | Request request = new Request.Builder().url(uri). 34 | method("POST", body).build(); 35 | Response response = TestService.client.newCall(request).execute(); 36 | assertOk(response, path); 37 | if (responseClass == null) { 38 | return null; 39 | } 40 | return TestService.gson.fromJson(response.body().string(), responseClass); 41 | } catch (Exception e) { 42 | throw new RuntimeException(e); // all errors are unexpected here 43 | } 44 | } 45 | 46 | private void assertOk(Response response, String path) { 47 | if (!response.isSuccessful()) { 48 | String body = ""; 49 | if (response.body() != null) { 50 | try { 51 | body = ": " + response.body().string(); 52 | } catch (Exception e) {} 53 | } 54 | throw new RuntimeException("HTTP error " + response.code() + " from callback to " + baseUri + path + body); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /contract-tests/service/src/main/java/sdktest/BigSegmentStoreFixture.java: -------------------------------------------------------------------------------- 1 | package sdktest; 2 | 3 | import com.launchdarkly.sdk.server.subsystems.BigSegmentStore; 4 | import com.launchdarkly.sdk.server.subsystems.BigSegmentStoreTypes.Membership; 5 | import com.launchdarkly.sdk.server.subsystems.BigSegmentStoreTypes.StoreMetadata; 6 | import com.launchdarkly.sdk.server.subsystems.ClientContext; 7 | import com.launchdarkly.sdk.server.subsystems.ComponentConfigurer; 8 | 9 | import java.io.IOException; 10 | 11 | import sdktest.BigSegmentCallbackRepresentation.BigSegmentStoreGetMembershipParams; 12 | import sdktest.BigSegmentCallbackRepresentation.BigSegmentStoreGetMembershipResponse; 13 | import sdktest.BigSegmentCallbackRepresentation.BigSegmentStoreGetMetadataResponse; 14 | 15 | public class BigSegmentStoreFixture implements BigSegmentStore, ComponentConfigurer { 16 | private final BigSegmentCallbackService service; 17 | 18 | public BigSegmentStoreFixture(BigSegmentCallbackService service) { 19 | this.service = service; 20 | } 21 | 22 | @Override 23 | public void close() throws IOException { 24 | service.close(); 25 | } 26 | 27 | @Override 28 | public Membership getMembership(String contextHash) { 29 | BigSegmentStoreGetMembershipParams params = new BigSegmentStoreGetMembershipParams(); 30 | params.contextHash = contextHash; 31 | BigSegmentStoreGetMembershipResponse resp = 32 | service.post("/getMembership", params, BigSegmentStoreGetMembershipResponse.class); 33 | return new Membership() { 34 | @Override 35 | public Boolean checkMembership(String segmentRef) { 36 | return resp.values == null ? null : resp.values.get(segmentRef); 37 | } 38 | }; 39 | } 40 | 41 | @Override 42 | public StoreMetadata getMetadata() { 43 | BigSegmentStoreGetMetadataResponse resp = 44 | service.post("/getMetadata", null, BigSegmentStoreGetMetadataResponse.class); 45 | return new StoreMetadata(resp.lastUpToDate); 46 | } 47 | 48 | @Override 49 | public BigSegmentStore build(ClientContext context) { 50 | return this; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /contract-tests/service/src/main/java/sdktest/HookCallbackService.java: -------------------------------------------------------------------------------- 1 | package sdktest; 2 | 3 | import okhttp3.MediaType; 4 | import okhttp3.Request; 5 | import okhttp3.RequestBody; 6 | import okhttp3.Response; 7 | 8 | import java.net.URI; 9 | 10 | public class HookCallbackService { 11 | private final URI serviceUri; 12 | 13 | public HookCallbackService(URI serviceUri) { 14 | this.serviceUri = serviceUri; 15 | } 16 | 17 | public void post(Object params) { 18 | try { 19 | RequestBody body = RequestBody.create( 20 | TestService.gson.toJson(params == null ? "{}" : params), 21 | MediaType.parse("application/json")); 22 | Request request = new Request.Builder().url(serviceUri.toString()). 23 | method("POST", body).build(); 24 | Response response = TestService.client.newCall(request).execute(); 25 | assertOk(response); 26 | } catch (Exception e) { 27 | throw new RuntimeException(e); // all errors are unexpected here 28 | } 29 | } 30 | 31 | private void assertOk(Response response) { 32 | if (!response.isSuccessful()) { 33 | String body = ""; 34 | if (response.body() != null) { 35 | try { 36 | body = ": " + response.body().string(); 37 | } catch (Exception e) {} 38 | } 39 | throw new RuntimeException("HTTP error " + response.code() + " from callback to " + serviceUri + body); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /contract-tests/service/src/main/java/sdktest/MigrationCallbackService.java: -------------------------------------------------------------------------------- 1 | package sdktest; 2 | 3 | import okhttp3.MediaType; 4 | import okhttp3.Request; 5 | import okhttp3.RequestBody; 6 | import okhttp3.Response; 7 | 8 | import java.net.URL; 9 | 10 | public class MigrationCallbackService { 11 | private final URL serviceUrl; 12 | 13 | public MigrationCallbackService(URL serviceUrl) { 14 | this.serviceUrl = serviceUrl; 15 | } 16 | 17 | public String post(String payload) { 18 | try { 19 | RequestBody body = RequestBody.create( 20 | payload != null ? payload : "", 21 | MediaType.parse("application/text")); 22 | Request request = new Request.Builder().url(serviceUrl). 23 | method("POST", body).build(); 24 | Response response = TestService.client.newCall(request).execute(); 25 | if(!response.isSuccessful()) { 26 | throw new RuntimeException("Non success status code."); 27 | } 28 | return response.body().string(); 29 | } catch (Exception e) { 30 | throw new RuntimeException(e); // all errors are unexpected here 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /contract-tests/service/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | %d{yyyy-MM-dd HH:mm:ss} [%logger] %level: %msg%n 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /contract-tests/settings.gradle: -------------------------------------------------------------------------------- 1 | include ":service" 2 | include ":sdk" 3 | project(":sdk").projectDir = new File("..") 4 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | version=7.4.1 2 | # The following empty ossrh properties are used by LaunchDarkly's internal integration testing framework 3 | # and should not be needed for typical development purposes (including by third-party developers). 4 | ossrhUsername= 5 | ossrhPassword= 6 | 7 | # See https://github.com/gradle/gradle/issues/11308 regarding the following property 8 | systemProp.org.gradle.internal.publish.checksums.insecure=true 9 | -------------------------------------------------------------------------------- /gradle.properties.example: -------------------------------------------------------------------------------- 1 | # To release a version of this SDK, copy this file to ~/.gradle/gradle.properties and fill in the values. 2 | githubUser = YOUR_GITHUB_USERNAME 3 | githubPassword = YOUR_GITHUB_PASSWORD 4 | signing.keyId = 5669D902 5 | signing.password = SIGNING_PASSWORD 6 | signing.secretKeyRingFile = SECRET_RING_FILE 7 | ossrhUsername = launchdarkly 8 | ossrhPassword = OSSHR_PASSWORD 9 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/launchdarkly/java-server-sdk/16b474bff9151adb5f9f5dd52350a53402f2f697/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /packaging-test/run-non-osgi-test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | function run_test() { 6 | rm -f ${TEMP_OUTPUT} 7 | touch ${TEMP_OUTPUT} 8 | classpath=$(echo "$@" | sed -e 's/ /:/g') 9 | java -classpath "$classpath" testapp.TestApp | tee ${TEMP_OUTPUT} 10 | grep "TestApp: PASS" ${TEMP_OUTPUT} >/dev/null 11 | } 12 | 13 | # It does not make sense to test the "thin" jar without Gson. The SDK uses Gson internally 14 | # and can't work without it; in the default jar and the "all" jar, it has its own embedded 15 | # copy of Gson, but the "thin" jar does not include any third-party dependencies so you must 16 | # provide all of them including Gson. 17 | echo "" 18 | if [[ "$@" =~ $thin_sdk_regex ]]; then 19 | echo " non-OSGi runtime test - without Jackson" 20 | filtered_deps="" 21 | json_jar_regex=".*jackson.*" 22 | for dep in $@; do 23 | if [[ ! "$dep" =~ $json_jar_regex ]]; then 24 | filtered_deps="$filtered_deps $dep" 25 | fi 26 | done 27 | run_test $filtered_deps 28 | grep "skipping LDJackson tests" ${TEMP_OUTPUT} >/dev/null || \ 29 | (echo "FAIL: should have skipped LDJackson tests but did not; test setup was incorrect" && exit 1) 30 | else 31 | echo " non-OSGi runtime test - without Gson or Jackson" 32 | filtered_deps="" 33 | json_jar_regex=".*gson.*|.*jackson.*" 34 | for dep in $@; do 35 | if [[ ! "$dep" =~ $json_jar_regex ]]; then 36 | filtered_deps="$filtered_deps $dep" 37 | fi 38 | done 39 | run_test $filtered_deps 40 | grep "skipping LDGson tests" ${TEMP_OUTPUT} >/dev/null || \ 41 | (echo "FAIL: should have skipped LDGson tests but did not; test setup was incorrect" && exit 1) 42 | grep "skipping LDJackson tests" ${TEMP_OUTPUT} >/dev/null || \ 43 | (echo "FAIL: should have skipped LDJackson tests but did not; test setup was incorrect" && exit 1) 44 | fi 45 | 46 | echo "" 47 | echo " non-OSGi runtime test - with Gson and Jackson" 48 | run_test $@ 49 | grep "LDGson tests OK" ${TEMP_OUTPUT} >/dev/null || (echo "FAIL: should have run LDGson tests but did not" && exit 1) 50 | grep "LDJackson tests OK" ${TEMP_OUTPUT} >/dev/null || (echo "FAIL: should have run LDJackson tests but did not" && exit 1) 51 | -------------------------------------------------------------------------------- /packaging-test/run-osgi-test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | # This script uses Felix to run the test application as an OSGi bundle, with or without 6 | # additional bundles to support the optional Gson and Jackson integrations. We are 7 | # verifying that the SDK itself works correctly as an OSGi bundle, and also that its 8 | # imports of other bundles work correctly. 9 | # 10 | # This test is being run in CI using the lowest compatible JDK version. It may not work 11 | # in higher JDK versions due to incompatibilities with the version of Felix we are using. 12 | 13 | JAR_DEPS="$@" 14 | 15 | # We can't test the "thin" jar in OSGi, because some of our third-party dependencies 16 | # aren't available as OSGi bundles. That isn't a plausible use case anyway. 17 | thin_sdk_regex=".*launchdarkly-java-server-sdk-[^ ]*-thin\\.jar" 18 | if [[ "${JAR_DEPS}" =~ $thin_sdk_regex ]]; then 19 | exit 0 20 | fi 21 | 22 | rm -rf ${TEMP_BUNDLE_DIR} 23 | mkdir -p ${TEMP_BUNDLE_DIR} 24 | 25 | function copy_deps() { 26 | if [ -n "${JAR_DEPS}" ]; then 27 | cp ${JAR_DEPS} ${TEMP_BUNDLE_DIR} 28 | fi 29 | cp ${FELIX_BASE_BUNDLE_DIR}/* ${TEMP_BUNDLE_DIR} 30 | } 31 | 32 | function run_test() { 33 | rm -rf ${FELIX_DIR}/felix-cache 34 | rm -f ${TEMP_OUTPUT} 35 | touch ${TEMP_OUTPUT} 36 | cd ${FELIX_DIR} 37 | java -jar ${FELIX_JAR} -b ${TEMP_BUNDLE_DIR} | tee ${TEMP_OUTPUT} 38 | grep "TestApp: PASS" ${TEMP_OUTPUT} >/dev/null 39 | } 40 | 41 | echo "" 42 | echo " OSGi runtime test - without Gson or Jackson" 43 | copy_deps 44 | rm ${TEMP_BUNDLE_DIR}/*gson*.jar ${TEMP_BUNDLE_DIR}/*jackson*.jar 45 | ls ${TEMP_BUNDLE_DIR} 46 | run_test 47 | grep "skipping LDGson tests" ${TEMP_OUTPUT} >/dev/null || \ 48 | (echo "FAIL: should have skipped LDGson tests but did not; test setup was incorrect" && exit 1) 49 | grep "skipping LDJackson tests" ${TEMP_OUTPUT} >/dev/null || \ 50 | (echo "FAIL: should have skipped LDJackson tests but did not; test setup was incorrect" && exit 1) 51 | 52 | echo "" 53 | echo " OSGi runtime test - with Gson and Jackson" 54 | copy_deps 55 | run_test 56 | grep "LDGson tests OK" ${TEMP_OUTPUT} >/dev/null || (echo "FAIL: should have run LDGson tests but did not" && exit 1) 57 | grep "LDJackson tests OK" ${TEMP_OUTPUT} >/dev/null || (echo "FAIL: should have run LDJackson tests but did not" && exit 1) 58 | -------------------------------------------------------------------------------- /packaging-test/test-app/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | buildscript { 3 | repositories { 4 | mavenCentral() 5 | } 6 | } 7 | 8 | plugins { 9 | id "java" 10 | id "java-library" 11 | id "biz.aQute.bnd.builder" version "5.0.1" 12 | id "com.athaydes.osgi-run" version "1.6.0" 13 | } 14 | 15 | repositories { 16 | mavenCentral() 17 | } 18 | 19 | allprojects { 20 | group = "com.launchdarkly" 21 | version = "1.0.0" 22 | archivesBaseName = 'test-app-bundle' 23 | sourceCompatibility = 1.8 24 | targetCompatibility = 1.8 25 | } 26 | 27 | ext.versions = [ 28 | "gson": "2.8.9", 29 | "jackson": "2.10.0" 30 | ] 31 | 32 | dependencies { 33 | // Note, the SDK build must have already been run before this, since we're using its product as a dependency 34 | implementation fileTree(dir: "../../build/libs", include: "launchdarkly-java-server-sdk-*-thin.jar") 35 | implementation "com.fasterxml.jackson.core:jackson-core:${versions.jackson}" 36 | implementation "com.fasterxml.jackson.core:jackson-databind:${versions.jackson}" 37 | implementation "com.google.code.gson:gson:${versions.gson}" 38 | implementation "org.slf4j:slf4j-api:1.7.22" 39 | implementation "org.osgi:osgi_R4_core:1.0" 40 | osgiRuntime "org.slf4j:slf4j-simple:1.7.22" 41 | } 42 | 43 | task exportDependencies(type: Copy, dependsOn: compileJava) { 44 | into "../temp/dependencies-app" 45 | from configurations.runtimeClasspath.resolvedConfiguration.resolvedArtifacts.collect { it.file } 46 | } 47 | 48 | jar { 49 | bnd( 50 | // This consumer-policy directive completely turns off version checking for the test app's 51 | // OSGi imports, so for instance if the app uses version 2.x of package P, the import will 52 | // just be for p rather than p;version="[2.x,3)". One wouldn't normally do this, but we 53 | // need to be able to run the CI tests for snapshot/beta versions, and bnd does not handle 54 | // those correctly (5.0.0-beta1 will become "[5.0.0,6)" which will not work because the 55 | // beta is semantically *before* 5.0.0). 56 | '-consumer-policy': '', 57 | 'Bundle-Activator': 'testapp.TestAppOsgiEntryPoint', 58 | 'Import-Package': 'com.launchdarkly.sdk,com.launchdarkly.sdk.json' + 59 | ',com.launchdarkly.sdk.server,org.slf4j' + 60 | ',org.osgi.framework' + 61 | ',com.google.gson;resolution:=optional' + 62 | ',com.fasterxml.jackson.*;resolution:=optional' 63 | ) 64 | 65 | finalizedBy(exportDependencies) 66 | } 67 | 68 | runOsgi { 69 | bundles = [ ] // we don't need a CLI or anything like that - just the SLF4j dependency shown above 70 | } 71 | -------------------------------------------------------------------------------- /packaging-test/test-app/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'test-app-bundle' 2 | -------------------------------------------------------------------------------- /packaging-test/test-app/src/main/java/testapp/JsonSerializationTestData.java: -------------------------------------------------------------------------------- 1 | package testapp; 2 | 3 | import com.launchdarkly.sdk.*; 4 | import java.util.*; 5 | 6 | public class JsonSerializationTestData { 7 | public static class TestItem { 8 | final Object objectToSerialize; 9 | final String expectedJson; 10 | 11 | private TestItem(Object objectToSerialize, String expectedJson) { 12 | this.objectToSerialize = objectToSerialize; 13 | this.expectedJson = expectedJson; 14 | } 15 | } 16 | 17 | public static TestItem[] TEST_ITEMS = new TestItem[] { 18 | new TestItem( 19 | LDValue.buildArray().add(1).add(2).build(), 20 | "[1,2]" 21 | ), 22 | new TestItem( 23 | Collections.singletonMap("value", LDValue.buildArray().add(1).add(2).build()), 24 | "{\"value\":[1,2]}" 25 | ), 26 | new TestItem( 27 | EvaluationReason.off(), 28 | "{\"kind\":\"OFF\"}" 29 | ), 30 | new TestItem( 31 | LDContext.create("userkey"), 32 | "{\"kind\":\"user\",\"key\":\"userkey\"}" 33 | ) 34 | }; 35 | 36 | public static boolean assertJsonEquals(String expectedJson, String actualJson, Object objectToSerialize) { 37 | if (!LDValue.parse(actualJson).equals(LDValue.parse(expectedJson))) { 38 | TestApp.addError("JSON encoding of " + objectToSerialize.getClass() + " should have been " + 39 | expectedJson + ", was " + actualJson, null); 40 | return false; 41 | } 42 | return true; 43 | } 44 | } -------------------------------------------------------------------------------- /packaging-test/test-app/src/main/java/testapp/TestApp.java: -------------------------------------------------------------------------------- 1 | package testapp; 2 | 3 | import com.launchdarkly.sdk.*; 4 | import com.launchdarkly.sdk.json.*; 5 | import com.launchdarkly.sdk.server.*; 6 | import java.util.*; 7 | import org.slf4j.*; 8 | 9 | public class TestApp { 10 | private static final Logger logger = LoggerFactory.getLogger(TestApp.class); // proves SLF4J API is on classpath 11 | 12 | private static List errors = new ArrayList<>(); 13 | 14 | public static void main(String[] args) throws Exception { 15 | try { 16 | LDConfig config = new LDConfig.Builder() 17 | .offline(true) 18 | .build(); 19 | LDClient client = new LDClient("fake-sdk-key", config); 20 | log("client creation OK"); 21 | } catch (RuntimeException e) { 22 | addError("client creation failed", e); 23 | } 24 | 25 | try { 26 | boolean jsonOk = true; 27 | for (JsonSerializationTestData.TestItem item: JsonSerializationTestData.TEST_ITEMS) { 28 | if (!(item instanceof JsonSerializable)) { 29 | continue; // things without our marker interface, like a Map, can't be passed to JsonSerialization.serialize 30 | } 31 | String actualJson = JsonSerialization.serialize((JsonSerializable)item.objectToSerialize); 32 | if (!JsonSerializationTestData.assertJsonEquals(item.expectedJson, actualJson, item.objectToSerialize)) { 33 | jsonOk = false; 34 | } 35 | } 36 | if (jsonOk) { 37 | log("JsonSerialization tests OK"); 38 | } 39 | } catch (RuntimeException e) { 40 | addError("unexpected error in JsonSerialization tests", e); 41 | } 42 | 43 | try { 44 | Class.forName("testapp.TestAppGsonTests"); // see TestAppGsonTests for why we're loading it in this way 45 | } catch (NoClassDefFoundError e) { 46 | log("skipping LDGson tests because Gson is not in the classpath"); 47 | } catch (RuntimeException e) { 48 | addError("unexpected error in LDGson tests", e); 49 | } 50 | 51 | try { 52 | Class.forName("testapp.TestAppJacksonTests"); // see TestAppJacksonTests for why we're loading it in this way 53 | } catch (NoClassDefFoundError e) { 54 | log("skipping LDJackson tests because Jackson is not in the classpath"); 55 | } catch (RuntimeException e) { 56 | addError("unexpected error in LDJackson tests", e); 57 | } 58 | 59 | if (errors.isEmpty()) { 60 | log("PASS"); 61 | } else { 62 | for (String err: errors) { 63 | log("ERROR: " + err); 64 | } 65 | log("FAIL"); 66 | System.exit(1); 67 | } 68 | } 69 | 70 | public static void addError(String message, Throwable e) { 71 | if (e != null) { 72 | errors.add(message + ": " + e); 73 | e.printStackTrace(); 74 | } else { 75 | errors.add(message); 76 | } 77 | } 78 | 79 | public static void log(String message) { 80 | System.out.println("TestApp: " + message); 81 | } 82 | } -------------------------------------------------------------------------------- /packaging-test/test-app/src/main/java/testapp/TestAppGsonTests.java: -------------------------------------------------------------------------------- 1 | package testapp; 2 | 3 | import com.google.gson.*; 4 | import com.launchdarkly.sdk.*; 5 | import com.launchdarkly.sdk.json.*; 6 | 7 | // This code is in its own class that is loaded dynamically because some of our test scenarios 8 | // involve running TestApp without having Gson in the classpath, to make sure the SDK does not 9 | // *require* the presence of an external Gson even though it can interoperate with one. 10 | 11 | public class TestAppGsonTests { 12 | // Use static block so simply loading this class causes the tests to execute 13 | static { 14 | // First try referencing Gson, so we fail right away if it's not on the classpath 15 | Class c = Gson.class; 16 | try { 17 | runGsonTests(); 18 | } catch (NoClassDefFoundError e) { 19 | // If we've even gotten to this static block, then Gson itself *is* on the application's 20 | // classpath, so this must be some other kind of classloading error that we do want to 21 | // report. For instance, a NoClassDefFound error for Gson at this point, if we're in 22 | // OSGi, would mean that the SDK bundle is unable to see the external Gson classes. 23 | TestApp.addError("unexpected error in LDGson tests", e); 24 | } 25 | } 26 | 27 | public static void runGsonTests() { 28 | Gson gson = new GsonBuilder().registerTypeAdapterFactory(LDGson.typeAdapters()).create(); 29 | 30 | boolean ok = true; 31 | for (JsonSerializationTestData.TestItem item: JsonSerializationTestData.TEST_ITEMS) { 32 | String actualJson = gson.toJson(item.objectToSerialize); 33 | if (!JsonSerializationTestData.assertJsonEquals(item.expectedJson, actualJson, item.objectToSerialize)) { 34 | ok = false; 35 | } 36 | } 37 | 38 | if (ok) { 39 | TestApp.log("LDGson tests OK"); 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /packaging-test/test-app/src/main/java/testapp/TestAppJacksonTests.java: -------------------------------------------------------------------------------- 1 | package testapp; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.launchdarkly.sdk.*; 5 | import com.launchdarkly.sdk.json.*; 6 | 7 | // This code is in its own class that is loaded dynamically because some of our test scenarios 8 | // involve running TestApp without having Jackson in the classpath, to make sure the SDK does not 9 | // *require* the presence of an external Jackson even though it can interoperate with one. 10 | 11 | public class TestAppJacksonTests { 12 | // Use static block so simply loading this class causes the tests to execute 13 | static { 14 | // First try referencing Jackson, so we fail right away if it's not on the classpath 15 | Class c = ObjectMapper.class; 16 | try { 17 | runJacksonTests(); 18 | } catch (Exception e) { 19 | // If we've even gotten to this static block, then Jackson itself *is* on the application's 20 | // classpath, so this must be some other kind of classloading error that we do want to 21 | // report. For instance, a NoClassDefFound error for Jackson at this point, if we're in 22 | // OSGi, would mean that the SDK bundle is unable to see the external Jackson classes. 23 | TestApp.addError("unexpected error in LDJackson tests", e); 24 | } 25 | } 26 | 27 | public static void runJacksonTests() throws Exception { 28 | ObjectMapper jacksonMapper = new ObjectMapper(); 29 | jacksonMapper.registerModule(LDJackson.module()); 30 | 31 | boolean ok = true; 32 | for (JsonSerializationTestData.TestItem item: JsonSerializationTestData.TEST_ITEMS) { 33 | String actualJson = jacksonMapper.writeValueAsString(item.objectToSerialize); 34 | if (!JsonSerializationTestData.assertJsonEquals(item.expectedJson, actualJson, item.objectToSerialize)) { 35 | ok = false; 36 | } 37 | } 38 | 39 | if (ok) { 40 | TestApp.log("LDJackson tests OK"); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /packaging-test/test-app/src/main/java/testapp/TestAppOsgiEntryPoint.java: -------------------------------------------------------------------------------- 1 | package testapp; 2 | 3 | import org.osgi.framework.BundleActivator; 4 | import org.osgi.framework.BundleContext; 5 | 6 | public class TestAppOsgiEntryPoint implements BundleActivator { 7 | public void start(BundleContext context) throws Exception { 8 | System.out.println("TestApp: starting test bundle"); 9 | 10 | TestApp.main(new String[0]); 11 | 12 | System.exit(0); 13 | } 14 | 15 | public void stop(BundleContext context) throws Exception { 16 | } 17 | } -------------------------------------------------------------------------------- /scripts/release.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # This script updates the version for the java-server-sdk library and releases the artifact + javadoc 3 | # It will only work if you have the proper credentials set up in ~/.gradle/gradle.properties 4 | 5 | # It takes exactly one argument: the new version. 6 | # It should be run from the root of this git repo like this: 7 | # ./scripts/release.sh 4.0.9 8 | 9 | # When done you should commit and push the changes made. 10 | 11 | set -uxe 12 | echo "Starting java-server-sdk release." 13 | 14 | $(dirname $0)/update-version.sh $1 15 | 16 | ./gradlew clean publish closeAndReleaseRepository 17 | ./gradlew publishGhPages 18 | echo "Finished java-server-sdk release." 19 | -------------------------------------------------------------------------------- /scripts/update-version.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | VERSION=$1 4 | 5 | # Update version in gradle.properties file: 6 | sed -i.bak "s/^version.*$/version=${VERSION}/" gradle.properties 7 | rm -f gradle.properties.bak 8 | 9 | # Update version in README.md: 10 | sed -i.bak "s/.*<\/version>/${VERSION}<\/version>/" README.md 11 | sed -i.bak "s/\"com.launchdarkly:launchdarkly-java-server-sdk:.*\"/\"com.launchdarkly:launchdarkly-java-server-sdk:${VERSION}\"/" README.md 12 | rm -f README.md.bak 13 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'launchdarkly-java-server-sdk' 2 | -------------------------------------------------------------------------------- /src/main/java/com/launchdarkly/sdk/json/SdkSerializationExtensions.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.json; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import com.launchdarkly.sdk.server.FeatureFlagsState; 5 | 6 | // See JsonSerialization.getDeserializableClasses in java-sdk-common. 7 | 8 | abstract class SdkSerializationExtensions { 9 | private SdkSerializationExtensions() {} 10 | 11 | public static Iterable> getDeserializableClasses() { 12 | return ImmutableList.>of( 13 | FeatureFlagsState.class 14 | ); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/launchdarkly/sdk/server/BigSegmentStoreStatusProviderImpl.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server; 2 | 3 | import com.launchdarkly.sdk.server.interfaces.BigSegmentStoreStatusProvider; 4 | 5 | final class BigSegmentStoreStatusProviderImpl implements BigSegmentStoreStatusProvider { 6 | private final EventBroadcasterImpl statusNotifier; 7 | private final BigSegmentStoreWrapper storeWrapper; 8 | 9 | BigSegmentStoreStatusProviderImpl( 10 | EventBroadcasterImpl bigSegmentStatusNotifier, 11 | BigSegmentStoreWrapper storeWrapper) { 12 | this.storeWrapper = storeWrapper; 13 | this.statusNotifier = bigSegmentStatusNotifier; 14 | } 15 | 16 | @Override 17 | public Status getStatus() { 18 | return storeWrapper == null ? new Status(false, false) : storeWrapper.getStatus(); 19 | } 20 | 21 | @Override 22 | public void addStatusListener(StatusListener listener) { 23 | statusNotifier.register(listener); 24 | } 25 | 26 | @Override 27 | public void removeStatusListener(StatusListener listener) { 28 | statusNotifier.unregister(listener); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/launchdarkly/sdk/server/DataSourceStatusProviderImpl.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server; 2 | 3 | import com.launchdarkly.sdk.server.interfaces.DataSourceStatusProvider; 4 | 5 | import java.time.Duration; 6 | 7 | final class DataSourceStatusProviderImpl implements DataSourceStatusProvider { 8 | private final EventBroadcasterImpl dataSourceStatusNotifier; 9 | private final DataSourceUpdatesImpl dataSourceUpdates; 10 | 11 | DataSourceStatusProviderImpl( 12 | EventBroadcasterImpl dataSourceStatusNotifier, 13 | DataSourceUpdatesImpl dataSourceUpdates 14 | ) { 15 | this.dataSourceStatusNotifier = dataSourceStatusNotifier; 16 | this.dataSourceUpdates = dataSourceUpdates; 17 | } 18 | 19 | @Override 20 | public Status getStatus() { 21 | return dataSourceUpdates.getLastStatus(); 22 | } 23 | 24 | @Override 25 | public boolean waitFor(State desiredState, Duration timeout) throws InterruptedException { 26 | return dataSourceUpdates.waitFor(desiredState, timeout); 27 | } 28 | 29 | @Override 30 | public void addStatusListener(StatusListener listener) { 31 | dataSourceStatusNotifier.register(listener); 32 | } 33 | 34 | @Override 35 | public void removeStatusListener(StatusListener listener) { 36 | dataSourceStatusNotifier.unregister(listener); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/launchdarkly/sdk/server/DataStoreStatusProviderImpl.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server; 2 | 3 | import com.launchdarkly.sdk.server.interfaces.DataStoreStatusProvider; 4 | import com.launchdarkly.sdk.server.subsystems.DataStore; 5 | 6 | final class DataStoreStatusProviderImpl implements DataStoreStatusProvider { 7 | private final DataStore store; 8 | private final DataStoreUpdatesImpl dataStoreUpdates; 9 | 10 | DataStoreStatusProviderImpl( 11 | DataStore store, 12 | DataStoreUpdatesImpl dataStoreUpdates 13 | ) { 14 | this.store = store; 15 | this.dataStoreUpdates = dataStoreUpdates; 16 | } 17 | 18 | @Override 19 | public Status getStatus() { 20 | return dataStoreUpdates.lastStatus.get(); 21 | } 22 | 23 | @Override 24 | public void addStatusListener(StatusListener listener) { 25 | dataStoreUpdates.statusBroadcaster.register(listener); 26 | } 27 | 28 | @Override 29 | public void removeStatusListener(StatusListener listener) { 30 | dataStoreUpdates.statusBroadcaster.unregister(listener); 31 | } 32 | 33 | @Override 34 | public boolean isStatusMonitoringEnabled() { 35 | return store.isStatusMonitoringEnabled(); 36 | } 37 | 38 | @Override 39 | public CacheStats getCacheStats() { 40 | return store.getCacheStats(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/launchdarkly/sdk/server/DataStoreUpdatesImpl.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server; 2 | 3 | import com.launchdarkly.sdk.server.interfaces.DataStoreStatusProvider; 4 | import com.launchdarkly.sdk.server.subsystems.DataStoreUpdateSink; 5 | 6 | import java.util.concurrent.atomic.AtomicReference; 7 | 8 | class DataStoreUpdatesImpl implements DataStoreUpdateSink { 9 | // package-private because it's convenient to use these from DataStoreStatusProviderImpl 10 | final EventBroadcasterImpl statusBroadcaster; 11 | final AtomicReference lastStatus; 12 | 13 | DataStoreUpdatesImpl( 14 | EventBroadcasterImpl statusBroadcaster 15 | ) { 16 | this.statusBroadcaster = statusBroadcaster; 17 | this.lastStatus = new AtomicReference<>(new DataStoreStatusProvider.Status(true, false)); // initially "available" 18 | } 19 | 20 | @Override 21 | public void updateStatus(DataStoreStatusProvider.Status newStatus) { 22 | if (newStatus != null) { 23 | DataStoreStatusProvider.Status oldStatus = lastStatus.getAndSet(newStatus); 24 | if (!newStatus.equals(oldStatus)) { 25 | statusBroadcaster.broadcast(newStatus); 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/launchdarkly/sdk/server/DefaultEventProcessorWrapper.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server; 2 | 3 | import com.launchdarkly.logging.LDLogger; 4 | import com.launchdarkly.sdk.EvaluationReason; 5 | import com.launchdarkly.sdk.LDContext; 6 | import com.launchdarkly.sdk.LDValue; 7 | import com.launchdarkly.sdk.internal.events.DefaultEventProcessor; 8 | import com.launchdarkly.sdk.internal.events.Event; 9 | import com.launchdarkly.sdk.internal.events.EventsConfiguration; 10 | import com.launchdarkly.sdk.server.subsystems.ClientContext; 11 | import com.launchdarkly.sdk.server.subsystems.EventProcessor; 12 | 13 | import java.io.IOException; 14 | import java.util.Optional; 15 | 16 | final class DefaultEventProcessorWrapper implements EventProcessor { 17 | private final DefaultEventProcessor eventProcessor; 18 | final EventsConfiguration eventsConfig; // visible for testing 19 | 20 | DefaultEventProcessorWrapper(ClientContext clientContext, EventsConfiguration eventsConfig) { 21 | this.eventsConfig = eventsConfig; 22 | LDLogger baseLogger = clientContext.getBaseLogger(); 23 | LDLogger logger = baseLogger.subLogger(Loggers.EVENTS_LOGGER_NAME); 24 | eventProcessor = new DefaultEventProcessor( 25 | eventsConfig, 26 | ClientContextImpl.get(clientContext).sharedExecutor, 27 | clientContext.getThreadPriority(), 28 | logger 29 | ); 30 | } 31 | 32 | @Override 33 | public void recordEvaluationEvent(LDContext context, String flagKey, int flagVersion, int variation, 34 | LDValue value, EvaluationReason reason, LDValue defaultValue, String prerequisiteOfFlagKey, 35 | boolean requireFullEvent, Long debugEventsUntilDate, boolean excludeFromSummaries, 36 | Long samplingRatio) { 37 | eventProcessor.sendEvent(new Event.FeatureRequest( 38 | System.currentTimeMillis(), 39 | flagKey, 40 | context, 41 | flagVersion, 42 | variation, 43 | value, 44 | defaultValue, 45 | reason, 46 | prerequisiteOfFlagKey, 47 | requireFullEvent, 48 | debugEventsUntilDate, 49 | false, 50 | samplingRatio != null ? samplingRatio : 1, 51 | excludeFromSummaries 52 | )); 53 | } 54 | 55 | @Override 56 | public void recordIdentifyEvent(LDContext context) { 57 | eventProcessor.sendEvent(new Event.Identify(System.currentTimeMillis(), context)); 58 | } 59 | 60 | @Override 61 | public void recordCustomEvent(LDContext context, String eventKey, LDValue data, Double metricValue) { 62 | eventProcessor.sendEvent(new Event.Custom(System.currentTimeMillis(), eventKey, context, data, metricValue)); 63 | } 64 | 65 | @Override 66 | public void recordMigrationEvent(MigrationOpTracker tracker) { 67 | Optional event = tracker.createEvent(); 68 | if(event.isPresent()) { 69 | eventProcessor.sendEvent(event.get()); 70 | } 71 | } 72 | 73 | @Override 74 | public void flush() { 75 | eventProcessor.flushAsync(); 76 | } 77 | 78 | @Override 79 | public void close() throws IOException { 80 | eventProcessor.close(); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/com/launchdarkly/sdk/server/EvalResultAndFlag.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | import org.jetbrains.annotations.Nullable; 5 | 6 | class EvalResultAndFlag { 7 | private final EvalResult result; 8 | private final DataModel.FeatureFlag flag; 9 | 10 | EvalResultAndFlag(@NotNull EvalResult result, @Nullable DataModel.FeatureFlag flag) { 11 | this.result = result; 12 | this.flag = flag; 13 | } 14 | 15 | EvalResult getResult() { 16 | return result; 17 | } 18 | 19 | DataModel.FeatureFlag getFlag() { 20 | return flag; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/launchdarkly/sdk/server/EvaluationOptions.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server; 2 | 3 | /** 4 | * Container class for options that can be provided along with the evaluation invocation to influence various 5 | * behavior of the evaluation. 6 | */ 7 | final class EvaluationOptions { 8 | final boolean recordEvents; 9 | final boolean includeReasonsWithEvents; 10 | 11 | /** 12 | * @param recordEvents when true, events will be recorded while the evaluation is performed 13 | * @param includeReasonsWithEvents when true, any events that are recorded will include reasons 14 | */ 15 | private EvaluationOptions(boolean recordEvents, boolean includeReasonsWithEvents) { 16 | this.recordEvents = recordEvents; 17 | this.includeReasonsWithEvents = includeReasonsWithEvents; 18 | 19 | } 20 | 21 | /** 22 | * During evaluation, no events will be recorded. 23 | */ 24 | public static final EvaluationOptions NO_EVENTS = new EvaluationOptions(false, false); 25 | 26 | /** 27 | * During evaluation, events will be recorded, but they will not include reasons. 28 | */ 29 | public static final EvaluationOptions EVENTS_WITHOUT_REASONS = new EvaluationOptions(true, false); 30 | 31 | /** 32 | * During evaluation, events will be recorded and those events will include reasons. 33 | */ 34 | public static final EvaluationOptions EVENTS_WITH_REASONS = new EvaluationOptions(true, true); 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/launchdarkly/sdk/server/EvaluationRecorder.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server; 2 | 3 | import com.launchdarkly.sdk.EvaluationReason; 4 | import com.launchdarkly.sdk.LDContext; 5 | import com.launchdarkly.sdk.LDValue; 6 | 7 | /** 8 | * This interface exists to provide abstraction of event recording during the evaluation process so that it 9 | * can be customized by the {@link EvaluationOptions} provided at the time of evaluation. This interface also 10 | * helps organize the structure of recording events and helps ensure consistency. 11 | */ 12 | interface EvaluationRecorder { 13 | default void recordEvaluation(DataModel.FeatureFlag flag, LDContext context, EvalResult result, LDValue defaultValue) { 14 | // default is no op 15 | } 16 | default void recordPrerequisiteEvaluation(DataModel.FeatureFlag flag, DataModel.FeatureFlag prereqOfFlag, LDContext context, EvalResult result) { 17 | // default is no op 18 | } 19 | default void recordEvaluationError(DataModel.FeatureFlag flag, LDContext context, LDValue defaultValue, EvaluationReason.ErrorKind errorKind) { 20 | // default is no op 21 | } 22 | default void recordEvaluationUnknownFlagError(String flagKey, LDContext context, LDValue defaultValue, EvaluationReason.ErrorKind errorKind) { 23 | // default is no op 24 | } 25 | } -------------------------------------------------------------------------------- /src/main/java/com/launchdarkly/sdk/server/EvaluatorBucketing.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server; 2 | 3 | import com.launchdarkly.sdk.AttributeRef; 4 | import com.launchdarkly.sdk.ContextKind; 5 | import com.launchdarkly.sdk.LDContext; 6 | import com.launchdarkly.sdk.LDValue; 7 | 8 | import org.apache.commons.codec.digest.DigestUtils; 9 | 10 | /** 11 | * Encapsulates the logic for percentage rollouts. 12 | */ 13 | abstract class EvaluatorBucketing { 14 | private EvaluatorBucketing() {} 15 | 16 | private static final float LONG_SCALE = (float) 0xFFFFFFFFFFFFFFFL; 17 | 18 | // Computes a bucket value for a rollout or experiment. If an error condition prevents 19 | // us from computing a valid bucket value, we return 0, which will cause the evaluator 20 | // to select the first bucket. A special case is if no context of the desired kind is 21 | // found, in which case we return the special value -1; this similarly will cause the 22 | // first bucket to be chosen (since it is less than the end value of the bucket, just 23 | // as 0 is), but also tells the evaluator that inExperiment must be set to false. 24 | static float computeBucketValue( 25 | boolean isExperiment, 26 | Integer seed, 27 | LDContext context, 28 | ContextKind contextKind, 29 | String flagOrSegmentKey, 30 | AttributeRef attr, 31 | String salt 32 | ) { 33 | LDContext matchContext = context.getIndividualContext(contextKind); 34 | if (matchContext == null) { 35 | return -1; 36 | } 37 | LDValue contextValue; 38 | if (isExperiment || attr == null) { 39 | contextValue = LDValue.of(matchContext.getKey()); 40 | } else { 41 | if (!attr.isValid()) { 42 | return 0; 43 | } 44 | contextValue = matchContext.getValue(attr); 45 | if (contextValue.isNull()) { 46 | return 0; 47 | } 48 | } 49 | 50 | String idHash = getBucketableStringValue(contextValue); 51 | if (idHash == null) { 52 | return 0; 53 | } 54 | 55 | String prefix; 56 | if (seed != null) { 57 | prefix = seed.toString(); 58 | } else { 59 | prefix = flagOrSegmentKey + "." + salt; 60 | } 61 | String hash = DigestUtils.sha1Hex(prefix + "." + idHash).substring(0, 15); 62 | long longVal = Long.parseLong(hash, 16); 63 | return (float) longVal / LONG_SCALE; 64 | } 65 | 66 | private static String getBucketableStringValue(LDValue userValue) { 67 | switch (userValue.getType()) { 68 | case STRING: 69 | return userValue.stringValue(); 70 | case NUMBER: 71 | return userValue.isInt() ? String.valueOf(userValue.intValue()) : null; 72 | default: 73 | return null; 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/com/launchdarkly/sdk/server/EvaluatorInterface.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server; 2 | 3 | import com.launchdarkly.sdk.LDContext; 4 | import com.launchdarkly.sdk.LDValue; 5 | import com.launchdarkly.sdk.LDValueType; 6 | 7 | /** 8 | * An Evaluator is able to calculate evaluation results for flags against the provided context. 9 | */ 10 | public interface EvaluatorInterface { 11 | 12 | /** 13 | * Evaluates the provided flag. 14 | * 15 | * @param method the top level customer facing method that led to this invocation 16 | * @param flagKey of the flag that will be evaluated 17 | * @param context to use during the evaluation 18 | * @param defaultValue the value that will be returned in the result if an issue prevents the evaluator from 19 | * successfully calculating an evaluation result. 20 | * @param requireType that will be asserted against the evaluator's result. If the assertion fails, the default 21 | * value is used in the returned result. 22 | * @param options that are used to control more specific behavior of the evaluation 23 | * @return the evaluation result and flag object 24 | */ 25 | EvalResultAndFlag evalAndFlag(String method, String flagKey, LDContext context, LDValue defaultValue, 26 | LDValueType requireType, EvaluationOptions options); 27 | 28 | /** 29 | * Evaluates all flags. 30 | *

31 | * It is up to each implementation whether events will be logged during evaluation. 32 | * 33 | * @param context to use during the evaluation 34 | * @param options optional {@link FlagsStateOption} values affecting how the state is computed 35 | * @return a {@link FeatureFlagsState} object (will never be null; see {@link FeatureFlagsState#isValid()} 36 | */ 37 | FeatureFlagsState allFlagsState(LDContext context, FlagsStateOption... options); 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/launchdarkly/sdk/server/EvaluatorTypeConversion.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server; 2 | 3 | import com.launchdarkly.sdk.LDValue; 4 | 5 | import java.time.Instant; 6 | import java.time.ZonedDateTime; 7 | import java.util.regex.Pattern; 8 | import java.util.regex.PatternSyntaxException; 9 | 10 | abstract class EvaluatorTypeConversion { 11 | private EvaluatorTypeConversion() {} 12 | 13 | static Instant valueToDateTime(LDValue value) { 14 | if (value.isNumber()) { 15 | return Instant.ofEpochMilli(value.longValue()); 16 | } else if (value.isString()) { 17 | try { 18 | return ZonedDateTime.parse(value.stringValue()).toInstant(); 19 | } catch (Throwable t) { 20 | return null; 21 | } 22 | } else { 23 | return null; 24 | } 25 | } 26 | 27 | static Pattern valueToRegex(LDValue value) { 28 | if (!value.isString()) { 29 | return null; 30 | } 31 | try { 32 | return Pattern.compile(value.stringValue()); 33 | } catch (PatternSyntaxException e) { 34 | return null; 35 | } 36 | } 37 | 38 | static SemanticVersion valueToSemVer(LDValue value) { 39 | if (!value.isString()) { 40 | return null; 41 | } 42 | try { 43 | return SemanticVersion.parse(value.stringValue(), true); 44 | } catch (SemanticVersion.InvalidVersionException e) { 45 | return null; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/launchdarkly/sdk/server/FeatureRequestor.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server; 2 | 3 | import com.launchdarkly.sdk.internal.http.HttpErrors.HttpErrorException; 4 | import com.launchdarkly.sdk.server.subsystems.DataStoreTypes.FullDataSet; 5 | import com.launchdarkly.sdk.server.subsystems.DataStoreTypes.ItemDescriptor; 6 | 7 | import java.io.Closeable; 8 | import java.io.IOException; 9 | 10 | /** 11 | * Internal abstraction for polling requests. Currently this is only used by PollingProcessor, and 12 | * the only implementation is DefaultFeatureRequestor, but using an interface allows us to mock out 13 | * the HTTP behavior and test the rest of PollingProcessor separately. 14 | */ 15 | interface FeatureRequestor extends Closeable { 16 | /** 17 | * Makes a request to the LaunchDarkly server-side SDK polling endpoint, 18 | * 19 | * @param returnDataEvenIfCached true if the method should return non-nil data no matter what; 20 | * false if it should return {@code null} when the latest data is already in the cache 21 | * @return the data, or {@code null} as above 22 | * @throws IOException for network errors 23 | * @throws HttpErrorException for HTTP error responses 24 | */ 25 | FullDataSet getAllData(boolean returnDataEvenIfCached) throws IOException, HttpErrorException; 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/launchdarkly/sdk/server/FlagTrackerImpl.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server; 2 | 3 | import com.launchdarkly.sdk.LDContext; 4 | import com.launchdarkly.sdk.LDValue; 5 | import com.launchdarkly.sdk.server.interfaces.FlagChangeEvent; 6 | import com.launchdarkly.sdk.server.interfaces.FlagChangeListener; 7 | import com.launchdarkly.sdk.server.interfaces.FlagTracker; 8 | import com.launchdarkly.sdk.server.interfaces.FlagValueChangeEvent; 9 | import com.launchdarkly.sdk.server.interfaces.FlagValueChangeListener; 10 | 11 | import java.util.concurrent.atomic.AtomicReference; 12 | import java.util.function.BiFunction; 13 | 14 | final class FlagTrackerImpl implements FlagTracker { 15 | private final EventBroadcasterImpl flagChangeBroadcaster; 16 | private final BiFunction evaluateFn; 17 | 18 | FlagTrackerImpl( 19 | EventBroadcasterImpl flagChangeBroadcaster, 20 | BiFunction evaluateFn 21 | ) { 22 | this.flagChangeBroadcaster = flagChangeBroadcaster; 23 | this.evaluateFn = evaluateFn; 24 | } 25 | 26 | @Override 27 | public void addFlagChangeListener(FlagChangeListener listener) { 28 | flagChangeBroadcaster.register(listener); 29 | } 30 | 31 | @Override 32 | public void removeFlagChangeListener(FlagChangeListener listener) { 33 | flagChangeBroadcaster.unregister(listener); 34 | } 35 | 36 | @Override 37 | public FlagChangeListener addFlagValueChangeListener(String flagKey, LDContext context, FlagValueChangeListener listener) { 38 | FlagValueChangeAdapter adapter = new FlagValueChangeAdapter(flagKey, context, listener); 39 | addFlagChangeListener(adapter); 40 | return adapter; 41 | } 42 | 43 | private final class FlagValueChangeAdapter implements FlagChangeListener { 44 | private final String flagKey; 45 | private final LDContext context; 46 | private final FlagValueChangeListener listener; 47 | private final AtomicReference value; 48 | 49 | FlagValueChangeAdapter(String flagKey, LDContext context, FlagValueChangeListener listener) { 50 | this.flagKey = flagKey; 51 | this.context = context; 52 | this.listener = listener; 53 | this.value = new AtomicReference<>(evaluateFn.apply(flagKey, context)); 54 | } 55 | 56 | @Override 57 | public void onFlagChange(FlagChangeEvent event) { 58 | if (event.getKey().equals(flagKey)) { 59 | LDValue newValue = evaluateFn.apply(flagKey, context); 60 | LDValue oldValue = value.getAndSet(newValue); 61 | if (!newValue.equals(oldValue)) { 62 | listener.onFlagValueChange(new FlagValueChangeEvent(flagKey, oldValue, newValue)); 63 | } 64 | } 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/com/launchdarkly/sdk/server/FlagsStateOption.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server; 2 | 3 | import com.launchdarkly.sdk.EvaluationReason; 4 | import com.launchdarkly.sdk.server.interfaces.LDClientInterface; 5 | 6 | /** 7 | * Optional parameters that can be passed to {@link LDClientInterface#allFlagsState(com.launchdarkly.sdk.LDContext, FlagsStateOption...)}. 8 | * @since 4.3.0 9 | */ 10 | public final class FlagsStateOption { 11 | private final String description; 12 | 13 | private FlagsStateOption(String description) { 14 | this.description = description; 15 | } 16 | 17 | @Override 18 | public String toString() { 19 | return description; 20 | } 21 | 22 | /** 23 | * Specifies that only flags marked for use with the client-side SDK should be included in the state object. 24 | * By default, all flags are included. 25 | */ 26 | public static final FlagsStateOption CLIENT_SIDE_ONLY = new FlagsStateOption("CLIENT_SIDE_ONLY"); 27 | 28 | /** 29 | * Specifies that {@link EvaluationReason} data should be captured in the state object. By default, it is not. 30 | */ 31 | public static final FlagsStateOption WITH_REASONS = new FlagsStateOption("WITH_REASONS"); 32 | 33 | /** 34 | * Specifies that any flag metadata that is normally only used for event generation - such as flag versions and 35 | * evaluation reasons - should be omitted for any flag that does not have event tracking or debugging turned on. 36 | * This reduces the size of the JSON data if you are passing the flag state to the front end. 37 | * @since 4.4.0 38 | */ 39 | public static final FlagsStateOption DETAILS_ONLY_FOR_TRACKED_FLAGS = new FlagsStateOption("DETAILS_ONLY_FOR_TRACKED_FLAGS"); 40 | 41 | static boolean hasOption(FlagsStateOption[] options, FlagsStateOption option) { 42 | for (FlagsStateOption o: options) { 43 | if (o == option) { 44 | return true; 45 | } 46 | } 47 | return false; 48 | } 49 | } -------------------------------------------------------------------------------- /src/main/java/com/launchdarkly/sdk/server/Loggers.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server; 2 | 3 | /** 4 | * Static logger instances to be shared by implementation code in the main {@code com.launchdarkly.sdk.server} 5 | * package. 6 | *

7 | * The goal here is 1. to centralize logger references rather than having many calls to 8 | * {@code LoggerFactory.getLogger()} all over the code, and 2. to encourage usage of a basic set of 9 | * logger names that are not tied to class names besides the main LDClient class. Most class names in 10 | * the SDK are package-private implementation details that are not meaningful to users, so in terms of 11 | * both being able to see the relevant area of functionality at a glance when reading a log and also 12 | * convenience in defining SLF4J logger name filters, it is preferable to use these stable names. 13 | *

14 | * Code in other packages such as {@code com.launchdarkly.sdk.server.integrations} cannot use these 15 | * package-private fields, but should still use equivalent logger names as appropriate. 16 | */ 17 | abstract class Loggers { 18 | private Loggers() {} 19 | 20 | static final String BASE_LOGGER_NAME = LDClient.class.getName(); 21 | static final String BIG_SEGMENTS_LOGGER_NAME = "BigSegments"; 22 | static final String DATA_SOURCE_LOGGER_NAME = "DataSource"; 23 | static final String DATA_STORE_LOGGER_NAME = "DataStore"; 24 | static final String EVALUATION_LOGGER_NAME = "Evaluation"; 25 | static final String EVENTS_LOGGER_NAME = "Events"; 26 | static final String HOOKS_LOGGER_NAME = "Hooks"; 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/launchdarkly/sdk/server/MigrationOp.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server; 2 | 3 | /** 4 | * The type of migration operation. 5 | */ 6 | public enum MigrationOp { 7 | READ("read"), 8 | WRITE("write"); 9 | 10 | private final String strValue; 11 | 12 | MigrationOp(final String strValue) { 13 | this.strValue = strValue; 14 | } 15 | 16 | @Override 17 | public String toString() { 18 | return strValue; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/launchdarkly/sdk/server/MigrationOrigin.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server; 2 | 3 | /** 4 | * The origin/source for a migration step. 5 | */ 6 | public enum MigrationOrigin { 7 | /** 8 | * The "old" implementation. 9 | */ 10 | OLD, 11 | 12 | /** 13 | * The "new" implementation. 14 | */ 15 | NEW 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/launchdarkly/sdk/server/MigrationStage.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server; 2 | 3 | import java.util.Arrays; 4 | 5 | /** 6 | * Stage denotes one of six possible stages a technology migration could be a 7 | * part of, progressing through the following order. 8 | *

9 | * Off DualWrite Shadow Live RampDown Complete 10 | */ 11 | public enum MigrationStage { 12 | /** 13 | * Off - migration hasn't started, "old" is authoritative for reads and writes 14 | */ 15 | OFF("off"), 16 | 17 | /** 18 | * DualWrite - write to both "old" and "new", "old" is authoritative for reads 19 | */ 20 | DUAL_WRITE("dualwrite"), 21 | 22 | /** 23 | * Shadow - both "new" and "old" versions run with a preference for "old" 24 | */ 25 | SHADOW("shadow"), 26 | 27 | /** 28 | * Live - both "new" and "old" versions run with a preference for "new" 29 | */ 30 | LIVE("live"), 31 | 32 | /** 33 | * RampDown - only read from "new", write to "old" and "new" 34 | */ 35 | RAMP_DOWN("rampdown"), 36 | 37 | /** 38 | * Complete - migration is done 39 | */ 40 | COMPLETE("complete"); 41 | 42 | private final String strValue; 43 | 44 | MigrationStage(final String strValue) { 45 | this.strValue = strValue; 46 | } 47 | 48 | @Override 49 | public String toString() { 50 | return strValue; 51 | } 52 | 53 | /** 54 | * Check if the provided string is a migration stage. 55 | * 56 | * @param strValue The string to check. 57 | * @return True if the string represents a migration stage. 58 | */ 59 | public static boolean isStage(String strValue) { 60 | return Arrays.stream(MigrationStage.values()).anyMatch(item -> item.strValue.equals(strValue)); 61 | } 62 | 63 | /** 64 | * Convert a string into a migration stage. 65 | *

66 | * If the string is not a stage, then the provided default will be returned. 67 | * 68 | * @param strValue The string to convert. 69 | * @param defaultStage The default value to use if the string does not represent a migration stage. 70 | * @return The converted migration stage. 71 | */ 72 | public static MigrationStage of(String strValue, MigrationStage defaultStage) { 73 | return Arrays.stream(MigrationStage.values()) 74 | .filter(item -> item.strValue.equals(strValue)) 75 | .findFirst() 76 | .orElse(defaultStage); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/com/launchdarkly/sdk/server/MigrationStageEnforcingEvaluator.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server; 2 | 3 | import com.launchdarkly.logging.LDLogger; 4 | import com.launchdarkly.sdk.EvaluationDetail; 5 | import com.launchdarkly.sdk.EvaluationReason; 6 | import com.launchdarkly.sdk.LDContext; 7 | import com.launchdarkly.sdk.LDValue; 8 | import com.launchdarkly.sdk.LDValueType; 9 | 10 | /** 11 | * This class exists to enforce that migration variation results are stages from the {@link MigrationStage} enum. 12 | */ 13 | class MigrationStageEnforcingEvaluator implements EvaluatorInterface { 14 | 15 | private final EvaluatorInterface underlyingEvaluator; 16 | private final LDLogger logger; 17 | 18 | MigrationStageEnforcingEvaluator(EvaluatorInterface underlyingEvaluator, LDLogger logger) { 19 | this.underlyingEvaluator = underlyingEvaluator; 20 | this.logger = logger; 21 | } 22 | 23 | @Override 24 | public EvalResultAndFlag evalAndFlag(String method, String flagKey, LDContext context, LDValue defaultValue, LDValueType requireType, EvaluationOptions options) { 25 | EvalResultAndFlag res = underlyingEvaluator.evalAndFlag(method, flagKey, context, defaultValue, requireType, options); 26 | 27 | EvaluationDetail resDetail = res.getResult().getAsString(); 28 | String resStageString = resDetail.getValue(); 29 | if (!MigrationStage.isStage(resStageString)) { 30 | logger.error("Unrecognized MigrationState for \"{}\"; returning default value.", flagKey); 31 | return new EvalResultAndFlag(EvalResult.error(EvaluationReason.ErrorKind.WRONG_TYPE, defaultValue), res.getFlag()); 32 | } 33 | 34 | return res; 35 | } 36 | 37 | @Override 38 | public FeatureFlagsState allFlagsState(LDContext context, FlagsStateOption... options) { 39 | // this decorator is a pass through for the all flag state case 40 | return underlyingEvaluator.allFlagsState(context, options); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/launchdarkly/sdk/server/MigrationVariation.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server; 2 | 3 | import com.launchdarkly.sdk.LDContext; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | /** 7 | * Result of an {@link LDClient#migrationVariation(String, LDContext, MigrationStage)} call. 8 | *

9 | * This includes the stage of the migration as well as a tracker. 10 | */ 11 | public final class MigrationVariation { 12 | private final MigrationStage stage; 13 | private final MigrationOpTracker tracker; 14 | 15 | public MigrationVariation(@NotNull MigrationStage stage, @NotNull MigrationOpTracker tracker) { 16 | this.stage = stage; 17 | this.tracker = tracker; 18 | } 19 | 20 | /** 21 | * The result of the flag evaluation. This will be either one of the flag's variations or 22 | * the default value that was passed to {@link LDClient#migrationVariation(String, LDContext, MigrationStage)}. 23 | * @return The migration stage. 24 | */ 25 | public MigrationStage getStage() { 26 | return this.stage; 27 | } 28 | 29 | /** 30 | * A tracker which can be used to generate analytics for the migration. 31 | * @return The tracker. 32 | */ 33 | public MigrationOpTracker getTracker() { 34 | return this.tracker; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/launchdarkly/sdk/server/NoOpEventProcessor.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server; 2 | 3 | import com.launchdarkly.sdk.EvaluationReason; 4 | import com.launchdarkly.sdk.LDContext; 5 | import com.launchdarkly.sdk.LDValue; 6 | import com.launchdarkly.sdk.server.subsystems.EventProcessor; 7 | 8 | import java.io.IOException; 9 | 10 | /** 11 | * An {@link EventProcessor} that does nothing when invoked. 12 | */ 13 | class NoOpEventProcessor implements EventProcessor { 14 | 15 | @Override 16 | public void recordEvaluationEvent(LDContext context, String flagKey, int flagVersion, int variation, LDValue value, 17 | EvaluationReason reason, LDValue defaultValue, String prerequisiteOfFlagKey, 18 | boolean requireFullEvent, Long debugEventsUntilDate, boolean excludeFromSummaries, 19 | Long samplingRatio) { 20 | // no-op 21 | } 22 | 23 | @Override 24 | public void recordIdentifyEvent(LDContext context) { 25 | // no-op 26 | } 27 | 28 | @Override 29 | public void recordCustomEvent(LDContext context, String eventKey, LDValue data, Double metricValue) { 30 | // no-op 31 | } 32 | 33 | @Override 34 | public void recordMigrationEvent(MigrationOpTracker tracker) { 35 | // no-op 36 | } 37 | 38 | @Override 39 | public void flush() { 40 | // no-op 41 | } 42 | 43 | @Override 44 | public void close() throws IOException { 45 | // no-op 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/launchdarkly/sdk/server/ServerSideEventContextDeduplicator.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server; 2 | 3 | import com.launchdarkly.sdk.LDContext; 4 | import com.launchdarkly.sdk.internal.events.EventContextDeduplicator; 5 | 6 | import java.time.Duration; 7 | 8 | final class ServerSideEventContextDeduplicator implements EventContextDeduplicator { 9 | private final SimpleLRUCache contextKeys; 10 | private final Duration flushInterval; 11 | 12 | public ServerSideEventContextDeduplicator( 13 | int capacity, 14 | Duration flushInterval 15 | ) { 16 | this.contextKeys = new SimpleLRUCache<>(capacity); 17 | this.flushInterval = flushInterval; 18 | } 19 | 20 | @Override 21 | public Long getFlushInterval() { 22 | return flushInterval.toMillis(); 23 | } 24 | 25 | @Override 26 | public boolean processContext(LDContext context) { 27 | String key = context.getFullyQualifiedKey(); 28 | if (key == null || key.isEmpty()) { 29 | return false; 30 | } 31 | String previousValue = contextKeys.put(key, key); 32 | return previousValue == null; 33 | } 34 | 35 | @Override 36 | public void flush() { 37 | contextKeys.clear(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/launchdarkly/sdk/server/SimpleLRUCache.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server; 2 | 3 | import java.util.LinkedHashMap; 4 | import java.util.Map; 5 | 6 | /** 7 | * A very basic implementation of a LRU cache with a fixed capacity. Note that in this 8 | * implementation, entries only become new again when written, not when read. 9 | * See: http://chriswu.me/blog/a-lru-cache-in-10-lines-of-java/ 10 | */ 11 | @SuppressWarnings("serial") 12 | class SimpleLRUCache extends LinkedHashMap { 13 | private final int capacity; 14 | 15 | SimpleLRUCache(int capacity) { 16 | super(16, 0.75f, true); 17 | this.capacity = capacity; 18 | } 19 | 20 | @Override 21 | protected boolean removeEldestEntry(Map.Entry eldest) { 22 | return size() > capacity; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/launchdarkly/sdk/server/StandardEndpoints.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server; 2 | 3 | import com.launchdarkly.logging.LDLogger; 4 | 5 | import java.net.URI; 6 | 7 | abstract class StandardEndpoints { 8 | private StandardEndpoints() {} 9 | 10 | static final URI DEFAULT_STREAMING_BASE_URI = URI.create("https://stream.launchdarkly.com"); 11 | static final URI DEFAULT_POLLING_BASE_URI = URI.create("https://app.launchdarkly.com"); 12 | static final URI DEFAULT_EVENTS_BASE_URI = URI.create("https://events.launchdarkly.com"); 13 | 14 | static final String STREAMING_REQUEST_PATH = "/all"; 15 | static final String POLLING_REQUEST_PATH = "/sdk/latest-all"; 16 | 17 | /** 18 | * Internal method to decide which URI a given component should connect to. 19 | *

20 | * Always returns some URI, falling back on the default if necessary, but logs a warning if we detect that the application 21 | * set some custom endpoints but not this one. 22 | * 23 | * @param serviceEndpointsValue the value set in ServiceEndpoints (this is either the default URI, a custom URI, or null) 24 | * @param defaultValue the constant default URI value defined in StandardEndpoints 25 | * @param description a human-readable string for the type of endpoint being selected, for logging purposes 26 | * @param logger the logger to which we should print the warning, if needed 27 | * @return the base URI we should connect to 28 | */ 29 | static URI selectBaseUri(URI serviceEndpointsValue, URI defaultValue, String description, LDLogger logger) { 30 | if (serviceEndpointsValue != null) { 31 | return serviceEndpointsValue; 32 | } 33 | logger.warn("You have set custom ServiceEndpoints without specifying the {} base URI; connections may not work properly", description); 34 | return defaultValue; 35 | } 36 | 37 | /** 38 | * Internal method to determine whether a given base URI was set to a custom value or not. 39 | *

40 | * This boolean value is only used for our diagnostic events. We only check if the value 41 | * differs from the default; if the base URI was "overridden" in configuration, but 42 | * happens to be equal to the default URI, we don't count that as custom 43 | * for the purposes of this diagnostic. 44 | * 45 | * @param serviceEndpointsValue the value set in ServiceEndpoints 46 | * @param defaultValue the constant default URI value defined in StandardEndpoints 47 | * @return true iff the base URI was customized 48 | */ 49 | static boolean isCustomBaseUri(URI serviceEndpointsValue, URI defaultValue) { 50 | return serviceEndpointsValue != null && !serviceEndpointsValue.equals(defaultValue); 51 | } 52 | } -------------------------------------------------------------------------------- /src/main/java/com/launchdarkly/sdk/server/Version.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server; 2 | 3 | abstract class Version { 4 | private Version() {} 5 | 6 | // This constant is updated automatically by our Gradle script during a release, if the project version has changed 7 | static final String SDK_VERSION = "7.4.1"; 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/launchdarkly/sdk/server/integrations/ApplicationInfoBuilder.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server.integrations; 2 | 3 | import com.launchdarkly.sdk.server.Components; 4 | import com.launchdarkly.sdk.server.interfaces.ApplicationInfo; 5 | 6 | /** 7 | * Contains methods for configuring the SDK's application metadata. 8 | *

9 | * Application metadata may be used in LaunchDarkly analytics or other product features, but does not affect feature flag evaluations. 10 | *

11 | * If you want to set non-default values for any of these fields, create a builder with 12 | * {@link Components#applicationInfo()}, change its properties with the methods of this class, 13 | * and pass it to {@link com.launchdarkly.sdk.server.LDConfig.Builder#applicationInfo(ApplicationInfoBuilder)}: 14 | *


15 |  *     LDConfig config = new LDConfig.Builder()
16 |  *         .applicationInfo(
17 |  *             Components.applicationInfo()
18 |  *                 .applicationId("authentication-service")
19 |  *                 .applicationVersion("1.0.0")
20 |  *         )
21 |  *         .build();
22 |  * 
23 | *

24 | * 25 | * @since 5.8.0 26 | */ 27 | public final class ApplicationInfoBuilder { 28 | private String applicationId; 29 | private String applicationVersion; 30 | 31 | /** 32 | * Create an empty ApplicationInfoBuilder. 33 | * 34 | * @see Components#applicationInfo() 35 | */ 36 | public ApplicationInfoBuilder() {} 37 | 38 | /** 39 | * Sets a unique identifier representing the application where the LaunchDarkly SDK is running. 40 | *

41 | * This can be specified as any string value as long as it only uses the following characters: ASCII 42 | * letters, ASCII digits, period, hyphen, underscore. A string containing any other characters will be 43 | * ignored. 44 | * 45 | * @param applicationId the application identifier 46 | * @return the builder 47 | */ 48 | public ApplicationInfoBuilder applicationId(String applicationId) { 49 | this.applicationId = applicationId; 50 | return this; 51 | } 52 | 53 | /** 54 | * Sets a unique identifier representing the version of the application where the LaunchDarkly SDK 55 | * is running. 56 | *

57 | * This can be specified as any string value as long as it only uses the following characters: ASCII 58 | * letters, ASCII digits, period, hyphen, underscore. A string containing any other characters will be 59 | * ignored. 60 | * 61 | * @param applicationVersion the application version 62 | * @return the builder 63 | */ 64 | public ApplicationInfoBuilder applicationVersion(String applicationVersion) { 65 | this.applicationVersion = applicationVersion; 66 | return this; 67 | } 68 | 69 | /** 70 | * Called internally by the SDK to create the configuration object. 71 | * 72 | * @return the configuration object 73 | */ 74 | public ApplicationInfo createApplicationInfo() { 75 | return new ApplicationInfo(applicationId, applicationVersion); 76 | } 77 | 78 | public static ApplicationInfoBuilder fromApplicationInfo(ApplicationInfo info) { 79 | ApplicationInfoBuilder newBuilder = new ApplicationInfoBuilder(); 80 | newBuilder.applicationId = info.getApplicationId(); 81 | newBuilder.applicationVersion = info.getApplicationVersion(); 82 | return newBuilder; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/com/launchdarkly/sdk/server/integrations/EvaluationSeriesContext.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server.integrations; 2 | 3 | import com.launchdarkly.sdk.LDContext; 4 | import com.launchdarkly.sdk.LDValue; 5 | 6 | import java.util.Map; 7 | 8 | /** 9 | * Represents parameters associated with a feature flag evaluation. An instance of this class is provided to some 10 | * stages of series of a {@link Hook} implementation. For example, see {@link Hook#beforeEvaluation(EvaluationSeriesContext, Map)} 11 | */ 12 | public class EvaluationSeriesContext { 13 | 14 | /** 15 | * The variation method that was used to invoke the evaluation. The stability of this string is not 16 | * guaranteed and should not be used in conditional logic. 17 | */ 18 | public final String method; 19 | 20 | /** 21 | * The key of the feature flag being evaluated. 22 | */ 23 | public final String flagKey; 24 | 25 | /** 26 | * The context the evaluation was for. 27 | */ 28 | public final LDContext context; 29 | 30 | /** 31 | * The user-provided default value for the evaluation. 32 | */ 33 | public final LDValue defaultValue; 34 | 35 | /** 36 | * @param method the variation method that was used to invoke the evaluation. 37 | * @param key the key of the feature flag being evaluated. 38 | * @param context the context the evaluation was for. 39 | * @param defaultValue the user-provided default value for the evaluation. 40 | */ 41 | public EvaluationSeriesContext(String method, String key, LDContext context, LDValue defaultValue) { 42 | this.flagKey = key; 43 | this.context = context; 44 | this.defaultValue = defaultValue; 45 | this.method = method; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/launchdarkly/sdk/server/integrations/HookMetadata.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server.integrations; 2 | 3 | /** 4 | * Metadata about the {@link Hook} implementation. 5 | */ 6 | public abstract class HookMetadata { 7 | 8 | private final String name; 9 | 10 | public HookMetadata(String name) { 11 | this.name = name; 12 | } 13 | 14 | /** 15 | * @return a friendly name for the {@link Hook} this {@link HookMetadata} belongs to. 16 | */ 17 | public String getName() { 18 | return name; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/launchdarkly/sdk/server/integrations/HooksConfigurationBuilder.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server.integrations; 2 | 3 | import com.launchdarkly.sdk.server.Components; 4 | import com.launchdarkly.sdk.server.subsystems.HookConfiguration; 5 | 6 | import java.util.ArrayList; 7 | import java.util.Collections; 8 | import java.util.List; 9 | 10 | /** 11 | * Contains methods for configuring the SDK's 'hooks'. 12 | *

13 | * If you want to add hooks, use {@link Components#hooks()}, configure accordingly, and pass it 14 | * to {@link com.launchdarkly.sdk.server.LDConfig.Builder#hooks(HooksConfigurationBuilder)}. 15 | * 16 | *


17 |  *     List hooks = createSomeHooks();
18 |  *     LDConfig config = new LDConfig.Builder()
19 |  *         .hooks(
20 |  *             Components.hooks()
21 |  *                 .setHooks(hooks)
22 |  *         )
23 |  *         .build();
24 |  * 
25 | *

26 | * Note that this class is abstract; the actual implementation is created by calling {@link Components#hooks()}. 27 | */ 28 | public abstract class HooksConfigurationBuilder { 29 | 30 | /** 31 | * The current set of hooks the builder has. 32 | */ 33 | protected List hooks = Collections.emptyList(); 34 | 35 | /** 36 | * Adds the provided list of hooks to the configuration. Note that the order of hooks is important and controls 37 | * the order in which they will be executed. See {@link Hook} for more details. 38 | * 39 | * @param hooks to be added to the configuration 40 | * @return the builder 41 | */ 42 | public HooksConfigurationBuilder setHooks(List hooks) { 43 | // copy to avoid list manipulations impacting the SDK 44 | this.hooks = Collections.unmodifiableList(new ArrayList<>(hooks)); 45 | return this; 46 | } 47 | 48 | /** 49 | * @return the hooks configuration 50 | */ 51 | abstract public HookConfiguration build(); 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/launchdarkly/sdk/server/integrations/PollingDataSourceBuilder.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server.integrations; 2 | 3 | import com.launchdarkly.sdk.server.Components; 4 | import com.launchdarkly.sdk.server.LDConfig.Builder; 5 | import com.launchdarkly.sdk.server.subsystems.ComponentConfigurer; 6 | import com.launchdarkly.sdk.server.subsystems.DataSource; 7 | 8 | import java.time.Duration; 9 | 10 | /** 11 | * Contains methods for configuring the polling data source. 12 | *

13 | * Polling is not the default behavior; by default, the SDK uses a streaming connection to receive feature flag 14 | * data from LaunchDarkly. In polling mode, the SDK instead makes a new HTTP request to LaunchDarkly at regular 15 | * intervals. HTTP caching allows it to avoid redundantly downloading data if there have been no changes, but 16 | * polling is still less efficient than streaming and should only be used on the advice of LaunchDarkly support. 17 | *

18 | * To use polling mode, create a builder with {@link Components#pollingDataSource()}, 19 | * change its properties with the methods of this class, and pass it to {@link Builder#dataSource(ComponentConfigurer)}: 20 | *


21 |  *     LDConfig config = new LDConfig.Builder()
22 |  *         .dataSource(Components.pollingDataSource().pollInterval(Duration.ofSeconds(45)))
23 |  *         .build();
24 |  * 
25 | *

26 | * Note that this class is abstract; the actual implementation is created by calling {@link Components#pollingDataSource()}. 27 | * 28 | * @since 4.12.0 29 | */ 30 | public abstract class PollingDataSourceBuilder implements ComponentConfigurer { 31 | /** 32 | * The default and minimum value for {@link #pollInterval(Duration)}: 30 seconds. 33 | */ 34 | public static final Duration DEFAULT_POLL_INTERVAL = Duration.ofSeconds(30); 35 | 36 | protected Duration pollInterval = DEFAULT_POLL_INTERVAL; 37 | 38 | protected String payloadFilter; 39 | 40 | /** 41 | * Sets the interval at which the SDK will poll for feature flag updates. 42 | *

43 | * The default and minimum value is {@link #DEFAULT_POLL_INTERVAL}. Values less than this will be 44 | * set to the default. 45 | * 46 | * @param pollInterval the polling interval; null to use the default 47 | * @return the builder 48 | */ 49 | public PollingDataSourceBuilder pollInterval(Duration pollInterval) { 50 | if (pollInterval == null) { 51 | this.pollInterval = DEFAULT_POLL_INTERVAL; 52 | } else { 53 | this.pollInterval = pollInterval.compareTo(DEFAULT_POLL_INTERVAL) < 0 ? DEFAULT_POLL_INTERVAL : pollInterval; 54 | } 55 | return this; 56 | } 57 | 58 | /** 59 | * Sets the Payload Filter that will be used to filter the objects (flags, segments, etc.) 60 | * from this data source. 61 | * 62 | * @param payloadFilter the filter to be used 63 | * @return the builder 64 | */ 65 | public PollingDataSourceBuilder payloadFilter(String payloadFilter) { 66 | this.payloadFilter = payloadFilter; 67 | return this; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/com/launchdarkly/sdk/server/integrations/StreamingDataSourceBuilder.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server.integrations; 2 | 3 | import java.time.Duration; 4 | 5 | import com.launchdarkly.sdk.server.Components; 6 | import com.launchdarkly.sdk.server.LDConfig.Builder; 7 | import com.launchdarkly.sdk.server.subsystems.ComponentConfigurer; 8 | import com.launchdarkly.sdk.server.subsystems.DataSource; 9 | 10 | /** 11 | * Contains methods for configuring the streaming data source. 12 | *

13 | * By default, the SDK uses a streaming connection to receive feature flag data from LaunchDarkly. If you want 14 | * to customize the behavior of the connection, create a builder with {@link Components#streamingDataSource()}, 15 | * change its properties with the methods of this class, and pass it to {@link Builder#dataSource(ComponentConfigurer)}: 16 | *


17 |  *     LDConfig config = new LDConfig.Builder()
18 |  *         .dataSource(Components.streamingDataSource().initialReconnectDelayMillis(500))
19 |  *         .build();
20 |  * 
21 | *

22 | * Note that this class is abstract; the actual implementation is created by calling {@link Components#streamingDataSource()}. 23 | * 24 | * @since 4.12.0 25 | */ 26 | public abstract class StreamingDataSourceBuilder implements ComponentConfigurer { 27 | /** 28 | * The default value for {@link #initialReconnectDelay(Duration)}: 1000 milliseconds. 29 | */ 30 | public static final Duration DEFAULT_INITIAL_RECONNECT_DELAY = Duration.ofMillis(1000); 31 | 32 | protected Duration initialReconnectDelay = DEFAULT_INITIAL_RECONNECT_DELAY; 33 | 34 | protected String payloadFilter; 35 | 36 | /** 37 | * Sets the initial reconnect delay for the streaming connection. 38 | *

39 | * The streaming service uses a backoff algorithm (with jitter) every time the connection needs 40 | * to be reestablished. The delay for the first reconnection will start near this value, and then 41 | * increase exponentially for any subsequent connection failures. 42 | *

43 | * The default value is {@link #DEFAULT_INITIAL_RECONNECT_DELAY}. 44 | * 45 | * @param initialReconnectDelay the reconnect time base value; null to use the default 46 | * @return the builder 47 | */ 48 | 49 | public StreamingDataSourceBuilder initialReconnectDelay(Duration initialReconnectDelay) { 50 | this.initialReconnectDelay = initialReconnectDelay == null ? DEFAULT_INITIAL_RECONNECT_DELAY : initialReconnectDelay; 51 | return this; 52 | } 53 | 54 | /** 55 | * Sets the Payload Filter that will be used to filter the objects (flags, segments, etc.) 56 | * from this data source. 57 | * 58 | * @param payloadFilter the filter to be used 59 | * @return the builder 60 | */ 61 | public StreamingDataSourceBuilder payloadFilter(String payloadFilter) { 62 | this.payloadFilter = payloadFilter; 63 | return this; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/com/launchdarkly/sdk/server/integrations/WrapperInfoBuilder.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server.integrations; 2 | 3 | import com.launchdarkly.sdk.server.interfaces.WrapperInfo; 4 | 5 | /** 6 | * Contains methods for configuring wrapper information. 7 | *

8 | * This builder is primarily intended for use by LaunchDarkly in developing wrapper SDKs. 9 | *

10 | * If the WrapperBuilder is used, then it will replace the wrapper information from the HttpPropertiesBuilder. 11 | * Additionally, any wrapper SDK may overwrite any application developer provided wrapper information. 12 | */ 13 | public abstract class WrapperInfoBuilder { 14 | protected String wrapperName; 15 | protected String wrapperVersion; 16 | 17 | /** 18 | * Set the name of the wrapper. 19 | * 20 | * @param wrapperName the name of the wrapper 21 | * @return the builder 22 | */ 23 | public WrapperInfoBuilder wrapperName(String wrapperName) { 24 | this.wrapperName = wrapperName; 25 | return this; 26 | } 27 | 28 | /** 29 | * Set the version of the wrapper. 30 | *

31 | * This information will not be used unless the wrapperName is also set. 32 | * 33 | * @param wrapperVersion the version of the wrapper 34 | * @return the builder 35 | */ 36 | public WrapperInfoBuilder wrapperVersion(String wrapperVersion) { 37 | this.wrapperVersion = wrapperVersion; 38 | return this; 39 | } 40 | 41 | public abstract WrapperInfo build(); 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/launchdarkly/sdk/server/integrations/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This package contains integration tools for connecting the SDK to other software components, or 3 | * configuring how it connects to LaunchDarkly. 4 | *

5 | * In the current main LaunchDarkly Java SDK library, this package contains the configuration builders 6 | * for the standard SDK components such as {@link com.launchdarkly.sdk.server.integrations.StreamingDataSourceBuilder}, 7 | * the {@link com.launchdarkly.sdk.server.integrations.PersistentDataStoreBuilder} builder for use with 8 | * database integrations (the specific database integrations themselves are provided by add-on libraries), 9 | * and {@link com.launchdarkly.sdk.server.integrations.FileData} (for reading flags from a file in testing). 10 | */ 11 | package com.launchdarkly.sdk.server.integrations; 12 | -------------------------------------------------------------------------------- /src/main/java/com/launchdarkly/sdk/server/integrations/reactor/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This package contains components for using the SDK in reactive stream programming. 3 | */ 4 | package com.launchdarkly.sdk.server.integrations.reactor; 5 | -------------------------------------------------------------------------------- /src/main/java/com/launchdarkly/sdk/server/interfaces/ApplicationInfo.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server.interfaces; 2 | 3 | import com.launchdarkly.sdk.server.integrations.ApplicationInfoBuilder; 4 | 5 | /** 6 | * Encapsulates the SDK's application metadata. 7 | *

8 | * See {@link ApplicationInfoBuilder} for more details on these properties. 9 | * 10 | * @since 5.8.0 11 | */ 12 | public final class ApplicationInfo { 13 | private String applicationId; 14 | private String applicationVersion; 15 | 16 | /** 17 | * Used internally by the SDK to store application metadata. 18 | * 19 | * @param applicationId the application ID 20 | * @param applicationVersion the application version 21 | * @see ApplicationInfoBuilder 22 | */ 23 | public ApplicationInfo(String applicationId, String applicationVersion) { 24 | this.applicationId = applicationId; 25 | this.applicationVersion = applicationVersion; 26 | } 27 | 28 | /** 29 | * A unique identifier representing the application where the LaunchDarkly SDK is running. 30 | * 31 | * @return the application identifier, or null 32 | */ 33 | public String getApplicationId() { 34 | return applicationId; 35 | } 36 | 37 | /** 38 | * A unique identifier representing the version of the application where the 39 | * LaunchDarkly SDK is running. 40 | * 41 | * @return the application version, or null 42 | */ 43 | public String getApplicationVersion() { 44 | return applicationVersion; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/launchdarkly/sdk/server/interfaces/BigSegmentsConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server.interfaces; 2 | 3 | import com.launchdarkly.sdk.server.integrations.BigSegmentsConfigurationBuilder; 4 | import com.launchdarkly.sdk.server.subsystems.BigSegmentStore; 5 | 6 | import java.time.Duration; 7 | 8 | /** 9 | * Encapsulates the SDK's configuration with regard to Big Segments. 10 | *

11 | * Big Segments are a specific type of user segments. For more information, read the 12 | * LaunchDarkly documentation 13 | * . 14 | *

15 | * See {@link BigSegmentsConfigurationBuilder} for more details on these properties. 16 | * 17 | * @see BigSegmentsConfigurationBuilder 18 | * @since 5.7.0 19 | */ 20 | public final class BigSegmentsConfiguration { 21 | private final BigSegmentStore bigSegmentStore; 22 | private final int userCacheSize; 23 | private final Duration userCacheTime; 24 | private final Duration statusPollInterval; 25 | private final Duration staleAfter; 26 | 27 | /** 28 | * Creates a new {@link BigSegmentsConfiguration} instance with the specified values. 29 | *

30 | * See {@link BigSegmentsConfigurationBuilder} for more information on the configuration fields. 31 | * 32 | * @param bigSegmentStore the Big Segments store instance 33 | * @param userCacheSize the user cache size 34 | * @param userCacheTime the user cache time 35 | * @param statusPollInterval the status poll interval 36 | * @param staleAfter the interval after which store data is considered stale 37 | */ 38 | public BigSegmentsConfiguration(BigSegmentStore bigSegmentStore, 39 | int userCacheSize, 40 | Duration userCacheTime, 41 | Duration statusPollInterval, 42 | Duration staleAfter) { 43 | this.bigSegmentStore = bigSegmentStore; 44 | this.userCacheSize = userCacheSize; 45 | this.userCacheTime = userCacheTime; 46 | this.statusPollInterval = statusPollInterval; 47 | this.staleAfter = staleAfter; 48 | } 49 | 50 | /** 51 | * Gets the data store instance that is used for Big Segments data. 52 | * 53 | * @return the configured Big Segment store 54 | */ 55 | public BigSegmentStore getStore() { 56 | return this.bigSegmentStore; 57 | } 58 | 59 | /** 60 | * Gets the value set by {@link BigSegmentsConfigurationBuilder#userCacheSize(int)} 61 | * 62 | * @return the configured user cache size limit 63 | */ 64 | public int getUserCacheSize() { 65 | return this.userCacheSize; 66 | } 67 | 68 | /** 69 | * Gets the value set by {@link BigSegmentsConfigurationBuilder#userCacheTime(Duration)} 70 | * 71 | * @return the configured user cache time duration 72 | */ 73 | public Duration getUserCacheTime() { 74 | return this.userCacheTime; 75 | } 76 | 77 | /** 78 | * Gets the value set by {@link BigSegmentsConfigurationBuilder#statusPollInterval(Duration)} 79 | * 80 | * @return the configured status poll interval 81 | */ 82 | public Duration getStatusPollInterval() { 83 | return this.statusPollInterval; 84 | } 85 | 86 | /** 87 | * Gets the value set by {@link BigSegmentsConfigurationBuilder#staleAfter(Duration)} 88 | * 89 | * @return the configured stale after interval 90 | */ 91 | public Duration getStaleAfter() { 92 | return this.staleAfter; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/com/launchdarkly/sdk/server/interfaces/ConsistencyCheck.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server.interfaces; 2 | 3 | /** 4 | * Consistency check result. 5 | */ 6 | public enum ConsistencyCheck { 7 | /** 8 | * Consistency was checked and found to be inconsistent. 9 | */ 10 | INCONSISTENT, 11 | 12 | /** 13 | * Consistency was checked and found to be consistent. 14 | */ 15 | CONSISTENT, 16 | 17 | /** 18 | * Consistency check was not performed. 19 | */ 20 | NOT_CHECKED 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/launchdarkly/sdk/server/interfaces/FlagChangeEvent.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server.interfaces; 2 | 3 | /** 4 | * Parameter class used with {@link FlagChangeListener}. 5 | *

6 | * This is not an analytics event to be sent to LaunchDarkly; it is a notification to the application. 7 | * 8 | * @since 5.0.0 9 | * @see FlagChangeListener 10 | * @see FlagValueChangeEvent 11 | * @see FlagTracker#addFlagChangeListener(FlagChangeListener) 12 | */ 13 | public class FlagChangeEvent { 14 | private final String key; 15 | 16 | /** 17 | * Constructs a new instance. 18 | * 19 | * @param key the feature flag key 20 | */ 21 | public FlagChangeEvent(String key) { 22 | this.key = key; 23 | } 24 | 25 | /** 26 | * Returns the key of the feature flag whose configuration has changed. 27 | *

28 | * The specified flag may have been modified directly, or this may be an indirect change due to a change 29 | * in some other flag that is a prerequisite for this flag, or a user segment that is referenced in the 30 | * flag's rules. 31 | * 32 | * @return the flag key 33 | */ 34 | public String getKey() { 35 | return key; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/launchdarkly/sdk/server/interfaces/FlagChangeListener.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server.interfaces; 2 | 3 | /** 4 | * An event listener that is notified when a feature flag's configuration has changed. 5 | *

6 | * As described in {@link FlagTracker#addFlagChangeListener(FlagChangeListener)}, this notification 7 | * does not mean that the flag now returns a different value for any particular user, only that it 8 | * may do so. LaunchDarkly feature flags can be configured to return a single value for all 9 | * users, or to have complex targeting behavior. To know what effect the change would have for any 10 | * given set of user properties, you would need to re-evaluate the flag by calling one of the 11 | * {@code variation} methods on the client. 12 | * 13 | *


14 |  *     FlagChangeListener listenForChanges = event -> {
15 |  *         System.out.println("a flag has changed: " + event.getKey());
16 |  *     };
17 |  *     client.getFlagTracker().addFlagChangeListener(listenForChanges);
18 |  * 
19 | * 20 | * In simple use cases where you know that the flag configuration does not vary per user, or where you 21 | * know ahead of time what user properties you will evaluate the flag with, it may be more convenient 22 | * to use {@link FlagValueChangeListener}. 23 | * 24 | * @since 5.0.0 25 | * @see FlagValueChangeListener 26 | * @see FlagTracker#addFlagChangeListener(FlagChangeListener) 27 | * @see FlagTracker#removeFlagChangeListener(FlagChangeListener) 28 | */ 29 | public interface FlagChangeListener { 30 | /** 31 | * The SDK calls this method when a feature flag's configuration has changed in some way. 32 | * 33 | * @param event the event parameters 34 | */ 35 | void onFlagChange(FlagChangeEvent event); 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/launchdarkly/sdk/server/interfaces/FlagValueChangeEvent.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server.interfaces; 2 | 3 | import com.launchdarkly.sdk.LDValue; 4 | 5 | /** 6 | * Parameter class used with {@link FlagValueChangeListener}. 7 | *

8 | * This is not an analytics event to be sent to LaunchDarkly; it is a notification to the application. 9 | * 10 | * @since 5.0.0 11 | * @see FlagValueChangeListener 12 | * @see FlagTracker#addFlagValueChangeListener(String, com.launchdarkly.sdk.LDContext, FlagValueChangeListener) 13 | */ 14 | public class FlagValueChangeEvent extends FlagChangeEvent { 15 | private final LDValue oldValue; 16 | private final LDValue newValue; 17 | 18 | /** 19 | * Constructs a new instance. 20 | * 21 | * @param key the feature flag key 22 | * @param oldValue the previous flag value 23 | * @param newValue the new flag value 24 | */ 25 | public FlagValueChangeEvent(String key, LDValue oldValue, LDValue newValue) { 26 | super(key); 27 | this.oldValue = LDValue.normalize(oldValue); 28 | this.newValue = LDValue.normalize(newValue); 29 | } 30 | 31 | /** 32 | * Returns the last known value of the flag for the specified evaluation context prior to the update. 33 | *

34 | * Since flag values can be of any JSON data type, this is represented as {@link LDValue}. That class 35 | * has methods for converting to a primitive Java type such as {@link LDValue#booleanValue()}. 36 | *

37 | * If the flag did not exist before or could not be evaluated, this will be {@link LDValue#ofNull()}. 38 | * Note that there is no application default value parameter as there is for the {@code variation} 39 | * methods; it is up to your code to substitute whatever fallback value is appropriate. 40 | * 41 | * @return the previous flag value 42 | */ 43 | public LDValue getOldValue() { 44 | return oldValue; 45 | } 46 | 47 | /** 48 | * Returns the new value of the flag for the specified evaluation context. 49 | *

50 | * Since flag values can be of any JSON data type, this is represented as {@link LDValue}. That class 51 | * has methods for converting to a primitive Java type such {@link LDValue#booleanValue()}. 52 | *

53 | * If the flag was deleted or could not be evaluated, this will be {@link LDValue#ofNull()}. 54 | * Note that there is no application default value parameter as there is for the {@code variation} 55 | * methods; it is up to your code to substitute whatever fallback value is appropriate. 56 | * 57 | * @return the new flag value 58 | */ 59 | public LDValue getNewValue() { 60 | return newValue; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/launchdarkly/sdk/server/interfaces/FlagValueChangeListener.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server.interfaces; 2 | 3 | /** 4 | * An event listener that is notified when a feature flag's value has changed for a specific 5 | * evaluation context. 6 | *

7 | * Use this in conjunction with {@link FlagTracker#addFlagValueChangeListener(String, com.launchdarkly.sdk.LDContext, FlagValueChangeListener)} 8 | * if you want the client to re-evaluate a flag for a specific evaluation context whenever the 9 | * flag's configuration has changed, and notify you only if the new value is different from the old 10 | * value. The listener will not be notified if the flag's configuration is changed in some way that does 11 | * not affect its value for that context. 12 | * 13 | *


14 |  *     String flagKey = "my-important-flag";
15 |  *     LDContext contextForFlagEvaluation = LDContext.create("context-key-for-global-flag-state");
16 |  *     FlagValueChangeListener listenForNewValue = event -> {
17 |  *         if (event.getKey().equals(flagKey)) {
18 |  *             doSomethingWithNewValue(event.getNewValue().booleanValue());
19 |  *         }
20 |  *     };
21 |  *     client.getFlagTracker().addFlagValueChangeListener(flagKey,
22 |  *         contextForFlagEvaluation, listenForNewValue);
23 |  * 
24 | * 25 | * In the above example, the value provided in {@code event.getNewValue()} is the result of calling 26 | * {@code client.jsonValueVariation(flagKey, contextForFlagEvaluation, LDValue.ofNull())} after the flag 27 | * has changed. 28 | * 29 | * @since 5.0.0 30 | * @see FlagChangeListener 31 | * @see FlagTracker#addFlagValueChangeListener(String, com.launchdarkly.sdk.LDContext, FlagValueChangeListener) 32 | */ 33 | public interface FlagValueChangeListener { 34 | /** 35 | * The SDK calls this method when a feature flag's value has changed with regard to the specified 36 | * evaluation context. 37 | * 38 | * @param event the event parameters 39 | */ 40 | void onFlagValueChange(FlagValueChangeEvent event); 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/launchdarkly/sdk/server/interfaces/HttpAuthentication.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server.interfaces; 2 | 3 | /** 4 | * Represents a supported method of HTTP authentication, including proxy authentication. 5 | * 6 | * @since 4.13.0 7 | */ 8 | public interface HttpAuthentication { 9 | /** 10 | * Computes the {@code Authorization} or {@code Proxy-Authorization} header for an authentication challenge. 11 | * 12 | * @param challenges the authentication challenges provided by the server, if any (may be empty if this is 13 | * pre-emptive authentication) 14 | * @return the value for the authorization request header 15 | */ 16 | String provideAuthorization(Iterable challenges); 17 | 18 | /** 19 | * Properties of an HTTP authentication challenge. 20 | */ 21 | public static class Challenge { 22 | private final String scheme; 23 | private final String realm; 24 | 25 | /** 26 | * Constructs an instance. 27 | * 28 | * @param scheme the authentication scheme 29 | * @param realm the authentication realm or null 30 | */ 31 | public Challenge(String scheme, String realm) { 32 | this.scheme = scheme; 33 | this.realm = realm; 34 | } 35 | 36 | /** 37 | * The authentication scheme, such as "basic". 38 | * @return the authentication scheme 39 | */ 40 | public String getScheme() { 41 | return scheme; 42 | } 43 | 44 | /** 45 | * The authentication realm, if any. 46 | * @return the authentication realm or null 47 | */ 48 | public String getRealm() { 49 | return realm; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/launchdarkly/sdk/server/interfaces/ServiceEndpoints.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server.interfaces; 2 | 3 | import com.launchdarkly.sdk.server.integrations.ServiceEndpointsBuilder; 4 | import java.net.URI; 5 | 6 | /** 7 | * Specifies the base service URIs used by SDK components. 8 | *

9 | * See {@link ServiceEndpointsBuilder} for more details on these properties. 10 | */ 11 | public final class ServiceEndpoints { 12 | private URI streamingBaseUri; 13 | private URI pollingBaseUri; 14 | private URI eventsBaseUri; 15 | 16 | /** 17 | * Used internally by the SDK to store service endpoints. 18 | * @param streamingBaseUri the base URI for the streaming service 19 | * @param pollingBaseUri the base URI for the polling service 20 | * @param eventsBaseUri the base URI for the events service 21 | */ 22 | public ServiceEndpoints(URI streamingBaseUri, URI pollingBaseUri, URI eventsBaseUri) { 23 | this.streamingBaseUri = streamingBaseUri; 24 | this.pollingBaseUri = pollingBaseUri; 25 | this.eventsBaseUri = eventsBaseUri; 26 | } 27 | 28 | /** 29 | * The base URI for the streaming service. 30 | * @return the base URI, or null 31 | */ 32 | public URI getStreamingBaseUri() { 33 | return streamingBaseUri; 34 | } 35 | 36 | /** 37 | * The base URI for the polling service. 38 | * @return the base URI, or null 39 | */ 40 | public URI getPollingBaseUri() { 41 | return pollingBaseUri; 42 | } 43 | 44 | /** 45 | * The base URI for the events service. 46 | * @return the base URI, or null 47 | */ 48 | public URI getEventsBaseUri() { 49 | return eventsBaseUri; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/launchdarkly/sdk/server/interfaces/WrapperInfo.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server.interfaces; 2 | 3 | /** 4 | * Contains wrapper SDK information. 5 | *

6 | * This is intended for use within the SDK. 7 | */ 8 | final public class WrapperInfo { 9 | private final String wrapperName; 10 | private final String wrapperVersion; 11 | 12 | /** 13 | * Get the name of the wrapper. 14 | * 15 | * @return the wrapper name 16 | */ 17 | public String getWrapperName() { 18 | return wrapperName; 19 | } 20 | 21 | /** 22 | * Get the version of the wrapper. 23 | * 24 | * @return the wrapper version 25 | */ 26 | public String getWrapperVersion() { 27 | return wrapperVersion; 28 | } 29 | 30 | /** 31 | * Used internally by the SDK to track wrapper information. 32 | * 33 | * @param wrapperName the name of the wrapper 34 | * @param wrapperVersion the version of the wrapper 35 | */ 36 | public WrapperInfo(String wrapperName, String wrapperVersion) { 37 | this.wrapperName = wrapperName; 38 | this.wrapperVersion = wrapperVersion; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/launchdarkly/sdk/server/interfaces/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Types that are part of the public API, but are not needed for basic use of the SDK. 3 | *

4 | * Types in this namespace include: 5 | *

    6 | *
  • The interface {@link com.launchdarkly.sdk.server.interfaces.LDClientInterface}, which 7 | * allow the SDK client to be referenced via an interface rather than the concrete type 8 | * {@link com.launchdarkly.sdk.server.LDClient}.
  • 9 | *
  • Interfaces like {@link com.launchdarkly.sdk.server.interfaces.FlagTracker} that provide a 10 | * facade for some part of the SDK API; these are returned by methods like 11 | * {@link com.launchdarkly.sdk.server.LDClient#getFlagTracker()}.
  • 12 | *
  • Concrete types that are used as parameters within these interfaces, like 13 | * {@link com.launchdarkly.sdk.server.interfaces.FlagChangeEvent}.
  • 14 | *
15 | */ 16 | package com.launchdarkly.sdk.server.interfaces; 17 | -------------------------------------------------------------------------------- /src/main/java/com/launchdarkly/sdk/server/migrations/MigrationExecution.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server.migrations; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | import org.jetbrains.annotations.Nullable; 5 | 6 | import java.util.Optional; 7 | 8 | /** 9 | * This class is used to control the execution mechanism for migrations. 10 | *

11 | * Read operations may be executed in parallel, sequentially in a fixed order, or sequentially in a randomized order. 12 | *

13 | * This class facilitates correct combinations of parallel/serial with random/fixed. 14 | */ 15 | public class MigrationExecution { 16 | private final MigrationExecutionMode mode; 17 | private final MigrationSerialOrder order; 18 | 19 | private MigrationExecution( 20 | @NotNull MigrationExecutionMode mode, 21 | @Nullable MigrationSerialOrder order) { 22 | this.mode = mode; 23 | this.order = order; 24 | } 25 | 26 | /** 27 | * Construct a serial execution with the specified ordering. 28 | * 29 | * @param order The serial execution order fixed/random. 30 | * @return an execution instance 31 | */ 32 | public static MigrationExecution Serial(@NotNull MigrationSerialOrder order) { 33 | return new MigrationExecution(MigrationExecutionMode.SERIAL, order); 34 | } 35 | 36 | /** 37 | * Constructs a parallel execution. 38 | * 39 | * @return an execution instance 40 | */ 41 | public static MigrationExecution Parallel() { 42 | return new MigrationExecution(MigrationExecutionMode.PARALLEL, null); 43 | } 44 | 45 | /** 46 | * Get the current execution mode. 47 | * 48 | * @return The execution mode. 49 | */ 50 | public MigrationExecutionMode getMode() { 51 | return mode; 52 | } 53 | 54 | /** 55 | * If the execution mode is {@link MigrationExecutionMode#SERIAL}, then this will contain an execution order. 56 | * If the mode is not SERIAL, then this will return an empty optional. 57 | * 58 | * @return The optional execution mode. 59 | */ 60 | public Optional getOrder() { 61 | return Optional.ofNullable(order); 62 | } 63 | 64 | /** 65 | * A string representation of the migration execution. The return value from this function should only be used 66 | * for logging or human-read identification. It should not be used programmatically and will not follow semver. 67 | * 68 | * @return A string representation of the string. 69 | */ 70 | @Override 71 | public String toString() { 72 | String strValue = ""; 73 | 74 | strValue += mode.toString(); 75 | if (order != null) { 76 | strValue += "-"; 77 | strValue += order.toString(); 78 | } 79 | return strValue; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/com/launchdarkly/sdk/server/migrations/MigrationExecutionMode.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server.migrations; 2 | 3 | /** 4 | * Execution mode for a migration. 5 | *

6 | * This applies only to a single read, not multiple reads using the same migration. 7 | */ 8 | public enum MigrationExecutionMode { 9 | /** 10 | * Execute one read fully before executing another read. 11 | */ 12 | SERIAL, 13 | /** 14 | * Start reads in parallel and wait for them to both finish. 15 | */ 16 | PARALLEL 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/launchdarkly/sdk/server/migrations/MigrationMethodResult.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server.migrations; 2 | 3 | import org.jetbrains.annotations.Nullable; 4 | 5 | import java.util.Optional; 6 | 7 | /** 8 | * Results of a method associated with a migration origin. 9 | *

10 | * A result may either be a success, which will include a result type, or a failure. 11 | *

12 | * The static methods are intended to be used to create results in a migration method. 13 | *

14 | * An exception thrown from a migration method will be equivalent to using the 15 | * {@link MigrationMethodResult#Failure(Exception)} method. 16 | * 17 | *


 18 |  *   .read((payload) -> {
 19 |  *       return MigrationMethodResult.Success("My Result!");
 20 |  *   })
 21 |  * 
22 | *

 23 |  *   .read((payload) -> {
 24 |  *       return MigrationMethodResult.Failure();
 25 |  *   })
 26 |  * 
27 | * 28 | * @param the type of the result 29 | */ 30 | public final class MigrationMethodResult { 31 | private MigrationMethodResult( 32 | boolean success, 33 | @Nullable T result, 34 | @Nullable Exception exception) { 35 | this.success = success; 36 | this.result = result; 37 | this.exception = exception; 38 | } 39 | 40 | private final boolean success; 41 | private final T result; 42 | 43 | private final Exception exception; 44 | 45 | /** 46 | * Construct a method result representing a failure. 47 | *

48 | * This method doesn't provide any information about the cause of the failure. It is recommended 49 | * to throw an exception or use {@link MigrationMethodResult#Failure(Exception)}. 50 | * 51 | * @return a method result 52 | * @param the type of the method result 53 | */ 54 | public static MigrationMethodResult Failure() { 55 | return new MigrationMethodResult<>(false, null, null); 56 | } 57 | 58 | /** 59 | * Construct a method result representing a failure based on an Exception. 60 | * 61 | * @param err the exception which caused the failure 62 | * @return a method result 63 | * @param the type of the method result 64 | */ 65 | public static MigrationMethodResult Failure(Exception err) { 66 | return new MigrationMethodResult<>(false, null, err); 67 | } 68 | 69 | /** 70 | * Create a successful method result. 71 | * 72 | * @param result the result of the method 73 | * @return a method result 74 | * @param the type of the method result 75 | */ 76 | public static MigrationMethodResult Success(U result) { 77 | return new MigrationMethodResult<>(true, result, null); 78 | } 79 | 80 | /** 81 | * Returns true if the method was successful. 82 | * 83 | * @return true if the method was successful 84 | */ 85 | public boolean isSuccess() { 86 | return success; 87 | } 88 | 89 | /** 90 | * Get the result of the method. 91 | * 92 | * @return the result, or an empty optional if no result was produced 93 | */ 94 | public Optional getResult() { 95 | return Optional.ofNullable(result); 96 | } 97 | 98 | /** 99 | * Get the exception associated with the method or an empty optional if there 100 | * was no exception. 101 | * 102 | * @return the exception, or an empty optional if no result was produced 103 | */ 104 | public Optional getException() { 105 | return Optional.ofNullable(exception); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/com/launchdarkly/sdk/server/migrations/MigrationSerialOrder.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server.migrations; 2 | 3 | /** 4 | * When using serial execution controls the order reads are executed. 5 | */ 6 | public enum MigrationSerialOrder { 7 | /** 8 | * Each time a read is performed randomize the order. 9 | */ 10 | RANDOM, 11 | /** 12 | * Always execute reads in the same order. 13 | */ 14 | FIXED 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/launchdarkly/sdk/server/migrations/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Implementations related to technology migrations. 3 | *

4 | * The {@link com.launchdarkly.sdk.server.migrations.MigrationBuilder} is the primary entrypoint in this package 5 | * and should be used to configure a technology migration. 6 | */ 7 | package com.launchdarkly.sdk.server.migrations; 8 | -------------------------------------------------------------------------------- /src/main/java/com/launchdarkly/sdk/server/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Main package for the LaunchDarkly Server-Side Java SDK, containing the client and configuration classes. 3 | *

4 | * You will most often use {@link com.launchdarkly.sdk.server.LDClient} (the SDK client) and 5 | * {@link com.launchdarkly.sdk.server.LDConfig} (configuration options for the client). 6 | *

7 | * Other commonly used types such as {@link com.launchdarkly.sdk.LDContext} are in the {@code com.launchdarkly.sdk} 8 | * package, since those are not server-side-specific and are shared with the LaunchDarkly Android SDK. 9 | */ 10 | package com.launchdarkly.sdk.server; 11 | -------------------------------------------------------------------------------- /src/main/java/com/launchdarkly/sdk/server/subsystems/BigSegmentStore.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server.subsystems; 2 | 3 | import java.io.Closeable; 4 | 5 | /** 6 | * Interface for a read-only data store that allows querying of user membership in Big Segments. 7 | *

8 | * Big Segments are a specific type of user segments. For more information, read the 9 | * LaunchDarkly documentation 10 | * . 11 | * 12 | * @since 5.7.0 13 | */ 14 | public interface BigSegmentStore extends Closeable { 15 | /** 16 | * Queries the store for a snapshot of the current segment state for a specific user. 17 | *

18 | * The {@code userHash} is a base64-encoded string produced by hashing the user key as defined by 19 | * the Big Segments specification; the store implementation does not need to know the details of 20 | * how this is done, because it deals only with already-hashed keys, but the string can be assumed 21 | * to only contain characters that are valid in base64. 22 | *

23 | * If the store is working, but no membership state is found for this user, the method may return 24 | * either {@code null} or an empty {@link BigSegmentStoreTypes.Membership}. It should not throw an 25 | * exception unless there is an unexpected database error or the retrieved data is malformed. 26 | * 27 | * @param userHash the hashed user identifier 28 | * @return the user's segment membership state or {@code null} 29 | */ 30 | BigSegmentStoreTypes.Membership getMembership(String userHash); 31 | 32 | /** 33 | * Returns information about the overall state of the store. 34 | *

35 | * This method will be called only when the SDK needs the latest state, so it should not be 36 | * cached. 37 | *

38 | * If the store is working, but no metadata has been stored in it yet, the method should return 39 | * {@code null}. It should not throw an exception unless there is an unexpected database error or 40 | * the retrieved data is malformed. 41 | * 42 | * @return the store metadata or null 43 | */ 44 | BigSegmentStoreTypes.StoreMetadata getMetadata(); 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/launchdarkly/sdk/server/subsystems/ComponentConfigurer.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server.subsystems; 2 | 3 | /** 4 | * The common interface for SDK component factories and configuration builders. Applications should not 5 | * need to implement this interface. 6 | * 7 | * @param the type of SDK component or configuration object being constructed 8 | * @since 6.0.0 9 | */ 10 | public interface ComponentConfigurer { 11 | /** 12 | * Called internally by the SDK to create an implementation instance. Applications should not need 13 | * to call this method. 14 | * 15 | * @param clientContext provides configuration properties and other components from the current 16 | * SDK client instance 17 | * @return a instance of the component type 18 | */ 19 | T build(ClientContext clientContext); 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/launchdarkly/sdk/server/subsystems/DataSource.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server.subsystems; 2 | 3 | import java.io.Closeable; 4 | import java.io.IOException; 5 | import java.util.concurrent.Future; 6 | 7 | /** 8 | * Interface for an object that receives updates to feature flags, user segments, and anything 9 | * else that might come from LaunchDarkly, and passes them to a {@link DataStore}. 10 | *

11 | * The standard implementations are: 12 | *

    13 | *
  • {@link com.launchdarkly.sdk.server.Components#streamingDataSource()} (the default), which 14 | * maintains a streaming connection to LaunchDarkly; 15 | *
  • {@link com.launchdarkly.sdk.server.Components#pollingDataSource()}, which polls for 16 | * updates at regular intervals; 17 | *
  • {@link com.launchdarkly.sdk.server.Components#externalUpdatesOnly()}, which does nothing 18 | * (on the assumption that another process will update the data store); 19 | *
  • {@link com.launchdarkly.sdk.server.integrations.FileData}, which reads flag data from 20 | * the filesystem. 21 | *
22 | * 23 | * @since 5.0.0 24 | */ 25 | public interface DataSource extends Closeable { 26 | /** 27 | * Starts the client. 28 | * @return {@link Future}'s completion status indicates the client has been initialized. 29 | */ 30 | Future start(); 31 | 32 | /** 33 | * Returns true once the client has been initialized and will never return false again. 34 | * @return true if the client has been initialized 35 | */ 36 | boolean isInitialized(); 37 | 38 | /** 39 | * Tells the component to shut down and release any resources it is using. 40 | * @throws IOException if there is an error while closing 41 | */ 42 | void close() throws IOException; 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/launchdarkly/sdk/server/subsystems/DataStoreUpdateSink.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server.subsystems; 2 | 3 | import com.launchdarkly.sdk.server.interfaces.DataStoreStatusProvider; 4 | 5 | /** 6 | * Interface that a data store implementation can use to report information back to the SDK. 7 | * 8 | * @since 5.0.0 9 | * @see ClientContext#getDataStoreUpdateSink() 10 | */ 11 | public interface DataStoreUpdateSink { 12 | /** 13 | * Reports a change in the data store's operational status. 14 | *

15 | * This is what makes the status monitoring mechanisms in {@link DataStoreStatusProvider} work. 16 | * 17 | * @param newStatus the updated status properties 18 | */ 19 | void updateStatus(DataStoreStatusProvider.Status newStatus); 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/launchdarkly/sdk/server/subsystems/DiagnosticDescription.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server.subsystems; 2 | 3 | import com.launchdarkly.sdk.LDValue; 4 | 5 | /** 6 | * Optional interface for components to describe their own configuration. 7 | *

8 | * The SDK uses a simplified JSON representation of its configuration when recording diagnostics data. 9 | * Any class that implements {@link ComponentConfigurer} may choose to contribute 10 | * values to this representation, although the SDK may or may not use them. For components that do not 11 | * implement this interface, the SDK may instead describe them using {@code getClass().getSimpleName()}. 12 | *

13 | * The {@link #describeConfiguration(ClientContext)} method should return either null or a JSON value. For 14 | * custom components, the value must be a string that describes the basic nature of this component 15 | * implementation (e.g. "Redis"). Built-in LaunchDarkly components may instead return a JSON object 16 | * containing multiple properties specific to the LaunchDarkly diagnostic schema. 17 | * 18 | * @since 4.12.0 19 | */ 20 | public interface DiagnosticDescription { 21 | /** 22 | * Used internally by the SDK to inspect the configuration. 23 | * @param clientContext allows access to the client configuration 24 | * @return an {@link LDValue} or null 25 | */ 26 | LDValue describeConfiguration(ClientContext clientContext); 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/launchdarkly/sdk/server/subsystems/EventSender.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server.subsystems; 2 | 3 | import java.io.Closeable; 4 | import java.net.URI; 5 | 6 | /** 7 | * Interface for a component that can deliver preformatted event data. 8 | *

9 | * By default, the SDK sends event data to the LaunchDarkly events service via HTTP. You may 10 | * provide a different implementation of event delivery by implementing this interface-- for 11 | * instance, to create a test fixture, or to store the data somewhere else. 12 | * 13 | * @see com.launchdarkly.sdk.server.integrations.EventProcessorBuilder#eventSender(ComponentConfigurer) 14 | * @since 4.14.0 15 | */ 16 | public interface EventSender extends Closeable { 17 | /** 18 | * Result type for event sending methods. 19 | */ 20 | public enum Result { 21 | /** 22 | * The EventSender successfully delivered the event(s). 23 | */ 24 | SUCCESS, 25 | 26 | /** 27 | * The EventSender was not able to deliver the events. 28 | */ 29 | FAILURE, 30 | 31 | /** 32 | * The EventSender was not able to deliver the events, and the nature of the error indicates that 33 | * the SDK should not attempt to send any more events. 34 | */ 35 | STOP 36 | }; 37 | 38 | /** 39 | * Attempt to deliver an analytics event data payload. 40 | *

41 | * This method will be called synchronously from an event delivery worker thread. 42 | * 43 | * @param data the preformatted JSON data, in UTF-8 encoding 44 | * @param eventCount the number of individual events in the data 45 | * @param eventsBaseUri the configured events endpoint base URI 46 | * @return a {@link Result} 47 | */ 48 | Result sendAnalyticsEvents(byte[] data, int eventCount, URI eventsBaseUri); 49 | 50 | /** 51 | * Attempt to deliver a diagnostic event data payload. 52 | *

53 | * This method will be called synchronously from an event delivery worker thread. 54 | * 55 | * @param data the preformatted JSON data, in UTF-8 encoding 56 | * @param eventsBaseUri the configured events endpoint base URI 57 | * @return a {@link Result} 58 | */ 59 | Result sendDiagnosticEvent(byte[] data, URI eventsBaseUri); 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/launchdarkly/sdk/server/subsystems/HookConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server.subsystems; 2 | 3 | import com.launchdarkly.sdk.server.integrations.HooksConfigurationBuilder; 4 | import com.launchdarkly.sdk.server.integrations.Hook; 5 | 6 | import java.util.Collections; 7 | import java.util.List; 8 | 9 | /** 10 | * Encapsulates the SDK's 'hooks' configuration. 11 | *

12 | * Use {@link HooksConfigurationBuilder} to construct an instance. 13 | */ 14 | public class HookConfiguration { 15 | 16 | private final List hooks; 17 | 18 | /** 19 | * @param hooks the list of {@link Hook} that will be registered. 20 | */ 21 | public HookConfiguration(List hooks) { 22 | this.hooks = Collections.unmodifiableList(hooks); 23 | } 24 | 25 | /** 26 | * @return an immutable list of hooks 27 | */ 28 | public List getHooks() { 29 | return hooks; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/launchdarkly/sdk/server/subsystems/LoggingConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server.subsystems; 2 | 3 | import com.launchdarkly.logging.LDLogAdapter; 4 | import com.launchdarkly.sdk.server.integrations.LoggingConfigurationBuilder; 5 | 6 | import java.time.Duration; 7 | 8 | /** 9 | * Encapsulates the SDK's general logging configuration. 10 | *

11 | * Use {@link LoggingConfigurationBuilder} to construct an instance. 12 | * 13 | * @since 5.0.0 14 | */ 15 | public final class LoggingConfiguration { 16 | private final String baseLoggerName; 17 | private final LDLogAdapter logAdapter; 18 | private final Duration logDataSourceOutageAsErrorAfter; 19 | 20 | /** 21 | * Creates an instance. 22 | * 23 | * @param baseLoggerName see {@link #getBaseLoggerName()} 24 | * @param logAdapter see {@link #getLogAdapter()} 25 | * @param logDataSourceOutageAsErrorAfter see {@link #getLogDataSourceOutageAsErrorAfter()} 26 | */ 27 | public LoggingConfiguration( 28 | String baseLoggerName, 29 | LDLogAdapter logAdapter, 30 | Duration logDataSourceOutageAsErrorAfter 31 | ) { 32 | this.baseLoggerName = baseLoggerName; 33 | this.logAdapter = logAdapter; 34 | this.logDataSourceOutageAsErrorAfter = logDataSourceOutageAsErrorAfter; 35 | } 36 | 37 | /** 38 | * Returns the configured base logger name. 39 | * @return the logger name 40 | * @since 5.10.0 41 | */ 42 | public String getBaseLoggerName() { 43 | return baseLoggerName; 44 | } 45 | 46 | /** 47 | * Returns the configured logging adapter. 48 | * @return the logging adapter 49 | * @since 5.10.0 50 | */ 51 | public LDLogAdapter getLogAdapter() { 52 | return logAdapter; 53 | } 54 | 55 | /** 56 | * The time threshold, if any, after which the SDK will log a data source outage at {@code ERROR} 57 | * level instead of {@code WARN} level. 58 | * 59 | * @return the error logging threshold, or null 60 | * @see LoggingConfigurationBuilder#logDataSourceOutageAsErrorAfter(java.time.Duration) 61 | */ 62 | public Duration getLogDataSourceOutageAsErrorAfter() { 63 | return logDataSourceOutageAsErrorAfter; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/com/launchdarkly/sdk/server/subsystems/SerializationException.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server.subsystems; 2 | 3 | /** 4 | * General exception class for all errors in serializing or deserializing JSON. 5 | *

6 | * The SDK uses this class to avoid depending on exception types from the underlying JSON framework 7 | * that it uses (currently Gson). 8 | *

9 | * This is currently an unchecked exception, because adding checked exceptions to existing SDK 10 | * interfaces would be a breaking change. In the future it will become a checked exception, to make 11 | * error-handling requirements clearer. However, public SDK client methods will not throw this 12 | * exception in any case; it is only relevant when implementing custom components. 13 | */ 14 | @SuppressWarnings("serial") 15 | public class SerializationException extends RuntimeException { 16 | /** 17 | * Creates an instance. 18 | * @param cause the underlying exception 19 | */ 20 | public SerializationException(Throwable cause) { 21 | super(cause); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/launchdarkly/sdk/server/subsystems/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Interfaces for implementation of LaunchDarkly SDK components. 3 | *

4 | * Most applications will not need to refer to these types. You will use them if you are creating a 5 | * plugin component, such as a database integration. They are also used as interfaces for the built-in 6 | * SDK components, so that plugin components can be used interchangeably with those: for instance, the 7 | * configuration method {@link com.launchdarkly.sdk.server.LDConfig.Builder#dataStore(ComponentConfigurer)} 8 | * references {@link com.launchdarkly.sdk.server.subsystems.DataStore} as an abstraction for the data 9 | * store component. 10 | *

11 | * The package also includes concrete types that are used as parameters within these interfaces. 12 | */ 13 | package com.launchdarkly.sdk.server.subsystems; 14 | -------------------------------------------------------------------------------- /src/templates/java/com/launchdarkly/sdk/server/Version.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server; 2 | 3 | abstract class Version { 4 | private Version() {} 5 | 6 | // This constant is updated automatically by our Gradle script during a release, if the project version has changed 7 | static final String SDK_VERSION = "@VERSION@"; 8 | } 9 | -------------------------------------------------------------------------------- /src/test/java/com/launchdarkly/sdk/server/BaseTest.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server; 2 | 3 | import com.launchdarkly.logging.LDLogAdapter; 4 | import com.launchdarkly.logging.LDLogLevel; 5 | import com.launchdarkly.logging.LDLogger; 6 | import com.launchdarkly.logging.LogCapture; 7 | import com.launchdarkly.logging.Logs; 8 | 9 | import org.junit.Rule; 10 | import org.junit.rules.TestWatcher; 11 | import org.junit.runner.Description; 12 | 13 | @SuppressWarnings("javadoc") 14 | public class BaseTest { 15 | @Rule public DumpLogIfTestFails dumpLogIfTestFails; 16 | 17 | protected final LDLogAdapter testLogging; 18 | protected final LDLogger testLogger; 19 | protected final LogCapture logCapture; 20 | 21 | protected BaseTest() { 22 | logCapture = Logs.capture(); 23 | testLogging = logCapture; 24 | testLogger = LDLogger.withAdapter(testLogging, ""); 25 | dumpLogIfTestFails = new DumpLogIfTestFails(); 26 | } 27 | 28 | /** 29 | * Creates a configuration builder with the basic properties that we want for all tests unless 30 | * otherwise specified: do not connect to an external data source, do not send events, and 31 | * redirect all logging to the test logger for the current test (which will be printed to the 32 | * console only if the test fails). 33 | * 34 | * @return a configuraiton builder 35 | */ 36 | protected LDConfig.Builder baseConfig() { 37 | return new LDConfig.Builder() 38 | .dataSource(Components.externalUpdatesOnly()) 39 | .events(Components.noEvents()) 40 | .logging(Components.logging(testLogging).level(LDLogLevel.DEBUG)); 41 | } 42 | 43 | class DumpLogIfTestFails extends TestWatcher { 44 | @Override 45 | protected void failed(Throwable e, Description description) { 46 | for (LogCapture.Message message: logCapture.getMessages()) { 47 | System.out.println("LOG {" + description.getDisplayName() + "} >>> " + message.toStringWithTimestamp()); 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/test/java/com/launchdarkly/sdk/server/BigSegmentStoreStatusProviderImplTest.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server; 2 | 3 | import static org.easymock.EasyMock.expect; 4 | import static org.easymock.EasyMock.same; 5 | import static org.junit.Assert.assertEquals; 6 | 7 | import com.launchdarkly.sdk.server.interfaces.BigSegmentStoreStatusProvider.Status; 8 | import com.launchdarkly.sdk.server.interfaces.BigSegmentStoreStatusProvider.StatusListener; 9 | 10 | import org.easymock.EasyMockSupport; 11 | import org.junit.Before; 12 | import org.junit.Test; 13 | 14 | @SuppressWarnings("javadoc") 15 | public class BigSegmentStoreStatusProviderImplTest extends BaseTest { 16 | 17 | // We don't need to extensively test status broadcasting behavior, just that the implementation 18 | // delegates to the BigSegmentStoreWrapper and EventBroadcasterImpl. 19 | 20 | private StatusListener mockStatusListener; 21 | private EventBroadcasterImpl mockEventBroadcaster; 22 | private final EasyMockSupport mocks = new EasyMockSupport(); 23 | 24 | @Before 25 | @SuppressWarnings("unchecked") 26 | public void setup() { 27 | mockEventBroadcaster = mocks.strictMock(EventBroadcasterImpl.class); 28 | mockStatusListener = mocks.strictMock(StatusListener.class); 29 | } 30 | 31 | @Test 32 | public void statusUnavailableWithNullWrapper() { 33 | mocks.replayAll(); 34 | BigSegmentStoreStatusProviderImpl statusProvider = new BigSegmentStoreStatusProviderImpl(mockEventBroadcaster, null); 35 | assertEquals(statusProvider.getStatus(), new Status(false, false)); 36 | mocks.verifyAll(); 37 | } 38 | 39 | @Test 40 | public void statusDelegatedToWrapper() { 41 | BigSegmentStoreWrapper storeWrapper = mocks.strictMock(BigSegmentStoreWrapper.class); 42 | expect(storeWrapper.getStatus()).andReturn(new Status(true, false)).once(); 43 | mocks.replayAll(); 44 | 45 | BigSegmentStoreStatusProviderImpl statusProvider = new BigSegmentStoreStatusProviderImpl(mockEventBroadcaster, storeWrapper); 46 | assertEquals(statusProvider.getStatus(), new Status(true, false)); 47 | mocks.verifyAll(); 48 | } 49 | 50 | @Test 51 | public void listenersDelegatedToEventBroadcaster() { 52 | mockEventBroadcaster.register(same(mockStatusListener)); 53 | mockEventBroadcaster.unregister(same(mockStatusListener)); 54 | mocks.replayAll(); 55 | 56 | BigSegmentStoreStatusProviderImpl statusProvider = new BigSegmentStoreStatusProviderImpl(mockEventBroadcaster, null); 57 | statusProvider.addStatusListener(mockStatusListener); 58 | statusProvider.removeStatusListener(mockStatusListener); 59 | mocks.verifyAll(); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/test/java/com/launchdarkly/sdk/server/DataStoreUpdatesImplTest.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server; 2 | 3 | import com.launchdarkly.sdk.server.interfaces.DataStoreStatusProvider; 4 | import com.launchdarkly.sdk.server.interfaces.DataStoreStatusProvider.Status; 5 | 6 | import org.junit.Test; 7 | 8 | import java.util.concurrent.BlockingQueue; 9 | import java.util.concurrent.LinkedBlockingQueue; 10 | import java.util.concurrent.TimeUnit; 11 | 12 | import static com.launchdarkly.sdk.server.TestComponents.sharedExecutor; 13 | import static com.launchdarkly.testhelpers.ConcurrentHelpers.assertNoMoreValues; 14 | import static com.launchdarkly.testhelpers.ConcurrentHelpers.awaitValue; 15 | import static org.hamcrest.MatcherAssert.assertThat; 16 | import static org.hamcrest.Matchers.equalTo; 17 | 18 | @SuppressWarnings("javadoc") 19 | public class DataStoreUpdatesImplTest extends BaseTest { 20 | private EventBroadcasterImpl broadcaster = 21 | EventBroadcasterImpl.forDataStoreStatus(sharedExecutor, testLogger); 22 | private final DataStoreUpdatesImpl updates = new DataStoreUpdatesImpl(broadcaster); 23 | 24 | @Test 25 | public void updateStatusBroadcastsNewStatus() { 26 | BlockingQueue statuses = new LinkedBlockingQueue<>(); 27 | broadcaster.register(statuses::add); 28 | 29 | updates.updateStatus(new Status(false, false)); 30 | 31 | Status newStatus = awaitValue(statuses, 200, TimeUnit.MILLISECONDS); 32 | assertThat(newStatus, equalTo(new Status(false, false))); 33 | 34 | assertNoMoreValues(statuses, 100, TimeUnit.MILLISECONDS); 35 | } 36 | 37 | @Test 38 | public void updateStatusDoesNothingIfNewStatusIsSame() { 39 | BlockingQueue statuses = new LinkedBlockingQueue<>(); 40 | broadcaster.register(statuses::add); 41 | 42 | updates.updateStatus(new Status(true, false)); 43 | 44 | assertNoMoreValues(statuses, 100, TimeUnit.MILLISECONDS); 45 | } 46 | 47 | @Test 48 | public void updateStatusDoesNothingIfNewStatusIsNull() { 49 | BlockingQueue statuses = new LinkedBlockingQueue<>(); 50 | broadcaster.register(statuses::add); 51 | 52 | updates.updateStatus(null); 53 | 54 | assertNoMoreValues(statuses, 100, TimeUnit.MILLISECONDS); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/test/java/com/launchdarkly/sdk/server/EvaluatorTestBase.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server; 2 | 3 | import com.launchdarkly.sdk.server.EvaluatorTestUtil.EvaluatorBuilder; 4 | 5 | @SuppressWarnings("javadoc") 6 | public class EvaluatorTestBase extends BaseTest { 7 | public EvaluatorBuilder evaluatorBuilder() { 8 | return new EvaluatorBuilder(testLogger); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/test/java/com/launchdarkly/sdk/server/FlagModelDeserializationTest.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server; 2 | 3 | import com.google.gson.Gson; 4 | import com.launchdarkly.sdk.LDValue; 5 | import com.launchdarkly.sdk.server.DataModel.Clause; 6 | import com.launchdarkly.sdk.server.DataModel.FeatureFlag; 7 | import com.launchdarkly.sdk.server.DataModel.Operator; 8 | import com.launchdarkly.sdk.server.DataModel.Prerequisite; 9 | import com.launchdarkly.sdk.server.DataModel.Rule; 10 | import com.launchdarkly.sdk.server.DataModel.Target; 11 | 12 | import org.junit.Test; 13 | 14 | import static com.launchdarkly.sdk.server.ModelBuilders.clause; 15 | import static com.launchdarkly.sdk.server.ModelBuilders.flagBuilder; 16 | import static com.launchdarkly.sdk.server.ModelBuilders.ruleBuilder; 17 | import static com.launchdarkly.sdk.server.ModelBuilders.target; 18 | import static org.hamcrest.MatcherAssert.assertThat; 19 | import static org.hamcrest.Matchers.notNullValue; 20 | import static org.junit.Assert.assertNotNull; 21 | 22 | @SuppressWarnings("javadoc") 23 | public class FlagModelDeserializationTest { 24 | private static final Gson gson = new Gson(); 25 | 26 | // The details of the preprocessed data are verified by DataModelPreprocessingTest; here we're 27 | // just verifying that the preprocessing is actually being done whenever we deserialize a flag. 28 | @Test 29 | public void preprocessingIsDoneOnDeserialization() { 30 | FeatureFlag originalFlag = flagBuilder("flagkey") 31 | .variations("a", "b") 32 | .prerequisites(new Prerequisite("abc", 0)) 33 | .targets(target(0, "x")) 34 | .rules(ruleBuilder().clauses( 35 | clause("key", Operator.in, LDValue.of("x"), LDValue.of("y")) 36 | ).build()) 37 | .build(); 38 | String flagJson = JsonHelpers.serialize(originalFlag); 39 | 40 | FeatureFlag flag = gson.fromJson(flagJson, FeatureFlag.class); 41 | assertNotNull(flag.preprocessed); 42 | for (Prerequisite p: flag.getPrerequisites()) { 43 | assertNotNull(p.preprocessed); 44 | } 45 | for (Target t: flag.getTargets()) { 46 | assertNotNull(t.preprocessed); 47 | } 48 | for (Rule r: flag.getRules()) { 49 | assertThat(r.preprocessed, notNullValue()); 50 | for (Clause c: r.getClauses()) { 51 | assertThat(c.preprocessed, notNullValue()); 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/test/java/com/launchdarkly/sdk/server/InMemoryDataStoreTest.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server; 2 | 3 | import com.launchdarkly.sdk.server.subsystems.DataStore; 4 | 5 | import org.junit.Test; 6 | 7 | import static org.junit.Assert.assertNull; 8 | 9 | @SuppressWarnings("javadoc") 10 | public class InMemoryDataStoreTest extends DataStoreTestBase { 11 | 12 | @Override 13 | protected DataStore makeStore() { 14 | return new InMemoryDataStore(); 15 | } 16 | 17 | @Test 18 | public void cacheStatsAreNull() { 19 | assertNull(makeStore().getCacheStats()); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/test/java/com/launchdarkly/sdk/server/JsonHelpersTest.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server; 2 | 3 | import com.google.gson.annotations.JsonAdapter; 4 | import com.launchdarkly.sdk.server.subsystems.SerializationException; 5 | 6 | import org.junit.Test; 7 | 8 | import static org.junit.Assert.assertEquals; 9 | import static org.junit.Assert.assertNotNull; 10 | import static org.junit.Assert.assertTrue; 11 | 12 | @SuppressWarnings("javadoc") 13 | public class JsonHelpersTest { 14 | @Test 15 | public void serialize() { 16 | MySerializableClass instance = new MySerializableClass(); 17 | instance.value = 3; 18 | assertEquals("{\"value\":3}", JsonHelpers.serialize(instance)); 19 | } 20 | 21 | @Test 22 | public void deserialize() { 23 | MySerializableClass instance = JsonHelpers.deserialize("{\"value\":3}", MySerializableClass.class); 24 | assertNotNull(instance); 25 | assertEquals(3, instance.value); 26 | } 27 | 28 | @Test(expected=SerializationException.class) 29 | public void deserializeInvalidJson() { 30 | JsonHelpers.deserialize("{\"value", MySerializableClass.class); 31 | } 32 | 33 | @Test 34 | public void postProcessingTypeAdapterFactoryCallsAfterDeserializedIfApplicable() { 35 | // This tests the mechanism that ensures afterDeserialize() is called on every FeatureFlag or 36 | // Segment that we deserialize. 37 | MyClassWithAnAfterDeserializeMethod instance = 38 | JsonHelpers.gsonInstance().fromJson("{}", MyClassWithAnAfterDeserializeMethod.class); 39 | assertNotNull(instance); 40 | assertTrue(instance.wasCalled); 41 | } 42 | 43 | @Test 44 | public void postProcessingTypeAdapterFactoryDoesNothingIfClassDoesNotImplementInterface() { 45 | // If we accidentally apply this type adapter to something inapplicable, it's a no-op. 46 | SomeOtherClass instance = JsonHelpers.gsonInstance().fromJson("{}", SomeOtherClass.class); 47 | assertNotNull(instance); 48 | } 49 | 50 | @Test 51 | public void postProcessingTypeAdapterFactoryDoesNotAffectSerialization() { 52 | MyClassWithAnAfterDeserializeMethod instance = new MyClassWithAnAfterDeserializeMethod(); 53 | String json = JsonHelpers.gsonInstance().toJson(instance); 54 | assertEquals("{\"wasCalled\":false}", json); 55 | } 56 | 57 | static class MySerializableClass { 58 | int value; 59 | } 60 | 61 | @JsonAdapter(JsonHelpers.PostProcessingDeserializableTypeAdapterFactory.class) 62 | static class MyClassWithAnAfterDeserializeMethod implements JsonHelpers.PostProcessingDeserializable { 63 | boolean wasCalled = false; 64 | 65 | @Override 66 | public void afterDeserialized() { 67 | wasCalled = true; 68 | } 69 | } 70 | 71 | @JsonAdapter(JsonHelpers.PostProcessingDeserializableTypeAdapterFactory.class) 72 | static class SomeOtherClass {} 73 | } 74 | -------------------------------------------------------------------------------- /src/test/java/com/launchdarkly/sdk/server/LDClientExternalUpdatesOnlyTest.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server; 2 | 3 | import com.launchdarkly.sdk.LDContext; 4 | import com.launchdarkly.sdk.LDValue; 5 | import com.launchdarkly.sdk.server.interfaces.DataSourceStatusProvider; 6 | import com.launchdarkly.sdk.server.subsystems.DataStore; 7 | 8 | import org.junit.Test; 9 | 10 | import java.io.IOException; 11 | 12 | import static com.launchdarkly.sdk.server.ModelBuilders.flagWithValue; 13 | import static com.launchdarkly.sdk.server.TestComponents.initedDataStore; 14 | import static com.launchdarkly.sdk.server.TestComponents.specificComponent; 15 | import static com.launchdarkly.sdk.server.TestUtil.upsertFlag; 16 | import static org.junit.Assert.assertEquals; 17 | import static org.junit.Assert.assertTrue; 18 | 19 | @SuppressWarnings("javadoc") 20 | public class LDClientExternalUpdatesOnlyTest extends BaseTest { 21 | @Test 22 | public void externalUpdatesOnlyClientHasNullDataSource() throws Exception { 23 | LDConfig config = baseConfig() 24 | .dataSource(Components.externalUpdatesOnly()) 25 | .build(); 26 | try (LDClient client = new LDClient("SDK_KEY", config)) { 27 | assertEquals(ComponentsImpl.NullDataSource.class, client.dataSource.getClass()); 28 | } 29 | } 30 | 31 | @Test 32 | public void externalUpdatesOnlyClientHasDefaultEventProcessor() throws Exception { 33 | LDConfig config = baseConfig() 34 | .dataSource(Components.externalUpdatesOnly()) 35 | .events(Components.sendEvents()) 36 | .build(); 37 | try (LDClient client = new LDClient("SDK_KEY", config)) { 38 | assertEquals(DefaultEventProcessorWrapper.class, client.eventProcessor.getClass()); 39 | } 40 | } 41 | 42 | @Test 43 | public void externalUpdatesOnlyClientIsInitialized() throws Exception { 44 | LDConfig config = baseConfig() 45 | .dataSource(Components.externalUpdatesOnly()) 46 | .build(); 47 | try (LDClient client = new LDClient("SDK_KEY", config)) { 48 | assertTrue(client.isInitialized()); 49 | 50 | assertEquals(DataSourceStatusProvider.State.VALID, client.getDataSourceStatusProvider().getStatus().getState()); 51 | } 52 | } 53 | 54 | @Test 55 | public void externalUpdatesOnlyClientGetsFlagFromDataStore() throws IOException { 56 | DataStore testDataStore = initedDataStore(); 57 | LDConfig config = baseConfig() 58 | .dataSource(Components.externalUpdatesOnly()) 59 | .dataStore(specificComponent(testDataStore)) 60 | .build(); 61 | DataModel.FeatureFlag flag = flagWithValue("key", LDValue.of(true)); 62 | upsertFlag(testDataStore, flag); 63 | try (LDClient client = new LDClient("SDK_KEY", config)) { 64 | assertTrue(client.boolVariation("key", LDContext.create("user"), false)); 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /src/test/java/com/launchdarkly/sdk/server/LDClientOfflineTest.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server; 2 | 3 | import com.google.common.collect.ImmutableMap; 4 | import com.launchdarkly.sdk.LDContext; 5 | import com.launchdarkly.sdk.LDValue; 6 | import com.launchdarkly.sdk.server.interfaces.DataSourceStatusProvider; 7 | import com.launchdarkly.sdk.server.subsystems.DataStore; 8 | 9 | import org.junit.Test; 10 | 11 | import java.io.IOException; 12 | 13 | import static com.launchdarkly.sdk.server.ModelBuilders.flagWithValue; 14 | import static com.launchdarkly.sdk.server.TestComponents.initedDataStore; 15 | import static com.launchdarkly.sdk.server.TestComponents.specificComponent; 16 | import static com.launchdarkly.sdk.server.TestUtil.upsertFlag; 17 | import static org.junit.Assert.assertEquals; 18 | import static org.junit.Assert.assertTrue; 19 | 20 | @SuppressWarnings("javadoc") 21 | public class LDClientOfflineTest extends BaseTest { 22 | private static final LDContext user = LDContext.create("user"); 23 | 24 | @Test 25 | public void offlineClientHasNullDataSource() throws IOException { 26 | LDConfig config = baseConfig() 27 | .offline(true) 28 | .build(); 29 | try (LDClient client = new LDClient("SDK_KEY", config)) { 30 | assertEquals(ComponentsImpl.NullDataSource.class, client.dataSource.getClass()); 31 | } 32 | } 33 | 34 | @Test 35 | public void offlineClientHasNoOpEventProcessor() throws IOException { 36 | LDConfig config = baseConfig() 37 | .offline(true) 38 | .build(); 39 | try (LDClient client = new LDClient("SDK_KEY", config)) { 40 | assertEquals(NoOpEventProcessor.class, client.eventProcessor.getClass()); 41 | } 42 | } 43 | 44 | @Test 45 | public void offlineClientIsInitialized() throws IOException { 46 | LDConfig config = baseConfig() 47 | .offline(true) 48 | .build(); 49 | try (LDClient client = new LDClient("SDK_KEY", config)) { 50 | assertTrue(client.isInitialized()); 51 | 52 | assertEquals(DataSourceStatusProvider.State.VALID, client.getDataSourceStatusProvider().getStatus().getState()); 53 | } 54 | } 55 | 56 | @Test 57 | public void offlineClientReturnsDefaultValue() throws IOException { 58 | LDConfig config = baseConfig() 59 | .offline(true) 60 | .build(); 61 | try (LDClient client = new LDClient("SDK_KEY", config)) { 62 | assertEquals("x", client.stringVariation("key", user, "x")); 63 | } 64 | } 65 | 66 | @Test 67 | public void offlineClientGetsFlagsStateFromDataStore() throws IOException { 68 | DataStore testDataStore = initedDataStore(); 69 | LDConfig config = baseConfig() 70 | .offline(true) 71 | .dataStore(specificComponent(testDataStore)) 72 | .build(); 73 | upsertFlag(testDataStore, flagWithValue("key", LDValue.of(true))); 74 | try (LDClient client = new LDClient("SDK_KEY", config)) { 75 | FeatureFlagsState state = client.allFlagsState(user); 76 | assertTrue(state.isValid()); 77 | assertEquals(ImmutableMap.of("key", LDValue.of(true)), state.toValuesMap()); 78 | } 79 | } 80 | } -------------------------------------------------------------------------------- /src/test/java/com/launchdarkly/sdk/server/MigrationBuilderTests.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server; 2 | 3 | import com.launchdarkly.sdk.server.integrations.TestData; 4 | import com.launchdarkly.sdk.server.interfaces.LDClientInterface; 5 | import com.launchdarkly.sdk.server.migrations.Migration; 6 | import com.launchdarkly.sdk.server.migrations.MigrationBuilder; 7 | import com.launchdarkly.sdk.server.migrations.MigrationMethodResult; 8 | import org.junit.Test; 9 | 10 | import java.util.Optional; 11 | 12 | import static org.junit.Assert.assertFalse; 13 | import static org.junit.Assert.assertTrue; 14 | 15 | public class MigrationBuilderTests extends BaseTest { 16 | private final TestData testData = TestData.dataSource(); 17 | 18 | private final LDClientInterface client = new LDClient("SDK_KEY", baseConfig() 19 | .dataSource(testData) 20 | .build()); 21 | 22 | @Test 23 | public void itCanMakeABasicMigration() { 24 | MigrationBuilder builder = new MigrationBuilder<>(client); 25 | builder.read((Void) -> MigrationMethodResult.Success("Old"), (Void)-> MigrationMethodResult.Success("New")); 26 | builder.write((Void) -> MigrationMethodResult.Success("Old"), (Void)-> MigrationMethodResult.Success("New")); 27 | Optional> migration = builder.build(); 28 | assertTrue(migration.isPresent()); 29 | } 30 | 31 | @Test 32 | public void itDoesNotCreateAMigrationIfReadImplementationIsNotSet() { 33 | MigrationBuilder builder = new MigrationBuilder<>(client); 34 | builder.write((Void) -> MigrationMethodResult.Success("Old"), (Void)-> MigrationMethodResult.Success("New")); 35 | Optional> migration = builder.build(); 36 | assertFalse(migration.isPresent()); 37 | } 38 | 39 | @Test 40 | public void itDoesNotCreateAMigrationIfWriteImplementationIsNotSet() { 41 | MigrationBuilder builder = new MigrationBuilder<>(client); 42 | builder.read((Void) -> MigrationMethodResult.Success("Old"), (Void)-> MigrationMethodResult.Success("New")); 43 | Optional> migration = builder.build(); 44 | assertFalse(migration.isPresent()); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/test/java/com/launchdarkly/sdk/server/MigrationExecutionFixture.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server; 2 | 3 | import com.launchdarkly.sdk.server.integrations.TestData; 4 | import com.launchdarkly.sdk.server.interfaces.LDClientInterface; 5 | import com.launchdarkly.sdk.server.migrations.Migration; 6 | import com.launchdarkly.sdk.server.migrations.MigrationBuilder; 7 | import com.launchdarkly.sdk.server.migrations.MigrationExecution; 8 | import com.launchdarkly.sdk.server.migrations.MigrationMethodResult; 9 | 10 | import static com.launchdarkly.sdk.server.TestComponents.specificComponent; 11 | 12 | import java.util.Optional; 13 | 14 | import static org.junit.Assert.assertTrue; 15 | 16 | /** 17 | * This fixtures simplifies tests which require tracking the execution of the various migration methods. 18 | */ 19 | public class MigrationExecutionFixture extends BaseTest { 20 | public final TestData testData = TestData.dataSource(); 21 | public final String flagKey = "test-flag"; 22 | 23 | public TestComponents.TestEventProcessor eventSink = new TestComponents.TestEventProcessor(); 24 | 25 | public final LDClientInterface client = new LDClient("SDK_KEY", baseConfig() 26 | .dataSource(testData) 27 | .events(specificComponent(eventSink)) 28 | .build()); 29 | 30 | public Migration migration; 31 | 32 | public boolean readOldCalled = false; 33 | public boolean writeOldCalled = false; 34 | public boolean readNewCalled = false; 35 | public boolean writeNewCalled = false; 36 | 37 | public boolean failOldWrite = false; 38 | 39 | public boolean failNewWrite = false; 40 | 41 | public boolean failOldRead = false; 42 | 43 | public boolean failNewRead = false; 44 | 45 | public String payloadReadOld = null; 46 | public String payloadReadNew = null; 47 | 48 | public String payloadWriteOld = null; 49 | public String payloadWriteNew = null; 50 | 51 | public MigrationExecutionFixture(MigrationExecution execution) { 52 | this(execution, true, true); 53 | } 54 | 55 | public MigrationExecutionFixture(MigrationExecution execution, boolean trackLatency, boolean trackErrors) { 56 | MigrationBuilder builder = new MigrationBuilder(client). 57 | readExecution(execution) 58 | .trackLatency(trackLatency) 59 | .trackErrors(trackErrors) 60 | .read((payload) -> { 61 | readOldCalled = true; 62 | payloadReadOld = payload; 63 | return failOldRead ? MigrationMethodResult.Failure() : MigrationMethodResult.Success("Old"); 64 | }, (payload) -> { 65 | readNewCalled = true; 66 | payloadReadNew = payload; 67 | return failNewRead ? MigrationMethodResult.Failure() : MigrationMethodResult.Success("New"); 68 | }) 69 | .write((payload) -> { 70 | writeOldCalled = true; 71 | payloadWriteOld = payload; 72 | return failOldWrite ? MigrationMethodResult.Failure() : MigrationMethodResult.Success("Old"); 73 | }, (payload) -> { 74 | writeNewCalled = true; 75 | payloadWriteNew = payload; 76 | return failNewWrite ? MigrationMethodResult.Failure() : MigrationMethodResult.Success("New"); 77 | }); 78 | Optional> res = builder.build(); 79 | assertTrue(res.isPresent()); 80 | migration = res.get(); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/test/java/com/launchdarkly/sdk/server/MigrationStageTests.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Test; 5 | import org.junit.experimental.runners.Enclosed; 6 | import org.junit.runner.RunWith; 7 | import org.junit.runners.Parameterized; 8 | 9 | import java.util.Arrays; 10 | 11 | @RunWith(Enclosed.class) 12 | public class MigrationStageTests { 13 | 14 | public static class BasicTests { 15 | @Test 16 | public void itHandlesWhenAStringIsNotAStage() { 17 | Assert.assertFalse(MigrationStage.isStage("potato")); 18 | } 19 | } 20 | 21 | @RunWith(Parameterized.class) 22 | public static class GivenEachStageTest { 23 | @Parameterized.Parameters(name = "{0}") 24 | public static Iterable data() { 25 | return Arrays.asList(new Object[][]{ 26 | {MigrationStage.OFF, "off"}, 27 | {MigrationStage.DUAL_WRITE, "dualwrite"}, 28 | {MigrationStage.SHADOW, "shadow"}, 29 | {MigrationStage.LIVE, "live"}, 30 | {MigrationStage.RAMP_DOWN, "rampdown"}, 31 | {MigrationStage.COMPLETE, "complete"} 32 | }); 33 | } 34 | 35 | @Parameterized.Parameter 36 | public MigrationStage stage; 37 | 38 | @Parameterized.Parameter(value = 1) 39 | public String stageString; 40 | 41 | @Test 42 | public void itCanConvertToAString() { 43 | Assert.assertEquals(stageString, stage.toString()); 44 | } 45 | 46 | @Test 47 | public void itCanTestAValueIsAStage() { 48 | Assert.assertTrue(MigrationStage.isStage(stageString)); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/test/java/com/launchdarkly/sdk/server/MigrationVariationTests.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server; 2 | 3 | import com.launchdarkly.sdk.LDContext; 4 | import com.launchdarkly.sdk.LDValue; 5 | import com.launchdarkly.sdk.server.integrations.TestData; 6 | import com.launchdarkly.sdk.server.interfaces.LDClientInterface; 7 | import org.junit.Assert; 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | import org.junit.runners.Parameterized; 11 | 12 | import java.util.Arrays; 13 | 14 | @RunWith(Parameterized.class) 15 | public class MigrationVariationTests extends BaseTest { 16 | 17 | @Parameterized.Parameters(name = "{0}" ) 18 | public static Iterable data() { 19 | return Arrays.asList( 20 | MigrationStage.OFF, 21 | MigrationStage.DUAL_WRITE, 22 | MigrationStage.SHADOW, 23 | MigrationStage.LIVE, 24 | MigrationStage.RAMP_DOWN, 25 | MigrationStage.COMPLETE 26 | ); 27 | } 28 | 29 | private final TestData testData = TestData.dataSource(); 30 | 31 | private final LDClientInterface client = new LDClient("SDK_KEY", baseConfig() 32 | .dataSource(testData) 33 | .build()); 34 | 35 | @Parameterized.Parameter 36 | public MigrationStage stage; 37 | 38 | @Test 39 | public void itEvaluatesDefaultForMissingFlag() { 40 | MigrationVariation resStage = client.migrationVariation("key", LDContext.create("potato"), stage); 41 | Assert.assertEquals(stage, resStage.getStage()); 42 | } 43 | 44 | @Test 45 | public void itDoesEvaluateDefaultForFlagWithInvalidStage() { 46 | final String flagKey = "test-flag"; 47 | final LDContext context = LDContext.create("test-key"); 48 | testData.update(testData.flag(flagKey).valueForAll(LDValue.of("potato"))); 49 | MigrationVariation resStage = client.migrationVariation(flagKey, context, stage); 50 | Assert.assertEquals(stage, resStage.getStage()); 51 | } 52 | 53 | @Test 54 | public void itEvaluatesCorrectValueForExistingFlag() { 55 | final String flagKey = "test-flag"; 56 | final LDContext context = LDContext.create("test-key"); 57 | testData.update(testData.flag(flagKey).valueForAll(LDValue.of(stage.toString()))); 58 | // Get a stage that is not the stage we are testing. 59 | MigrationStage defaultStage = Arrays.stream(MigrationStage.values()).filter(item -> item != stage).findFirst().get(); 60 | MigrationVariation resStage = client.migrationVariation(flagKey, context, defaultStage); 61 | Assert.assertEquals(stage, resStage.getStage()); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/test/java/com/launchdarkly/sdk/server/ServerSideEventContextDeduplicatorTest.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server; 2 | 3 | import com.launchdarkly.sdk.ContextKind; 4 | import com.launchdarkly.sdk.LDContext; 5 | import com.launchdarkly.sdk.internal.events.EventContextDeduplicator; 6 | 7 | import org.junit.Test; 8 | 9 | import java.time.Duration; 10 | 11 | import static org.hamcrest.MatcherAssert.assertThat; 12 | import static org.hamcrest.Matchers.equalTo; 13 | import static org.hamcrest.Matchers.is; 14 | 15 | @SuppressWarnings("javadoc") 16 | public class ServerSideEventContextDeduplicatorTest { 17 | private static final Duration LONG_INTERVAL = Duration.ofHours(3); 18 | 19 | @Test 20 | public void configuredFlushIntervalIsReturned() { 21 | EventContextDeduplicator ecd = new ServerSideEventContextDeduplicator(1000, LONG_INTERVAL); 22 | assertThat(ecd.getFlushInterval(), equalTo(LONG_INTERVAL.toMillis())); 23 | } 24 | 25 | @Test 26 | public void singleKindContextKeysAreDeduplicated() { 27 | EventContextDeduplicator ecd = new ServerSideEventContextDeduplicator(1000, LONG_INTERVAL); 28 | 29 | assertThat(ecd.processContext(LDContext.create("a")), is(true)); 30 | assertThat(ecd.processContext(LDContext.create("b")), is(true)); 31 | assertThat(ecd.processContext(LDContext.create("a")), is(false)); 32 | assertThat(ecd.processContext(LDContext.create("c")), is(true)); 33 | assertThat(ecd.processContext(LDContext.create("c")), is(false)); 34 | assertThat(ecd.processContext(LDContext.create("b")), is(false)); 35 | } 36 | 37 | @Test 38 | public void keysAreDisambiguatedByKind() { 39 | EventContextDeduplicator ecd = new ServerSideEventContextDeduplicator(1000, LONG_INTERVAL); 40 | ContextKind kind1 = ContextKind.of("kind1"), kind2 = ContextKind.of("kind2"); 41 | 42 | assertThat(ecd.processContext(LDContext.create(kind1, "a")), is(true)); 43 | assertThat(ecd.processContext(LDContext.create(kind1, "b")), is(true)); 44 | assertThat(ecd.processContext(LDContext.create(kind1, "a")), is(false)); 45 | assertThat(ecd.processContext(LDContext.create(kind2, "a")), is(true)); 46 | assertThat(ecd.processContext(LDContext.create(kind2, "a")), is(false)); 47 | } 48 | 49 | @Test 50 | public void multiKindContextIsDisambiguatedFromSingleKinds() { 51 | // This should work automatically because of the defined behavior of LDContext.fullyQualifiedKey() 52 | EventContextDeduplicator ecd = new ServerSideEventContextDeduplicator(1000, LONG_INTERVAL); 53 | ContextKind kind1 = ContextKind.of("kind1"), kind2 = ContextKind.of("kind2"); 54 | 55 | LDContext c1 = LDContext.create(kind1, "a"); 56 | LDContext c2 = LDContext.create(kind2, "a"); 57 | LDContext mc = LDContext.createMulti(c1, c2); 58 | 59 | assertThat(ecd.processContext(c1), is(true)); 60 | assertThat(ecd.processContext(c2), is(true)); 61 | assertThat(ecd.processContext(c1), is(false)); 62 | assertThat(ecd.processContext(c2), is(false)); 63 | assertThat(ecd.processContext(mc), is(true)); 64 | assertThat(ecd.processContext(mc), is(false)); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/test/java/com/launchdarkly/sdk/server/SimpleLRUCacheTest.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server; 2 | 3 | import com.launchdarkly.sdk.server.SimpleLRUCache; 4 | 5 | import org.junit.Test; 6 | 7 | import static org.junit.Assert.assertEquals; 8 | import static org.junit.Assert.assertNull; 9 | 10 | @SuppressWarnings("javadoc") 11 | public class SimpleLRUCacheTest { 12 | @Test 13 | public void getReturnsNullForNeverSeenValue() { 14 | SimpleLRUCache cache = new SimpleLRUCache<>(10); 15 | assertNull(cache.get("a")); 16 | } 17 | 18 | @Test 19 | public void putReturnsNullForNeverSeenValue() { 20 | SimpleLRUCache cache = new SimpleLRUCache<>(10); 21 | assertNull(cache.put("a", "1")); 22 | } 23 | 24 | @Test 25 | public void putReturnsPreviousValueForAlreadySeenValue() { 26 | SimpleLRUCache cache = new SimpleLRUCache<>(10); 27 | cache.put("a", "1"); 28 | assertEquals("1", cache.put("a", "2")); 29 | } 30 | 31 | @Test 32 | public void oldestValueIsDiscardedWhenCapacityIsExceeded() { 33 | SimpleLRUCache cache = new SimpleLRUCache<>(2); 34 | cache.put("a", "1"); 35 | cache.put("b", "2"); 36 | cache.put("c", "3"); 37 | assertEquals("3", cache.get("c")); 38 | assertEquals("2", cache.get("b")); 39 | assertNull(cache.get("a")); 40 | } 41 | 42 | @Test 43 | public void reAddingValueMakesItNewAgain() { 44 | SimpleLRUCache cache = new SimpleLRUCache<>(2); 45 | cache.put("a", "1"); 46 | cache.put("b", "2"); 47 | cache.put("c", "3"); 48 | cache.put("a", "1"); 49 | assertEquals("3", cache.get("c")); 50 | assertEquals("1", cache.get("a")); 51 | assertNull(cache.get("b")); 52 | } 53 | 54 | @Test 55 | public void zeroLengthCacheTreatsValuesAsNew() { 56 | SimpleLRUCache cache = new SimpleLRUCache<>(0); 57 | cache.put("a", "1"); 58 | assertNull(cache.put("a", "2")); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/test/java/com/launchdarkly/sdk/server/UtilTest.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server; 2 | 3 | import com.launchdarkly.sdk.server.interfaces.ApplicationInfo; 4 | import com.launchdarkly.sdk.server.interfaces.HttpAuthentication; 5 | 6 | import org.junit.Test; 7 | 8 | import java.time.Duration; 9 | 10 | import static com.launchdarkly.sdk.server.Util.applicationTagHeader; 11 | import static org.junit.Assert.assertEquals; 12 | import static org.junit.Assert.assertNull; 13 | 14 | import okhttp3.Authenticator; 15 | import okhttp3.Protocol; 16 | import okhttp3.Request; 17 | import okhttp3.Response; 18 | 19 | @SuppressWarnings("javadoc") 20 | public class UtilTest extends BaseTest { 21 | @Test 22 | public void useOurBasicAuthenticatorAsOkhttpProxyAuthenticator() throws Exception { 23 | HttpAuthentication ourAuth = Components.httpBasicAuthentication("user", "pass"); 24 | Authenticator okhttpAuth = Util.okhttpAuthenticatorFromHttpAuthStrategy(ourAuth); 25 | 26 | Request originalRequest = new Request.Builder().url("http://proxy").build(); 27 | Response resp1 = new Response.Builder() 28 | .request(originalRequest) 29 | .message("") 30 | .protocol(Protocol.HTTP_1_1) 31 | .header("Proxy-Authentication", "Basic realm=x") 32 | .code(407) 33 | .build(); 34 | 35 | Request newRequest = okhttpAuth.authenticate(null, resp1); 36 | 37 | assertEquals("Basic dXNlcjpwYXNz", newRequest.header("Proxy-Authorization")); 38 | 39 | // simulate the proxy rejecting us again 40 | Response resp2 = new Response.Builder() 41 | .request(newRequest) 42 | .message("") 43 | .protocol(Protocol.HTTP_1_1) 44 | .header("Proxy-Authentication", "Basic realm=x") 45 | .code(407) 46 | .build(); 47 | 48 | assertNull(okhttpAuth.authenticate(null, resp2)); // null tells OkHttp to give up 49 | } 50 | 51 | @Test 52 | public void describeDuration() { 53 | assertEquals("15 milliseconds", Util.describeDuration(Duration.ofMillis(15))); 54 | assertEquals("1500 milliseconds", Util.describeDuration(Duration.ofMillis(1500))); 55 | assertEquals("1 second", Util.describeDuration(Duration.ofMillis(1000))); 56 | assertEquals("2 seconds", Util.describeDuration(Duration.ofMillis(2000))); 57 | assertEquals("70 seconds", Util.describeDuration(Duration.ofMillis(70000))); 58 | assertEquals("1 minute", Util.describeDuration(Duration.ofMillis(60000))); 59 | assertEquals("2 minutes", Util.describeDuration(Duration.ofMillis(120000))); 60 | } 61 | 62 | @Test 63 | public void testApplicationTagHeader() { 64 | assertEquals("", applicationTagHeader(new ApplicationInfo(null, null), testLogger)); 65 | assertEquals("application-id/foo", applicationTagHeader(new ApplicationInfo("foo", null), testLogger)); 66 | assertEquals("application-version/1.0.0", 67 | applicationTagHeader(new ApplicationInfo(null, "1.0.0"), testLogger)); 68 | assertEquals("application-id/foo application-version/1.0.0", 69 | applicationTagHeader(new ApplicationInfo("foo", "1.0.0"), testLogger)); 70 | // Values with invalid characters get discarded 71 | assertEquals("", applicationTagHeader(new ApplicationInfo("invalid name", "lol!"), testLogger)); 72 | // Values over 64 chars get discarded 73 | assertEquals("", applicationTagHeader( 74 | new ApplicationInfo("look-at-this-incredibly-long-application-id-like-wow-it-sure-is-verbose", null), 75 | testLogger)); 76 | // Empty values get discarded 77 | assertEquals("", applicationTagHeader(new ApplicationInfo("", ""), testLogger)); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/test/java/com/launchdarkly/sdk/server/integrations/ApplicationInfoBuilderTest.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server.integrations; 2 | 3 | import com.launchdarkly.sdk.server.Components; 4 | import com.launchdarkly.sdk.server.interfaces.ApplicationInfo; 5 | 6 | import org.junit.Test; 7 | 8 | import static org.junit.Assert.assertEquals; 9 | import static org.junit.Assert.assertNull; 10 | 11 | @SuppressWarnings("javadoc") 12 | public class ApplicationInfoBuilderTest { 13 | @Test 14 | public void infoBuilder() { 15 | ApplicationInfo i1 = Components.applicationInfo() 16 | .createApplicationInfo(); 17 | assertNull(i1.getApplicationId()); 18 | assertNull(i1.getApplicationVersion()); 19 | 20 | ApplicationInfo i2 = Components.applicationInfo() 21 | .applicationId("authentication-service") 22 | .applicationVersion("1.0.0") 23 | .createApplicationInfo(); 24 | assertEquals("authentication-service", i2.getApplicationId()); 25 | assertEquals("1.0.0", i2.getApplicationVersion()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/test/java/com/launchdarkly/sdk/server/integrations/BigSegmentStoreTestBaseTest.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server.integrations; 2 | 3 | import com.launchdarkly.sdk.server.subsystems.BigSegmentStore; 4 | import com.launchdarkly.sdk.server.subsystems.BigSegmentStoreTypes.Membership; 5 | import com.launchdarkly.sdk.server.subsystems.BigSegmentStoreTypes.StoreMetadata; 6 | import com.launchdarkly.sdk.server.subsystems.ClientContext; 7 | import com.launchdarkly.sdk.server.subsystems.ComponentConfigurer; 8 | 9 | import java.io.IOException; 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | 13 | import static com.launchdarkly.sdk.server.subsystems.BigSegmentStoreTypes.createMembershipFromSegmentRefs; 14 | 15 | @SuppressWarnings("javadoc") 16 | public class BigSegmentStoreTestBaseTest extends BigSegmentStoreTestBase { 17 | // This runs BigSegmentStoreTestBase against a mock store implementation that is known to behave 18 | // as expected, to verify that the test suite logic has the correct expectations. 19 | 20 | private static class DataSet { 21 | StoreMetadata metadata = null; 22 | Map memberships = new HashMap<>(); 23 | } 24 | 25 | private final Map allData = new HashMap<>(); 26 | 27 | private DataSet getOrCreateDataSet(String prefix) { 28 | allData.putIfAbsent(prefix, new DataSet()); 29 | return allData.get(prefix); 30 | } 31 | 32 | @Override 33 | protected ComponentConfigurer makeStore(String prefix) { 34 | return new MockStoreFactory(getOrCreateDataSet(prefix)); 35 | } 36 | 37 | @Override 38 | protected void clearData(String prefix) { 39 | DataSet dataSet = getOrCreateDataSet(prefix); 40 | dataSet.metadata = null; 41 | dataSet.memberships.clear(); 42 | } 43 | 44 | @Override 45 | protected void setMetadata(String prefix, StoreMetadata metadata) { 46 | DataSet dataSet = getOrCreateDataSet(prefix); 47 | dataSet.metadata = metadata; 48 | } 49 | 50 | @Override 51 | protected void setSegments(String prefix, String userHashKey, Iterable includedSegmentRefs, Iterable excludedSegmentRefs) { 52 | DataSet dataSet = getOrCreateDataSet(prefix); 53 | dataSet.memberships.put(userHashKey, createMembershipFromSegmentRefs(includedSegmentRefs, excludedSegmentRefs)); 54 | } 55 | 56 | private static final class MockStoreFactory implements ComponentConfigurer { 57 | private final DataSet data; 58 | 59 | private MockStoreFactory(DataSet data) { 60 | this.data = data; 61 | } 62 | 63 | @Override 64 | public BigSegmentStore build(ClientContext context) { 65 | return new MockStore(data); 66 | } 67 | } 68 | 69 | private static final class MockStore implements BigSegmentStore { 70 | private final DataSet data; 71 | 72 | private MockStore(DataSet data) { 73 | this.data = data; 74 | } 75 | 76 | @Override 77 | public Membership getMembership(String userHash) { 78 | return data.memberships.get(userHash); 79 | } 80 | 81 | @Override 82 | public StoreMetadata getMetadata() { 83 | return data.metadata; 84 | } 85 | 86 | @Override 87 | public void close() throws IOException { } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/test/java/com/launchdarkly/sdk/server/integrations/ClientWithFileDataSourceTest.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server.integrations; 2 | 3 | import com.launchdarkly.sdk.LDContext; 4 | import com.launchdarkly.sdk.LDValue; 5 | import com.launchdarkly.sdk.server.Components; 6 | import com.launchdarkly.sdk.server.LDClient; 7 | import com.launchdarkly.sdk.server.LDConfig; 8 | 9 | import org.junit.Test; 10 | 11 | import static com.launchdarkly.sdk.server.integrations.FileDataSourceTestData.FLAG_VALUE_1; 12 | import static com.launchdarkly.sdk.server.integrations.FileDataSourceTestData.FLAG_VALUE_1_KEY; 13 | import static com.launchdarkly.sdk.server.integrations.FileDataSourceTestData.FULL_FLAG_1_KEY; 14 | import static com.launchdarkly.sdk.server.integrations.FileDataSourceTestData.FULL_FLAG_1_VALUE; 15 | import static com.launchdarkly.sdk.server.integrations.FileDataSourceTestData.resourceFilePath; 16 | import static org.hamcrest.MatcherAssert.assertThat; 17 | import static org.hamcrest.Matchers.equalTo; 18 | 19 | @SuppressWarnings("javadoc") 20 | public class ClientWithFileDataSourceTest { 21 | private static final LDContext user = LDContext.create("userkey"); 22 | 23 | private LDClient makeClient() throws Exception { 24 | FileDataSourceBuilder fdsb = FileData.dataSource() 25 | .filePaths(resourceFilePath("all-properties.json")); 26 | LDConfig config = new LDConfig.Builder() 27 | .dataSource(fdsb) 28 | .events(Components.noEvents()) 29 | .build(); 30 | return new LDClient("sdkKey", config); 31 | } 32 | 33 | @Test 34 | public void fullFlagDefinitionEvaluatesAsExpected() throws Exception { 35 | try (LDClient client = makeClient()) { 36 | assertThat(client.jsonValueVariation(FULL_FLAG_1_KEY, user, LDValue.of("default")), 37 | equalTo(FULL_FLAG_1_VALUE)); 38 | } 39 | } 40 | 41 | @Test 42 | public void simplifiedFlagEvaluatesAsExpected() throws Exception { 43 | try (LDClient client = makeClient()) { 44 | assertThat(client.jsonValueVariation(FLAG_VALUE_1_KEY, user, LDValue.of("default")), 45 | equalTo(FLAG_VALUE_1)); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/test/java/com/launchdarkly/sdk/server/integrations/FileDataSourceTestData.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server.integrations; 2 | 3 | import com.google.common.collect.ImmutableMap; 4 | import com.google.common.collect.ImmutableSet; 5 | import com.launchdarkly.sdk.LDValue; 6 | 7 | import java.net.URISyntaxException; 8 | import java.net.URL; 9 | import java.nio.file.Files; 10 | import java.nio.file.Path; 11 | import java.nio.file.Paths; 12 | import java.util.Map; 13 | import java.util.Set; 14 | 15 | @SuppressWarnings("javadoc") 16 | public class FileDataSourceTestData { 17 | // These should match the data in our test files 18 | public static final String FULL_FLAG_1_KEY = "flag1"; 19 | public static final LDValue FULL_FLAG_1 = 20 | LDValue.parse("{\"key\":\"flag1\",\"on\":true,\"fallthrough\":{\"variation\":2},\"variations\":[\"fall\",\"off\",\"on\"]}"); 21 | public static final LDValue FULL_FLAG_1_VALUE = LDValue.of("on"); 22 | public static final Map FULL_FLAGS = 23 | ImmutableMap.of(FULL_FLAG_1_KEY, FULL_FLAG_1); 24 | 25 | public static final String FLAG_VALUE_1_KEY = "flag2"; 26 | public static final LDValue FLAG_VALUE_1 = LDValue.of("value2"); 27 | public static final Map FLAG_VALUES = 28 | ImmutableMap.of(FLAG_VALUE_1_KEY, FLAG_VALUE_1); 29 | 30 | public static final String FULL_SEGMENT_1_KEY = "seg1"; 31 | public static final LDValue FULL_SEGMENT_1 = LDValue.parse("{\"key\":\"seg1\",\"included\":[\"user1\"]}"); 32 | public static final Map FULL_SEGMENTS = 33 | ImmutableMap.of(FULL_SEGMENT_1_KEY, FULL_SEGMENT_1); 34 | 35 | public static final Set ALL_FLAG_KEYS = ImmutableSet.of(FULL_FLAG_1_KEY, FLAG_VALUE_1_KEY); 36 | public static final Set ALL_SEGMENT_KEYS = ImmutableSet.of(FULL_SEGMENT_1_KEY); 37 | 38 | public static Path resourceFilePath(String filename) throws URISyntaxException { 39 | URL resource = FileDataSourceTestData.class.getClassLoader().getResource(resourceLocation(filename)); 40 | return Paths.get(resource.toURI()); 41 | } 42 | 43 | public static String resourceLocation(String filename) throws URISyntaxException { 44 | return "filesource/" + filename; 45 | } 46 | 47 | public static String getResourceContents(String filename) throws Exception { 48 | return new String(Files.readAllBytes(resourceFilePath(filename))); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/test/java/com/launchdarkly/sdk/server/integrations/FlagFileParserJsonTest.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server.integrations; 2 | 3 | import com.launchdarkly.sdk.server.integrations.FileDataSourceParsing.JsonFlagFileParser; 4 | 5 | @SuppressWarnings("javadoc") 6 | public class FlagFileParserJsonTest extends FlagFileParserTestBase { 7 | public FlagFileParserJsonTest() { 8 | super(new JsonFlagFileParser(), ".json"); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/test/java/com/launchdarkly/sdk/server/integrations/FlagFileParserYamlTest.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server.integrations; 2 | 3 | import com.launchdarkly.sdk.server.integrations.FileDataSourceParsing.YamlFlagFileParser; 4 | 5 | @SuppressWarnings("javadoc") 6 | public class FlagFileParserYamlTest extends FlagFileParserTestBase { 7 | public FlagFileParserYamlTest() { 8 | super(new YamlFlagFileParser(), ".yml"); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/test/java/com/launchdarkly/sdk/server/integrations/HookConfigurationBuilderTest.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server.integrations; 2 | 3 | import com.launchdarkly.sdk.server.Components; 4 | import com.launchdarkly.sdk.server.subsystems.HookConfiguration; 5 | import org.junit.Test; 6 | 7 | import java.util.Arrays; 8 | 9 | import static org.easymock.EasyMock.mock; 10 | import static org.junit.Assert.assertEquals; 11 | import static org.junit.Assert.assertSame; 12 | 13 | public class HookConfigurationBuilderTest { 14 | 15 | @Test 16 | public void emptyHooksAsDefault() { 17 | HookConfiguration configuration = Components.hooks().build(); 18 | assertEquals(0, configuration.getHooks().size()); 19 | } 20 | 21 | @Test 22 | public void canSetHooks() { 23 | Hook hookA = mock(Hook.class); 24 | Hook hookB = mock(Hook.class); 25 | HookConfiguration configuration = Components.hooks().setHooks(Arrays.asList(hookA, hookB)).build(); 26 | assertEquals(2, configuration.getHooks().size()); 27 | assertSame(hookA, configuration.getHooks().get(0)); 28 | assertSame(hookB, configuration.getHooks().get(1)); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/test/java/com/launchdarkly/sdk/server/integrations/PersistentDataStoreBuilderTest.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server.integrations; 2 | 3 | import com.launchdarkly.sdk.server.integrations.PersistentDataStoreBuilder.StaleValuesPolicy; 4 | import com.launchdarkly.sdk.server.subsystems.ComponentConfigurer; 5 | import com.launchdarkly.sdk.server.subsystems.PersistentDataStore; 6 | 7 | import org.junit.Test; 8 | 9 | import java.time.Duration; 10 | 11 | import static com.launchdarkly.sdk.server.Components.persistentDataStore; 12 | import static com.launchdarkly.sdk.server.integrations.PersistentDataStoreBuilder.DEFAULT_CACHE_TTL; 13 | import static org.junit.Assert.assertEquals; 14 | import static org.junit.Assert.assertFalse; 15 | import static org.junit.Assert.assertSame; 16 | import static org.junit.Assert.assertTrue; 17 | 18 | @SuppressWarnings("javadoc") 19 | public class PersistentDataStoreBuilderTest { 20 | private static final ComponentConfigurer factory = context -> null; 21 | 22 | @Test 23 | public void factory() { 24 | assertSame(factory, persistentDataStore(factory).persistentDataStoreConfigurer); 25 | } 26 | 27 | @Test 28 | public void cacheTime() { 29 | assertEquals(DEFAULT_CACHE_TTL, persistentDataStore(factory).cacheTime); 30 | 31 | assertEquals(Duration.ofMinutes(3), persistentDataStore(factory).cacheTime(Duration.ofMinutes(3)).cacheTime); 32 | 33 | assertEquals(Duration.ofMillis(3), persistentDataStore(factory).cacheMillis(3).cacheTime); 34 | 35 | assertEquals(Duration.ofSeconds(3), persistentDataStore(factory).cacheSeconds(3).cacheTime); 36 | 37 | assertEquals(DEFAULT_CACHE_TTL, 38 | persistentDataStore(factory).cacheTime(Duration.ofMinutes(3)).cacheTime(null).cacheTime); 39 | 40 | assertEquals(Duration.ZERO, persistentDataStore(factory).noCaching().cacheTime); 41 | 42 | assertEquals(Duration.ofMillis(-1), persistentDataStore(factory).cacheForever().cacheTime); 43 | } 44 | 45 | @Test 46 | public void staleValuesPolicy() { 47 | assertEquals(StaleValuesPolicy.EVICT, persistentDataStore(factory).staleValuesPolicy); 48 | 49 | assertEquals(StaleValuesPolicy.REFRESH, 50 | persistentDataStore(factory).staleValuesPolicy(StaleValuesPolicy.REFRESH).staleValuesPolicy); 51 | 52 | assertEquals(StaleValuesPolicy.EVICT, 53 | persistentDataStore(factory).staleValuesPolicy(StaleValuesPolicy.REFRESH).staleValuesPolicy(null).staleValuesPolicy); 54 | } 55 | 56 | @Test 57 | public void recordCacheStats() { 58 | assertFalse(persistentDataStore(factory).recordCacheStats); 59 | 60 | assertTrue(persistentDataStore(factory).recordCacheStats(true).recordCacheStats); 61 | 62 | assertFalse(persistentDataStore(factory).recordCacheStats(true).recordCacheStats(false).recordCacheStats); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/test/java/com/launchdarkly/sdk/server/integrations/PersistentDataStoreGenericTest.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server.integrations; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import com.launchdarkly.sdk.server.TestComponents; 5 | import com.launchdarkly.sdk.server.subsystems.ComponentConfigurer; 6 | import com.launchdarkly.sdk.server.subsystems.PersistentDataStore; 7 | 8 | import org.junit.runner.RunWith; 9 | import org.junit.runners.Parameterized; 10 | import org.junit.runners.Parameterized.Parameters; 11 | 12 | /** 13 | * This verifies that PersistentDataStoreTestBase behaves as expected as long as the PersistentDataStore 14 | * implementation behaves as expected. Since there aren't any actual database integrations built into the 15 | * SDK project, and PersistentDataStoreTestBase will be used by external projects like java-server-sdk-redis, 16 | * we want to make sure the test logic is correct regardless of database implementation details. 17 | * 18 | * PersistentDataStore implementations may be able to persist the version and deleted state as metadata 19 | * separate from the serialized item string; or they may not, in which case a little extra parsing is 20 | * necessary. MockPersistentDataStore is able to simulate both of these scenarios, and we test both here. 21 | */ 22 | @SuppressWarnings("javadoc") 23 | @RunWith(Parameterized.class) 24 | public class PersistentDataStoreGenericTest extends PersistentDataStoreTestBase { 25 | private final MockPersistentDataStore.MockDatabaseInstance sharedData = new MockPersistentDataStore.MockDatabaseInstance(); 26 | private final TestMode testMode; 27 | 28 | static class TestMode { 29 | final boolean persistOnlyAsString; 30 | 31 | TestMode(boolean persistOnlyAsString) { 32 | this.persistOnlyAsString = persistOnlyAsString; 33 | } 34 | 35 | @Override 36 | public String toString() { 37 | return "TestMode(" + (persistOnlyAsString ? "persistOnlyAsString" : "persistWithMetadata") + ")"; 38 | } 39 | } 40 | 41 | @Parameters(name="{0}") 42 | public static Iterable data() { 43 | return ImmutableList.of( 44 | new TestMode(false), 45 | new TestMode(true) 46 | ); 47 | } 48 | 49 | public PersistentDataStoreGenericTest(TestMode testMode) { 50 | this.testMode = testMode; 51 | } 52 | 53 | @Override 54 | protected ComponentConfigurer buildStore(String prefix) { 55 | MockPersistentDataStore store = new MockPersistentDataStore(sharedData, prefix); 56 | store.persistOnlyAsString = testMode.persistOnlyAsString; 57 | return TestComponents.specificComponent(store); 58 | } 59 | 60 | @Override 61 | protected void clearAllData() { 62 | synchronized (sharedData) { 63 | for (String prefix: sharedData.dataByPrefix.keySet()) { 64 | sharedData.dataByPrefix.get(prefix).clear(); 65 | } 66 | } 67 | } 68 | 69 | @Override 70 | protected boolean setUpdateHook(MockPersistentDataStore storeUnderTest, Runnable hook) { 71 | storeUnderTest.updateHook = hook; 72 | return true; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/test/java/com/launchdarkly/sdk/server/integrations/PollingDataSourceBuilderTest.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server.integrations; 2 | 3 | import org.junit.Test; 4 | 5 | import java.time.Duration; 6 | 7 | import static com.launchdarkly.sdk.server.Components.pollingDataSource; 8 | import static com.launchdarkly.sdk.server.integrations.PollingDataSourceBuilder.DEFAULT_POLL_INTERVAL; 9 | import static org.junit.Assert.assertEquals; 10 | 11 | @SuppressWarnings("javadoc") 12 | public class PollingDataSourceBuilderTest { 13 | @Test 14 | public void pollInterval() { 15 | assertEquals(DEFAULT_POLL_INTERVAL, pollingDataSource().pollInterval); 16 | 17 | assertEquals(Duration.ofMinutes(7), 18 | pollingDataSource().pollInterval(Duration.ofMinutes(7)).pollInterval); 19 | 20 | assertEquals(DEFAULT_POLL_INTERVAL, 21 | pollingDataSource().pollInterval(Duration.ofMinutes(7)).pollInterval(null).pollInterval); 22 | 23 | assertEquals(DEFAULT_POLL_INTERVAL, 24 | pollingDataSource().pollInterval(Duration.ofMillis(1)).pollInterval); 25 | } 26 | 27 | @Test 28 | public void testPayloadFilter() { 29 | assertEquals(null, pollingDataSource().payloadFilter); 30 | 31 | assertEquals("aFilter", 32 | pollingDataSource().payloadFilter("aFilter").payloadFilter); 33 | 34 | assertEquals(null, 35 | pollingDataSource().payloadFilter("aFilter").payloadFilter(null).payloadFilter); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/test/java/com/launchdarkly/sdk/server/integrations/StreamingDataSourceBuilderTest.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server.integrations; 2 | 3 | import org.junit.Test; 4 | 5 | import java.time.Duration; 6 | 7 | import static com.launchdarkly.sdk.server.Components.streamingDataSource; 8 | import static com.launchdarkly.sdk.server.integrations.StreamingDataSourceBuilder.DEFAULT_INITIAL_RECONNECT_DELAY; 9 | import static org.junit.Assert.assertEquals; 10 | 11 | @SuppressWarnings("javadoc") 12 | public class StreamingDataSourceBuilderTest { 13 | @Test 14 | public void initialReconnectDelay() { 15 | assertEquals(DEFAULT_INITIAL_RECONNECT_DELAY, streamingDataSource().initialReconnectDelay); 16 | 17 | assertEquals(Duration.ofMillis(222), 18 | streamingDataSource().initialReconnectDelay(Duration.ofMillis(222)).initialReconnectDelay); 19 | 20 | assertEquals(DEFAULT_INITIAL_RECONNECT_DELAY, 21 | streamingDataSource().initialReconnectDelay(Duration.ofMillis(222)).initialReconnectDelay(null).initialReconnectDelay); 22 | } 23 | 24 | @Test 25 | public void testPayloadFilter() { 26 | assertEquals(null, streamingDataSource().payloadFilter); 27 | 28 | assertEquals("aFilter", 29 | streamingDataSource().payloadFilter("aFilter").payloadFilter); 30 | 31 | assertEquals(null, 32 | streamingDataSource().payloadFilter("aFilter").payloadFilter(null).payloadFilter); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/test/java/com/launchdarkly/sdk/server/integrations/WrapperInfoBuilderTest.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server.integrations; 2 | 3 | import com.launchdarkly.sdk.server.Components; 4 | import com.launchdarkly.sdk.server.interfaces.ApplicationInfo; 5 | 6 | import com.launchdarkly.sdk.server.interfaces.WrapperInfo; 7 | import org.junit.Test; 8 | 9 | import static org.junit.Assert.assertEquals; 10 | import static org.junit.Assert.assertNull; 11 | 12 | @SuppressWarnings("javadoc") 13 | public class WrapperInfoBuilderTest { 14 | @Test 15 | public void theDefaultInstanceContainsNullValues() { 16 | WrapperInfo defaultInstance = Components.wrapperInfo() 17 | .build(); 18 | assertNull(defaultInstance.getWrapperName()); 19 | assertNull(defaultInstance.getWrapperVersion()); 20 | } 21 | 22 | @Test 23 | public void setValuesAreReflectedInBuiltInstance() { 24 | WrapperInfo clojureWrapper = Components.wrapperInfo() 25 | .wrapperName("Clojure") 26 | .wrapperVersion("0.0.1") 27 | .build(); 28 | assertEquals("Clojure", clojureWrapper.getWrapperName()); 29 | assertEquals("0.0.1", clojureWrapper.getWrapperVersion()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/com/launchdarkly/sdk/server/interfaces/DataStoreStatusProviderTypesTest.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server.interfaces; 2 | 3 | import com.launchdarkly.sdk.server.interfaces.DataStoreStatusProvider.CacheStats; 4 | import com.launchdarkly.sdk.server.interfaces.DataStoreStatusProvider.Status; 5 | import com.launchdarkly.testhelpers.TypeBehavior; 6 | 7 | import org.junit.Test; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | import static org.hamcrest.MatcherAssert.assertThat; 13 | import static org.hamcrest.Matchers.equalTo; 14 | 15 | @SuppressWarnings("javadoc") 16 | public class DataStoreStatusProviderTypesTest { 17 | @Test 18 | public void statusProperties() { 19 | Status s1 = new Status(true, false); 20 | assertThat(s1.isAvailable(), equalTo(true)); 21 | assertThat(s1.isRefreshNeeded(), equalTo(false)); 22 | 23 | Status s2 = new Status(false, true); 24 | assertThat(s2.isAvailable(), equalTo(false)); 25 | assertThat(s2.isRefreshNeeded(), equalTo(true)); 26 | } 27 | 28 | @Test 29 | public void statusEquality() { 30 | List> allPermutations = new ArrayList<>(); 31 | allPermutations.add(() -> new Status(false, false)); 32 | allPermutations.add(() -> new Status(false, true)); 33 | allPermutations.add(() -> new Status(true, false)); 34 | allPermutations.add(() -> new Status(true, true)); 35 | TypeBehavior.checkEqualsAndHashCode(allPermutations); 36 | } 37 | 38 | @Test 39 | public void statusStringRepresentation() { 40 | assertThat(new Status(true, false).toString(), equalTo("Status(true,false)")); 41 | } 42 | 43 | @Test 44 | public void cacheStatsProperties() { 45 | CacheStats stats = new CacheStats(1, 2, 3, 4, 5, 6); 46 | assertThat(stats.getHitCount(), equalTo(1L)); 47 | assertThat(stats.getMissCount(), equalTo(2L)); 48 | assertThat(stats.getLoadSuccessCount(), equalTo(3L)); 49 | assertThat(stats.getLoadExceptionCount(), equalTo(4L)); 50 | assertThat(stats.getTotalLoadTime(), equalTo(5L)); 51 | assertThat(stats.getEvictionCount(), equalTo(6L)); 52 | } 53 | 54 | @Test 55 | public void cacheStatsEquality() { 56 | List> allPermutations = new ArrayList<>(); 57 | int[] values = new int[] { 0, 1, 2 }; 58 | for (int hit: values) { 59 | for (int miss: values) { 60 | for (int loadSuccess: values) { 61 | for (int loadException: values) { 62 | for (int totalLoad: values) { 63 | for (int eviction: values) { 64 | allPermutations.add(() -> new CacheStats(hit, miss, loadSuccess, loadException, totalLoad, eviction)); 65 | } 66 | } 67 | } 68 | } 69 | } 70 | } 71 | TypeBehavior.checkEqualsAndHashCode(allPermutations); 72 | } 73 | 74 | @Test 75 | public void cacheStatsStringRepresentation() { 76 | CacheStats stats = new CacheStats(1, 2, 3, 4, 5, 6); 77 | assertThat(stats.toString(), equalTo("{hit=1, miss=2, loadSuccess=3, loadException=4, totalLoadTime=5, evictionCount=6}")); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/test/java/com/launchdarkly/sdk/server/interfaces/HttpAuthenticationTypesTest.java: -------------------------------------------------------------------------------- 1 | package com.launchdarkly.sdk.server.interfaces; 2 | 3 | import com.launchdarkly.sdk.server.interfaces.HttpAuthentication.Challenge; 4 | 5 | import org.junit.Test; 6 | 7 | import static org.hamcrest.MatcherAssert.assertThat; 8 | import static org.hamcrest.Matchers.equalTo; 9 | 10 | @SuppressWarnings("javadoc") 11 | public class HttpAuthenticationTypesTest { 12 | @Test 13 | public void challengeProperties() { 14 | Challenge c = new Challenge("Basic", "realm"); 15 | assertThat(c.getScheme(), equalTo("Basic")); 16 | assertThat(c.getRealm(), equalTo("realm")); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/test/resources/filesource/all-properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "flags": { 3 | "flag1": { 4 | "key": "flag1", 5 | "on": true, 6 | "fallthrough": { 7 | "variation": 2 8 | }, 9 | "variations": [ "fall", "off", "on" ] 10 | } 11 | }, 12 | "flagValues": { 13 | "flag2": "value2" 14 | }, 15 | "segments": { 16 | "seg1": { 17 | "key": "seg1", 18 | "included": ["user1"] 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/test/resources/filesource/all-properties.yml: -------------------------------------------------------------------------------- 1 | --- 2 | flags: 3 | flag1: 4 | key: flag1 5 | "on": true 6 | fallthrough: 7 | variation: 2 8 | variations: 9 | - fall 10 | - "off" 11 | - "on" 12 | flagValues: 13 | flag2: value2 14 | segments: 15 | seg1: 16 | key: seg1 17 | included: ["user1"] 18 | -------------------------------------------------------------------------------- /src/test/resources/filesource/flag-only.json: -------------------------------------------------------------------------------- 1 | { 2 | "flags": { 3 | "flag1": { 4 | "key": "flag1", 5 | "on": true, 6 | "fallthrough": { 7 | "variation": 2 8 | }, 9 | "variations": [ "fall", "off", "on" ] 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /src/test/resources/filesource/flag-only.yml: -------------------------------------------------------------------------------- 1 | --- 2 | flags: 3 | flag1: 4 | key: flag1 5 | "on": true 6 | fallthrough: 7 | variation: 2 8 | variations: 9 | - fall 10 | - "off" 11 | - "on" 12 | -------------------------------------------------------------------------------- /src/test/resources/filesource/flag-with-duplicate-key.json: -------------------------------------------------------------------------------- 1 | { 2 | "flags": { 3 | "another": { 4 | "key": "another", 5 | "on": true 6 | }, 7 | "flag1": { 8 | "key": "flag1", 9 | "on": false 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /src/test/resources/filesource/malformed.json: -------------------------------------------------------------------------------- 1 | { 2 | -------------------------------------------------------------------------------- /src/test/resources/filesource/malformed.yml: -------------------------------------------------------------------------------- 1 | - a 2 | b: ~ 3 | -------------------------------------------------------------------------------- /src/test/resources/filesource/no-data.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/launchdarkly/java-server-sdk/16b474bff9151adb5f9f5dd52350a53402f2f697/src/test/resources/filesource/no-data.json -------------------------------------------------------------------------------- /src/test/resources/filesource/segment-only.json: -------------------------------------------------------------------------------- 1 | { 2 | "segments": { 3 | "seg1": { 4 | "key": "seg1", 5 | "included": ["user1"] 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/test/resources/filesource/segment-only.yml: -------------------------------------------------------------------------------- 1 | --- 2 | segments: 3 | seg1: 4 | key: seg1 5 | included: ["user1"] 6 | -------------------------------------------------------------------------------- /src/test/resources/filesource/segment-with-duplicate-key.json: -------------------------------------------------------------------------------- 1 | { 2 | "segments": { 3 | "another": { 4 | "key": "another", 5 | "included": [] 6 | }, 7 | "seg1": { 8 | "key": "seg1", 9 | "included": ["user1a"] 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/test/resources/filesource/value-only.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "flagValues": { 4 | "flag2": "value2" 5 | } 6 | } -------------------------------------------------------------------------------- /src/test/resources/filesource/value-only.yml: -------------------------------------------------------------------------------- 1 | --- 2 | flagValues: 3 | flag2: value2 4 | -------------------------------------------------------------------------------- /src/test/resources/filesource/value-with-duplicate-key.json: -------------------------------------------------------------------------------- 1 | { 2 | "flagValues": { 3 | "flag1": "value1", 4 | "flag2": "value2a" 5 | } 6 | } -------------------------------------------------------------------------------- /src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36}:%line - %msg%n 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | --------------------------------------------------------------------------------