1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-03-23 00:16:25 +01:00

unleash-client-java 'cloned' into it's own repo

This commit is contained in:
svelovla 2014-10-31 12:56:44 +01:00 committed by Ivar Conradi Østhus
parent 66f96affeb
commit 642a8e4fa0
22 changed files with 0 additions and 697 deletions

View File

@ -1,13 +0,0 @@
# The Unleash Client 4 Java
## Awesome API
It is really simple to use unleash.
```java
if(unleash.isEnabled("AwesomeFeature")) {
//do some magic
} else {
//do old boring stuff
}
```

View File

@ -1,76 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.sonatype.oss</groupId>
<artifactId>oss-parent</artifactId>
<version>7</version>
</parent>
<groupId>no.finn.unleash</groupId>
<artifactId>unleash-client-java</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<version.log4j2>2.0-rc1</version.log4j2>
</properties>
<dependencies>
<!--
TODO: we should write our own manual json serialize/deserializer
to avvoid this dependency
-->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.2.4</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>${version.log4j2}</version>
</dependency>
<!-- Test dependencies -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.9.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>${version.log4j2}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -1,35 +0,0 @@
package no.finn.unleash;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
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 read() {
LOG.info("Unleash will try to load feature toggle states from temporary backup");
try (FileReader reader = new FileReader(BACKUP_FILE)) {
BufferedReader br = new BufferedReader(reader);
return JsonToggleParser.fromJson(br);
} catch (FileNotFoundException e) {
LOG.warn("Unable to locate backup file:'{}'", BACKUP_FILE, e);
} catch (IOException e) {
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)) {
writer.write(JsonToggleParser.toJsonString(toggleCollection));
} catch (IOException e) {
LOG.warn("Unleash was unable to backup feature toggles to file: {}", BACKUP_FILE, e);
}
}
}

View File

@ -1,17 +0,0 @@
package no.finn.unleash;
import java.util.Map;
final class DefaultStrategy implements Strategy {
public static final String NAME = "default";
@Override
public String getName() {
return NAME;
}
@Override
public boolean isEnabled(Map<String, String> parameters) {
return true;
}
}

View File

@ -1,67 +0,0 @@
package no.finn.unleash;
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();
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 BackupFileHandler featureToggleBackupFileHandler;
private final ToggleFetcher toggleFetcher;
private ToggleCollection toggleCollection;
public FeatureToggleRepository(URI featuresUri, long pollIntervalSeconds) {
featureToggleBackupFileHandler = new BackupFileHandler();
toggleFetcher = new HttpToggleFetcher(featuresUri);
toggleCollection = featureToggleBackupFileHandler.read();
startBackgroundPolling(pollIntervalSeconds);
}
private ScheduledFuture startBackgroundPolling(long pollIntervalSeconds) {
try {
return TIMER.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
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", ex);
return null;
}
}
@Override
public Toggle getToggle(String name) {
return toggleCollection.getToggle(name);
}
}

View File

@ -1,62 +0,0 @@
package no.finn.unleash;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.nio.charset.StandardCharsets;
final class HttpToggleFetcher implements ToggleFetcher {
public static final int CONNECT_TIMEOUT = 10000;
private String etag = null;
private final URL toggleUrl;
public HttpToggleFetcher(URI repo) {
try {
toggleUrl = repo.toURL();
} catch (MalformedURLException ex) {
throw new UnleashException("Invalid repo uri", ex);
}
}
@Override
public Response fetchToggles() throws UnleashException {
HttpURLConnection connection = null;
try {
connection = (HttpURLConnection) toggleUrl.openConnection();
connection.setConnectTimeout(CONNECT_TIMEOUT);
connection.setReadTimeout(CONNECT_TIMEOUT);
connection.setRequestProperty("If-None-Match", etag);
connection.connect();
int responseCode = connection.getResponseCode();
if(responseCode < 300) {
return getToggleResponse(connection);
} else {
return new Response(Response.Status.NOT_CHANGED);
}
} catch (IOException e) {
throw new UnleashException("Could not fetch toggles", e);
} finally {
if(connection != null) {
connection.disconnect();
}
}
}
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.fromJson(reader);
return new Response(Response.Status.CHANGED, toggles);
}
}
}

