Skip to content

Commit 97f6dc9

Browse files
committed
MultiProvider: Added MultiProviderMetadata.java, refactored tests and removed json dep
1 parent f07dd25 commit 97f6dc9

File tree

7 files changed

+478
-189
lines changed

7 files changed

+478
-189
lines changed

pom.xml

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -70,12 +70,6 @@
7070
<version>2.0.17</version>
7171
</dependency>
7272

73-
<dependency>
74-
<groupId>org.json</groupId>
75-
<artifactId>json</artifactId>
76-
<version>20250517</version>
77-
</dependency>
78-
7973
<!-- test -->
8074
<dependency>
8175
<groupId>com.tngtech.archunit</groupId>

src/main/java/dev/openfeature/sdk/multiprovider/MultiProvider.java

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@
77
import dev.openfeature.sdk.ProviderEvaluation;
88
import dev.openfeature.sdk.Value;
99
import dev.openfeature.sdk.exceptions.GeneralError;
10+
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
1011
import java.util.ArrayList;
1112
import java.util.Collection;
1213
import java.util.Collections;
14+
import java.util.HashMap;
1315
import java.util.LinkedHashMap;
1416
import java.util.List;
1517
import java.util.Map;
@@ -19,7 +21,6 @@
1921
import java.util.concurrent.Future;
2022
import lombok.Getter;
2123
import lombok.extern.slf4j.Slf4j;
22-
import org.json.JSONObject;
2324

