Skip to content
Merged
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
21 changes: 19 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,14 @@ JAVA Server Side SDK is based on Java SE 8 and is available on Maven Central. Yo
<dependency>
<groupId>co.featbit</groupId>
<artifactId>featbit-java-sdk</artifactId>
<version>1.2.0</version>
<version>1.3.0</version>
</dependency>
</dependencies>
```

- Install the SDK using Gradle
```
implementation 'co.featbit:featbit-java-sdk:1.2.0'
implementation 'co.featbit:featbit-java-sdk:1.3.0'
```

### Prerequisite
Expand Down Expand Up @@ -286,6 +286,23 @@ String value = states.getString("flag key", user, "Not Found");
> If evaluation called before Java SDK client initialized, you set the wrong flag key/user for the evaluation or the related feature flag
is not found SDK will return the default value you set. `EvalDetail` will explain the details of the latest evaluation including error raison.

### Flag Tracking
You can register registers a listener to be notified of feature flag changes in general.

Note that a flag value change listener is bound to a specific user and flag key.

The flag value change listener will be notified whenever the SDK receives any change to any feature flag's configuration, or to a user segment that is referenced by a feature flag.
To register a flag value change listener, use 'FlagTracker#addFlagValueChangeListener' method.

The flag value change listener just call the `onFlagValueChange` method **_only if_** the flag value changes.

```java
client.getFlagTracker().addFlagValueChangeListener(flagKey, user, event -> {
// do something
});
```


### Offline Mode
In some situations, you might want to stop making remote calls to FeatBit. Here is how:

Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>co.featbit</groupId>
<artifactId>featbit-java-sdk</artifactId>
<version>1.2.0</version>
<version>1.3.0</version>

<name>featbit/featbit-java-sdk</name>

Expand Down
15 changes: 15 additions & 0 deletions src/main/java/co/featbit/server/DataModel.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@
import com.google.common.collect.ImmutableMap;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.JsonAdapter;
import com.google.gson.reflect.TypeToken;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;

import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public abstract class DataModel {

Expand Down Expand Up @@ -346,6 +349,18 @@ public String getVariationType() {
return variationType;
}

Boolean containsSegment(String segmentId) {
return getRules().stream()
.flatMap(rule -> rule.getConditions().stream())
.filter(cond -> StringUtils.isBlank(cond.getOp()))
.flatMap(cond -> {
List<String> segments = JsonHelper
.deserialize(cond.getValue(), new TypeToken<List<String>>() {
}.getType());
return segments.stream();
}).collect(Collectors.toList()).contains(segmentId);
}

@Override
public void afterDeserialization() {
this.timestamp = updatedAt.getTime();
Expand Down
11 changes: 11 additions & 0 deletions src/main/java/co/featbit/server/EventBroadcaster.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package co.featbit.server;

public interface EventBroadcaster<Listener, Event> {
void addListener(Listener listener);

void removeListener(Listener listener);

boolean hasListeners();

void broadcast(Event event);
}
67 changes: 67 additions & 0 deletions src/main/java/co/featbit/server/EventBroadcasterImpl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package co.featbit.server;

import org.slf4j.Logger;

import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.function.BiConsumer;

class EventBroadcasterImpl<Listener, Event> implements EventBroadcaster<Listener, Event> {
private final CopyOnWriteArrayList<Listener> listeners = new CopyOnWriteArrayList<>();
private final BiConsumer<Listener, Event> broadcaster;
private final ExecutorService executor;
private final Logger logger;

EventBroadcasterImpl(
BiConsumer<Listener, Event> broadcaster,
ExecutorService executor,
Logger logger
) {
this.broadcaster = broadcaster;
this.executor = executor;
this.logger = logger;
}

static EventBroadcasterImpl<FlagChange.FlagChangeListener, FlagChange.FlagChangeEvent> forFlagChangeEvents(
ExecutorService executor, Logger logger) {
return new EventBroadcasterImpl<>(FlagChange.FlagChangeListener::onFlagChange, executor, logger);
}

static EventBroadcasterImpl<Status.StateListener, Status.State> forDataUpdateStates(
ExecutorService executor, Logger logger) {
return new EventBroadcasterImpl<>(Status.StateListener::onStateChange, executor, logger);
}

@Override
public void addListener(Listener listener) {
listeners.add(listener);
}

@Override
public void removeListener(Listener listener) {
listeners.remove(listener);
}

@Override
public boolean hasListeners() {
return !listeners.isEmpty();
}

@Override
public void broadcast(Event event) {
if (executor == null) {
return;
}
for (Listener listener : listeners) {
executor.submit(() -> {
try {
broadcaster.accept(listener, event);
} catch (Exception e) {
logger.error("Unexpected exception in event listener", e);
}
});
}
}


}
23 changes: 18 additions & 5 deletions src/main/java/co/featbit/server/FBClientImp.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@
import java.io.IOException;
import java.time.Duration;
import java.util.Map;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.*;
import java.util.function.Consumer;

import static co.featbit.server.Evaluator.*;
Expand All @@ -36,8 +34,12 @@ public final class FBClientImp implements FBClient {
private final Status.DataUpdater dataUpdater;
private final InsightProcessor insightProcessor;

private final ExecutorService sharedExecutorService;

private final Consumer<InsightTypes.Event> eventHandler;

private final FlagTracker flagTracker;

/**
* Creates a new client to connect to feature flag center with a specified configuration.
* <p>
Expand Down Expand Up @@ -109,13 +111,18 @@ public FBClientImp(String envSecret, FBConfig config) {
return item == null ? null : (DataModel.Segment) item;
};
this.evaluator = new EvaluatorImp(flagGetter, segmentGetter);

this.sharedExecutorService = new ScheduledThreadPoolExecutor(1, Utils.createThreadFactory("featbit-shared-worker-%d", true));
EventBroadcasterImpl<Status.StateListener, Status.State> dataUpdateStateNotifier = EventBroadcasterImpl.forDataUpdateStates(this.sharedExecutorService, logger);
EventBroadcasterImpl<FlagChange.FlagChangeListener, FlagChange.FlagChangeEvent> flagChangeEventNotifier = EventBroadcasterImpl.forFlagChangeEvents(this.sharedExecutorService, logger);
this.flagTracker = new FlagTrackerImpl(flagChangeEventNotifier, (key, user) -> variation(key, user, null));
//data updator
Status.DataUpdaterImpl dataUpdatorImpl = new Status.DataUpdaterImpl(this.storage);
Status.DataUpdaterImpl dataUpdatorImpl = new Status.DataUpdaterImpl(this.storage, dataUpdateStateNotifier, flagChangeEventNotifier);
this.dataUpdater = dataUpdatorImpl;
//data processor
this.dataSynchronizer = config.getDataSynchronizerFactory().createDataSynchronizer(context, dataUpdatorImpl);
//data update status provider
this.dataUpdateStatusProvider = new Status.DataUpdateStatusProviderImpl(dataUpdatorImpl);
this.dataUpdateStatusProvider = new Status.DataUpdateStatusProviderImpl(dataUpdatorImpl, dataUpdateStateNotifier);

// data sync
Duration startWait = config.getStartWaitTime();
Expand Down Expand Up @@ -287,6 +294,12 @@ public void close() throws IOException {
this.storage.close();
this.dataSynchronizer.close();
this.insightProcessor.close();
this.sharedExecutorService.shutdownNow();
}

@Override
public FlagTracker getFlagTracker() {
return this.flagTracker;
}

@Override
Expand Down
36 changes: 36 additions & 0 deletions src/main/java/co/featbit/server/FlagChange.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package co.featbit.server;

import java.util.Objects;

public abstract class FlagChange {

public static class FlagChangeEvent {
private final String key;

public FlagChangeEvent(String key) {
this.key = key;
}

public String getKey() {
return key;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
FlagChangeEvent that = (FlagChangeEvent) o;
return Objects.equals(key, that.key);
}

@Override
public int hashCode() {
return Objects.hash(key);
}
}

public interface FlagChangeListener {
void onFlagChange(FlagChangeEvent event);
}

}
65 changes: 65 additions & 0 deletions src/main/java/co/featbit/server/FlagTrackerImpl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package co.featbit.server;

import co.featbit.commons.model.FBUser;
import co.featbit.server.exterior.FlagTracker;
import co.featbit.server.exterior.FlagValueChangeEvent;
import co.featbit.server.exterior.FlagValueChangeListener;

import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiFunction;

class FlagTrackerImpl implements FlagTracker {

private final EventBroadcaster<FlagChange.FlagChangeListener, FlagChange.FlagChangeEvent> flagChangeEventNotifier;

private final BiFunction<String, FBUser, Object> evaluateFn;

FlagTrackerImpl(EventBroadcaster<FlagChange.FlagChangeListener, FlagChange.FlagChangeEvent> flagChangeEventNotifier,
BiFunction<String, FBUser, Object> evaluateFn) {
this.flagChangeEventNotifier = flagChangeEventNotifier;
this.evaluateFn = evaluateFn;
}

@Override
public FlagChange.FlagChangeListener addFlagValueChangeListener(String flagKey, FBUser user, FlagValueChangeListener listener) {
FlagChange.FlagChangeListener adapter = new FlagValueChangeAdapter(flagKey, user, listener);
addFlagChangeListener(adapter);
return adapter;
}

@Override
public void removeFlagChangeListener(FlagChange.FlagChangeListener listener) {
flagChangeEventNotifier.removeListener(listener);
}

@Override
public void addFlagChangeListener(FlagChange.FlagChangeListener listener) {
flagChangeEventNotifier.addListener(listener);
}

private final class FlagValueChangeAdapter implements FlagChange.FlagChangeListener {
private final String flagKey;
private final FBUser user;
private final FlagValueChangeListener listener;
private final AtomicReference<Object> value;

FlagValueChangeAdapter(String flagKey, FBUser user, FlagValueChangeListener listener) {
this.flagKey = flagKey;
this.user = user;
this.listener = listener;
this.value = new AtomicReference<>(evaluateFn.apply(flagKey, user));
}

@Override
public void onFlagChange(FlagChange.FlagChangeEvent event) {
if (event.getKey().equals(flagKey)) {
Object newValue = evaluateFn.apply(flagKey, user);
Object oldValue = value.getAndSet(newValue);
if (newValue != null && !newValue.equals(oldValue)) {
listener.onFlagValueChange(new FlagValueChangeEvent(flagKey, oldValue, newValue));
}
}
}

}
}
2 changes: 2 additions & 0 deletions src/main/java/co/featbit/server/Loggers.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ abstract class Loggers {
static final Logger EVENTS = LoggerFactory.getLogger(EVENTS_LOGGER_NAME);
private static final String UTILS_LOGGER_NAME = BASE_LOGGER_NAME + ".Utils";
static final Logger UTILS = LoggerFactory.getLogger(UTILS_LOGGER_NAME);
private static final String TEST_LOGGER_NAME = BASE_LOGGER_NAME + ".Test";
static final Logger TEST = LoggerFactory.getLogger(TEST_LOGGER_NAME);

Loggers() {
super();
Expand Down
Loading