View File

@ -1,29 +0,0 @@
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());
}
}

View File

@ -1,28 +0,0 @@
package no.finn.unleash;
import java.util.Collections;
final class Response {
enum Status {NOT_CHANGED, CHANGED}
private final Status status;
private final ToggleCollection toggleCollection;
public Response(Status status, ToggleCollection toggleCollection) {
this.status = status;
this.toggleCollection = toggleCollection;
}
public Response(Status status) {
this.status = status;
this.toggleCollection = new ToggleCollection(Collections.emptyList());
}
public Status getStatus() {
return status;
}
public ToggleCollection getToggleCollection() {
return toggleCollection;
}
}

View File

@ -1,9 +0,0 @@
package no.finn.unleash;
import java.util.Map;
public interface Strategy {
String getName();
boolean isEnabled(Map<String, String> parameters);
}

View File

@ -1,43 +0,0 @@
package no.finn.unleash;
import java.util.Map;
public final class Toggle {
private final String name;
private final boolean enabled;
private final String strategy;
private final Map<String, String> parameters;
public Toggle(String name, boolean enabled, String strategy, Map<String, String> parameters) {
this.name = name;
this.enabled = enabled;
this.strategy = strategy;
this.parameters = parameters;
}
public String getName() {
return name;
}
public boolean isEnabled() {
return enabled;
}
public String getStrategy() {
return strategy;
}
public Map<String, String> getParameters() {
return parameters;
}
@Override
public String toString() {
return "Toggle{" +
"name='" + name + '\'' +
", enabled=" + enabled +
", strategy='" + strategy + '\'' +
", parameters=" + parameters +
'}';
}
}

View File

@ -1,25 +0,0 @@
package no.finn.unleash;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
final class ToggleCollection {
private Collection<Toggle> features = Collections.emptyList();
private Map<String, Toggle> cache;
ToggleCollection(final Collection<Toggle> features) {
this.features = features;
cache = new HashMap<>();
features.forEach(toggle -> cache.put(toggle.getName(), toggle));
}
Collection<Toggle> getFeatures() {
return features;
}
Toggle getToggle(final String name) {
return cache.get(name);
}
}

View File

@ -1,5 +0,0 @@
package no.finn.unleash;
public interface ToggleFetcher {
Response fetchToggles() throws UnleashException;
}

View File

@ -1,5 +0,0 @@
package no.finn.unleash;
public interface ToggleRepository {
Toggle getToggle(String name);
}

View File

@ -1,17 +0,0 @@
package no.finn.unleash;
import java.util.Map;
final class UnknownStrategy implements Strategy {
public static final String NAME = "unknown";
@Override
public String getName() {
return NAME;
}
@Override
public boolean isEnabled(Map<String, String> parameters) {
return false;
}
}

View File

@ -1,55 +0,0 @@
package no.finn.unleash;
import java.util.HashMap;
import java.util.Map;
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;
public Unleash(ToggleRepository toggleRepository, Strategy... strategies) {
this.toggleRepository = toggleRepository;
this.strategyMap = buildStrategyMap(strategies);
}
public boolean isEnabled(final String toggleName) {
return isEnabled(toggleName, false);
}
public boolean isEnabled(final String toggleName, final boolean defaultSetting) {
Toggle toggle = toggleRepository.getToggle(toggleName);
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(DEFAULT_STRATEGY.getName(), DEFAULT_STRATEGY);
if (strategies != null) {
for (Strategy strategy : strategies) {
map.put(strategy.getName(), strategy);
}
}
return map;
}
private Strategy getStrategy(String strategy) {
if (strategyMap.containsKey(strategy)) {
return strategyMap.get(strategy);
} else {
return UNKNOWN_STRATEGY;
}
}
}

View File

