diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e7ba7782e..1cb2193c8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,7 +6,7 @@ on: action: required: true type: string - travis_tag: + github_tag: required: true type: string secrets: @@ -22,13 +22,12 @@ jobs: run_build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: set up JDK 8 uses: actions/setup-java@v2 with: java-version: '8' distribution: 'temurin' - cache: gradle - name: Grant execute permission for gradlew run: chmod +x gradlew - name: ${{ inputs.action }} @@ -37,4 +36,4 @@ jobs: MAVEN_SIGNING_PASSPHRASE: ${{ secrets.MAVEN_SIGNING_PASSPHRASE }} MAVEN_CENTRAL_USERNAME: ${{ secrets.MAVEN_CENTRAL_USERNAME }} MAVEN_CENTRAL_PASSWORD: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} - run: TRAVIS_TAG=${{ inputs.travis_tag }} ./gradlew ${{ inputs.action }} + run: GITHUB_TAG=${{ inputs.github_tag }} ./gradlew ${{ inputs.action }} diff --git a/.github/workflows/integration_test.yml b/.github/workflows/integration_test.yml index c54149edd..76fef5ad3 100644 --- a/.github/workflows/integration_test.yml +++ b/.github/workflows/integration_test.yml @@ -9,18 +9,16 @@ on: secrets: CI_USER_TOKEN: required: true - TRAVIS_COM_TOKEN: - required: true jobs: test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: # You should create a personal access token and store it in your repository token: ${{ secrets.CI_USER_TOKEN }} - repository: 'optimizely/travisci-tools' - path: 'home/runner/travisci-tools' + repository: 'optimizely/ci-helper-tools' + path: 'home/runner/ci-helper-tools' ref: 'master' - name: set SDK Branch if PR env: @@ -28,14 +26,12 @@ jobs: if: ${{ github.event_name == 'pull_request' }} run: | echo "SDK_BRANCH=$HEAD_REF" >> $GITHUB_ENV - echo "TRAVIS_BRANCH=$HEAD_REF" >> $GITHUB_ENV - name: set SDK Branch if not pull request env: REF_NAME: ${{ github.ref_name }} if: ${{ github.event_name != 'pull_request' }} run: | echo "SDK_BRANCH=$REF_NAME" >> $GITHUB_ENV - echo "TRAVIS_BRANCH=$REF_NAME" >> $GITHUB_ENV - name: Trigger build env: SDK: java @@ -45,16 +41,13 @@ jobs: GITHUB_TOKEN: ${{ secrets.CI_USER_TOKEN }} EVENT_TYPE: ${{ github.event_name }} GITHUB_CONTEXT: ${{ toJson(github) }} - #REPO_SLUG: ${{ github.repository }} PULL_REQUEST_SLUG: ${{ github.repository }} UPSTREAM_REPO: ${{ github.repository }} PULL_REQUEST_SHA: ${{ github.event.pull_request.head.sha }} PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} UPSTREAM_SHA: ${{ github.sha }} - TOKEN: ${{ secrets.TRAVIS_COM_TOKEN }} - TRAVIS_COM_TOKEN: ${{ secrets.TRAVIS_COM_TOKEN }} EVENT_MESSAGE: ${{ github.event.message }} HOME: 'home/runner' run: | echo "$GITHUB_CONTEXT" - home/runner/travisci-tools/trigger-script-with-status-update.sh + home/runner/ci-helper-tools/trigger-script-with-status-update.sh diff --git a/.github/workflows/java.yml b/.github/workflows/java.yml index 1c6c57a02..95e8ccf8d 100644 --- a/.github/workflows/java.yml +++ b/.github/workflows/java.yml @@ -17,7 +17,7 @@ jobs: lint_markdown_files: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Ruby uses: ruby/setup-ruby@v1 with: @@ -31,10 +31,9 @@ jobs: integration_tests: if: ${{ startsWith(github.ref, 'refs/tags/') != true && github.event.inputs.SNAPSHOT != 'true' }} - uses: optimizely/java-sdk/.github/workflows/integration_test.yml@mnoman/fsc-gitaction-test + uses: optimizely/java-sdk/.github/workflows/integration_test.yml@master secrets: CI_USER_TOKEN: ${{ secrets.CI_USER_TOKEN }} - TRAVIS_COM_TOKEN: ${{ secrets.TRAVIS_COM_TOKEN }} fullstack_production_suite: if: ${{ startsWith(github.ref, 'refs/tags/') != true && github.event.inputs.SNAPSHOT != 'true' }} @@ -43,7 +42,6 @@ jobs: FULLSTACK_TEST_REPO: ProdTesting secrets: CI_USER_TOKEN: ${{ secrets.CI_USER_TOKEN }} - TRAVIS_COM_TOKEN: ${{ secrets.TRAVIS_COM_TOKEN }} test: if: ${{ startsWith(github.ref, 'refs/tags/') != true && github.event.inputs.SNAPSHOT != 'true' }} @@ -55,7 +53,7 @@ jobs: optimizely_default_parser: [GSON_CONFIG_PARSER, JACKSON_CONFIG_PARSER, JSON_CONFIG_PARSER, JSON_SIMPLE_CONFIG_PARSER] steps: - name: checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: set up JDK ${{ matrix.jdk }} uses: AdoptOpenJDK/install-jdk@v1 @@ -67,7 +65,7 @@ jobs: run: chmod +x gradlew - name: Gradle cache - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: | ~/.gradle/caches @@ -85,19 +83,19 @@ jobs: - name: Check on failures if: always() && steps.unit_tests.outcome != 'success' run: | - cat /home/runner/java-sdk/core-api/build/reports/findbugs/main.html - cat /home/runner/java-sdk/core-api/build/reports/findbugs/test.html + cat /Users/runner/work/java-sdk/core-api/build/reports/spotbugs/main.html + cat /Users/runner/work/java-sdk/core-api/build/reports/spotbugs/test.html - name: Check on success if: always() && steps.unit_tests.outcome == 'success' run: | - ./gradlew coveralls uploadArchives --console plain + ./gradlew coveralls --console plain publish: if: startsWith(github.ref, 'refs/tags/') uses: optimizely/java-sdk/.github/workflows/build.yml@master with: action: ship - travis_tag: ${GITHUB_REF#refs/*/} + github_tag: ${GITHUB_REF#refs/*/} secrets: MAVEN_SIGNING_KEY_BASE64: ${{ secrets.MAVEN_SIGNING_KEY_BASE64 }} MAVEN_SIGNING_PASSPHRASE: ${{ secrets.MAVEN_SIGNING_PASSPHRASE }} @@ -109,7 +107,7 @@ jobs: uses: optimizely/java-sdk/.github/workflows/build.yml@master with: action: ship - travis_tag: BB-SNAPSHOT + github_tag: BB-SNAPSHOT secrets: MAVEN_SIGNING_KEY_BASE64: ${{ secrets.MAVEN_SIGNING_KEY_BASE64 }} MAVEN_SIGNING_PASSPHRASE: ${{ secrets.MAVEN_SIGNING_PASSPHRASE }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 104422c93..565bfcd5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Optimizely Java X SDK Changelog +## [4.2.2] +May 28th, 2025 + +### Fixes +- Added experimentId and variationId to decision notification ([#569](https://github.com/optimizely/java-sdk/pull/569)). + +## [4.2.1] +Feb 19th, 2025 + +### Fixes +- Fix big integer conversion ([#556](https://github.com/optimizely/java-sdk/pull/556)). + ## [4.2.0] November 6th, 2024 diff --git a/README.md b/README.md index 33e55928d..1a7370c43 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,5 @@ # Optimizely Java SDK -[![Build Status](https://travis-ci.org/optimizely/java-sdk.svg?branch=master)](https://travis-ci.org/optimizely/java-sdk) [![Apache 2.0](https://img.shields.io/badge/license-APACHE%202.0-blue.svg)](http://www.apache.org/licenses/LICENSE-2.0) This repository houses the Java SDK for use with Optimizely Feature Experimentation and Optimizely Full Stack (legacy). @@ -71,7 +70,7 @@ You can run all unit tests with: ### Checking for bugs -We utilize [FindBugs](http://findbugs.sourceforge.net/) to identify possible bugs in the SDK. To run the check: +We utilize [SpotBugs](https://spotbugs.github.io/) to identify possible bugs in the SDK. To run the check: ``` @@ -176,3 +175,4 @@ License (Apache 2.0): [https://github.com/apache/httpcomponents-client/blob/mast - Ruby - https://github.com/optimizely/ruby-sdk - Swift - https://github.com/optimizely/swift-sdk + diff --git a/build.gradle b/build.gradle index b8405e39b..54426f6e7 100644 --- a/build.gradle +++ b/build.gradle @@ -1,10 +1,11 @@ plugins { - id 'com.github.kt3k.coveralls' version '2.8.2' + id 'com.github.kt3k.coveralls' version '2.12.2' id 'jacoco' - id 'me.champeau.gradle.jmh' version '0.4.5' + id 'me.champeau.gradle.jmh' version '0.5.3' id 'nebula.optional-base' version '3.2.0' - id 'com.github.hierynomus.license' version '0.15.0' - id 'com.github.spotbugs' version "4.5.0" + id 'com.github.hierynomus.license' version '0.16.1' + id 'com.github.spotbugs' version "6.0.14" + id 'maven-publish' } allprojects { @@ -23,7 +24,7 @@ allprojects { allprojects { group = 'com.optimizely.ab' - def travis_defined_version = System.getenv('TRAVIS_TAG') + def travis_defined_version = System.getenv('GITHUB_TAG') if (travis_defined_version != null) { version = travis_defined_version } @@ -72,6 +73,7 @@ configure(publishedProjects) { spotbugs { spotbugsJmh.enabled = false + reportLevel = com.github.spotbugs.snom.Confidence.valueOf('HIGH') } test { @@ -94,23 +96,30 @@ configure(publishedProjects) { } dependencies { - compile group: 'commons-codec', name: 'commons-codec', version: commonCodecVersion + implementation group: 'commons-codec', name: 'commons-codec', version: commonCodecVersion - testCompile group: 'junit', name: 'junit', version: junitVersion - testCompile group: 'org.mockito', name: 'mockito-core', version: mockitoVersion - testCompile group: 'org.hamcrest', name: 'hamcrest-all', version: hamcrestVersion - testCompile group: 'com.google.guava', name: 'guava', version: guavaVersion + testImplementation group: 'junit', name: 'junit', version: junitVersion + testImplementation group: 'org.mockito', name: 'mockito-core', version: mockitoVersion + testImplementation group: 'org.hamcrest', name: 'hamcrest-all', version: hamcrestVersion + testImplementation group: 'com.google.guava', name: 'guava', version: guavaVersion // logging dependencies (logback) - testCompile group: 'ch.qos.logback', name: 'logback-classic', version: logbackVersion - testCompile group: 'ch.qos.logback', name: 'logback-core', version: logbackVersion + testImplementation group: 'ch.qos.logback', name: 'logback-classic', version: logbackVersion + testImplementation group: 'ch.qos.logback', name: 'logback-core', version: logbackVersion - testCompile group: 'com.google.code.gson', name: 'gson', version: gsonVersion - testCompile group: 'org.json', name: 'json', version: jsonVersion - testCompile group: 'com.googlecode.json-simple', name: 'json-simple', version: jsonSimpleVersion - testCompile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: jacksonVersion + testImplementation group: 'com.google.code.gson', name: 'gson', version: gsonVersion + testImplementation group: 'org.json', name: 'json', version: jsonVersion + testImplementation group: 'com.googlecode.json-simple', name: 'json-simple', version: jsonSimpleVersion + testImplementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: jacksonVersion } + configurations.all { + resolutionStrategy { + force "junit:junit:${junitVersion}" + } + } + + def docTitle = "Optimizely Java SDK" if (name.equals('core-httpclient-impl')) { docTitle = "Optimizely Java SDK: Httpclient" diff --git a/core-api/build.gradle b/core-api/build.gradle index d2609a97d..602131cd3 100644 --- a/core-api/build.gradle +++ b/core-api/build.gradle @@ -1,9 +1,10 @@ dependencies { - compile group: 'org.slf4j', name: 'slf4j-api', version: slf4jVersion - compile group: 'com.fasterxml.jackson.core', name: 'jackson-annotations', version: jacksonVersion - - compile group: 'com.google.code.findbugs', name: 'annotations', version: findbugsAnnotationVersion - compile group: 'com.google.code.findbugs', name: 'jsr305', version: findbugsJsrVersion + implementation group: 'org.slf4j', name: 'slf4j-api', version: slf4jVersion + implementation group: 'com.fasterxml.jackson.core', name: 'jackson-annotations', version: jacksonVersion + implementation group: 'com.google.code.findbugs', name: 'annotations', version: findbugsAnnotationVersion + implementation group: 'com.google.code.findbugs', name: 'jsr305', version: findbugsJsrVersion + testImplementation group: 'junit', name: 'junit', version: junitVersion + testImplementation group: 'ch.qos.logback', name: 'logback-classic', version: logbackVersion // an assortment of json parsers compileOnly group: 'com.google.code.gson', name: 'gson', version: gsonVersion, optional @@ -12,6 +13,11 @@ dependencies { compileOnly group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: jacksonVersion, optional } +tasks.named('processJmhResources') { + duplicatesStrategy = DuplicatesStrategy.WARN +} + + test { useJUnit { excludeCategories 'com.optimizely.ab.categories.ExhaustiveTest' @@ -24,6 +30,7 @@ task exhaustiveTest(type: Test) { } } + task generateVersionFile { // add the build version information into a file that'll go into the distribution ext.buildVersion = new File(projectDir, "src/main/resources/optimizely-build-version") diff --git a/core-api/src/main/java/com/optimizely/ab/Optimizely.java b/core-api/src/main/java/com/optimizely/ab/Optimizely.java index 0e260072e..6eead11c6 100644 --- a/core-api/src/main/java/com/optimizely/ab/Optimizely.java +++ b/core-api/src/main/java/com/optimizely/ab/Optimizely.java @@ -20,44 +20,80 @@ import com.optimizely.ab.bucketing.DecisionService; import com.optimizely.ab.bucketing.FeatureDecision; import com.optimizely.ab.bucketing.UserProfileService; -import com.optimizely.ab.config.*; +import com.optimizely.ab.config.AtomicProjectConfigManager; +import com.optimizely.ab.config.DatafileProjectConfig; +import com.optimizely.ab.config.EventType; +import com.optimizely.ab.config.Experiment; +import com.optimizely.ab.config.FeatureFlag; +import com.optimizely.ab.config.FeatureVariable; +import com.optimizely.ab.config.FeatureVariableUsageInstance; +import com.optimizely.ab.config.ProjectConfig; +import com.optimizely.ab.config.ProjectConfigManager; +import com.optimizely.ab.config.Variation; import com.optimizely.ab.config.parser.ConfigParseException; import com.optimizely.ab.error.ErrorHandler; import com.optimizely.ab.error.NoOpErrorHandler; -import com.optimizely.ab.event.*; -import com.optimizely.ab.event.internal.*; +import com.optimizely.ab.event.EventHandler; +import com.optimizely.ab.event.EventProcessor; +import com.optimizely.ab.event.ForwardingEventProcessor; +import com.optimizely.ab.event.LogEvent; +import com.optimizely.ab.event.NoopEventHandler; +import com.optimizely.ab.event.internal.BuildVersionInfo; +import com.optimizely.ab.event.internal.ClientEngineInfo; +import com.optimizely.ab.event.internal.EventFactory; +import com.optimizely.ab.event.internal.UserEvent; +import com.optimizely.ab.event.internal.UserEventFactory; import com.optimizely.ab.event.internal.payload.EventBatch; import com.optimizely.ab.internal.NotificationRegistry; -import com.optimizely.ab.notification.*; -import com.optimizely.ab.odp.*; +import com.optimizely.ab.notification.ActivateNotification; +import com.optimizely.ab.notification.DecisionNotification; +import com.optimizely.ab.notification.FeatureTestSourceInfo; +import com.optimizely.ab.notification.NotificationCenter; +import com.optimizely.ab.notification.NotificationHandler; +import com.optimizely.ab.notification.RolloutSourceInfo; +import com.optimizely.ab.notification.SourceInfo; +import com.optimizely.ab.notification.TrackNotification; +import com.optimizely.ab.notification.UpdateConfigNotification; +import com.optimizely.ab.odp.ODPEvent; +import com.optimizely.ab.odp.ODPManager; +import com.optimizely.ab.odp.ODPSegmentManager; +import com.optimizely.ab.odp.ODPSegmentOption; import com.optimizely.ab.optimizelyconfig.OptimizelyConfig; import com.optimizely.ab.optimizelyconfig.OptimizelyConfigManager; import com.optimizely.ab.optimizelyconfig.OptimizelyConfigService; -import com.optimizely.ab.optimizelydecision.*; +import com.optimizely.ab.optimizelydecision.DecisionMessage; +import com.optimizely.ab.optimizelydecision.DecisionReasons; +import com.optimizely.ab.optimizelydecision.DecisionResponse; +import com.optimizely.ab.optimizelydecision.DefaultDecisionReasons; +import com.optimizely.ab.optimizelydecision.OptimizelyDecideOption; +import com.optimizely.ab.optimizelydecision.OptimizelyDecision; import com.optimizely.ab.optimizelyjson.OptimizelyJSON; -import java.util.concurrent.locks.ReentrantLock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.annotation.concurrent.ThreadSafe; - import java.io.Closeable; -import java.util.*; -import java.util.stream.Collectors; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.locks.ReentrantLock; import static com.optimizely.ab.internal.SafetyUtils.tryClose; /** * Top-level container class for Optimizely functionality. * Thread-safe, so can be created as a singleton and safely passed around. - * + *

