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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## 1.0.0

- Introduce support for Full Stack projects in Optimizely X with no breaking changes from previous version
- Update whitelisting to take precedence over audience condition evaluation
- Introduce more graceful exception handling in instantiation and core methods

## 0.1.71

- Add support for v2 backend endpoint and datafile
Expand Down
9 changes: 4 additions & 5 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
# Contributing to the Optimizely Java SDK

We welcome contributions and feedback! Please read the [README](README.md) to set up your development environment,
then read the guidelines below for information on submitting your code.
We welcome contributions and feedback! All contributors must sign our [Contributor License Agreement (CLA)](https://docs.google.com/a/optimizely.com/forms/d/e/1FAIpQLSf9cbouWptIpMgukAKZZOIAhafvjFCV8hS00XJLWQnWDFtwtA/viewform) to be eligible to contribute. Please read the [README](README.md) to set up your development environment, then read the guidelines below for information on submitting your code.

## Development process

1. Create a branch off of `master`: `git checkout -b YOUR_NAME/branch_name`.
1. Create a branch off of `devel`: `git checkout -b YOUR_NAME/branch_name`.
2. Commit your changes. Make sure to add tests!
3. Run `./gradlew clean check` to make sure there are no possible bugs.
4. `git push` your changes to GitHub.
5. Make sure that all unit tests are passing and that there are no merge conflicts between your branch and `master`.
6. Open a pull request from `YOUR_NAME/branch_name` to `master`.
5. Make sure that all unit tests are passing and that there are no merge conflicts between your branch and `devel`.
6. Open a pull request from `YOUR_NAME/branch_name` to `devel`.
7. A repository maintainer will review your pull request and, if all goes well, merge it!

## Pull request acceptance criteria
Expand Down
14 changes: 9 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ Optimizely Java SDK
[![Build Status](https://travis-ci.org/optimizely/java-sdk.svg?branch=master)](https://travis-ci.org/optimizely/java-sdk)
[![Apache 2.0](https://img.shields.io/github/license/nebula-plugins/gradle-extra-configurations-plugin.svg)](http://www.apache.org/licenses/LICENSE-2.0)

This repository houses the Java SDK for Optimizely's server-side testing product, which is currently in private beta.
This repository houses the Java SDK for Optimizely's Full Stack product.

## Getting Started

Expand All @@ -18,14 +18,19 @@ following in your `build.gradle` and substitute `VERSION` for the latest SDK ver
```
repositories {
maven {
mavenCentral()
url "http://optimizely.bintray.com/optimizely"
}
}

dependencies {
compile 'com.optimizely.ab:core-api:{VERSION}'
// optional event dispatcher implementation
compile 'com.optimizely.ab:core-httpclient-impl:{VERSION}'
// The SDK integrates with multiple JSON parsers, here we use
// Jackson.
compile 'com.fasterxml.jackson.core:jackson-core:2.7.1'
compile 'com.fasterxml.jackson.core:jackson-annotations:2.7.1'
compile 'com.fasterxml.jackson.core:jackson-databind:2.7.1'
}
```

Expand All @@ -40,9 +45,8 @@ The supplied `pom` files on Bintray define module dependencies.

### Using the SDK

See the Optimizely server-side testing [developer documentation](http://developers.optimizely.com/server/reference/index.html) to learn how to set
up your first custom project and use the SDK. **Please note that you must be a member of the private server-side testing beta to create custom
projects and use this SDK.**
See the Optimizely Full Stack [developer documentation](http://developers.optimizely.com/server/reference/index.html) to learn how to set
up your first Java project and use the SDK.

## Development

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package com.optimizely.ab;

import com.optimizely.ab.config.Variation;
import com.optimizely.ab.config.parser.ConfigParseException;
import com.optimizely.ab.event.NoopEventHandler;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
Expand Down Expand Up @@ -65,7 +66,7 @@ public class OptimizelyBenchmark {

@Setup
@SuppressFBWarnings(value="OBL_UNSATISFIED_OBLIGATION_EXCEPTION_EDGE", justification="stream is safely closed")
public void setup() throws IOException {
public void setup() throws IOException, ConfigParseException {
Properties properties = new Properties();
InputStream propertiesStream = getClass().getResourceAsStream("/benchmark.properties");
properties.load(propertiesStream);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
*/
package com.optimizely.ab;

import com.optimizely.ab.config.parser.ConfigParseException;
import com.optimizely.ab.event.EventHandler;
import com.optimizely.ab.event.NoopEventHandler;

Expand Down Expand Up @@ -68,7 +69,7 @@ public void setup() throws IOException {
}

@Benchmark
public Optimizely measureOptimizelyCreation() throws IOException {
public Optimizely measureOptimizelyCreation() throws IOException, ConfigParseException {
return Optimizely.builder(datafile, eventHandler).build();
}
}
21 changes: 15 additions & 6 deletions core-api/src/main/java/com/optimizely/ab/Optimizely.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import com.optimizely.ab.config.Experiment;
import com.optimizely.ab.config.ProjectConfig;
import com.optimizely.ab.config.Variation;
import com.optimizely.ab.config.parser.ConfigParseException;
import com.optimizely.ab.config.parser.DefaultConfigParser;
import com.optimizely.ab.error.ErrorHandler;
import com.optimizely.ab.error.NoOpErrorHandler;
Expand Down Expand Up @@ -169,7 +170,11 @@ private Optimizely(@Nonnull ProjectConfig projectConfig,
logger.info("Activating user \"{}\" in experiment \"{}\".", userId, experiment.getKey());
logger.debug("Dispatching impression event to URL {} with params {} and payload \"{}\".",
impressionEvent.getEndpointUrl(), impressionEvent.getRequestParams(), impressionEvent.getBody());
eventHandler.dispatchEvent(impressionEvent);
try {
eventHandler.dispatchEvent(impressionEvent);
} catch (Exception e) {
logger.error("Unexpected exception in event dispatcher", e);
}

return variation;
}
Expand Down Expand Up @@ -239,14 +244,19 @@ private void track(@Nonnull String eventName,
logger.info("Tracking event \"{}\" for user \"{}\".", eventName, userId);
logger.debug("Dispatching conversion event to URL {} with params {} and payload \"{}\".",
conversionEvent.getEndpointUrl(), conversionEvent.getRequestParams(), conversionEvent.getBody());
eventHandler.dispatchEvent(conversionEvent);
try {
eventHandler.dispatchEvent(conversionEvent);
} catch (Exception e) {
logger.error("Unexpected exception in event dispatcher", e);
}
}

//======== getVariation calls ========//

public @Nullable Variation getVariation(@Nonnull Experiment experiment,
@Nonnull String userId) throws UnknownExperimentException {
return bucketer.bucket(experiment, userId);

return getVariation(getProjectConfig(), experiment, Collections.<String, String>emptyMap(), userId);
}

public @Nullable Variation getVariation(@Nonnull String experimentKey,
Expand Down Expand Up @@ -296,8 +306,7 @@ private void track(@Nonnull String eventName,
/**
* @return a {@link ProjectConfig} instance given a json string
*/
private static ProjectConfig getProjectConfig(String datafile) {
//TODO(vignesh): add validation logic here
private static ProjectConfig getProjectConfig(String datafile) throws ConfigParseException {
return DefaultConfigParser.getInstance().parseProjectConfig(datafile);
}

Expand Down Expand Up @@ -466,7 +475,7 @@ protected Builder withConfig(ProjectConfig projectConfig) {
return this;
}

public Optimizely build() {
public Optimizely build() throws ConfigParseException {
if (projectConfig == null) {
projectConfig = Optimizely.getProjectConfig(datafile);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,10 @@
*/
package com.optimizely.ab.config.parser;

import com.optimizely.ab.OptimizelyRuntimeException;

/**
* Wrapper around all types of JSON parser exceptions.
*/
public final class ConfigParseException extends OptimizelyRuntimeException {

public final class ConfigParseException extends Exception {
public ConfigParseException(String message) {
super(message);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public ProjectConfig parseProjectConfig(@Nonnull String json) throws ConfigParse
try {
return gson.fromJson(json, ProjectConfig.class);
} catch (JsonParseException e) {
throw new ConfigParseException("unable to parse project config: " + json, e);
throw new ConfigParseException("Unable to parse datafile: " + json, e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public ProjectConfig parseProjectConfig(@Nonnull String json) throws ConfigParse
try {
return mapper.readValue(json, ProjectConfig.class);
} catch (IOException e) {
throw new ConfigParseException("unable to parse project config: " + json, e);
throw new ConfigParseException("Unable to parse datafile: " + json, e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ public ProjectConfig parseProjectConfig(@Nonnull String json) throws ConfigParse
return new ProjectConfig(accountId, projectId, version, revision, groups, experiments, attributes, events,
audiences);
} catch (JSONException e) {
throw new ConfigParseException("unable to parse project config: " + json, e);
throw new ConfigParseException("Unable to parse datafile: " + json, e);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ public ProjectConfig parseProjectConfig(@Nonnull String json) throws ConfigParse
return new ProjectConfig(accountId, projectId, version, revision, groups, experiments, attributes, events,
audiences);
} catch (ParseException e) {
throw new ConfigParseException("unable to parse project config: " + json, e);
throw new ConfigParseException("Unable to parse datafile: " + json, e);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,10 @@
*/
package com.optimizely.ab.event;

import java.util.Map;

/**
* Implementations are responsible for dispatching event's to the Optimizely event end-point.
*/
public interface EventHandler {

void dispatchEvent(LogEvent logEvent);
void dispatchEvent(LogEvent logEvent) throws Exception;
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ public static boolean validatePreconditions(ProjectConfig projectConfig, Experim
return false;
}

if (experiment.getUserIdToVariationKeyMap().containsKey(userId)) {
return true;
}

if (!isUserInExperiment(projectConfig, experiment, attributes)) {
logger.info("User \"{}\" does not meet conditions to be in experiment \"{}\".", userId, experiment.getKey());
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,15 @@
*/
package com.optimizely.ab;

import ch.qos.logback.classic.Level;

import com.optimizely.ab.bucketing.UserExperimentRecord;
import com.optimizely.ab.config.ProjectConfigTestUtils;
import com.optimizely.ab.config.parser.ConfigParseException;
import com.optimizely.ab.error.ErrorHandler;
import com.optimizely.ab.error.NoOpErrorHandler;
import com.optimizely.ab.event.EventHandler;
import com.optimizely.ab.internal.LogbackVerifier;

import org.junit.Rule;
import org.junit.Test;
Expand All @@ -37,6 +41,7 @@
import static com.optimizely.ab.config.ProjectConfigTestUtils.validProjectConfigV2;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.mock;

Expand All @@ -52,6 +57,9 @@ public class OptimizelyBuilderTest {
@Rule
public MockitoRule rule = MockitoJUnit.rule();

@Rule
public LogbackVerifier logbackVerifier = new LogbackVerifier();

@Mock private EventHandler mockEventHandler;

@Mock private ErrorHandler mockErrorHandler;
Expand Down Expand Up @@ -101,9 +109,15 @@ public void withDefaultErrorHandler() throws Exception {
public void withUserExperimentRecord() throws Exception {
UserExperimentRecord userExperimentRecord = mock(UserExperimentRecord.class);
Optimizely optimizelyClient = Optimizely.builder(validConfigJsonV2(), mockEventHandler)
.withUserExperimentRecord(userExperimentRecord)
.build();
.withUserExperimentRecord(userExperimentRecord)
.build();

assertThat(optimizelyClient.bucketer.getUserExperimentRecord(), is(userExperimentRecord));
}

@Test
public void builderThrowsConfigParseExceptionForInvalidDatafile() throws Exception {
thrown.expect(ConfigParseException.class);
Optimizely.builder("{invalidDatafile}", mockEventHandler).build();
}
}
Loading