├── .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 | *
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 | *
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 | *
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 | *
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 | *
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 | *
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 | *
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 | *