* Example instantiation: *

  *     Optimizely optimizely = Optimizely.builder(projectWatcher, eventHandler).build();
  * 
- * + *

* To activate an experiment and perform variation specific processing: *

  *     Variation variation = optimizely.activate(experimentKey, userId, attributes);
@@ -136,7 +172,9 @@ private Optimizely(@Nonnull EventHandler eventHandler,
             if (projectConfigManager.getSDKKey() != null) {
                 NotificationRegistry.getInternalNotificationCenter(projectConfigManager.getSDKKey()).
                     addNotificationHandler(UpdateConfigNotification.class,
-                        configNotification -> { updateODPSettings(); });
+                        configNotification -> {
+                            updateODPSettings();
+                        });
             }
 
         }
@@ -634,6 +672,53 @@ public Integer getFeatureVariableInteger(@Nonnull String featureKey,
         return variableValue;
     }
 
+    /**
+     * Get the Long value of the specified variable in the feature.
+     *
+     * @param featureKey  The unique key of the feature.
+     * @param variableKey The unique key of the variable.
+     * @param userId      The ID of the user.
+     * @return The Integer value of the integer single variable feature.
+     * Null if the feature or variable could not be found.
+     */
+    @Nullable
+    public Long getFeatureVariableLong(@Nonnull String featureKey,
+                                       @Nonnull String variableKey,
+                                       @Nonnull String userId) {
+        return getFeatureVariableLong(featureKey, variableKey, userId, Collections.emptyMap());
+    }
+
+    /**
+     * Get the Integer value of the specified variable in the feature.
+     *
+     * @param featureKey  The unique key of the feature.
+     * @param variableKey The unique key of the variable.
+     * @param userId      The ID of the user.
+     * @param attributes  The user's attributes.
+     * @return The Integer value of the integer single variable feature.
+     * Null if the feature or variable could not be found.
+     */
+    @Nullable
+    public Long getFeatureVariableLong(@Nonnull String featureKey,
+                                       @Nonnull String variableKey,
+                                       @Nonnull String userId,
+                                       @Nonnull Map attributes) {
+        try {
+            return getFeatureVariableValueForType(
+                featureKey,
+                variableKey,
+                userId,
+                attributes,
+                FeatureVariable.INTEGER_TYPE
+            );
+
+        } catch (Exception exception) {
+            logger.error("NumberFormatException while trying to parse value as Long. {}", String.valueOf(exception));
+        }
+
+        return null;
+    }
+
     /**
      * Get the String value of the specified variable in the feature.
      *
@@ -828,8 +913,13 @@ Object convertStringToType(String variableValue, String type) {
                     try {
                         return Integer.parseInt(variableValue);
                     } catch (NumberFormatException exception) {
-                        logger.error("NumberFormatException while trying to parse \"" + variableValue +
-                            "\" as Integer. " + exception.toString());
+                        try {
+                            return Long.parseLong(variableValue);
+                        } catch (NumberFormatException longException) {
+                            logger.error("NumberFormatException while trying to parse \"{}\" as Integer. {}",
+                                variableValue,
+                                exception.toString());
+                        }
                     }
                     break;
                 case FeatureVariable.JSON_TYPE:
@@ -845,11 +935,10 @@ Object convertStringToType(String variableValue, String type) {
     /**
      * Get the values of all variables in the feature.
      *
-     * @param featureKey  The unique key of the feature.
-     * @param userId      The ID of the user.
+     * @param featureKey The unique key of the feature.
+     * @param userId     The ID of the user.
      * @return An OptimizelyJSON instance for all variable values.
      * Null if the feature could not be found.
-     *
      */
     @Nullable
     public OptimizelyJSON getAllFeatureVariables(@Nonnull String featureKey,
@@ -860,12 +949,11 @@ public OptimizelyJSON getAllFeatureVariables(@Nonnull String featureKey,
     /**
      * Get the values of all variables in the feature.
      *
-     * @param featureKey  The unique key of the feature.
-     * @param userId      The ID of the user.
-     * @param attributes  The user's attributes.
+     * @param featureKey The unique key of the feature.
+     * @param userId     The ID of the user.
+     * @param attributes The user's attributes.
      * @return An OptimizelyJSON instance for all variable values.
      * Null if the feature could not be found.
-     *
      */
     @Nullable
     public OptimizelyJSON getAllFeatureVariables(@Nonnull String featureKey,
@@ -949,7 +1037,6 @@ public OptimizelyJSON getAllFeatureVariables(@Nonnull String featureKey,
      * @param attributes The user's attributes.
      * @return List of the feature keys that are enabled for the user if the userId is empty it will
      * return Empty List.
-     *
      */
     public List getEnabledFeatures(@Nonnull String userId, @Nonnull Map attributes) {
         List enabledFeaturesList = new ArrayList();
@@ -1164,10 +1251,10 @@ public OptimizelyConfig getOptimizelyConfig() {
 
     /**
      * Create a context of the user for which decision APIs will be called.
-     *
+     * 

* A user context will be created successfully even when the SDK is not fully configured yet. * - * @param userId The user ID to be used for bucketing. + * @param userId The user ID to be used for bucketing. * @param attributes: A map of attribute names to current user attribute values. * @return An OptimizelyUserContext associated with this OptimizelyClient. */ @@ -1216,6 +1303,8 @@ private OptimizelyDecision createOptimizelyDecision( ProjectConfig projectConfig ) { String userId = user.getUserId(); + String experimentId = null; + String variationId = null; Boolean flagEnabled = false; if (flagDecision.variation != null) { @@ -1249,6 +1338,8 @@ private OptimizelyDecision createOptimizelyDecision( Boolean decisionEventDispatched = false; + experimentId = flagDecision.experiment != null ? flagDecision.experiment.getId() : null; + variationId = flagDecision.variation != null ? flagDecision.variation.getId() : null; Map attributes = user.getAttributes(); Map copiedAttributes = new HashMap<>(attributes); @@ -1275,6 +1366,8 @@ private OptimizelyDecision createOptimizelyDecision( .withRuleKey(ruleKey) .withReasons(reasonsToReport) .withDecisionEventDispatched(decisionEventDispatched) + .withExperimentId(experimentId) + .withVariationId(variationId) .build(); notificationCenter.send(decisionNotification); @@ -1289,15 +1382,15 @@ private OptimizelyDecision createOptimizelyDecision( } Map decideForKeys(@Nonnull OptimizelyUserContext user, - @Nonnull List keys, - @Nonnull List options) { + @Nonnull List keys, + @Nonnull List options) { return decideForKeys(user, keys, options, false); } private Map decideForKeys(@Nonnull OptimizelyUserContext user, - @Nonnull List keys, - @Nonnull List options, - boolean ignoreDefaultOptions) { + @Nonnull List keys, + @Nonnull List options, + boolean ignoreDefaultOptions) { Map decisionMap = new HashMap<>(); ProjectConfig projectConfig = getProjectConfig(); @@ -1308,7 +1401,7 @@ private Map decideForKeys(@Nonnull OptimizelyUserCon if (keys.isEmpty()) return decisionMap; - List allOptions = ignoreDefaultOptions ? options: getAllOptions(options); + List allOptions = ignoreDefaultOptions ? options : getAllOptions(options); Map flagDecisions = new HashMap<>(); Map decisionReasonsMap = new HashMap<>(); @@ -1351,7 +1444,7 @@ private Map decideForKeys(@Nonnull OptimizelyUserCon decisionReasonsMap.get(flagKey).merge(decision.getReasons()); } - for (String key: validKeys) { + for (String key : validKeys) { FeatureDecision flagDecision = flagDecisions.get(key); DecisionReasons decisionReasons = decisionReasonsMap.get((key)); @@ -1484,9 +1577,9 @@ public int addLogEventNotificationHandler(NotificationHandler handler) /** * Convenience method for adding NotificationHandlers * - * @param clazz The class of NotificationHandler + * @param clazz The class of NotificationHandler * @param handler NotificationHandler handler - * @param This is the type parameter + * @param This is the type parameter * @return A handler Id (greater than 0 if succeeded) */ public int addNotificationHandler(Class clazz, NotificationHandler handler) { @@ -1535,10 +1628,10 @@ public ODPManager getODPManager() { /** * Send an event to the ODP server. * - * @param type the event type (default = "fullstack"). - * @param action the event action name. + * @param type the event type (default = "fullstack"). + * @param action the event action name. * @param identifiers a dictionary for identifiers. The caller must provide at least one key-value pair unless non-empty common identifiers have been set already with {@link ODPManager.Builder#withUserCommonIdentifiers(Map) }. - * @param data a dictionary for associated data. The default event data will be added to this data before sending to the ODP server. + * @param data a dictionary for associated data. The default event data will be added to this data before sending to the ODP server. */ public void sendODPEvent(@Nullable String type, @Nonnull String action, @Nullable Map identifiers, @Nullable Map data) { ProjectConfig projectConfig = getProjectConfig(); @@ -1586,7 +1679,7 @@ private void updateODPSettings() { * {@link Builder#withDatafile(java.lang.String)} and * {@link Builder#withEventHandler(com.optimizely.ab.event.EventHandler)} * respectively. - * + *

* Example: *

      *     Optimizely optimizely = Optimizely.builder()
@@ -1595,7 +1688,7 @@ private void updateODPSettings() {
      *         .build();
      * 
* - * @param datafile A datafile + * @param datafile A datafile * @param eventHandler An EventHandler * @return An Optimizely builder */ @@ -1644,7 +1737,8 @@ public Builder(@Nonnull String datafile, this.datafile = datafile; } - public Builder() { } + public Builder() { + } public Builder withErrorHandler(ErrorHandler errorHandler) { this.errorHandler = errorHandler; @@ -1686,7 +1780,7 @@ public Builder withUserProfileService(UserProfileService userProfileService) { * Override the SDK name and version (for client SDKs like android-sdk wrapping the core java-sdk) to be included in events. * * @param clientEngineName the client engine name ("java-sdk", "android-sdk", "flutter-sdk", etc.). - * @param clientVersion the client SDK version. + * @param clientVersion the client SDK version. * @return An Optimizely builder */ public Builder withClientInfo(String clientEngineName, String clientVersion) { diff --git a/core-api/src/main/java/com/optimizely/ab/notification/DecisionNotification.java b/core-api/src/main/java/com/optimizely/ab/notification/DecisionNotification.java index d97e5bf40..ab3fdc03d 100644 --- a/core-api/src/main/java/com/optimizely/ab/notification/DecisionNotification.java +++ b/core-api/src/main/java/com/optimizely/ab/notification/DecisionNotification.java @@ -364,6 +364,8 @@ public static class FlagDecisionNotificationBuilder { public final static String RULE_KEY = "ruleKey"; public final static String REASONS = "reasons"; public final static String DECISION_EVENT_DISPATCHED = "decisionEventDispatched"; + public final static String EXPERIMENT_ID = "experimentId"; + public final static String VARIATION_ID = "variationId"; private String flagKey; private Boolean enabled; @@ -374,6 +376,8 @@ public static class FlagDecisionNotificationBuilder { private String ruleKey; private List reasons; private Boolean decisionEventDispatched; + private String experimentId; + private String variationId; private Map decisionInfo; @@ -422,6 +426,16 @@ public FlagDecisionNotificationBuilder withDecisionEventDispatched(Boolean dispa return this; } + public FlagDecisionNotificationBuilder withExperimentId(String experimentId) { + this.experimentId = experimentId; + return this; + } + + public FlagDecisionNotificationBuilder withVariationId(String variationId) { + this.variationId = variationId; + return this; + } + public DecisionNotification build() { if (flagKey == null) { throw new OptimizelyRuntimeException("flagKey not set"); @@ -439,6 +453,8 @@ public DecisionNotification build() { put(RULE_KEY, ruleKey); put(REASONS, reasons); put(DECISION_EVENT_DISPATCHED, decisionEventDispatched); + put(EXPERIMENT_ID, experimentId); + put(VARIATION_ID, variationId); }}; return new DecisionNotification( diff --git a/core-api/src/test/java/com/optimizely/ab/OptimizelyTest.java b/core-api/src/test/java/com/optimizely/ab/OptimizelyTest.java index 260de9945..b444dbc26 100644 --- a/core-api/src/test/java/com/optimizely/ab/OptimizelyTest.java +++ b/core-api/src/test/java/com/optimizely/ab/OptimizelyTest.java @@ -1101,7 +1101,7 @@ public void trackEventWithNullAttributeValues() throws Exception { * (i.e., not in the config) is passed through. *

* In this case, the track event call should not remove the unknown attribute from the given map but should go on and track the event successfully. - * + *

* TODO: Is this a dupe?? Also not sure the intent of the test since the attributes are stripped by the EventFactory */ @Test @@ -1569,8 +1569,7 @@ private NotificationHandler getDecisionListener( final String testType, final String testUserId, final Map testUserAttributes, - final Map testDecisionInfo) - { + final Map testDecisionInfo) { return decisionNotification -> { assertEquals(decisionNotification.getType(), testType); assertEquals(decisionNotification.getUserId(), testUserId); @@ -1609,10 +1608,10 @@ public void activateEndToEndWithDecisionListener() throws Exception { int notificationId = optimizely.notificationCenter.getNotificationManager(DecisionNotification.class) .addHandler( - getDecisionListener(NotificationCenter.DecisionNotificationType.FEATURE_TEST.toString(), - userId, - testUserAttributes, - testDecisionInfoMap)); + getDecisionListener(NotificationCenter.DecisionNotificationType.FEATURE_TEST.toString(), + userId, + testUserAttributes, + testDecisionInfoMap)); // activate the experiment Variation actualVariation = optimizely.activate(activatedExperiment.getKey(), userId, null); @@ -1752,7 +1751,8 @@ public void getEnabledFeaturesWithNoFeatureEnabled() throws Exception { any(OptimizelyUserContext.class), any(ProjectConfig.class) ); - int notificationId = optimizely.addDecisionNotificationHandler( decisionNotification -> { }); + int notificationId = optimizely.addDecisionNotificationHandler(decisionNotification -> { + }); List featureFlags = optimizely.getEnabledFeatures(genericUserId, Collections.emptyMap()); assertTrue(featureFlags.isEmpty()); @@ -2012,10 +2012,10 @@ public void getFeatureVariableWithListenerUserInExperimentFeatureOn() throws Exc testDecisionInfoMap)); assertEquals(optimizely.getFeatureVariableString( - validFeatureKey, - validVariableKey, - testUserId, - Collections.singletonMap(ATTRIBUTE_HOUSE_KEY, AUDIENCE_GRYFFINDOR_VALUE)), + validFeatureKey, + validVariableKey, + testUserId, + Collections.singletonMap(ATTRIBUTE_HOUSE_KEY, AUDIENCE_GRYFFINDOR_VALUE)), expectedValue); // Verify that listener being called @@ -2062,10 +2062,10 @@ public void getFeatureVariableWithListenerUserInExperimentFeatureOff() { testDecisionInfoMap)); assertEquals(optimizely.getFeatureVariableString( - validFeatureKey, - validVariableKey, - userID, - null), + validFeatureKey, + validVariableKey, + userID, + null), expectedValue); // Verify that listener being called @@ -2109,10 +2109,10 @@ public void getFeatureVariableWithListenerUserInRollOutFeatureOn() throws Except testDecisionInfoMap)); assertEquals(optimizely.getFeatureVariableString( - validFeatureKey, - validVariableKey, - genericUserId, - Collections.singletonMap(ATTRIBUTE_HOUSE_KEY, AUDIENCE_GRYFFINDOR_VALUE)), + validFeatureKey, + validVariableKey, + genericUserId, + Collections.singletonMap(ATTRIBUTE_HOUSE_KEY, AUDIENCE_GRYFFINDOR_VALUE)), expectedValue); // Verify that listener being called @@ -2156,10 +2156,10 @@ public void getFeatureVariableWithListenerUserNotInRollOutFeatureOff() { testDecisionInfoMap)); assertEquals(optimizely.getFeatureVariableBoolean( - validFeatureKey, - validVariableKey, - genericUserId, - Collections.singletonMap(ATTRIBUTE_HOUSE_KEY, AUDIENCE_GRYFFINDOR_VALUE)), + validFeatureKey, + validVariableKey, + genericUserId, + Collections.singletonMap(ATTRIBUTE_HOUSE_KEY, AUDIENCE_GRYFFINDOR_VALUE)), expectedValue); // Verify that listener being called @@ -2201,12 +2201,14 @@ public void getFeatureVariableIntegerWithListenerUserInRollOutFeatureOn() { testUserAttributes, testDecisionInfoMap)); - assertEquals((long) optimizely.getFeatureVariableInteger( - validFeatureKey, - validVariableKey, - genericUserId, - Collections.singletonMap(ATTRIBUTE_HOUSE_KEY, AUDIENCE_GRYFFINDOR_VALUE)), - (long) expectedValue); + assertEquals( + expectedValue, + (long) optimizely.getFeatureVariableInteger( + validFeatureKey, + validVariableKey, + genericUserId, + Collections.singletonMap(ATTRIBUTE_HOUSE_KEY, AUDIENCE_GRYFFINDOR_VALUE)) + ); // Verify that listener being called assertTrue(isListenerCalled); @@ -2251,10 +2253,10 @@ public void getFeatureVariableDoubleWithListenerUserInExperimentFeatureOn() thro testDecisionInfoMap)); assertEquals(optimizely.getFeatureVariableDouble( - validFeatureKey, - validVariableKey, - genericUserId, - Collections.singletonMap(ATTRIBUTE_HOUSE_KEY, AUDIENCE_SLYTHERIN_VALUE)), + validFeatureKey, + validVariableKey, + genericUserId, + Collections.singletonMap(ATTRIBUTE_HOUSE_KEY, AUDIENCE_SLYTHERIN_VALUE)), Math.PI, 2); // Verify that listener being called @@ -2453,7 +2455,7 @@ public void getAllFeatureVariablesWithListenerUserInExperimentFeatureOff() { assertTrue(isListenerCalled); assertTrue(optimizely.notificationCenter.removeNotificationListener(notificationId)); } - + /** * Verify that the {@link Optimizely#activate(String, String, Map)} call * correctly builds an endpoint url and request params @@ -2526,7 +2528,7 @@ public void activateWithListenerNullAttributes() throws Exception { * com.optimizely.ab.notification.NotificationListener)} properly used * and the listener is * added and notified when an experiment is activated. - * + *

* Feels redundant with the above tests */ @SuppressWarnings("unchecked") @@ -2572,7 +2574,7 @@ public void addNotificationListenerFromNotificationCenter() throws Exception { /** * Verify that {@link com.optimizely.ab.notification.NotificationCenter} properly * calls and the listener is removed and no longer notified when an experiment is activated. - * + *

* TODO move this to NotificationCenter. */ @SuppressWarnings("unchecked") @@ -2619,7 +2621,7 @@ public void removeNotificationListenerNotificationCenter() throws Exception { * Verify that {@link com.optimizely.ab.notification.NotificationCenter} * clearAllListerners removes all listeners * and no longer notified when an experiment is activated. - * + *

* TODO Should be part of NotificationCenter tests. */ @SuppressWarnings("unchecked") @@ -2741,7 +2743,7 @@ public void trackEventWithListenerNullAttributes() throws Exception { //======== Feature Accessor Tests ========// /** - * Verify {@link Optimizely#getFeatureVariableValueForType(String, String, String, Map, FeatureVariable.VariableType)} + * Verify {@link Optimizely#getFeatureVariableValueForType(String, String, String, Map, String)} * returns null and logs a message * when it is called with a feature key that has no corresponding feature in the datafile. */ @@ -2770,7 +2772,7 @@ public void getFeatureVariableValueForTypeReturnsNullWhenFeatureNotFound() throw } /** - * Verify {@link Optimizely#getFeatureVariableValueForType(String, String, String, Map, FeatureVariable.VariableType)} + * Verify {@link Optimizely#getFeatureVariableValueForType(String, String, String, Map, String)} * returns null and logs a message * when the feature key is valid, but no variable could be found for the variable key in the feature. */ @@ -2796,7 +2798,7 @@ public void getFeatureVariableValueForTypeReturnsNullWhenVariableNotFoundInValid } /** - * Verify {@link Optimizely#getFeatureVariableValueForType(String, String, String, Map, FeatureVariable.VariableType)} + * Verify {@link Optimizely#getFeatureVariableValueForType(String, String, String, Map, String)} * returns null when the variable's type does not match the type with which it was attempted to be accessed. */ @Test @@ -2825,7 +2827,7 @@ public void getFeatureVariableValueReturnsNullWhenVariableTypeDoesNotMatch() thr } /** - * Verify {@link Optimizely#getFeatureVariableValueForType(String, String, String, Map, FeatureVariable.VariableType)} + * Verify {@link Optimizely#getFeatureVariableValueForType(String, String, String, Map, String)} * returns the String default value of a feature variable * when the feature is not attached to an experiment or a rollout. */ @@ -2866,7 +2868,7 @@ public void getFeatureVariableValueForTypeReturnsDefaultValueWhenFeatureIsNotAtt } /** - * Verify {@link Optimizely#getFeatureVariableValueForType(String, String, String, Map, FeatureVariable.VariableType)} + * Verify {@link Optimizely#getFeatureVariableValueForType(String, String, String, Map, String)} * returns the String default value for a feature variable * when the feature is attached to an experiment and no rollout, but the user is excluded from the experiment. */ @@ -2910,7 +2912,7 @@ public void getFeatureVariableValueReturnsDefaultValueWhenFeatureIsAttachedToOne } /** - * Verify {@link Optimizely#getFeatureVariableValueForType(String, String, String, Map, FeatureVariable.VariableType)} + * Verify {@link Optimizely#getFeatureVariableValueForType(String, String, String, Map, String)} * is called when the variation is not null and feature enabled is false * returns the default variable value */ @@ -2964,10 +2966,10 @@ public void getFeatureVariableUserInExperimentFeatureOn() throws Exception { Optimizely optimizely = optimizelyBuilder.build(); assertEquals(optimizely.getFeatureVariableString( - validFeatureKey, - validVariableKey, - testUserId, - Collections.singletonMap(ATTRIBUTE_HOUSE_KEY, AUDIENCE_GRYFFINDOR_VALUE)), + validFeatureKey, + validVariableKey, + testUserId, + Collections.singletonMap(ATTRIBUTE_HOUSE_KEY, AUDIENCE_GRYFFINDOR_VALUE)), expectedValue); logbackVerifier.expectMessage( @@ -2994,10 +2996,10 @@ public void getFeatureVariableUserInExperimentFeatureOff() { Optimizely optimizely = optimizelyBuilder.build(); assertEquals(optimizely.getFeatureVariableString( - validFeatureKey, - validVariableKey, - userID, - null), + validFeatureKey, + validVariableKey, + userID, + null), expectedValue); } @@ -3017,10 +3019,10 @@ public void getFeatureVariableUserInRollOutFeatureOn() throws Exception { Optimizely optimizely = optimizelyBuilder.build(); assertEquals(optimizely.getFeatureVariableString( - validFeatureKey, - validVariableKey, - genericUserId, - Collections.singletonMap(ATTRIBUTE_HOUSE_KEY, AUDIENCE_GRYFFINDOR_VALUE)), + validFeatureKey, + validVariableKey, + genericUserId, + Collections.singletonMap(ATTRIBUTE_HOUSE_KEY, AUDIENCE_GRYFFINDOR_VALUE)), expectedValue); } @@ -3040,10 +3042,10 @@ public void getFeatureVariableUserNotInRollOutFeatureOff() { Optimizely optimizely = optimizelyBuilder.build(); assertEquals(optimizely.getFeatureVariableBoolean( - validFeatureKey, - validVariableKey, - genericUserId, - Collections.singletonMap(ATTRIBUTE_HOUSE_KEY, AUDIENCE_GRYFFINDOR_VALUE)), + validFeatureKey, + validVariableKey, + genericUserId, + Collections.singletonMap(ATTRIBUTE_HOUSE_KEY, AUDIENCE_GRYFFINDOR_VALUE)), expectedValue); } @@ -3062,12 +3064,39 @@ public void getFeatureVariableIntegerUserInRollOutFeatureOn() { Optimizely optimizely = optimizelyBuilder.build(); - assertEquals((long) optimizely.getFeatureVariableInteger( - validFeatureKey, - validVariableKey, - genericUserId, - Collections.singletonMap(ATTRIBUTE_HOUSE_KEY, AUDIENCE_GRYFFINDOR_VALUE)), - (long) expectedValue); + assertEquals( + expectedValue, + (int) optimizely.getFeatureVariableInteger( + validFeatureKey, + validVariableKey, + genericUserId, + Collections.singletonMap(ATTRIBUTE_HOUSE_KEY, AUDIENCE_GRYFFINDOR_VALUE)) + ); + } + + /** + * Verify that the {@link Optimizely#getFeatureVariableInteger(String, String, String, Map)} + * is called when feature is in rollout and feature enabled is true + * return rollout variable value + */ + @Test + public void getFeatureVariableLongUserInRollOutFeatureOn() { + assumeTrue(datafileVersion >= Integer.parseInt(ProjectConfig.Version.V4.toString())); + + final String validFeatureKey = FEATURE_SINGLE_VARIABLE_INTEGER_KEY; + String validVariableKey = VARIABLE_INTEGER_VARIABLE_KEY; + int expectedValue = 7; + + Optimizely optimizely = optimizelyBuilder.build(); + + assertEquals( + expectedValue, + (int) optimizely.getFeatureVariableInteger( + validFeatureKey, + validVariableKey, + genericUserId, + Collections.singletonMap(ATTRIBUTE_HOUSE_KEY, AUDIENCE_GRYFFINDOR_VALUE)) + ); } /** @@ -3085,15 +3114,15 @@ public void getFeatureVariableDoubleUserInExperimentFeatureOn() throws Exception Optimizely optimizely = optimizelyBuilder.build(); assertEquals(optimizely.getFeatureVariableDouble( - validFeatureKey, - validVariableKey, - genericUserId, - Collections.singletonMap(ATTRIBUTE_HOUSE_KEY, AUDIENCE_SLYTHERIN_VALUE)), + validFeatureKey, + validVariableKey, + genericUserId, + Collections.singletonMap(ATTRIBUTE_HOUSE_KEY, AUDIENCE_SLYTHERIN_VALUE)), Math.PI, 2); } /** - * Verify {@link Optimizely#getFeatureVariableValueForType(String, String, String, Map, FeatureVariable.VariableType)} + * Verify {@link Optimizely#getFeatureVariableValueForType(String, String, String, Map, String)} * returns the default value for the feature variable * when there is no variable usage present for the variation the user is bucketed into. */ @@ -4160,6 +4189,18 @@ public void convertStringToTypeIntegerCatchesExceptionFromParsing() throws Numbe ); } + /** + * Verify that {@link Optimizely#convertStringToType(String, String)} + * is able to parse Long. + */ + @Test + public void convertStringToTypeIntegerReturnsLongCorrectly() throws NumberFormatException { + String longValue = "8949425362117"; + + Optimizely optimizely = optimizelyBuilder.build(); + assertEquals(Long.valueOf(longValue), optimizely.convertStringToType(longValue, FeatureVariable.INTEGER_TYPE)); + } + /** * Verify {@link Optimizely#getFeatureVariableInteger(String, String, String)} * calls through to {@link Optimizely#getFeatureVariableInteger(String, String, String, Map)} @@ -4234,7 +4275,7 @@ public void getFeatureVariableIntegerReturnsNullWhenUserIdIsNull() throws Except * Verify {@link Optimizely#getFeatureVariableInteger(String, String, String)} * calls through to {@link Optimizely#getFeatureVariableInteger(String, String, String, Map)} * and both return the parsed Integer value from the value returned from - * {@link Optimizely#getFeatureVariableValueForType(String, String, String, Map, FeatureVariable.VariableType)}. + * {@link Optimizely#getFeatureVariableValueForType(String, String, String, Map, String)}. */ @Test public void getFeatureVariableIntegerReturnsWhatInternalReturns() throws Exception { @@ -4333,8 +4374,8 @@ public void getFeatureVariableJSONUserInExperimentFeatureOn() throws Exception { assertEquals(json.toMap().get("k1"), "s1"); assertEquals(json.toMap().get("k2"), 103.5); assertEquals(json.toMap().get("k3"), false); - assertEquals(((Map)json.toMap().get("k4")).get("kk1"), "ss1"); - assertEquals(((Map)json.toMap().get("k4")).get("kk2"), true); + assertEquals(((Map) json.toMap().get("k4")).get("kk1"), "ss1"); + assertEquals(((Map) json.toMap().get("k4")).get("kk2"), true); assertEquals(json.getValue("k1", String.class), "s1"); assertEquals(json.getValue("k4.kk2", Boolean.class), true); @@ -4368,15 +4409,15 @@ public void getFeatureVariableJSONUserInExperimentFeatureOff() throws Exception assertEquals(json.toMap().get("k1"), "v1"); assertEquals(json.toMap().get("k2"), 3.5); assertEquals(json.toMap().get("k3"), true); - assertEquals(((Map)json.toMap().get("k4")).get("kk1"), "vv1"); - assertEquals(((Map)json.toMap().get("k4")).get("kk2"), false); + assertEquals(((Map) json.toMap().get("k4")).get("kk1"), "vv1"); + assertEquals(((Map) json.toMap().get("k4")).get("kk2"), false); assertEquals(json.getValue("k1", String.class), "v1"); assertEquals(json.getValue("k4.kk2", Boolean.class), false); } /** - * Verify that the {@link Optimizely#getAllFeatureVariables(String,String, Map)} + * Verify that the {@link Optimizely#getAllFeatureVariables(String, String, Map)} * is called when feature is in experiment and feature enabled is true * returns variable value */ @@ -4398,12 +4439,12 @@ public void getAllFeatureVariablesUserInExperimentFeatureOn() throws Exception { assertEquals(json.toMap().get("first_letter"), "F"); assertEquals(json.toMap().get("rest_of_name"), "red"); - Map subMap = (Map)json.toMap().get("json_patched"); + Map subMap = (Map) json.toMap().get("json_patched"); assertEquals(subMap.get("k1"), "s1"); assertEquals(subMap.get("k2"), 103.5); assertEquals(subMap.get("k3"), false); - assertEquals(((Map)subMap.get("k4")).get("kk1"), "ss1"); - assertEquals(((Map)subMap.get("k4")).get("kk2"), true); + assertEquals(((Map) subMap.get("k4")).get("kk1"), "ss1"); + assertEquals(((Map) subMap.get("k4")).get("kk2"), true); assertEquals(json.getValue("first_letter", String.class), "F"); assertEquals(json.getValue("json_patched.k1", String.class), "s1"); @@ -4435,12 +4476,12 @@ public void getAllFeatureVariablesUserInExperimentFeatureOff() throws Exception assertEquals(json.toMap().get("first_letter"), "H"); assertEquals(json.toMap().get("rest_of_name"), "arry"); - Map subMap = (Map)json.toMap().get("json_patched"); + Map subMap = (Map) json.toMap().get("json_patched"); assertEquals(subMap.get("k1"), "v1"); assertEquals(subMap.get("k2"), 3.5); assertEquals(subMap.get("k3"), true); - assertEquals(((Map)subMap.get("k4")).get("kk1"), "vv1"); - assertEquals(((Map)subMap.get("k4")).get("kk2"), false); + assertEquals(((Map) subMap.get("k4")).get("kk1"), "vv1"); + assertEquals(((Map) subMap.get("k4")).get("kk2"), false); assertEquals(json.getValue("first_letter", String.class), "H"); assertEquals(json.getValue("json_patched.k1", String.class), "v1"); @@ -4448,7 +4489,7 @@ public void getAllFeatureVariablesUserInExperimentFeatureOff() throws Exception } /** - * Verify {@link Optimizely#getAllFeatureVariables(String,String, Map)} with invalid parameters + * Verify {@link Optimizely#getAllFeatureVariables(String, String, Map)} with invalid parameters */ @SuppressFBWarnings("NP_NONNULL_PARAM_VIOLATION") @Test @@ -4532,7 +4573,8 @@ public void testAddTrackNotificationHandler() { NotificationManager manager = optimizely.getNotificationCenter() .getNotificationManager(TrackNotification.class); - int notificationId = optimizely.addTrackNotificationHandler(message -> {}); + int notificationId = optimizely.addTrackNotificationHandler(message -> { + }); assertTrue(manager.remove(notificationId)); } @@ -4542,7 +4584,8 @@ public void testAddDecisionNotificationHandler() { NotificationManager manager = optimizely.getNotificationCenter() .getNotificationManager(DecisionNotification.class); - int notificationId = optimizely.addDecisionNotificationHandler(message -> {}); + int notificationId = optimizely.addDecisionNotificationHandler(message -> { + }); assertTrue(manager.remove(notificationId)); } @@ -4552,7 +4595,8 @@ public void testAddUpdateConfigNotificationHandler() { NotificationManager manager = optimizely.getNotificationCenter() .getNotificationManager(UpdateConfigNotification.class); - int notificationId = optimizely.addUpdateConfigNotificationHandler(message -> {}); + int notificationId = optimizely.addUpdateConfigNotificationHandler(message -> { + }); assertTrue(manager.remove(notificationId)); } @@ -4562,7 +4606,8 @@ public void testAddLogEventNotificationHandler() { NotificationManager manager = optimizely.getNotificationCenter() .getNotificationManager(LogEvent.class); - int notificationId = optimizely.addLogEventNotificationHandler(message -> {}); + int notificationId = optimizely.addLogEventNotificationHandler(message -> { + }); assertTrue(manager.remove(notificationId)); } diff --git a/core-api/src/test/java/com/optimizely/ab/OptimizelyUserContextTest.java b/core-api/src/test/java/com/optimizely/ab/OptimizelyUserContextTest.java index 34cf61543..bb2d36192 100644 --- a/core-api/src/test/java/com/optimizely/ab/OptimizelyUserContextTest.java +++ b/core-api/src/test/java/com/optimizely/ab/OptimizelyUserContextTest.java @@ -707,6 +707,8 @@ public void decisionNotification() { OptimizelyJSON variables = optimizely.getAllFeatureVariables(flagKey, userId); String ruleKey = "exp_no_audience"; List reasons = Collections.emptyList(); + String experimentId = "10420810910"; + String variationId = "10418551353"; final Map testDecisionInfoMap = new HashMap<>(); testDecisionInfoMap.put(FLAG_KEY, flagKey); @@ -715,6 +717,8 @@ public void decisionNotification() { testDecisionInfoMap.put(VARIABLES, variables.toMap()); testDecisionInfoMap.put(RULE_KEY, ruleKey); testDecisionInfoMap.put(REASONS, reasons); + testDecisionInfoMap.put(EXPERIMENT_ID, experimentId); + testDecisionInfoMap.put(VARIATION_ID, variationId); Map attributes = Collections.singletonMap("gender", "f"); OptimizelyUserContext user = optimizely.createUserContext(userId, attributes); diff --git a/core-api/src/test/java/com/optimizely/ab/config/ValidProjectConfigV4.java b/core-api/src/test/java/com/optimizely/ab/config/ValidProjectConfigV4.java index 0ed8d5945..faacfda76 100644 --- a/core-api/src/test/java/com/optimizely/ab/config/ValidProjectConfigV4.java +++ b/core-api/src/test/java/com/optimizely/ab/config/ValidProjectConfigV4.java @@ -266,6 +266,19 @@ public class ValidProjectConfigV4 { FeatureVariable.INTEGER_TYPE, null ); + private static final String FEATURE_SINGLE_VARIABLE_LONG_ID = "964006971"; + public static final String FEATURE_SINGLE_VARIABLE_LONG_KEY = "long_single_variable_feature"; + private static final String VARIABLE_LONG_VARIABLE_ID = "4339640697"; + public static final String VARIABLE_LONG_VARIABLE_KEY = "long_variable"; + private static final String VARIABLE_LONG_DEFAULT_VALUE = "379993881340"; + private static final FeatureVariable VARIABLE_LONG_VARIABLE = new FeatureVariable( + VARIABLE_LONG_VARIABLE_ID, + VARIABLE_LONG_VARIABLE_KEY, + VARIABLE_LONG_DEFAULT_VALUE, + null, + FeatureVariable.INTEGER_TYPE, + null + ); private static final String FEATURE_SINGLE_VARIABLE_BOOLEAN_ID = "2591051011"; public static final String FEATURE_SINGLE_VARIABLE_BOOLEAN_KEY = "boolean_single_variable_feature"; private static final String VARIABLE_BOOLEAN_VARIABLE_ID = "3974680341"; diff --git a/core-httpclient-impl/build.gradle b/core-httpclient-impl/build.gradle index e4cdd4b99..ab5644555 100644 --- a/core-httpclient-impl/build.gradle +++ b/core-httpclient-impl/build.gradle @@ -1,8 +1,10 @@ dependencies { - compile project(':core-api') - compileOnly group: 'com.google.code.gson', name: 'gson', version: gsonVersion - compile group: 'org.apache.httpcomponents', name: 'httpclient', version: httpClientVersion - testCompile 'org.mock-server:mockserver-netty:5.1.1' + implementation project(':core-api') + implementation group: 'org.apache.httpcomponents', name: 'httpclient', version: httpClientVersion + implementation group: 'org.slf4j', name: 'slf4j-api', version: slf4jVersion + implementation group: 'com.google.code.findbugs', name: 'annotations', version: findbugsAnnotationVersion + implementation group: 'com.google.code.findbugs', name: 'jsr305', version: findbugsJsrVersion + testImplementation 'org.mock-server:mockserver-netty:5.1.1' } task exhaustiveTest { diff --git a/core-httpclient-impl/src/main/java/com/optimizely/ab/config/HttpProjectConfigManager.java b/core-httpclient-impl/src/main/java/com/optimizely/ab/config/HttpProjectConfigManager.java index 095e32a67..2e99d3ae9 100644 --- a/core-httpclient-impl/src/main/java/com/optimizely/ab/config/HttpProjectConfigManager.java +++ b/core-httpclient-impl/src/main/java/com/optimizely/ab/config/HttpProjectConfigManager.java @@ -24,6 +24,7 @@ import java.util.concurrent.ThreadFactory; import java.util.concurrent.locks.ReentrantLock; import javax.annotation.Nullable; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.apache.http.*; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.methods.CloseableHttpResponse; @@ -309,6 +310,7 @@ public Builder withPollingInterval(Long period, TimeUnit timeUnit) { return this; } + @SuppressFBWarnings("EI_EXPOSE_REP2") public Builder withNotificationCenter(NotificationCenter notificationCenter) { this.notificationCenter = notificationCenter; return this; diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index c44b679ac..7454180f2 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a9a50f830..ffed3a254 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Mon Sep 24 09:56:45 PDT 2018 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip