mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-20 00:08:02 +01:00
#30 , refactoring
This commit is contained in:
parent
ed27d891b5
commit
06b450da7b
@ -36,12 +36,6 @@
|
||||
<version>${version.log4j2}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
<version>4.3.1</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Test dependencies -->
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
@ -57,6 +51,12 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-core</artifactId>
|
||||
<version>${version.log4j2}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
|
@ -1,35 +1,32 @@
|
||||
package no.finn.unleash.repository;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
package no.finn.unleash;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
class FeatureToggleBackupFileHandler {
|
||||
import java.io.*;
|
||||
import java.util.Collections;
|
||||
|
||||
class BackupFileHandler {
|
||||
private static final Logger LOG = LogManager.getLogger();
|
||||
private static final String BACKUP_FILE =
|
||||
System.getProperty("java.io.tmpdir") + File.separatorChar + "unleash-repo.json";
|
||||
|
||||
|
||||
ToggleCollection readBackupFile() {
|
||||
ToggleCollection read() {
|
||||
LOG.info("Unleash will try to load feature toggle states from temporary backup");
|
||||
try(FileReader reader = new FileReader(BACKUP_FILE)) {
|
||||
try (FileReader reader = new FileReader(BACKUP_FILE)) {
|
||||
BufferedReader br = new BufferedReader(reader);
|
||||
return JsonToggleParser.collectionFormJson(br);
|
||||
return JsonToggleParser.fromJson(br);
|
||||
} catch (FileNotFoundException e) {
|
||||
LOG.warn("Unable to locate backup file:'{}'", BACKUP_FILE, e);
|
||||
} catch (IOException e) {
|
||||
//TODO: error if file corrupt, warning if file not found.
|
||||
LOG.warn("Unleash was unable to feature toggle states from temporary backup", e);
|
||||
return new ToggleCollection(Collections.emptyList());
|
||||
LOG.error("Failed to read backup file:'{}'", BACKUP_FILE, e);
|
||||
}
|
||||
return new ToggleCollection(Collections.emptyList());
|
||||
}
|
||||
|
||||
void write(ToggleCollection toggleCollection) {
|
||||
try(FileWriter writer = new FileWriter(BACKUP_FILE)) {
|
||||
try (FileWriter writer = new FileWriter(BACKUP_FILE)) {
|
||||
writer.write(JsonToggleParser.toJsonString(toggleCollection));
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Unleash was unable to backup feature toggles to file: {}", BACKUP_FILE, e);
|
@ -1,8 +1,8 @@
|
||||
package no.finn.unleash.strategy;
|
||||
package no.finn.unleash;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public final class DefaultStrategy implements Strategy {
|
||||
final class DefaultStrategy implements Strategy {
|
||||
public static final String NAME = "default";
|
||||
|
||||
@Override
|
@ -1,18 +1,11 @@
|
||||
package no.finn.unleash.repository;
|
||||
package no.finn.unleash;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import no.finn.unleash.Toggle;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
public final class FeatureToggleRepository implements ToggleRepository {
|
||||
private static final Logger LOG = LogManager.getLogger();
|
||||
|
||||
@ -32,16 +25,16 @@ public final class FeatureToggleRepository implements ToggleRepository {
|
||||
TIMER.setRemoveOnCancelPolicy(true);
|
||||
}
|
||||
|
||||
private final FeatureToggleBackupFileHandler featureToggleBackupFileHandler;
|
||||
private final BackupFileHandler featureToggleBackupFileHandler;
|
||||
private final ToggleFetcher toggleFetcher;
|
||||
|
||||
private ToggleCollection toggleCollection;
|
||||
|
||||
public FeatureToggleRepository(URI featuresUri, long pollIntervalSeconds) {
|
||||
featureToggleBackupFileHandler = new FeatureToggleBackupFileHandler();
|
||||
featureToggleBackupFileHandler = new BackupFileHandler();
|
||||
toggleFetcher = new HttpToggleFetcher(featuresUri);
|
||||
|
||||
toggleCollection = featureToggleBackupFileHandler.readBackupFile();
|
||||
toggleCollection = featureToggleBackupFileHandler.read();
|
||||
startBackgroundPolling(pollIntervalSeconds);
|
||||
}
|
||||
|
||||
@ -50,26 +43,25 @@ public final class FeatureToggleRepository implements ToggleRepository {
|
||||
return TIMER.scheduleAtFixedRate(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ToggleResponse response = toggleFetcher.fetchToggles();
|
||||
if(response.getStatus() == ToggleResponse.Status.CHANGED) {
|
||||
featureToggleBackupFileHandler.write(toggleCollection);
|
||||
toggleCollection = response.getToggleCollection();
|
||||
try {
|
||||
Response response = toggleFetcher.fetchToggles();
|
||||
if (response.getStatus() == Response.Status.CHANGED) {
|
||||
featureToggleBackupFileHandler.write(toggleCollection);
|
||||
toggleCollection = response.getToggleCollection();
|
||||
}
|
||||
} catch (UnleashException e) {
|
||||
LOG.warn("Could not refresh feature toggles", e);
|
||||
}
|
||||
}
|
||||
}, pollIntervalSeconds, pollIntervalSeconds, TimeUnit.SECONDS);
|
||||
} catch (RejectedExecutionException ex) {
|
||||
LOG.error("Unleash background task crashed");
|
||||
LOG.error("Unleash background task crashed", ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Toggle getToggle(String name) throws ToggleException {
|
||||
public Toggle getToggle(String name) {
|
||||
return toggleCollection.getToggle(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Toggle> getToggles() throws ToggleException {
|
||||
return null;
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package no.finn.unleash.repository;
|
||||
package no.finn.unleash;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
@ -10,7 +10,7 @@ import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public final class HttpToggleFetcher implements ToggleFetcher {
|
||||
final class HttpToggleFetcher implements ToggleFetcher {
|
||||
public static final int CONNECT_TIMEOUT = 10000;
|
||||
private String etag = null;
|
||||
|
||||
@ -20,12 +20,12 @@ public final class HttpToggleFetcher implements ToggleFetcher {
|
||||
try {
|
||||
toggleUrl = repo.toURL();
|
||||
} catch (MalformedURLException ex) {
|
||||
throw new IllegalArgumentException("Invalid repo uri", ex);
|
||||
throw new UnleashException("Invalid repo uri", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ToggleResponse fetchToggles() throws ToggleException {
|
||||
public Response fetchToggles() throws UnleashException {
|
||||
HttpURLConnection connection = null;
|
||||
try {
|
||||
connection = (HttpURLConnection) toggleUrl.openConnection();
|
||||
@ -38,10 +38,10 @@ public final class HttpToggleFetcher implements ToggleFetcher {
|
||||
if(responseCode < 300) {
|
||||
return getToggleResponse(connection);
|
||||
} else {
|
||||
return new ToggleResponse(ToggleResponse.Status.NOT_CHANGED);
|
||||
return new Response(Response.Status.NOT_CHANGED);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new ToggleException("Could not fetch toggles", e);
|
||||
throw new UnleashException("Could not fetch toggles", e);
|
||||
} finally {
|
||||
if(connection != null) {
|
||||
connection.disconnect();
|
||||
@ -49,14 +49,14 @@ public final class HttpToggleFetcher implements ToggleFetcher {
|
||||
}
|
||||
}
|
||||
|
||||
private ToggleResponse getToggleResponse(HttpURLConnection request) throws IOException {
|
||||
private Response getToggleResponse(HttpURLConnection request) throws IOException {
|
||||
etag = request.getHeaderField("ETag");
|
||||
|
||||
try(BufferedReader reader = new BufferedReader(
|
||||
new InputStreamReader((InputStream) request.getContent(), StandardCharsets.UTF_8))) {
|
||||
|
||||
ToggleCollection toggles = JsonToggleParser.collectionFormJson(reader);
|
||||
return new ToggleResponse(ToggleResponse.Status.CHANGED, toggles);
|
||||
ToggleCollection toggles = JsonToggleParser.fromJson(reader);
|
||||
return new Response(Response.Status.CHANGED, toggles);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package no.finn.unleash;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
import java.io.Reader;
|
||||
import java.util.Collection;
|
||||
|
||||
final class JsonToggleParser {
|
||||
|
||||
private JsonToggleParser() {
|
||||
}
|
||||
|
||||
public static String toJsonString(ToggleCollection toggleCollection) {
|
||||
Gson gson = new GsonBuilder().create();
|
||||
return gson.toJson(toggleCollection);
|
||||
}
|
||||
|
||||
public static Collection<Toggle> fromJson(String jsonString) {
|
||||
Gson gson = new GsonBuilder().create();
|
||||
return gson.fromJson(jsonString, ToggleCollection.class).getFeatures();
|
||||
}
|
||||
|
||||
public static ToggleCollection fromJson(Reader reader) {
|
||||
Gson gson = new GsonBuilder().create();
|
||||
ToggleCollection gsonCollection = gson.fromJson(reader, ToggleCollection.class);
|
||||
return new ToggleCollection(gsonCollection.getFeatures());
|
||||
}
|
||||
}
|
@ -1,19 +1,19 @@
|
||||
package no.finn.unleash.repository;
|
||||
package no.finn.unleash;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
public final class ToggleResponse {
|
||||
final class Response {
|
||||
enum Status {NOT_CHANGED, CHANGED}
|
||||
|
||||
private final Status status;
|
||||
private final ToggleCollection toggleCollection;
|
||||
|
||||
public ToggleResponse(Status status, ToggleCollection toggleCollection) {
|
||||
public Response(Status status, ToggleCollection toggleCollection) {
|
||||
this.status = status;
|
||||
this.toggleCollection = toggleCollection;
|
||||
}
|
||||
|
||||
public ToggleResponse(Status status) {
|
||||
public Response(Status status) {
|
||||
this.status = status;
|
||||
this.toggleCollection = new ToggleCollection(Collections.emptyList());
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package no.finn.unleash.strategy;
|
||||
package no.finn.unleash;
|
||||
|
||||
import java.util.Map;
|
||||
|
@ -1,12 +1,10 @@
|
||||
package no.finn.unleash.repository;
|
||||
package no.finn.unleash;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import no.finn.unleash.Toggle;
|
||||
|
||||
final class ToggleCollection {
|
||||
private Collection<Toggle> features = Collections.emptyList();
|
||||
private Map<String, Toggle> cache;
|
@ -0,0 +1,5 @@
|
||||
package no.finn.unleash;
|
||||
|
||||
public interface ToggleFetcher {
|
||||
Response fetchToggles() throws UnleashException;
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package no.finn.unleash;
|
||||
|
||||
public interface ToggleRepository {
|
||||
Toggle getToggle(String name);
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
package no.finn.unleash.strategy;
|
||||
package no.finn.unleash;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public final class UnknownStrategy implements Strategy {
|
||||
final class UnknownStrategy implements Strategy {
|
||||
public static final String NAME = "unknown";
|
||||
|
||||
@Override
|
@ -3,18 +3,12 @@ package no.finn.unleash;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import no.finn.unleash.repository.ToggleException;
|
||||
import no.finn.unleash.repository.ToggleRepository;
|
||||
import no.finn.unleash.strategy.DefaultStrategy;
|
||||
import no.finn.unleash.strategy.Strategy;
|
||||
import no.finn.unleash.strategy.UnknownStrategy;
|
||||
|
||||
public final class Unleash {
|
||||
private static final DefaultStrategy DEFAULT_STRATEGY = new DefaultStrategy();
|
||||
private static final UnknownStrategy UNKNOWN_STRATEGY = new UnknownStrategy();
|
||||
|
||||
private final ToggleRepository toggleRepository;
|
||||
private final Map<String, Strategy> strategyMap;
|
||||
private final UnknownStrategy unknownStrategy = new UnknownStrategy();
|
||||
private final DefaultStrategy defaultStrategy = new DefaultStrategy();
|
||||
|
||||
|
||||
public Unleash(ToggleRepository toggleRepository, Strategy... strategies) {
|
||||
this.toggleRepository = toggleRepository;
|
||||
@ -26,28 +20,23 @@ public final class Unleash {
|
||||
}
|
||||
|
||||
public boolean isEnabled(final String toggleName, final boolean defaultSetting) {
|
||||
try {
|
||||
Toggle toggle = toggleRepository.getToggle(toggleName);
|
||||
Toggle toggle = toggleRepository.getToggle(toggleName);
|
||||
|
||||
if(toggle == null) {
|
||||
return defaultSetting;
|
||||
}
|
||||
|
||||
Strategy strategy = getStrategy(toggle.getStrategy());
|
||||
return toggle.isEnabled() && strategy.isEnabled(toggle.getParameters());
|
||||
|
||||
} catch (ToggleException rx) {
|
||||
if (toggle == null) {
|
||||
return defaultSetting;
|
||||
}
|
||||
|
||||
Strategy strategy = getStrategy(toggle.getStrategy());
|
||||
return toggle.isEnabled() && strategy.isEnabled(toggle.getParameters());
|
||||
}
|
||||
|
||||
private Map<String, Strategy> buildStrategyMap(Strategy[] strategies) {
|
||||
Map<String, Strategy> map = new HashMap<>();
|
||||
|
||||
map.put(defaultStrategy.getName(), defaultStrategy);
|
||||
map.put(DEFAULT_STRATEGY.getName(), DEFAULT_STRATEGY);
|
||||
|
||||
if(strategies != null) {
|
||||
for(Strategy strategy : strategies) {
|
||||
if (strategies != null) {
|
||||
for (Strategy strategy : strategies) {
|
||||
map.put(strategy.getName(), strategy);
|
||||
}
|
||||
}
|
||||
@ -56,12 +45,11 @@ public final class Unleash {
|
||||
}
|
||||
|
||||
|
||||
|
||||
private Strategy getStrategy(String strategy) {
|
||||
if(strategyMap.containsKey(strategy)) {
|
||||
if (strategyMap.containsKey(strategy)) {
|
||||
return strategyMap.get(strategy);
|
||||
} else {
|
||||
return unknownStrategy;
|
||||
return UNKNOWN_STRATEGY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,8 @@
|
||||
package no.finn.unleash;
|
||||
|
||||
public class UnleashException extends RuntimeException {
|
||||
|
||||
public UnleashException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
@ -1,58 +0,0 @@
|
||||
package no.finn.unleash.repository;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
|
||||
import no.finn.unleash.Toggle;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
|
||||
//TODO: take advantage of Etag and 304 responses.
|
||||
public class HTTPToggleRepository implements ToggleRepository {
|
||||
private static final Log LOG = LogFactory.getLog(HTTPToggleRepository.class);
|
||||
|
||||
private final HttpClient httpClient;
|
||||
private final String serverEndpoint;
|
||||
|
||||
public HTTPToggleRepository(final String serverEndpoint) {
|
||||
this.serverEndpoint = serverEndpoint;
|
||||
this.httpClient = HttpClients.createDefault();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Toggle getToggle(final String name) throws ToggleException {
|
||||
try {
|
||||
for (Toggle toggle : fetchToggles()) {
|
||||
if (name.equals(toggle.getName())) {
|
||||
return toggle;
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Could not fetch toggles via HTTP", e);
|
||||
throw new ToggleException("Could not fetch toggles via HTTP");
|
||||
}
|
||||
|
||||
throw new ToggleException("unknown toggle: " + name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Collection<Toggle> getToggles() {
|
||||
try {
|
||||
return fetchToggles();
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Could not fetch toggles via HTTP");
|
||||
throw new ToggleException("Could not fetch toggles via HTTP");
|
||||
}
|
||||
}
|
||||
|
||||
private Collection<Toggle> fetchToggles() throws IOException {
|
||||
HttpResponse httpResponse = httpClient.execute(new HttpGet(serverEndpoint));
|
||||
final String jsonString = EntityUtils.toString(httpResponse.getEntity());
|
||||
return JsonToggleParser.fromJson(jsonString);
|
||||
}
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
package no.finn.unleash.repository;
|
||||
|
||||
import java.io.Reader;
|
||||
import java.util.Collection;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import no.finn.unleash.Toggle;
|
||||
|
||||
public final class JsonToggleParser {
|
||||
|
||||
private JsonToggleParser() {
|
||||
}
|
||||
|
||||
public static Toggle toToggle(String jsonString) {
|
||||
Gson gson = new GsonBuilder().create();
|
||||
return gson.fromJson(jsonString, Toggle.class);
|
||||
}
|
||||
|
||||
public static String toJsonString(Collection<Toggle> toggles) {
|
||||
Gson gson = new GsonBuilder().create();
|
||||
return gson.toJson(new ToggleCollection(toggles));
|
||||
}
|
||||
|
||||
public static String toJsonString(ToggleCollection toggleCollection) {
|
||||
Gson gson = new GsonBuilder().create();
|
||||
return gson.toJson(toggleCollection);
|
||||
}
|
||||
|
||||
|
||||
public static Collection<Toggle> fromJson(String jsonString) {
|
||||
Gson gson = new GsonBuilder().create();
|
||||
return gson.fromJson(jsonString,ToggleCollection.class).getFeatures();
|
||||
}
|
||||
|
||||
public static ToggleCollection collectionFormJson(String jsonString) {
|
||||
Gson gson = new GsonBuilder().create();
|
||||
return gson.fromJson(jsonString, ToggleCollection.class);
|
||||
}
|
||||
|
||||
public static Collection<Toggle> fromJson(Reader reader) {
|
||||
Gson gson = new GsonBuilder().create();
|
||||
return gson.fromJson(reader,ToggleCollection.class).getFeatures();
|
||||
}
|
||||
|
||||
public static ToggleCollection collectionFormJson(Reader reader) {
|
||||
Gson gson = new GsonBuilder().create();
|
||||
ToggleCollection gsonCollection = gson.fromJson(reader, ToggleCollection.class);
|
||||
return new ToggleCollection(gsonCollection.getFeatures());
|
||||
}
|
||||
}
|
@ -1,137 +0,0 @@
|
||||
package no.finn.unleash.repository;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import no.finn.unleash.Toggle;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
public class PollingToggleRepository implements ToggleRepository {
|
||||
private static final Log LOG = LogFactory.getLog(PollingToggleRepository.class);
|
||||
private static final ScheduledThreadPoolExecutor TIMER = new ScheduledThreadPoolExecutor(
|
||||
1,
|
||||
new ThreadFactory() {
|
||||
@Override
|
||||
public Thread newThread(final Runnable r) {
|
||||
Thread thread = Executors.defaultThreadFactory().newThread(r);
|
||||
thread.setName("unleash-toggle-repository");
|
||||
thread.setDaemon(true);
|
||||
return thread;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
static {
|
||||
TIMER.setRemoveOnCancelPolicy(true);
|
||||
}
|
||||
|
||||
private final ToggleRepository toggleRepository;
|
||||
private final int pollIntervalSeconds;
|
||||
private Map<String, Toggle> togglesCache;
|
||||
|
||||
|
||||
public PollingToggleRepository(final ToggleRepository toggleRepository, final int pollIntervalSeconds) {
|
||||
this.toggleRepository = toggleRepository;
|
||||
this.pollIntervalSeconds = pollIntervalSeconds;
|
||||
|
||||
this.togglesCache = new HashMap<>();
|
||||
updateTogglesCache();
|
||||
startBackgroundPolling();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Toggle getToggle(final String name) {
|
||||
return togglesCache.get(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Collection<Toggle> getToggles() {
|
||||
return Collections.unmodifiableCollection(togglesCache.values());
|
||||
}
|
||||
|
||||
private void updateTogglesCache() {
|
||||
try {
|
||||
Map<String, Toggle> freshToggleMap = new HashMap<>();
|
||||
|
||||
for (Toggle toggle : fetchToggles()) {
|
||||
freshToggleMap.put(toggle.getName(), toggle);
|
||||
}
|
||||
|
||||
this.togglesCache = Collections.unmodifiableMap(freshToggleMap);
|
||||
|
||||
} catch (ToggleException e) {
|
||||
//Do nothing
|
||||
}
|
||||
}
|
||||
|
||||
private ScheduledFuture startBackgroundPolling() {
|
||||
try {
|
||||
return TIMER.scheduleAtFixedRate(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
updateTogglesCache();
|
||||
}
|
||||
}, pollIntervalSeconds, pollIntervalSeconds, TimeUnit.SECONDS);
|
||||
} catch (RejectedExecutionException ex) {
|
||||
LOG.error("Unleash background task crashed");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private Collection<Toggle> fetchToggles() throws ToggleException {
|
||||
try {
|
||||
Collection<Toggle> toggles = toggleRepository.getToggles();
|
||||
storeRepoAsTempFile(JsonToggleParser.toJsonString(toggles));
|
||||
return toggles;
|
||||
} catch (ToggleException ex) {
|
||||
if (togglesCache.isEmpty()) {
|
||||
return loadFromTempFile();
|
||||
}
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private Collection<Toggle> loadFromTempFile() throws ToggleException {
|
||||
LOG.info("Unleash will try to load feature toggle states from temporary backup");
|
||||
try (FileReader reader = new FileReader(pathToTmpBackupFile())) {
|
||||
BufferedReader br = new BufferedReader(reader);
|
||||
StringBuilder builder = new StringBuilder();
|
||||
String line;
|
||||
while ((line = br.readLine()) != null) {
|
||||
builder.append(line);
|
||||
}
|
||||
return JsonToggleParser.fromJson(builder.toString());
|
||||
} catch (IOException e) {
|
||||
LOG.error("Unleash was unable to feature toggle repo from temporary backup: " + pathToTmpBackupFile());
|
||||
throw new ToggleException("Unleash was unable to feature toggle states from temporary backup");
|
||||
}
|
||||
}
|
||||
|
||||
private void storeRepoAsTempFile(final String serverResponse) {
|
||||
try (FileWriter writer = new FileWriter(pathToTmpBackupFile())) {
|
||||
writer.write(serverResponse);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private static String pathToTmpBackupFile() {
|
||||
return System.getProperty("java.io.tmpdir") + File.separatorChar + "unleash-repo.json";
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
package no.finn.unleash.repository;
|
||||
|
||||
public class ToggleException extends RuntimeException {
|
||||
public ToggleException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ToggleException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
package no.finn.unleash.repository;
|
||||
|
||||
public interface ToggleFetcher {
|
||||
ToggleResponse fetchToggles() throws ToggleException;
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
package no.finn.unleash.repository;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import no.finn.unleash.Toggle;
|
||||
|
||||
public interface ToggleRepository {
|
||||
Toggle getToggle(String name) throws ToggleException;
|
||||
|
||||
Collection<Toggle> getToggles() throws ToggleException;
|
||||
}
|
@ -1,4 +1,6 @@
|
||||
package no.finn.unleash.repository;
|
||||
package no.finn.unleash;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
@ -7,9 +9,6 @@ import java.io.InputStreamReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import no.finn.unleash.Toggle;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
@ -3,9 +3,6 @@ package no.finn.unleash;
|
||||
import java.net.URI;
|
||||
import java.util.Random;
|
||||
|
||||
import no.finn.unleash.repository.FeatureToggleRepository;
|
||||
import no.finn.unleash.repository.ToggleRepository;
|
||||
|
||||
public class ManualTesting {
|
||||
public static void main(String[] args) throws Exception {
|
||||
ToggleRepository repository = new FeatureToggleRepository(URI.create("http://localhost:4242/features"), 1);
|
||||
|
@ -1,18 +1,11 @@
|
||||
package no.finn.unleash;
|
||||
|
||||
import no.finn.unleash.repository.ToggleException;
|
||||
import no.finn.unleash.repository.ToggleRepository;
|
||||
import no.finn.unleash.strategy.Strategy;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.Mockito.anyMap;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
public class UnleashTest {
|
||||
|
||||
@ -39,13 +32,6 @@ public class UnleashTest {
|
||||
assertThat(unleash.isEnabled("test"), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void failing_repository_should_obey_default() {
|
||||
when(toggleRepository.getToggle("test")).thenThrow(new ToggleException("service down"));
|
||||
|
||||
assertThat(unleash.isEnabled("test", true), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unknown_feature_should_be_considered_inactive() {
|
||||
when(toggleRepository.getToggle("test")).thenReturn(null);
|
||||
|
@ -0,0 +1,17 @@
|
||||
package no.finn.unleash.example;
|
||||
|
||||
import no.finn.unleash.Strategy;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
final class CustomStrategy implements Strategy {
|
||||
@Override
|
||||
public String getName() {
|
||||
return "custom";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled(Map<String, String> parameters) {
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package no.finn.unleash.example;
|
||||
|
||||
import no.finn.unleash.FeatureToggleRepository;
|
||||
import no.finn.unleash.ToggleRepository;
|
||||
import no.finn.unleash.Unleash;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
|
||||
public class UnleashUsageTest {
|
||||
|
||||
@Test
|
||||
public void wire() {
|
||||
ToggleRepository repository = new FeatureToggleRepository(URI.create("http://localhost:4242/features"), 1);
|
||||
|
||||
Unleash unleash = new Unleash(repository, new CustomStrategy());
|
||||
|
||||
assertFalse(unleash.isEnabled("myFeature"));
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
package no.finn.unleash.repository;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
public class HttpToggleFetcherTest {
|
||||
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void explore() {
|
||||
HttpToggleFetcher httpToggleFetcher = new HttpToggleFetcher(URI.create("http://localhost:4242/features"));
|
||||
|
||||
ToggleResponse toggleResponse = httpToggleFetcher.fetchToggles();
|
||||
toggleResponse = httpToggleFetcher.fetchToggles();
|
||||
System.out.println("toggleResponse = " + toggleResponse);
|
||||
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user