Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 24 additions & 5 deletions src/main/java/dev/openfeature/sdk/HookSupport.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package dev.openfeature.sdk;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
Expand All @@ -16,19 +17,37 @@
/**
* Sets the {@link Hook}-{@link HookContext}-{@link Pair} list in the given data object with {@link HookContext}
* set to null. Filters hooks by supported {@link FlagValueType}.
* Sources are iterated in order: provider → options → client → API.
*
* @param hookSupportData the data object to modify
* @param hooks the hooks to set
* @param providerHooks provider-level hooks
* @param optionHooks per-evaluation option hooks
* @param clientHooks client-level hooks
* @param apiHooks API-level hooks
* @param type the flag value type to filter unsupported hooks
*/
public void setHooks(HookSupportData hookSupportData, List<Hook> hooks, FlagValueType type) {
public void setHooks(
HookSupportData hookSupportData,
Collection<Hook> providerHooks,

Check warning on line 31 in src/main/java/dev/openfeature/sdk/HookSupport.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Provide the parametrized type for this generic.

See more on https://sonarcloud.io/project/issues?id=open-feature_java-sdk&issues=AZ6W8-Ktp6McGkyPQEWV&open=AZ6W8-Ktp6McGkyPQEWV&pullRequest=1956
Collection<Hook> optionHooks,

Check warning on line 32 in src/main/java/dev/openfeature/sdk/HookSupport.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Provide the parametrized type for this generic.

See more on https://sonarcloud.io/project/issues?id=open-feature_java-sdk&issues=AZ6W8-Ktp6McGkyPQEWW&open=AZ6W8-Ktp6McGkyPQEWW&pullRequest=1956
Collection<Hook> clientHooks,

Check warning on line 33 in src/main/java/dev/openfeature/sdk/HookSupport.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Provide the parametrized type for this generic.

See more on https://sonarcloud.io/project/issues?id=open-feature_java-sdk&issues=AZ6W8-Ktp6McGkyPQEWX&open=AZ6W8-Ktp6McGkyPQEWX&pullRequest=1956
Collection<Hook> apiHooks,

Check warning on line 34 in src/main/java/dev/openfeature/sdk/HookSupport.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Provide the parametrized type for this generic.

See more on https://sonarcloud.io/project/issues?id=open-feature_java-sdk&issues=AZ6W8-Ktp6McGkyPQEWY&open=AZ6W8-Ktp6McGkyPQEWY&pullRequest=1956
FlagValueType type) {
List<Pair<Hook, HookContext>> hookContextPairs = new ArrayList<>();
for (Hook hook : hooks) {
addFilteredHooks(hookContextPairs, providerHooks, type);
addFilteredHooks(hookContextPairs, optionHooks, type);
addFilteredHooks(hookContextPairs, clientHooks, type);
addFilteredHooks(hookContextPairs, apiHooks, type);
hookSupportData.hooks = hookContextPairs;
}

private static void addFilteredHooks(
List<Pair<Hook, HookContext>> dest, Collection<Hook> source, FlagValueType type) {

Check warning on line 45 in src/main/java/dev/openfeature/sdk/HookSupport.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Provide the parametrized type for this generic.

See more on https://sonarcloud.io/project/issues?id=open-feature_java-sdk&issues=AZ6W8-Ktp6McGkyPQEWZ&open=AZ6W8-Ktp6McGkyPQEWZ&pullRequest=1956

Check warning on line 45 in src/main/java/dev/openfeature/sdk/HookSupport.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Provide the parametrized type for this generic.

See more on https://sonarcloud.io/project/issues?id=open-feature_java-sdk&issues=AZ6W8-Ktp6McGkyPQEWa&open=AZ6W8-Ktp6McGkyPQEWa&pullRequest=1956

Check warning on line 45 in src/main/java/dev/openfeature/sdk/HookSupport.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Provide the parametrized type for this generic.

See more on https://sonarcloud.io/project/issues?id=open-feature_java-sdk&issues=AZ6W8-Ktp6McGkyPQEWb&open=AZ6W8-Ktp6McGkyPQEWb&pullRequest=1956
for (Hook hook : source) {

Check warning on line 46 in src/main/java/dev/openfeature/sdk/HookSupport.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Provide the parametrized type for this generic.

See more on https://sonarcloud.io/project/issues?id=open-feature_java-sdk&issues=AZ6W8-Ktp6McGkyPQEWc&open=AZ6W8-Ktp6McGkyPQEWc&pullRequest=1956
if (hook.supportsFlagValueType(type)) {
hookContextPairs.add(Pair.of(hook, null));
dest.add(Pair.of(hook, null));
}
}
hookSupportData.hooks = hookContextPairs;
}
Comment on lines +29 to 51
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To prevent potential NullPointerExceptions and further optimize performance, we should:

  1. Add Null Checks: Ensure that we safely handle cases where any of the hook collections (such as providerHooks or optionHooks) or individual hooks within them are null.
  2. Pre-size the ArrayList: Since we are aiming to optimize performance and eliminate unnecessary allocations, pre-sizing the ArrayList with the sum of the sizes of the non-null collections avoids internal array resizing overhead during population.
    public void setHooks(
            HookSupportData hookSupportData,
            Collection<Hook> providerHooks,
            Collection<Hook> optionHooks,
            Collection<Hook> clientHooks,
            Collection<Hook> apiHooks,
            FlagValueType type) {
        int expectedSize = 0;
        if (providerHooks != null) {
            expectedSize += providerHooks.size();
        }
        if (optionHooks != null) {
            expectedSize += optionHooks.size();
        }
        if (clientHooks != null) {
            expectedSize += clientHooks.size();
        }
        if (apiHooks != null) {
            expectedSize += apiHooks.size();
        }

        List<Pair<Hook, HookContext>> hookContextPairs = new ArrayList<>(expectedSize);
        addFilteredHooks(hookContextPairs, providerHooks, type);
        addFilteredHooks(hookContextPairs, optionHooks, type);
        addFilteredHooks(hookContextPairs, clientHooks, type);
        addFilteredHooks(hookContextPairs, apiHooks, type);
        hookSupportData.hooks = hookContextPairs;
    }

    private static void addFilteredHooks(
            List<Pair<Hook, HookContext>> dest, Collection<Hook> source, FlagValueType type) {
        if (source == null) {
            return;
        }
        for (Hook hook : source) {
            if (hook != null && hook.supportsFlagValueType(type)) {
                dest.add(Pair.of(hook, null));
            }
        }
    }


/**
Expand Down
11 changes: 7 additions & 4 deletions src/main/java/dev/openfeature/sdk/OpenFeatureClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import dev.openfeature.sdk.exceptions.GeneralError;
import dev.openfeature.sdk.exceptions.OpenFeatureError;
import dev.openfeature.sdk.exceptions.ProviderNotReadyError;
import dev.openfeature.sdk.internal.ObjectUtils;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.ArrayList;
import java.util.Arrays;
Expand Down Expand Up @@ -185,9 +184,13 @@ private <T> FlagEvaluationDetails<T> evaluateFlag(
final var state = stateManager.getState();

// Hooks are initialized as early as possible to enable the execution of error stages
var mergedHooks = ObjectUtils.merge(
provider.getProviderHooks(), flagOptions.getHooks(), clientHooks, openfeatureApi.getMutableHooks());
hookSupport.setHooks(hookSupportData, mergedHooks, type);
hookSupport.setHooks(
hookSupportData,
provider.getProviderHooks(),
flagOptions.getHooks(),
clientHooks,
openfeatureApi.getMutableHooks(),
type);

var sharedHookContext =
new SharedHookContext(key, type, this.getMetadata(), provider.getMetadata(), defaultValue);
Expand Down
41 changes: 36 additions & 5 deletions src/test/java/dev/openfeature/sdk/HookSupportTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import dev.openfeature.sdk.fixtures.HookFixtures;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -38,7 +39,13 @@ void shouldMergeEvaluationContextsOnBeforeHooksCorrectly() {
var sharedContext = getBaseHookContextForType(FlagValueType.STRING);
var hookSupportData = new HookSupportData();
hookSupportData.evaluationContext = layered;
hookSupport.setHooks(hookSupportData, Arrays.asList(hook1, hook2), FlagValueType.STRING);
hookSupport.setHooks(
hookSupportData,
Collections.emptyList(),
Collections.emptyList(),
Arrays.asList(hook1, hook2),
Collections.emptyList(),
FlagValueType.STRING);
hookSupport.setHookContexts(hookSupportData, sharedContext, layered);

hookSupport.executeBeforeHooks(hookSupportData);
Expand All @@ -57,7 +64,13 @@ void shouldAlwaysCallGenericHook(FlagValueType flagValueType) {
Hook<?> genericHook = mockGenericHook();

var hookSupportData = new HookSupportData();
hookSupport.setHooks(hookSupportData, List.of(genericHook), flagValueType);
hookSupport.setHooks(
hookSupportData,
Collections.emptyList(),
Collections.emptyList(),
List.of(genericHook),
Collections.emptyList(),
flagValueType);

callAllHooks(hookSupportData);

Expand All @@ -73,7 +86,13 @@ void shouldAlwaysCallGenericHook(FlagValueType flagValueType) {
void shouldPassDataAcrossStages(FlagValueType flagValueType) {
var testHook = new TestHookWithData();
var hookSupportData = new HookSupportData();
hookSupport.setHooks(hookSupportData, List.of(testHook), flagValueType);
hookSupport.setHooks(
hookSupportData,
Collections.emptyList(),
Collections.emptyList(),
List.of(testHook),
Collections.emptyList(),
flagValueType);
hookSupport.setHookContexts(
hookSupportData,
getBaseHookContextForType(flagValueType),
Expand Down Expand Up @@ -102,7 +121,13 @@ void shouldIsolateDataBetweenHooks(FlagValueType flagValueType) {
var testHook2 = new TestHookWithData(2);

var hookSupportData = new HookSupportData();
hookSupport.setHooks(hookSupportData, List.of(testHook1, testHook2), flagValueType);
hookSupport.setHooks(
hookSupportData,
Collections.emptyList(),
Collections.emptyList(),
List.of(testHook1, testHook2),
Collections.emptyList(),
flagValueType);
hookSupport.setHookContexts(
hookSupportData,
getBaseHookContextForType(flagValueType),
Expand Down Expand Up @@ -132,7 +157,13 @@ public Optional<EvaluationContext> before(HookContext ctx, Map hints) {
var layeredEvaluationContext =
new LayeredEvaluationContext(evaluationContextWithValue("key", "value"), null, null, null);
hookSupportData.evaluationContext = layeredEvaluationContext;
hookSupport.setHooks(hookSupportData, List.of(recursiveHook, emptyHook), FlagValueType.STRING);
hookSupport.setHooks(
hookSupportData,
Collections.emptyList(),
Collections.emptyList(),
List.of(recursiveHook, emptyHook),
Collections.emptyList(),
FlagValueType.STRING);
hookSupport.setHookContexts(
hookSupportData, getBaseHookContextForType(FlagValueType.STRING), layeredEvaluationContext);

Expand Down
Loading