diff --git a/.github/component_owners.yml b/.github/component_owners.yml
index c5641fde7..61d24eb6b 100644
--- a/.github/component_owners.yml
+++ b/.github/component_owners.yml
@@ -35,6 +35,9 @@ components:
- novalisdenahi
providers/statsig:
- liran2000
+ providers/prefab:
+ - liran2000
+ - jkebinger
providers/multiprovider:
- liran2000
tools/flagd-http-connector:
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index 27073e080..e5da39301 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -10,6 +10,7 @@
"providers/configcat": "0.1.0",
"providers/statsig": "0.1.0",
"providers/multiprovider": "0.0.1",
+ "providers/prefab": "0.0.1",
"tools/junit-openfeature": "0.1.2",
"tools/flagd-http-connector": "0.0.2",
".": "0.2.2"
diff --git a/pom.xml b/pom.xml
index 91b920c12..e63e88424 100644
--- a/pom.xml
+++ b/pom.xml
@@ -39,6 +39,7 @@
providers/flipt
providers/configcat
providers/statsig
+ providers/prefab
providers/multiprovider
tools/flagd-http-connector
diff --git a/providers/prefab/CHANGELOG.md b/providers/prefab/CHANGELOG.md
new file mode 100644
index 000000000..825c32f0d
--- /dev/null
+++ b/providers/prefab/CHANGELOG.md
@@ -0,0 +1 @@
+# Changelog
diff --git a/providers/prefab/README.md b/providers/prefab/README.md
new file mode 100644
index 000000000..654f80a85
--- /dev/null
+++ b/providers/prefab/README.md
@@ -0,0 +1,59 @@
+# Unofficial Prefab OpenFeature Provider for Java
+
+[Prefab](https://www.prefab.cloud/) OpenFeature Provider can provide usage for Prefab via OpenFeature Java SDK.
+
+## Installation
+
+
+
+```xml
+
+
+ dev.openfeature.contrib.providers
+ prefab
+ 0.0.1
+
+```
+
+
+
+## Usage
+Prefab OpenFeature Provider is using Prefab Java SDK.
+
+### Usage Example
+
+```
+PrefabProviderConfig prefabProviderConfig = PrefabProviderConfig.builder().sdkKey(sdkKey).build();
+prefabProvider = new PrefabProvider(prefabProviderConfig);
+OpenFeatureAPI.getInstance().setProviderAndWait(prefabProvider);
+
+
+Options options = new Options().setApikey(sdkKey);
+PrefabProviderConfig prefabProviderConfig = PrefabProviderConfig.builder()
+ .options(options).build();
+PrefabProvider prefabProvider = new PrefabProvider(prefabProviderConfig);
+OpenFeatureAPI.getInstance().setProviderAndWait(prefabProvider);
+
+boolean featureEnabled = client.getBooleanValue(FLAG_NAME, false);
+
+MutableContext evaluationContext = new MutableContext();
+evaluationContext.add("user.key", "key1");
+evaluationContext.add("team.domain", "prefab.cloud");
+featureEnabled = client.getBooleanValue(USERS_FLAG_NAME, false, evaluationContext);
+```
+
+See [PrefabProviderTest](./src/test/java/dev/openfeature/contrib/providers/prefab/PrefabProviderTest.java)
+for more information.
+
+## Notes
+Some Prefab custom operations are supported from the provider client via:
+
+```java
+prefabProvider.getPrefabCloudClient()...
+```
+
+## Prefab Provider Tests Strategies
+
+Unit test based on Prefab local features file.
+See [PrefabProviderTest](./src/test/java/dev/openfeature/contrib/providers/prefab/PrefabProviderTest.java)
+for more information.
diff --git a/providers/prefab/lombok.config b/providers/prefab/lombok.config
new file mode 100644
index 000000000..bcd1afdae
--- /dev/null
+++ b/providers/prefab/lombok.config
@@ -0,0 +1,5 @@
+# This file is needed to avoid errors throw by findbugs when working with lombok.
+lombok.addSuppressWarnings = true
+lombok.addLombokGeneratedAnnotation = true
+config.stopBubbling = true
+lombok.extern.findbugs.addSuppressFBWarnings = true
diff --git a/providers/prefab/pom.xml b/providers/prefab/pom.xml
new file mode 100644
index 000000000..ebb7bd279
--- /dev/null
+++ b/providers/prefab/pom.xml
@@ -0,0 +1,40 @@
+
+
+ 4.0.0
+
+ dev.openfeature.contrib
+ parent
+ 0.1.0
+ ../../pom.xml
+
+ dev.openfeature.contrib.providers
+ prefab
+ 0.0.1
+
+ prefab
+ Prefab provider for Java
+ https://www.prefab.cloud
+
+
+
+ cloud.prefab
+ client
+ 0.3.20
+
+
+
+ org.slf4j
+ slf4j-api
+ 2.0.16
+
+
+
+ org.apache.logging.log4j
+ log4j-slf4j2-impl
+ 2.23.1
+ test
+
+
+
+
diff --git a/providers/prefab/src/main/java/dev/openfeature/contrib/providers/prefab/ContextTransformer.java b/providers/prefab/src/main/java/dev/openfeature/contrib/providers/prefab/ContextTransformer.java
new file mode 100644
index 000000000..ebc7dafa7
--- /dev/null
+++ b/providers/prefab/src/main/java/dev/openfeature/contrib/providers/prefab/ContextTransformer.java
@@ -0,0 +1,37 @@
+package dev.openfeature.contrib.providers.prefab;
+
+import cloud.prefab.context.PrefabContext;
+import cloud.prefab.context.PrefabContextSet;
+import cloud.prefab.context.PrefabContextSetReadable;
+import dev.openfeature.sdk.EvaluationContext;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Transformer from OpenFeature context to Prefab context.
+ */
+public class ContextTransformer {
+
+ private ContextTransformer() {}
+
+ protected static PrefabContextSetReadable transform(EvaluationContext ctx) {
+ Map contextsMap = new HashMap<>();
+ ctx.asObjectMap().forEach((k, v) -> {
+ String[] parts = k.split("\\.", 2);
+ if (parts.length < 2) {
+ throw new IllegalArgumentException("context key structure should be in the form of x.y: " + k);
+ }
+ contextsMap.putIfAbsent(parts[0], PrefabContext.newBuilder(parts[0]));
+ PrefabContext.Builder contextBuilder = contextsMap.get(parts[0]);
+ contextBuilder.put(parts[1], Objects.toString(v, null));
+ });
+ PrefabContextSet prefabContextSet = new PrefabContextSet();
+ contextsMap.forEach((key, value) -> {
+ PrefabContext prefabContext = value.build();
+ prefabContextSet.addContext(prefabContext);
+ });
+
+ return prefabContextSet;
+ }
+}
diff --git a/providers/prefab/src/main/java/dev/openfeature/contrib/providers/prefab/PrefabProvider.java b/providers/prefab/src/main/java/dev/openfeature/contrib/providers/prefab/PrefabProvider.java
new file mode 100644
index 000000000..d795c1294
--- /dev/null
+++ b/providers/prefab/src/main/java/dev/openfeature/contrib/providers/prefab/PrefabProvider.java
@@ -0,0 +1,138 @@
+package dev.openfeature.contrib.providers.prefab;
+
+import cloud.prefab.client.PrefabCloudClient;
+import cloud.prefab.context.PrefabContextSetReadable;
+import cloud.prefab.domain.Prefab;
+import dev.openfeature.sdk.EvaluationContext;
+import dev.openfeature.sdk.EventProvider;
+import dev.openfeature.sdk.Metadata;
+import dev.openfeature.sdk.ProviderEvaluation;
+import dev.openfeature.sdk.ProviderEventDetails;
+import dev.openfeature.sdk.Value;
+import dev.openfeature.sdk.exceptions.GeneralError;
+import java.util.Collections;
+import java.util.Optional;
+import java.util.concurrent.atomic.AtomicBoolean;
+import lombok.Getter;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * Provider implementation for Prefab.
+ */
+@Slf4j
+public class PrefabProvider extends EventProvider {
+
+ @Getter
+ private static final String NAME = "Prefab";
+
+ public static final String PROVIDER_NOT_YET_INITIALIZED = "provider not yet initialized";
+ public static final String UNKNOWN_ERROR = "unknown error";
+
+ private final PrefabProviderConfig prefabProviderConfig;
+
+ @Getter
+ private PrefabCloudClient prefabCloudClient;
+
+ private final AtomicBoolean isInitialized = new AtomicBoolean(false);
+
+ /**
+ * Constructor.
+ *
+ * @param prefabProviderConfig prefabProvider Config
+ */
+ public PrefabProvider(PrefabProviderConfig prefabProviderConfig) {
+ this.prefabProviderConfig = prefabProviderConfig;
+ }
+
+ /**
+ * Initialize the provider.
+ *
+ * @param evaluationContext evaluation context
+ * @throws Exception on error
+ */
+ @Override
+ public void initialize(EvaluationContext evaluationContext) throws Exception {
+ boolean initialized = isInitialized.getAndSet(true);
+ if (initialized) {
+ throw new GeneralError("already initialized");
+ }
+ super.initialize(evaluationContext);
+ prefabCloudClient = new PrefabCloudClient(prefabProviderConfig.getOptions());
+ log.info("finished initializing provider");
+
+ prefabProviderConfig.getOptions().addConfigChangeListener(changeEvent -> {
+ ProviderEventDetails providerEventDetails = ProviderEventDetails.builder()
+ .flagsChanged(Collections.singletonList(changeEvent.getKey()))
+ .message("config changed")
+ .build();
+ emitProviderConfigurationChanged(providerEventDetails);
+ });
+ }
+
+ @Override
+ public Metadata getMetadata() {
+ return () -> NAME;
+ }
+
+ @Override
+ public ProviderEvaluation getBooleanEvaluation(String key, Boolean defaultValue, EvaluationContext ctx) {
+ PrefabContextSetReadable context = ctx == null ? null : ContextTransformer.transform(ctx);
+ Boolean evaluatedValue = prefabCloudClient.featureFlagClient().featureIsOn(key, context);
+ return ProviderEvaluation.builder().value(evaluatedValue).build();
+ }
+
+ @Override
+ public ProviderEvaluation getStringEvaluation(String key, String defaultValue, EvaluationContext ctx) {
+ PrefabContextSetReadable context = ctx == null ? null : ContextTransformer.transform(ctx);
+ String evaluatedValue = defaultValue;
+ Optional opt = prefabCloudClient.featureFlagClient().get(key, context);
+ if (opt.isPresent()
+ && Prefab.ConfigValue.TypeCase.STRING.equals(opt.get().getTypeCase())) {
+ evaluatedValue = opt.get().getString();
+ }
+ return ProviderEvaluation.builder().value(evaluatedValue).build();
+ }
+
+ @Override
+ public ProviderEvaluation getIntegerEvaluation(String key, Integer defaultValue, EvaluationContext ctx) {
+ PrefabContextSetReadable context = ctx == null ? null : ContextTransformer.transform(ctx);
+ Integer evaluatedValue = defaultValue;
+ Optional opt = prefabCloudClient.featureFlagClient().get(key, context);
+ if (opt.isPresent() && Prefab.ConfigValue.TypeCase.INT.equals(opt.get().getTypeCase())) {
+ evaluatedValue = Math.toIntExact(opt.get().getInt());
+ }
+ return ProviderEvaluation.builder().value(evaluatedValue).build();
+ }
+
+ @Override
+ public ProviderEvaluation getDoubleEvaluation(String key, Double defaultValue, EvaluationContext ctx) {
+ PrefabContextSetReadable context = ctx == null ? null : ContextTransformer.transform(ctx);
+ Double evaluatedValue = defaultValue;
+ Optional opt = prefabCloudClient.featureFlagClient().get(key, context);
+ if (opt.isPresent()
+ && Prefab.ConfigValue.TypeCase.DOUBLE.equals(opt.get().getTypeCase())) {
+ evaluatedValue = opt.get().getDouble();
+ }
+ return ProviderEvaluation.builder().value(evaluatedValue).build();
+ }
+
+ @SneakyThrows
+ @Override
+ public ProviderEvaluation getObjectEvaluation(String key, Value defaultValue, EvaluationContext ctx) {
+ String defaultValueString = defaultValue == null ? null : defaultValue.asString();
+ ProviderEvaluation stringEvaluation = getStringEvaluation(key, defaultValueString, ctx);
+ Value evaluatedValue = new Value(stringEvaluation.getValue());
+ return ProviderEvaluation.builder().value(evaluatedValue).build();
+ }
+
+ @SneakyThrows
+ @Override
+ public void shutdown() {
+ super.shutdown();
+ log.info("shutdown");
+ if (prefabCloudClient != null) {
+ prefabCloudClient.close();
+ }
+ }
+}
diff --git a/providers/prefab/src/main/java/dev/openfeature/contrib/providers/prefab/PrefabProviderConfig.java b/providers/prefab/src/main/java/dev/openfeature/contrib/providers/prefab/PrefabProviderConfig.java
new file mode 100644
index 000000000..6d3c9a977
--- /dev/null
+++ b/providers/prefab/src/main/java/dev/openfeature/contrib/providers/prefab/PrefabProviderConfig.java
@@ -0,0 +1,14 @@
+package dev.openfeature.contrib.providers.prefab;
+
+import cloud.prefab.client.Options;
+import lombok.Builder;
+import lombok.Getter;
+
+/**
+ * Options for initializing prefab provider.
+ */
+@Getter
+@Builder
+public class PrefabProviderConfig {
+ private Options options;
+}
diff --git a/providers/prefab/src/test/java/dev/openfeature/contrib/providers/prefab/PrefabProviderTest.java b/providers/prefab/src/test/java/dev/openfeature/contrib/providers/prefab/PrefabProviderTest.java
new file mode 100644
index 000000000..f9ae9351c
--- /dev/null
+++ b/providers/prefab/src/test/java/dev/openfeature/contrib/providers/prefab/PrefabProviderTest.java
@@ -0,0 +1,202 @@
+package dev.openfeature.contrib.providers.prefab;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import cloud.prefab.client.Options;
+import cloud.prefab.context.PrefabContext;
+import cloud.prefab.context.PrefabContextSet;
+import cloud.prefab.context.PrefabContextSetReadable;
+import dev.openfeature.sdk.Client;
+import dev.openfeature.sdk.ImmutableContext;
+import dev.openfeature.sdk.MutableContext;
+import dev.openfeature.sdk.OpenFeatureAPI;
+import dev.openfeature.sdk.ProviderEventDetails;
+import dev.openfeature.sdk.Value;
+import dev.openfeature.sdk.exceptions.GeneralError;
+import java.io.File;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+/**
+ * PrefabProvider test, based local default config file.
+ */
+@Slf4j
+class PrefabProviderTest {
+
+ public static final String FLAG_NAME = "sample_bool";
+ public static final String VARIANT_FLAG_NAME = "sample";
+ public static final String VARIANT_FLAG_VALUE = "test sample value";
+ public static final String INT_FLAG_NAME = "sample_int";
+ public static final Integer INT_FLAG_VALUE = 123;
+ public static final String DOUBLE_FLAG_NAME = "sample_double";
+ public static final Double DOUBLE_FLAG_VALUE = 12.12;
+ public static final String USERS_FLAG_NAME = "test1";
+ private static PrefabProvider prefabProvider;
+ private static Client client;
+
+ @BeforeAll
+ static void setUp() {
+ File localDataFile = new File("src/test/resources/features.json");
+ Options options = new Options()
+ .setPrefabDatasource(Options.Datasources.ALL)
+ .setLocalDatafile(localDataFile.toString())
+ .setInitializationTimeoutSec(10);
+ PrefabProviderConfig prefabProviderConfig =
+ PrefabProviderConfig.builder().options(options).build();
+ prefabProvider = new PrefabProvider(prefabProviderConfig);
+ OpenFeatureAPI.getInstance().setProviderAndWait(prefabProvider);
+ client = OpenFeatureAPI.getInstance().getClient();
+ }
+
+ @AfterAll
+ static void shutdown() {
+ prefabProvider.shutdown();
+ }
+
+ @Test
+ void getBooleanEvaluation() {
+ assertEquals(
+ true,
+ prefabProvider
+ .getBooleanEvaluation(FLAG_NAME, false, new ImmutableContext())
+ .getValue());
+ assertEquals(true, client.getBooleanValue(FLAG_NAME, false));
+ assertEquals(
+ false,
+ prefabProvider
+ .getBooleanEvaluation("non-existing", false, new ImmutableContext())
+ .getValue());
+ assertEquals(false, client.getBooleanValue("non-existing", false));
+ }
+
+ @Test
+ void getStringEvaluation() {
+ assertEquals(
+ VARIANT_FLAG_VALUE,
+ prefabProvider
+ .getStringEvaluation(VARIANT_FLAG_NAME, "", new ImmutableContext())
+ .getValue());
+ assertEquals(VARIANT_FLAG_VALUE, client.getStringValue(VARIANT_FLAG_NAME, ""));
+ assertEquals(
+ "fallback_str",
+ prefabProvider
+ .getStringEvaluation("non-existing", "fallback_str", new ImmutableContext())
+ .getValue());
+ assertEquals("fallback_str", client.getStringValue("non-existing", "fallback_str"));
+ }
+
+ @Test
+ void getObjectEvaluation() {
+ assertEquals(
+ VARIANT_FLAG_VALUE,
+ prefabProvider
+ .getStringEvaluation(VARIANT_FLAG_NAME, "", new ImmutableContext())
+ .getValue());
+ assertEquals(new Value(VARIANT_FLAG_VALUE), client.getObjectValue(VARIANT_FLAG_NAME, new Value("")));
+ assertEquals(
+ new Value("fallback_str"),
+ prefabProvider
+ .getObjectEvaluation("non-existing", new Value("fallback_str"), new ImmutableContext())
+ .getValue());
+ assertEquals(new Value("fallback_str"), client.getObjectValue("non-existing", new Value("fallback_str")));
+ }
+
+ @Test
+ void getIntegerEvaluation() {
+ MutableContext evaluationContext = new MutableContext();
+ assertEquals(
+ INT_FLAG_VALUE,
+ prefabProvider
+ .getIntegerEvaluation(INT_FLAG_NAME, 1, evaluationContext)
+ .getValue());
+ assertEquals(INT_FLAG_VALUE, client.getIntegerValue(INT_FLAG_NAME, 1));
+ assertEquals(1, client.getIntegerValue("non-existing", 1));
+
+ // non-number flag value
+ assertEquals(1, client.getIntegerValue(VARIANT_FLAG_NAME, 1));
+ }
+
+ @Test
+ void getDoubleEvaluation() {
+ MutableContext evaluationContext = new MutableContext();
+ assertEquals(
+ DOUBLE_FLAG_VALUE,
+ prefabProvider
+ .getDoubleEvaluation(DOUBLE_FLAG_NAME, 1.1, evaluationContext)
+ .getValue());
+ assertEquals(DOUBLE_FLAG_VALUE, client.getDoubleValue(DOUBLE_FLAG_NAME, 1.1));
+ assertEquals(1.1, client.getDoubleValue("non-existing", 1.1));
+
+ // non-number flag value
+ assertEquals(1.1, client.getDoubleValue(VARIANT_FLAG_NAME, 1.1));
+ }
+
+ @Test
+ void getBooleanEvaluationByUser() {
+ MutableContext evaluationContext = new MutableContext();
+ evaluationContext.add("user.key", "key1");
+ evaluationContext.add("team.domain", "prefab.cloud");
+
+ assertEquals(
+ true,
+ prefabProvider
+ .getBooleanEvaluation(USERS_FLAG_NAME, false, evaluationContext)
+ .getValue());
+ assertEquals(true, client.getBooleanValue(USERS_FLAG_NAME, false, evaluationContext));
+ evaluationContext.add("team.domain", "other.com");
+ assertEquals(
+ false,
+ prefabProvider
+ .getBooleanEvaluation(USERS_FLAG_NAME, false, evaluationContext)
+ .getValue());
+ assertEquals(false, client.getBooleanValue(USERS_FLAG_NAME, false, evaluationContext));
+ }
+
+ @SneakyThrows
+ @Test
+ void shouldThrowIfNotInitialized() {
+ Options options = new Options()
+ .setApikey("test-sdk-key")
+ .setPrefabDatasource(Options.Datasources.LOCAL_ONLY)
+ .setInitializationTimeoutSec(10);
+ PrefabProviderConfig prefabProviderConfig =
+ PrefabProviderConfig.builder().options(options).build();
+ PrefabProvider tempPrefabProvider = new PrefabProvider(prefabProviderConfig);
+
+ OpenFeatureAPI.getInstance().setProviderAndWait("tempPrefabProvider", tempPrefabProvider);
+
+ assertThrows(GeneralError.class, () -> tempPrefabProvider.initialize(null));
+
+ tempPrefabProvider.shutdown();
+ }
+
+ @Test
+ void eventsTest() {
+ prefabProvider.emitProviderReady(ProviderEventDetails.builder().build());
+ prefabProvider.emitProviderError(ProviderEventDetails.builder().build());
+ assertDoesNotThrow(() -> prefabProvider.emitProviderConfigurationChanged(
+ ProviderEventDetails.builder().build()));
+ }
+
+ @SneakyThrows
+ @Test
+ void contextTransformTest() {
+
+ MutableContext evaluationContext = new MutableContext();
+ evaluationContext.add("user.key", "key1");
+ evaluationContext.add("team.domain", "prefab.cloud");
+
+ PrefabContextSet expectedContext = PrefabContextSet.from(
+ PrefabContext.newBuilder("user").put("key", "key1").build(),
+ PrefabContext.newBuilder("team").put("domain", "prefab.cloud").build());
+ PrefabContextSetReadable transformedContext = ContextTransformer.transform(evaluationContext);
+
+ // equals not implemented for User, using toString
+ assertEquals(expectedContext.toString(), transformedContext.toString());
+ }
+}
diff --git a/providers/prefab/src/test/resources/features.json b/providers/prefab/src/test/resources/features.json
new file mode 100644
index 000000000..5e6e29fcb
--- /dev/null
+++ b/providers/prefab/src/test/resources/features.json
@@ -0,0 +1,183 @@
+{
+ "configs": [
+ {
+ "id": "17235540036203003",
+ "projectId": "453",
+ "key": "sample_int",
+ "changedBy": {
+ "userId": "878",
+ "email": "liran2000@gmail.com"
+ },
+ "rows": [
+ {
+ "projectEnvId": "962",
+ "values": [
+ {
+ "value": {
+ "int": "123"
+ }
+ }
+ ]
+ }
+ ],
+ "allowableValues": [
+ {
+ "int": "123"
+ }
+ ],
+ "configType": "FEATURE_FLAG",
+ "valueType": "INT"
+ },
+ {
+ "id": "17235541126207669",
+ "projectId": "453",
+ "key": "sample_double",
+ "changedBy": {
+ "userId": "878",
+ "email": "liran2000@gmail.com"
+ },
+ "rows": [
+ {
+ "projectEnvId": "962",
+ "values": [
+ {
+ "value": {
+ "double": 12.12
+ }
+ }
+ ]
+ }
+ ],
+ "allowableValues": [
+ {
+ "double": 12.12
+ }
+ ],
+ "configType": "FEATURE_FLAG",
+ "valueType": "DOUBLE"
+ },
+ {
+ "id": "17235541571344121",
+ "projectId": "453",
+ "key": "sample_bool",
+ "changedBy": {
+ "userId": "878",
+ "email": "liran2000@gmail.com"
+ },
+ "rows": [
+ {
+ "projectEnvId": "962",
+ "values": [
+ {
+ "value": {
+ "bool": true
+ }
+ }
+ ]
+ }
+ ],
+ "allowableValues": [
+ {
+ "bool": false
+ },
+ {
+ "bool": true
+ }
+ ],
+ "configType": "FEATURE_FLAG",
+ "valueType": "BOOL"
+ },
+ {
+ "id": "17235603983939168",
+ "projectId": "453",
+ "key": "test1",
+ "changedBy": {
+ "userId": "878",
+ "email": "liran2000@gmail.com"
+ },
+ "rows": [
+ {
+ "projectEnvId": "962",
+ "values": [
+ {
+ "criteria": [
+ {
+ "propertyName": "user.key",
+ "operator": "PROP_IS_ONE_OF",
+ "valueToMatch": {
+ "stringList": {
+ "values": [
+ "key1"
+ ]
+ }
+ }
+ },
+ {
+ "propertyName": "team.domain",
+ "operator": "PROP_IS_ONE_OF",
+ "valueToMatch": {
+ "stringList": {
+ "values": [
+ "prefab.cloud"
+ ]
+ }
+ }
+ }
+ ],
+ "value": {
+ "bool": true
+ }
+ },
+ {
+ "value": {
+ "bool": false
+ }
+ }
+ ]
+ }
+ ],
+ "allowableValues": [
+ {
+ "bool": false
+ },
+ {
+ "bool": true
+ }
+ ],
+ "configType": "FEATURE_FLAG",
+ "valueType": "BOOL"
+ },
+ {
+ "id": "17235608162176898",
+ "projectId": "453",
+ "key": "sample",
+ "changedBy": {
+ "userId": "878",
+ "email": "liran2000@gmail.com"
+ },
+ "rows": [
+ {
+ "projectEnvId": "962",
+ "values": [
+ {
+ "value": {
+ "string": "test sample value"
+ }
+ }
+ ]
+ }
+ ],
+ "allowableValues": [
+ {
+ "string": "test sample value"
+ }
+ ],
+ "configType": "FEATURE_FLAG",
+ "valueType": "STRING"
+ }
+ ],
+ "configServicePointer": {
+ "projectId": "453",
+ "projectEnvId": "962"
+ }
+}
\ No newline at end of file
diff --git a/providers/prefab/src/test/resources/log4j2-test.xml b/providers/prefab/src/test/resources/log4j2-test.xml
new file mode 100644
index 000000000..aced30f8a
--- /dev/null
+++ b/providers/prefab/src/test/resources/log4j2-test.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/providers/prefab/version.txt b/providers/prefab/version.txt
new file mode 100644
index 000000000..8acdd82b7
--- /dev/null
+++ b/providers/prefab/version.txt
@@ -0,0 +1 @@
+0.0.1