2425
/** <b>Experimental:</b> Provider implementation for Multi-provider. */
2526
@Slf4j
@@ -31,10 +32,10 @@ public class MultiProvider extends EventProvider {
3132
public static final int INIT_THREADS_COUNT = 8;
3233
private final Map<String, FeatureProvider> providers;
3334
private final Strategy strategy;
34-
private String metadataName;
35+
private MultiProviderMetadata metadata;
3536

3637
/**
37-
* Constructs a MultiProvider with the given list of FeatureProviders, using a default strategy.
38+
* Constructs a MultiProvider with the given list of FeatureProviders, by default uses FirstMatchStrategy.
3839
*
3940
* @param providers the list of FeatureProviders to initialize the MultiProvider with
4041
*/
@@ -77,33 +78,34 @@ protected static Map<String, FeatureProvider> buildProviders(List<FeatureProvide
7778
*/
7879
@Override
7980
public void initialize(EvaluationContext evaluationContext) throws Exception {
80-
JSONObject json = new JSONObject();
81-
json.put("name", NAME);
82-
JSONObject providersMetadata = new JSONObject();
83-
json.put("originalMetadata", providersMetadata);
84-
ExecutorService initPool = Executors.newFixedThreadPool(INIT_THREADS_COUNT);
81+
var metadataBuilder = MultiProviderMetadata.builder();
82+
metadataBuilder.name(NAME);
83+
HashMap<String, Metadata> providersMetadata = new HashMap<>();
84+
ExecutorService initPool = Executors.newFixedThreadPool(Math.min(INIT_THREADS_COUNT, providers.size()));
8585
Collection<Callable<Boolean>> tasks = new ArrayList<>(providers.size());
8686
for (FeatureProvider provider : providers.values()) {
8787
tasks.add(() -> {
8888
provider.initialize(evaluationContext);
8989
return true;
9090
});
91-
JSONObject providerMetadata = new JSONObject();
92-
providerMetadata.put("name", provider.getMetadata().getName());
93-
providersMetadata.put(provider.getMetadata().getName(), providerMetadata);
91+
Metadata providerMetadata = provider.getMetadata();
92+
providersMetadata.put(providerMetadata.getName(), providerMetadata);
9493
}
94+
metadataBuilder.originalMetadata(providersMetadata);
9595
List<Future<Boolean>> results = initPool.invokeAll(tasks);
9696
for (Future<Boolean> result : results) {
9797
if (!result.get()) {
9898
throw new GeneralError("init failed");
9999
}
100100
}
101-
metadataName = json.toString();
101+
initPool.shutdown();
102+
metadata = metadataBuilder.build();
102103
}
103104

105+
@SuppressFBWarnings(value = "EI_EXPOSE_REP")
104106
@Override
105107
public Metadata getMetadata() {
106-
return () -> metadataName;
108+
return metadata;
107109
}
108110

109111
@Override
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package dev.openfeature.sdk.multiprovider;
2+
3+
import dev.openfeature.sdk.Metadata;
4+
import java.util.Map;
5+
import lombok.Builder;
6+
import lombok.Data;
7+
8+
/**
9+
* Metadata class for Multiprovider.
10+
*/
11+
@Data
12+
@Builder
13+
public class MultiProviderMetadata implements Metadata {
14+
String name;
15+
Map<String, Metadata> originalMetadata;
16+
17+
@Override
18+
public String getName() {
19+
return name;
20+
}
21+
}
Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
package dev.openfeature.sdk.multiProvider;
2+
3+
import static dev.openfeature.sdk.ErrorCode.FLAG_NOT_FOUND;
4+
import static org.mockito.Mockito.mock;
5+
import static org.mockito.Mockito.when;
6+
7+
import dev.openfeature.sdk.ErrorCode;
8+
import dev.openfeature.sdk.EvaluationContext;
9+
import dev.openfeature.sdk.FeatureProvider;
10+
import dev.openfeature.sdk.Metadata;
11+
import dev.openfeature.sdk.MutableContext;
12+
import dev.openfeature.sdk.ProviderEvaluation;
13+
import dev.openfeature.sdk.Value;
14+
import dev.openfeature.sdk.providers.memory.Flag;
15+
import dev.openfeature.sdk.providers.memory.InMemoryProvider;
16+
import java.util.HashMap;
17+
import java.util.LinkedHashMap;
18+
import java.util.Map;
19+
import org.junit.jupiter.api.BeforeEach;
20+
21+
public abstract class BaseStrategyTest {
22+
23+
protected FeatureProvider mockProvider1;
24+
protected FeatureProvider mockProvider2;
25+
protected FeatureProvider mockProvider3;
26+
27+
protected Metadata mockMetaData1;
28+
protected Metadata mockMetaData2;
29+
protected Metadata mockMetaData3;
30+
31+
protected InMemoryProvider inMemoryProvider1;
32+
protected InMemoryProvider inMemoryProvider2;
33+
34+
protected Map<String, FeatureProvider> orderedProviders;
35+
36+
protected EvaluationContext contextWithNewProvider;
37+
38+
protected static final String FLAG_KEY = "test-flag";
39+
protected static final String DEFAULT_STRING = "default";
40+
protected static final boolean DEFAULT_BOOLEAN = false;
41+
protected static final int DEFAULT_INTEGER = 0;
42+
protected static final double DEFAULT_DOUBLE = 0.0;
43+
44+
@BeforeEach
45+
void setUp() {
46+
setupMockProviders();
47+
setupInMemoryProviders();
48+
setupOrderedProviders();
49+
setupEvaluationContexts();
50+
}
51+
52+
protected void setupMockProviders() {
53+
mockProvider1 = mock(FeatureProvider.class);
54+
mockProvider2 = mock(FeatureProvider.class);
55+
mockProvider3 = mock(FeatureProvider.class);
56+
mockMetaData1 = mock(Metadata.class);
57+
mockMetaData2 = mock(Metadata.class);
58+
mockMetaData3 = mock(Metadata.class);
59+
when(mockMetaData1.getName()).thenReturn("provider1");
60+
when(mockMetaData2.getName()).thenReturn("provider2");
61+
when(mockMetaData3.getName()).thenReturn("provider3");
62+
when(mockProvider1.getMetadata()).thenReturn(mockMetaData1);
63+
when(mockProvider2.getMetadata()).thenReturn(mockMetaData2);
64+
when(mockProvider3.getMetadata()).thenReturn(mockMetaData3);
65+
}
66+
67+
protected void setupInMemoryProviders() {
68+
Map<String, Flag<?>> flags1 = createFlags1();
69+
Map<String, Flag<?>> flags2 = createFlags2();
70+
71+
inMemoryProvider1 = new InMemoryProvider(flags1) {
72+
public Metadata getMetadata() {
73+
return () -> "old-provider";
74+
}
75+
};
76+
77+
inMemoryProvider2 = new InMemoryProvider(flags2) {
78+
public Metadata getMetadata() {
79+
return () -> "new-provider";
80+
}
81+
};
82+
}
83+
84+
protected void setupOrderedProviders() {
85+
orderedProviders = new LinkedHashMap<>();
86+
orderedProviders.put("provider1", mockProvider1);
87+
orderedProviders.put("provider2", mockProvider2);
88+
orderedProviders.put("provider3", mockProvider3);
89+
}
90+
91+
protected void setupEvaluationContexts() {
92+
contextWithNewProvider = new MutableContext().add("provider", "new-provider");
93+
}
94+
95+
protected Map<String, Flag<?>> createFlags1() {
96+
Map<String, Flag<?>> flags = new HashMap<>();
97+
98+
flags.put(
99+
"b1",
100+
Flag.builder()
101+
.variant("on", true)
102+
.variant("off", false)
103+
.defaultVariant("on")
104+
.build());
105+
106+
flags.put(
107+
"i1",
108+
Flag.builder().variant("default", 1).defaultVariant("default").build());
109+
110+
flags.put(
111+
"d1",
112+
Flag.builder().variant("default", 1.0).defaultVariant("default").build());
113+
114+
flags.put(
115+
"s1",
116+
Flag.builder()
117+
.variant("default", "str1")
118+
.defaultVariant("default")
119+
.build());
120+
121+
flags.put(
122+
"o1",
123+
Flag.builder()
124+
.variant("default", new Value("v1"))
125+
.defaultVariant("default")
126+
.build());
127+
128+
return flags;
129+
}
130+
131+
protected Map<String, Flag<?>> createFlags2() {
132+
Map<String, Flag<?>> flags = new HashMap<>();
133+
134+
flags.put(
135+
"b1",
136+
Flag.builder()
137+
.variant("on", true)
138+
.variant("off", false)
139+
.defaultVariant("off")
140+
.build());
141+
142+
flags.put(
143+
"i1",
144+
Flag.builder().variant("default", 2).defaultVariant("default").build());
145+
146+
flags.put(
147+
"d1",
148+
Flag.builder().variant("default", 2.0).defaultVariant("default").build());
149+
150+
flags.put(
151+
"s1",
152+
Flag.builder()
153+
.variant("default", "str2")
154+
.defaultVariant("default")
155+
.build());
156+
157+
flags.put(
158+
"o1",
159+
Flag.builder()
160+
.variant("default", new Value("v2"))
161+
.defaultVariant("default")
162+
.build());
163+
164+
flags.put(
165+
"s2",
166+
Flag.builder()
167+
.variant("default", "s2str2")
168+
.defaultVariant("default")
169+
.build());
170+
171+
return flags;
172+
}
173+
174+
protected <T> ProviderEvaluation<T> createErrorResult(ErrorCode errorCode) {
175+
ProviderEvaluation<T> result = mock(ProviderEvaluation.class);
176+
when(result.getErrorCode()).thenReturn(errorCode);
177+
return result;
178+
}
179+
180+
protected void setupProviderFlagNotFound(FeatureProvider provider) {
181+
ProviderEvaluation<String> stringResult = createErrorResult(FLAG_NOT_FOUND);
182+
ProviderEvaluation<Boolean> booleanResult = createErrorResult(FLAG_NOT_FOUND);
183+
ProviderEvaluation<Integer> integerResult = createErrorResult(FLAG_NOT_FOUND);
184+
ProviderEvaluation<Double> doubleResult = createErrorResult(FLAG_NOT_FOUND);
185+
ProviderEvaluation<Value> objectResult = createErrorResult(FLAG_NOT_FOUND);
186+
187+
when(provider.getStringEvaluation(BaseStrategyTest.FLAG_KEY, DEFAULT_STRING, null))
188+
.thenReturn(stringResult);
189+
when(provider.getBooleanEvaluation(BaseStrategyTest.FLAG_KEY, DEFAULT_BOOLEAN, null))
190+
.thenReturn(booleanResult);
191+
when(provider.getIntegerEvaluation(BaseStrategyTest.FLAG_KEY, DEFAULT_INTEGER, null))
192+
.thenReturn(integerResult);
193+
when(provider.getDoubleEvaluation(BaseStrategyTest.FLAG_KEY, DEFAULT_DOUBLE, null))
194+
.thenReturn(doubleResult);
195+
when(provider.getObjectEvaluation(BaseStrategyTest.FLAG_KEY, null, null))
196+
.thenReturn(objectResult);
197+
}
198+
199+
protected void setupProviderError(FeatureProvider provider, ErrorCode errorCode) {
200+
ProviderEvaluation<String> result = createErrorResult(errorCode);
201+
when(provider.getStringEvaluation(BaseStrategyTest.FLAG_KEY, DEFAULT_STRING, null))
202+
.thenReturn(result);
203+
}
204+
205+
protected void setupProviderSuccess(FeatureProvider provider, String value) {
206+
ProviderEvaluation<String> result = mock(ProviderEvaluation.class);
207+
when(result.getErrorCode()).thenReturn(null);
208+
when(result.getValue()).thenReturn(value);
209+
when(provider.getStringEvaluation(BaseStrategyTest.FLAG_KEY, DEFAULT_STRING, null))
210+
.thenReturn(result);
211+
}
212+
}

0 commit comments

Comments
 (0)