@ -1,8 +0,0 @@
package no.finn.unleash;
public class UnleashException extends RuntimeException {
public UnleashException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -1,36 +0,0 @@
package no.finn.unleash;
import org.junit.Test;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
public class JsonToggleParserTest {
@Test
public void should_deserialize_correctly() throws IOException {
String content = readFile("/features.json");
List<Toggle> toggles = new ArrayList<>(JsonToggleParser.fromJson(content));
assertThat(toggles.size(), is(3));
}
private String readFile(String filename) throws IOException {
InputStream in = this.getClass().getResourceAsStream(filename);
InputStreamReader reader = new InputStreamReader(in);
BufferedReader br = new BufferedReader(reader);
StringBuilder builder = new StringBuilder();
String line;
while((line = br.readLine()) != null) {
builder.append(line);
}
return builder.toString();
}
}

View File

@ -1,49 +0,0 @@
package no.finn.unleash;
import java.net.URI;
import java.util.Random;
public class ManualTesting {
public static void main(String[] args) throws Exception {
ToggleRepository repository = new FeatureToggleRepository(URI.create("http://localhost:4242/features"), 1);
Unleash unleash = new Unleash(repository);
for(int i=0;i<100;i++) {
(new Thread(new UnleashThread(unleash, "thread-"+i, 100))).start();
}
}
static class UnleashThread implements Runnable {
final Unleash unleash;
final String name;
final int maxRounds;
int currentRound = 0;
UnleashThread(Unleash unleash, String name, int maxRounds) {
this.unleash = unleash;
this.name = name;
this.maxRounds = maxRounds;
}
public void run() {
while(currentRound < maxRounds) {
currentRound++;
long startTime = System.nanoTime();
boolean enabled = unleash.isEnabled("featureX");
long timeUsed = System.nanoTime() - startTime;
System.out.println(name + "\t" +"featureX" +":" + enabled + "\t " + timeUsed + "ns");
try {
//Wait 1 to 10ms before next round
Thread.sleep(new Random().nextInt(10000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}

View File

@ -1,56 +0,0 @@
package no.finn.unleash;
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.*;
public class UnleashTest {
private ToggleRepository toggleRepository;
private Unleash unleash;
@Before
public void setup() {
toggleRepository = mock(ToggleRepository.class);
unleash = new Unleash(toggleRepository);
}
@Test
public void known_toogle_and_strategy_should_be_active() {
when(toggleRepository.getToggle("test")).thenReturn(new Toggle("test", true, "default", null));
assertThat(unleash.isEnabled("test"), is(true));
}
@Test
public void unknown_strategy_should_be_considered_inactive() {
when(toggleRepository.getToggle("test")).thenReturn(new Toggle("test", true, "whoot_strat", null));
assertThat(unleash.isEnabled("test"), is(false));
}
@Test
public void unknown_feature_should_be_considered_inactive() {
when(toggleRepository.getToggle("test")).thenReturn(null);
assertThat(unleash.isEnabled("test"), is(false));
}
@Test
public void should_register_custom_strategies() {
//custom strategy
Strategy customStrategy = mock(Strategy.class);
when(customStrategy.getName()).thenReturn("custom");
//register custom strategy
unleash = new Unleash(toggleRepository, customStrategy);
when(toggleRepository.getToggle("test")).thenReturn(new Toggle("test", true, "custom", null));
unleash.isEnabled("test");
verify(customStrategy, times(1)).isEnabled(anyMap());
}
}

View File

@ -1,17 +0,0 @@
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;
}
}

View File

@ -1,22 +0,0 @@
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"));
}
}

View File

@ -1,23 +0,0 @@
{"features": [
{
"name": "featureX",
"enabled": true,
"strategy": "default"
},
{
"name": "featureY",
"enabled": false,
"strategy": "baz",
"parameters": {
"foo": "bar"
}
},
{
"name": "featureZ",
"enabled": true,
"strategy": "baz",
"parameters": {
"foo": "rab"
}
}
]}