diff --git a/core-httpclient-impl/README.md b/core-httpclient-impl/README.md
new file mode 100644
index 000000000..77b9e456f
--- /dev/null
+++ b/core-httpclient-impl/README.md
@@ -0,0 +1,189 @@
+# Java SDK Async Http Client
+
+This package provides default implementations of an Optimizely `EventHandler` and `ProjectConfigManager`. Also included
+in this package is a factory class, `OptimizelyFactory`, which can be used to reliably instantiate the Optimizely SDK
+with the default configuration of the `AsyncEventHandler` and `HttpProjectConfigManager`.
+
+## Installation
+
+### Gradle
+
+```groovy
+compile 'com.optimizely.ab:core-httpclient-impl:{VERSION}'
+```
+
+### Maven
+```xml
+
+ com.optimizely.ab
+ core-httpclient-impl
+ {VERSION}
+
+
+```
+
+### Basic usage
+```java
+import com.optimizely.ab.Optimizely;
+import com.optimizely.ab.OptimizelyFactory;
+
+public class App {
+
+ public static void main(String[] args) {
+ String sdkKey = args[0];
+ Optimizely optimizely = OptimizelyFactory.newDefaultInstance(sdkKey);
+ }
+}
+
+```
+
+### Advanced usage
+```java
+import com.optimizely.ab.Optimizely;
+import com.optimizely.ab.config.HttpProjectConfigManager;
+import com.optimizely.ab.event.AsyncEventHandler;
+
+import java.util.concurrent.TimeUnit;
+
+public class App {
+
+ public static void main(String[] args) {
+ String sdkKey = args[0];
+
+ EventHandler eventHandler = AsyncEventHandler.builder()
+ .withQueueCapacity(20000)
+ .withNumWorkers(5)
+ .build();
+
+ ProjectConfigManager projectConfigManager = HttpProjectConfigManager.builder()
+ .withSdkKey(sdkKey)
+ .withPollingInterval(1, TimeUnit.MINUTES)
+ .build();
+
+ Optimizely optimizely = Optimizely.builder()
+ .withConfig(projectConfigManager)
+ .withEventHandler(eventHandler)
+ .build();
+ }
+}
+```
+
+## AsyncEventHandler
+
+The [`AsyncEventHandler`](https://github.com/optimizely/java-sdk/blob/master/core-httpclient-impl/src/main/java/com/optimizely/ab/event/AsyncEventHandler.java)
+provides an implementation of the the [`EventHandler`](https://github.com/optimizely/java-sdk/blob/master/core-api/src/main/java/com/optimizely/ab/event/EventHandler.java)
+backed by a `ThreadPoolExecutor`. When events are
+triggered from the Optimizely SDK, they are immediately queued as discrete tasks to the executor and processed in the
+order they were submitted. Each worker is responsible for making outbound http requests to the
+Optimizely log endpoint for metric tracking. The default queue size and the number of workers are configurable via
+global properties and can be overridden via the `AsyncEventHandler.Builder`.
+
+### Usage
+
+To use the AsyncEventHandler, an instance must be built via the `AsyncEventHandler.Builder` then passed to the `Optimizely.Builder`
+
+```java
+EventHandler eventHandler = AsyncEventHandler.builder()
+ .withQueueCapacity(20000)
+ .withNumWorkers(5)
+ .build();
+```
+
+#### Queue capacity
+
+The queue capacity can be set to initialize the backing queue for the executor service. If the queue fills up, then
+events will be dropped and exception will be logged. Setting a higher queue value will prevent event loss, but will
+use up more memory in the event the workers can not keep up if the production rate.
+
+#### Number of workers
+
+The number of workers determines the number of threads used by the thread pool.
+
+#### Advanced configurations
+
+|Property Name|Default Value|Description|
+|---|---|---|
+|async.event.handler.queue.capacity|10000|Queue size for pending LogEvents|
+|async.event.handler.num.workers|2|Number of worker threads|
+|async.event.handler.max.connections|200|Max number of connections|
+|async.event.handler.event.max.per.route|20|Max number of connections per route|
+|async.event.handler.validate.after|5000|Time in milliseconds to maintain idol connections|
+
+
+## HttpProjectConfigManager
+
+The [`HttpProjectConfigManager`](https://github.com/optimizely/java-sdk/blob/master/core-httpclient-impl/src/main/java/com/optimizely/ab/config/HttpProjectConfigManager.java)
+is an implementation of the abstract [`PollingProjectConfigManager`](https://github.com/optimizely/java-sdk/blob/master/core-api/src/main/java/com/optimizely/ab/config/PollingProjectConfigManager.java).
+The `poll` method is extended and makes an http GET request to the configured url to asynchronously download the project data file
+and initialize an instance of the ProjectConfig. By default, the `HttpProjectConfigManager` will block until the
+first successful retrieval of the datafile, up to a configurable timeout. The frequency of the polling method and the
+blocking timeout can be set via the `HttpProjectConfigManager.Builder` with the default values being pulled from global
+properties.
+
+### Usage
+
+```java
+ProjectConfigManager projectConfigManager = HttpProjectConfigManager.builder()
+ .withSdkKey(sdkKey)
+ .withPollingInterval(1, TimeUnit.MINUTES)
+ .build();
+```
+
+#### SDK Key
+
+The SDK key is used to compose the outbound http request to the default datafile location hosted on the Optimizely CDN.
+
+#### Polling interval
+
+The polling interval is used to determine a fixed delay between consecutive http requests for the datafile.
+
+#### Initial Datafile
+
+An initial datafile can be provided via the builder to bootstrap the the `ProjectConfigManager` so that it can be used
+immediately without blocking execution.
+
+#### Advanced configurations
+
+|Property Name|Default Value|Description|
+|---|---|---|
+|http.project.config.manager.polling.duration|5|Fixed delay between fetches for the datafile|
+|http.project.config.manager.polling.unit|MINUTES|Time unit corresponding to polling interval|
+|http.project.config.manager.blocking.duration|10|Max duration spent waiting for initial bootstrapping|
+|http.project.config.manager.blocking.unit|SECONDS|Time unit corresponding to blocking duration|
+|http.project.config.manager.sdk.key|null|Optimizely project SDK key|
+
+
+## Optimizely properties file
+
+An Optimizely properties file, `optimizely.properties`, that is available within the runtime classpath can be used to configure
+the default values of a given Optimizely resource. Refer to the resource implementation for available configuration
+parameters.
+
+#### Example:
+```properties
+http.project.config.manager.polling.duration = 1
+http.project.config.manager.polling.unit = MINUTES
+
+async.event.handler.queue.capacity = 20000
+async.event.handler.num.workers = 5
+```
+
+## OptimizelyFactory
+
+The [`OptimizelyFactory`](https://github.com/optimizely/java-sdk/blob/master/core-httpclient-impl/src/main/java/com/optimizely/ab/OptimizelyFactory.java)
+included in this package provides basic utility to instantiate the Optimizely SDK
+with a minimal number of provided configuration options. Configuration properties are sourced from Java system properties,
+environment variables or from an `optimizely.properties` file, in that order. Not all configuration and initialization
+are captured via the `OptimizelyFactory`, for those use cases the resources can be built via their respective builder
+classes.
+
+### Usage
+The SDK key is required to be provided at runtime either directly via the factory method:
+```Java
+Optimizely optimizely = OptimizelyFactory.newDefaultInstance(<>);
+```
+
+If the SDK is provided via a global property then the empty signature can be used:
+```Java
+Optimizely optimizely = OptimizelyFactory.newDefaultInstance();
+```
diff --git a/core-httpclient-impl/src/main/java/com/optimizely/ab/config/HttpProjectConfigManager.java b/core-httpclient-impl/src/main/java/com/optimizely/ab/config/HttpProjectConfigManager.java
index 82cbf268c..37d60ca20 100644
--- a/core-httpclient-impl/src/main/java/com/optimizely/ab/config/HttpProjectConfigManager.java
+++ b/core-httpclient-impl/src/main/java/com/optimizely/ab/config/HttpProjectConfigManager.java
@@ -241,11 +241,15 @@ public HttpProjectConfigManager build(boolean defer) {
if (period <= 0) {
logger.warn("Invalid polling interval {}, {}. Defaulting to {}, {}",
period, timeUnit, DEFAULT_POLLING_DURATION, DEFAULT_POLLING_UNIT);
+ period = DEFAULT_POLLING_DURATION;
+ timeUnit = DEFAULT_POLLING_UNIT;
}
if (blockingTimeoutPeriod <= 0) {
logger.warn("Invalid polling interval {}, {}. Defaulting to {}, {}",
blockingTimeoutPeriod, blockingTimeoutUnit, DEFAULT_BLOCKING_DURATION, DEFAULT_BLOCKING_UNIT);
+ blockingTimeoutPeriod = DEFAULT_BLOCKING_DURATION;
+ blockingTimeoutUnit = DEFAULT_BLOCKING_UNIT;
}
if (httpClient == null) {
diff --git a/core-httpclient-impl/src/test/java/com/optimizely/ab/config/HttpProjectConfigManagerTest.java b/core-httpclient-impl/src/test/java/com/optimizely/ab/config/HttpProjectConfigManagerTest.java
index 4b2e8acfd..aa5555de4 100644
--- a/core-httpclient-impl/src/test/java/com/optimizely/ab/config/HttpProjectConfigManagerTest.java
+++ b/core-httpclient-impl/src/test/java/com/optimizely/ab/config/HttpProjectConfigManagerTest.java
@@ -82,6 +82,11 @@ public void tearDown() {
}
projectConfigManager.close();
+
+ System.clearProperty("optimizely." + HttpProjectConfigManager.CONFIG_BLOCKING_UNIT);
+ System.clearProperty("optimizely." + HttpProjectConfigManager.CONFIG_BLOCKING_DURATION);
+ System.clearProperty("optimizely." + HttpProjectConfigManager.CONFIG_POLLING_UNIT);
+ System.clearProperty("optimizely." + HttpProjectConfigManager.CONFIG_POLLING_DURATION);
}
@Test
@@ -236,6 +241,7 @@ public void testGetDatafileHttpResponse5XX() throws Exception {
projectConfigManager.getDatafileFromResponse(getResponse);
}
+ @Test
public void testInvalidPayload() throws Exception {
reset(mockHttpClient);
CloseableHttpResponse invalidPayloadResponse = mock(CloseableHttpResponse.class);
@@ -257,6 +263,35 @@ public void testInvalidPayload() throws Exception {
assertNull(projectConfigManager.getConfig());
}
+ @Test
+ public void testInvalidPollingIntervalFromSystemProperties() throws Exception {
+ System.setProperty("optimizely." + HttpProjectConfigManager.CONFIG_POLLING_DURATION, "-1");
+ projectConfigManager = builder()
+ .withOptimizelyHttpClient(mockHttpClient)
+ .withSdkKey("sdk-key")
+ .build();
+ }
+
+ @Test
+ public void testInvalidBlockingIntervalFromSystemProperties() throws Exception {
+ reset(mockHttpClient);
+ CloseableHttpResponse invalidPayloadResponse = mock(CloseableHttpResponse.class);
+ StatusLine statusLine = mock(StatusLine.class);
+
+ when(statusLine.getStatusCode()).thenReturn(200);
+ when(invalidPayloadResponse.getStatusLine()).thenReturn(statusLine);
+ when(invalidPayloadResponse.getEntity()).thenReturn(new StringEntity("I am an invalid response!"));
+
+ when(mockHttpClient.execute(any(HttpGet.class)))
+ .thenReturn(invalidPayloadResponse);
+
+ System.setProperty("optimizely." + HttpProjectConfigManager.CONFIG_BLOCKING_DURATION, "-1");
+ projectConfigManager = builder()
+ .withOptimizelyHttpClient(mockHttpClient)
+ .withSdkKey("sdk-key")
+ .build();
+ }
+
@Test
@Ignore
public void testBasicFetch() throws Exception {