From 3c5bacbcee791db886011dec91c8144866676112 Mon Sep 17 00:00:00 2001 From: chandresh pancholi Date: Thu, 7 Oct 2021 01:40:59 +0530 Subject: [PATCH] experiment api, fixing userId, deviceId, flexible rollout strategy Signed-off-by: chandresh pancholi --- litmus-client/pom.xml | 6 +- .../LitmusExperimentBootstrapHandler.java | 21 ++-- .../client/ExperimentBackupHandlerFile.java | 21 ++-- .../medici/client/HttpExperimentFetcher.java | 24 ++++- .../com/navi/medici/config/LitmusConfig.java | 5 + .../com/navi/medici/litmus/DefaultLitmus.java | 21 ++-- .../repository/ExperimentRepository.java | 2 +- .../navi/medici/repository/InMemoryCache.java | 21 ++++ .../LitmusExperimentRepository.java | 6 +- .../medici/strategy/DeviceWithIdStrategy.java | 25 ++--- .../medici/strategy/UserWithIdStrategy.java | 24 ++--- .../com/navi/medici/util/JacksonUtils.java | 30 ++++++ ...itmusExperimentCollectionDeserializer.java | 97 ------------------- .../util/JsonLitmusExperimentParser.java | 31 ------ .../java/com/navi/medici/util/LitmusURLs.java | 11 +++ .../controller/v1/ExperimentController.java | 22 +++-- .../controller/v1/SegmentController.java | 21 +++- .../service/experiment/ExperimentService.java | 11 +++ .../experiment/ExperimentServiceImpl.java | 66 +++++++++++++ .../service/{ => segment}/SegmentService.java | 5 +- .../{ => segment}/SegmentServiceImpl.java | 6 +- .../src/main/resources/application.properties | 2 + .../navi/medici/entity/ExperimentEntity.java | 2 +- .../query/experiment/ExperimentQueryImpl.java | 5 + .../query/experiment/IExperimentQuery.java | 2 + .../202110061546-create-experiment-table.sql | 23 +++++ .../CustomLitmusContextProvider.java | 21 ++++ .../navi/medici/container/MockContainer.java | 3 +- litmus-model/pom.xml | 7 ++ .../navi/medici/context/LitmusContext.java | 92 +++++++++++++++++- .../medici/experiment/LitmusExperiment.java | 28 ------ ...mentRequest.java => LitmusExperiment.java} | 16 ++- .../response/LitmusExperimentCollection.java | 41 +++++--- .../response/LitmusExperimentResponse.java | 8 +- .../navi/medici/response/LitmusResponse.java | 34 +++++++ .../medici/strategy/ActivationStrategy.java | 46 ++++----- 36 files changed, 525 insertions(+), 281 deletions(-) create mode 100644 litmus-client/src/main/java/com/navi/medici/repository/InMemoryCache.java create mode 100644 litmus-client/src/main/java/com/navi/medici/util/JacksonUtils.java delete mode 100644 litmus-client/src/main/java/com/navi/medici/util/JsonLitmusExperimentCollectionDeserializer.java delete mode 100644 litmus-client/src/main/java/com/navi/medici/util/JsonLitmusExperimentParser.java create mode 100644 litmus-core/src/main/java/com/navi/medici/service/experiment/ExperimentService.java create mode 100644 litmus-core/src/main/java/com/navi/medici/service/experiment/ExperimentServiceImpl.java rename litmus-core/src/main/java/com/navi/medici/service/{ => segment}/SegmentService.java (68%) rename litmus-core/src/main/java/com/navi/medici/service/{ => segment}/SegmentServiceImpl.java (94%) create mode 100644 litmus-liquibase/src/main/resources/db/changelog/202110061546-create-experiment-table.sql create mode 100644 litmus-mock/src/main/java/com/navi/medici/container/CustomLitmusContextProvider.java delete mode 100644 litmus-model/src/main/java/com/navi/medici/experiment/LitmusExperiment.java rename litmus-model/src/main/java/com/navi/medici/request/v1/{ExperimentRequest.java => LitmusExperiment.java} (74%) create mode 100644 litmus-model/src/main/java/com/navi/medici/response/LitmusResponse.java diff --git a/litmus-client/pom.xml b/litmus-client/pom.xml index a5acb23..7f223cd 100644 --- a/litmus-client/pom.xml +++ b/litmus-client/pom.xml @@ -71,9 +71,9 @@ - com.google.code.gson - gson - 2.8.8 + com.fasterxml.jackson.core + jackson-databind + 2.13.0 diff --git a/litmus-client/src/main/java/com/navi/medici/bootstrap/LitmusExperimentBootstrapHandler.java b/litmus-client/src/main/java/com/navi/medici/bootstrap/LitmusExperimentBootstrapHandler.java index 1993fc3..80a296e 100644 --- a/litmus-client/src/main/java/com/navi/medici/bootstrap/LitmusExperimentBootstrapHandler.java +++ b/litmus-client/src/main/java/com/navi/medici/bootstrap/LitmusExperimentBootstrapHandler.java @@ -3,15 +3,16 @@ package com.navi.medici.bootstrap; import com.navi.medici.annotation.Nullable; import com.navi.medici.config.LitmusConfig; import com.navi.medici.response.LitmusExperimentCollection; -import com.navi.medici.util.JsonLitmusExperimentParser; -import java.io.StringReader; +import com.navi.medici.util.JacksonUtils; import java.util.Collections; +import java.util.concurrent.ConcurrentHashMap; import lombok.extern.log4j.Log4j2; @Log4j2 public class LitmusExperimentBootstrapHandler { private final LitmusExperimentBootstrapProvider litmusExperimentBootstrapProvider; + private final JacksonUtils jacksonUtils; public LitmusExperimentBootstrapHandler(LitmusConfig litmusConfig) { if (litmusConfig.getLitmusExperimentBootstrapProvider() != null) { @@ -19,23 +20,25 @@ public class LitmusExperimentBootstrapHandler { } else { this.litmusExperimentBootstrapProvider = new LitmusExperimentBootstrapFileProvider(); } + this.jacksonUtils = new JacksonUtils(); } public LitmusExperimentCollection parse(@Nullable String jsonString) { if (jsonString != null) { - try (StringReader stringReader = new StringReader(jsonString)) { - return JsonLitmusExperimentParser.fromJson(stringReader); - } catch (IllegalStateException ise) { - log.error("Failed to read litmus experiments bootstrap", ise); - } + return jacksonUtils.stringToObject(jsonString, LitmusExperimentCollection.class); } - return new LitmusExperimentCollection(Collections.emptyList()); + return LitmusExperimentCollection.builder() + .litmusExperiments(Collections.emptyList()) + .build(); } public LitmusExperimentCollection read() { if (litmusExperimentBootstrapProvider != null) { return parse(litmusExperimentBootstrapProvider.read()); } - return new LitmusExperimentCollection(Collections.emptyList()); + + return LitmusExperimentCollection.builder() + .litmusExperiments(Collections.emptyList()) + .build(); } } diff --git a/litmus-client/src/main/java/com/navi/medici/client/ExperimentBackupHandlerFile.java b/litmus-client/src/main/java/com/navi/medici/client/ExperimentBackupHandlerFile.java index 4046a73..014c967 100644 --- a/litmus-client/src/main/java/com/navi/medici/client/ExperimentBackupHandlerFile.java +++ b/litmus-client/src/main/java/com/navi/medici/client/ExperimentBackupHandlerFile.java @@ -1,35 +1,36 @@ package com.navi.medici.client; -import com.google.gson.JsonParseException; import com.navi.medici.config.LitmusConfig; import com.navi.medici.exception.LitmusException; -import com.navi.medici.experiment.LitmusExperiment; +import com.navi.medici.request.v1.LitmusExperiment; import com.navi.medici.response.LitmusExperimentCollection; -import com.navi.medici.util.JsonLitmusExperimentParser; -import java.io.BufferedReader; +import com.navi.medici.util.JacksonUtils; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.util.Collections; import java.util.List; +import java.util.concurrent.ConcurrentHashMap; import lombok.extern.log4j.Log4j2; +import org.apache.logging.log4j.core.util.IOUtils; @Log4j2 public class ExperimentBackupHandlerFile implements ExperimentBackupHandler { private final String backupFile; + private final JacksonUtils jacksonUtils; public ExperimentBackupHandlerFile(LitmusConfig config) { this.backupFile = config.getBackupFile(); + this.jacksonUtils = new JacksonUtils(); } @Override public LitmusExperimentCollection read() { log.info("Litmus will try to load experiments states from temporary backup"); try (FileReader reader = new FileReader(backupFile)) { - BufferedReader br = new BufferedReader(reader); - LitmusExperimentCollection experimentCollection = JsonLitmusExperimentParser.fromJson(br); + LitmusExperimentCollection experimentCollection = jacksonUtils.stringToObject(IOUtils.toString(reader), LitmusExperimentCollection.class); return experimentCollection; } catch (FileNotFoundException e) { log.info( @@ -37,17 +38,19 @@ public class ExperimentBackupHandlerFile implements ExperimentBackupHandler { + backupFile + "'. \n" + "This is expected behavior the first time litmus runs in a new environment."); - } catch (IOException | IllegalStateException | JsonParseException e) { + } catch (IOException | IllegalStateException e) { log.error(""); } List emptyList = Collections.emptyList(); - return new LitmusExperimentCollection(emptyList); + return LitmusExperimentCollection.builder() + .litmusExperiments(emptyList) + .build(); } @Override public void write(LitmusExperimentCollection experimentCollection) { try (FileWriter writer = new FileWriter(backupFile)) { - writer.write(JsonLitmusExperimentParser.toJsonString(experimentCollection)); + writer.write(jacksonUtils.objectToString(experimentCollection)); } catch (IOException e) { throw new LitmusException( "Litmus was unable to backup experiments to file: " + backupFile, e); diff --git a/litmus-client/src/main/java/com/navi/medici/client/HttpExperimentFetcher.java b/litmus-client/src/main/java/com/navi/medici/client/HttpExperimentFetcher.java index ba302bf..0e3ad48 100644 --- a/litmus-client/src/main/java/com/navi/medici/client/HttpExperimentFetcher.java +++ b/litmus-client/src/main/java/com/navi/medici/client/HttpExperimentFetcher.java @@ -4,7 +4,8 @@ import com.navi.medici.config.LitmusConfig; import com.navi.medici.exception.LitmusException; import com.navi.medici.response.LitmusExperimentCollection; import com.navi.medici.response.LitmusExperimentResponse; -import com.navi.medici.util.JsonLitmusExperimentParser; +import com.navi.medici.response.LitmusResponse; +import com.navi.medici.util.JacksonUtils; import java.net.URL; import lombok.extern.log4j.Log4j2; import okhttp3.OkHttpClient; @@ -16,12 +17,16 @@ public class HttpExperimentFetcher implements ExperimentFetcher { private final OkHttpClient client; private final URL experimentUrl; + private final URL segmentIdUrl; + private final JacksonUtils jacksonUtils; public HttpExperimentFetcher(LitmusConfig litmusConfig) { this.client = new OkHttpClient(); this.experimentUrl = litmusConfig.getLitmusURLs() .getLitmusExperimentsURL(litmusConfig.getProjectName(), litmusConfig.getNamePrefix()); + this.segmentIdUrl = litmusConfig.getLitmusURLs().getSegmentIdURL(); + this.jacksonUtils = new JacksonUtils(); } @Override @@ -35,7 +40,7 @@ public class HttpExperimentFetcher implements ExperimentFetcher { var responseBody = response.body(); assert responseBody != null; - LitmusExperimentCollection experiments = JsonLitmusExperimentParser.fromJson(responseBody.charStream()); + LitmusExperimentCollection experiments = jacksonUtils.stringToObject(responseBody.string(), LitmusExperimentCollection.class); return new LitmusExperimentResponse(LitmusExperimentResponse.Status.CHANGED, experiments); } else if(response.code() == 304) { @@ -48,4 +53,19 @@ public class HttpExperimentFetcher implements ExperimentFetcher { throw new LitmusException("fetch experiments failed.", e); } } + + public LitmusResponse segmentIdExists(String segmentName, String id) { + Request request = new Request.Builder() + .url(String.format("%s?segment_name=%s&id=%s", this.segmentIdUrl, segmentName, id)) + .build(); + + try(Response response = client.newCall(request).execute()) { + var responseBody = response.body(); + assert responseBody != null; + + return jacksonUtils.stringToObject(responseBody.string(), LitmusResponse.class); + } catch (Exception e) { + throw new LitmusException("segment name to id matching exists.", e); + } + } } diff --git a/litmus-client/src/main/java/com/navi/medici/config/LitmusConfig.java b/litmus-client/src/main/java/com/navi/medici/config/LitmusConfig.java index 82adfe5..c1af2bb 100644 --- a/litmus-client/src/main/java/com/navi/medici/config/LitmusConfig.java +++ b/litmus-client/src/main/java/com/navi/medici/config/LitmusConfig.java @@ -203,6 +203,11 @@ public class LitmusConfig { return this; } + public Builder litmusContextProvider(LitmusContextProvider contextProvider) { + this.contextProvider = contextProvider; + return this; + } + public Builder projectName(String projectName) { this.projectName = projectName; return this; diff --git a/litmus-client/src/main/java/com/navi/medici/litmus/DefaultLitmus.java b/litmus-client/src/main/java/com/navi/medici/litmus/DefaultLitmus.java index 9066715..c92a061 100644 --- a/litmus-client/src/main/java/com/navi/medici/litmus/DefaultLitmus.java +++ b/litmus-client/src/main/java/com/navi/medici/litmus/DefaultLitmus.java @@ -7,11 +7,12 @@ import com.navi.medici.client.ExperimentBackupHandlerFile; import com.navi.medici.client.HttpExperimentFetcher; import com.navi.medici.config.LitmusConfig; import com.navi.medici.context.LitmusContext; -import com.navi.medici.experiment.LitmusExperiment; import com.navi.medici.provider.LitmusContextProvider; import com.navi.medici.repository.ExperimentRepository; import com.navi.medici.repository.LitmusExperimentRepository; +import com.navi.medici.request.v1.LitmusExperiment; import com.navi.medici.strategy.DefaultStrategy; +import com.navi.medici.strategy.DeviceWithIdStrategy; import com.navi.medici.strategy.FlexibleRolloutStrategy; import com.navi.medici.strategy.Strategy; import com.navi.medici.strategy.UnknownStrategy; @@ -26,13 +27,6 @@ import lombok.extern.log4j.Log4j2; @Log4j2 public class DefaultLitmus implements Litmus { - - private static final List BUILTIN_STRATEGIES = - Arrays.asList( - new DefaultStrategy(), - new UserWithIdStrategy(), - new FlexibleRolloutStrategy()); - public static final UnknownStrategy UNKNOWN_STRATEGY = new UnknownStrategy(); private final ExperimentRepository experimentRepository; @@ -59,7 +53,7 @@ public class DefaultLitmus implements Litmus { this( litmusConfig, experimentRepository, - buildStrategyMap(strategies), + buildStrategyMap(strategies, litmusConfig), litmusConfig.getContextProvider()); } @@ -146,9 +140,16 @@ public class DefaultLitmus implements Litmus { return ofNullable(experimentRepository.getLitmusExperiment(experimentName)); } - private static Map buildStrategyMap(@Nullable Strategy[] strategies) { + private static Map buildStrategyMap(@Nullable Strategy[] strategies, LitmusConfig litmusConfig) { Map map = new HashMap<>(); + List BUILTIN_STRATEGIES = + Arrays.asList( + new DefaultStrategy(), + new UserWithIdStrategy(new HttpExperimentFetcher(litmusConfig)), + new DeviceWithIdStrategy(new HttpExperimentFetcher(litmusConfig)), + new FlexibleRolloutStrategy()); + BUILTIN_STRATEGIES.forEach(strategy -> map.put(strategy.getName(), strategy)); if (strategies != null) { diff --git a/litmus-client/src/main/java/com/navi/medici/repository/ExperimentRepository.java b/litmus-client/src/main/java/com/navi/medici/repository/ExperimentRepository.java index 3fd488a..8ca89d5 100644 --- a/litmus-client/src/main/java/com/navi/medici/repository/ExperimentRepository.java +++ b/litmus-client/src/main/java/com/navi/medici/repository/ExperimentRepository.java @@ -1,7 +1,7 @@ package com.navi.medici.repository; import com.navi.medici.annotation.Nullable; -import com.navi.medici.experiment.LitmusExperiment; +import com.navi.medici.request.v1.LitmusExperiment; import java.util.List; public interface ExperimentRepository { diff --git a/litmus-client/src/main/java/com/navi/medici/repository/InMemoryCache.java b/litmus-client/src/main/java/com/navi/medici/repository/InMemoryCache.java new file mode 100644 index 0000000..a4c0239 --- /dev/null +++ b/litmus-client/src/main/java/com/navi/medici/repository/InMemoryCache.java @@ -0,0 +1,21 @@ +package com.navi.medici.repository; + +import com.navi.medici.request.v1.LitmusExperiment; +import com.navi.medici.response.LitmusExperimentCollection; +import java.util.concurrent.ConcurrentHashMap; + +public final class InMemoryCache { + private final ConcurrentHashMap cache; + + public InMemoryCache(LitmusExperimentCollection litmusExperimentCollection) { + this.cache = new ConcurrentHashMap<>(); + for (LitmusExperiment litmusExperiment : litmusExperimentCollection.getLitmusExperiments()) { + cache.put(litmusExperiment.getName(), litmusExperiment); + } + } + + public LitmusExperiment getLitmusExperiment(final String name) { + return cache.get(name); + } + +} diff --git a/litmus-client/src/main/java/com/navi/medici/repository/LitmusExperimentRepository.java b/litmus-client/src/main/java/com/navi/medici/repository/LitmusExperimentRepository.java index 40087b8..c822cf7 100644 --- a/litmus-client/src/main/java/com/navi/medici/repository/LitmusExperimentRepository.java +++ b/litmus-client/src/main/java/com/navi/medici/repository/LitmusExperimentRepository.java @@ -6,7 +6,7 @@ import com.navi.medici.client.ExperimentBackupHandler; import com.navi.medici.client.ExperimentFetcher; import com.navi.medici.config.LitmusConfig; import com.navi.medici.exception.LitmusException; -import com.navi.medici.experiment.LitmusExperiment; +import com.navi.medici.request.v1.LitmusExperiment; import com.navi.medici.response.LitmusExperimentCollection; import com.navi.medici.response.LitmusExperimentResponse; import com.navi.medici.scheduler.LitmusScheduledExecutor; @@ -22,6 +22,7 @@ public class LitmusExperimentRepository implements ExperimentRepository { private final ExperimentFetcher experimentFetcher; private LitmusExperimentCollection experimentCollection; + private InMemoryCache inMemoryCache; private boolean ready; public LitmusExperimentRepository( @@ -52,6 +53,7 @@ public class LitmusExperimentRepository implements ExperimentRepository { if (litmusConfig.isSynchronousFetchOnInitialisation()) { updateExperiments().run(); } + this.inMemoryCache = new InMemoryCache(this.experimentCollection); executor.setInterval(updateExperiments(), 0, litmusConfig.getFetchLitmusExperimentsInterval()); } @@ -77,7 +79,7 @@ public class LitmusExperimentRepository implements ExperimentRepository { @Override public @Nullable LitmusExperiment getLitmusExperiment(String name) { - return experimentCollection.getLitmusExperiment(name); + return inMemoryCache.getLitmusExperiment(name); } @Override diff --git a/litmus-client/src/main/java/com/navi/medici/strategy/DeviceWithIdStrategy.java b/litmus-client/src/main/java/com/navi/medici/strategy/DeviceWithIdStrategy.java index ade1f76..8562022 100644 --- a/litmus-client/src/main/java/com/navi/medici/strategy/DeviceWithIdStrategy.java +++ b/litmus-client/src/main/java/com/navi/medici/strategy/DeviceWithIdStrategy.java @@ -1,15 +1,20 @@ package com.navi.medici.strategy; -import static java.util.Arrays.asList; +import com.navi.medici.client.HttpExperimentFetcher; import com.navi.medici.context.LitmusContext; import java.util.Map; -import java.util.Optional; public class DeviceWithIdStrategy implements Strategy { - protected static final String PARAM = "deviceIds"; private static final String STRATEGY_NAME = "deviceWithId"; + private static final String SEGMENT = "segment"; + + private final HttpExperimentFetcher experimentFetcher; + + public DeviceWithIdStrategy(HttpExperimentFetcher experimentFetcher) { + this.experimentFetcher = experimentFetcher; + } @Override public String getName() { @@ -23,14 +28,10 @@ public class DeviceWithIdStrategy implements Strategy { @Override public boolean isEnabled(Map parameters, LitmusContext litmusContext) { - return litmusContext - .getDeviceId() - .map( - currentDeviceId -> - Optional.ofNullable(parameters.get(PARAM)) - .map(deviceIdString -> asList(deviceIdString.split(",\\s?"))) - .filter(f -> f.contains(currentDeviceId)) - .isPresent()) - .orElse(false); + var segmentName = parameters.get(SEGMENT); + var result = experimentFetcher.segmentIdExists(segmentName, litmusContext.getDeviceId().orElse(null)); + + return result.getData() != null && result.getData(); + } } diff --git a/litmus-client/src/main/java/com/navi/medici/strategy/UserWithIdStrategy.java b/litmus-client/src/main/java/com/navi/medici/strategy/UserWithIdStrategy.java index 19a828c..3853493 100644 --- a/litmus-client/src/main/java/com/navi/medici/strategy/UserWithIdStrategy.java +++ b/litmus-client/src/main/java/com/navi/medici/strategy/UserWithIdStrategy.java @@ -1,16 +1,21 @@ package com.navi.medici.strategy; -import static java.util.Arrays.asList; +import com.navi.medici.client.HttpExperimentFetcher; import com.navi.medici.context.LitmusContext; import java.util.Map; -import java.util.Optional; public class UserWithIdStrategy implements Strategy { - protected static final String PARAM = "userIds"; + private static final String SEGMENT = "segment"; private static final String STRATEGY_NAME = "userWithId"; + private final HttpExperimentFetcher experimentFetcher; + + public UserWithIdStrategy(HttpExperimentFetcher experimentFetcher) { + this.experimentFetcher = experimentFetcher; + } + @Override public String getName() { return STRATEGY_NAME; @@ -23,15 +28,10 @@ public class UserWithIdStrategy implements Strategy { @Override public boolean isEnabled(Map parameters, LitmusContext litmusContext) { - return litmusContext - .getUserId() - .map( - currentUserId -> - Optional.ofNullable(parameters.get(PARAM)) - .map(userIdString -> asList(userIdString.split(",\\s?"))) - .filter(f -> f.contains(currentUserId)) - .isPresent()) - .orElse(false); + var segmentName = parameters.get(SEGMENT); + var result = experimentFetcher.segmentIdExists(segmentName, litmusContext.getUserId().orElse(null)); + + return result.getData() != null && result.getData(); } } diff --git a/litmus-client/src/main/java/com/navi/medici/util/JacksonUtils.java b/litmus-client/src/main/java/com/navi/medici/util/JacksonUtils.java new file mode 100644 index 0000000..b67119e --- /dev/null +++ b/litmus-client/src/main/java/com/navi/medici/util/JacksonUtils.java @@ -0,0 +1,30 @@ +package com.navi.medici.util; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.log4j.Log4j2; + +@Log4j2 +public class JacksonUtils { + private final ObjectMapper objectMapper; + + public JacksonUtils() { + this.objectMapper = new ObjectMapper(); + } + + public String objectToString(Object o) { + try { + return objectMapper.writeValueAsString(o); + } catch (JsonProcessingException e) { + throw new RuntimeException("object to string conversion failed", e); + } + } + + public T stringToObject(String s, Class klazz) { + try { + return objectMapper.readValue(s, klazz); + } catch (JsonProcessingException e) { + throw new RuntimeException("string to object conversion failed", e); + } + } +} \ No newline at end of file diff --git a/litmus-client/src/main/java/com/navi/medici/util/JsonLitmusExperimentCollectionDeserializer.java b/litmus-client/src/main/java/com/navi/medici/util/JsonLitmusExperimentCollectionDeserializer.java deleted file mode 100644 index a95228e..0000000 --- a/litmus-client/src/main/java/com/navi/medici/util/JsonLitmusExperimentCollectionDeserializer.java +++ /dev/null @@ -1,97 +0,0 @@ -package com.navi.medici.util; - -import static java.util.Collections.singletonList; - -import com.google.gson.JsonArray; -import com.google.gson.JsonDeserializationContext; -import com.google.gson.JsonDeserializer; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParseException; -import com.google.gson.reflect.TypeToken; -import com.navi.medici.annotation.Nullable; -import com.navi.medici.experiment.LitmusExperiment; -import com.navi.medici.response.LitmusExperimentCollection; -import com.navi.medici.strategy.ActivationStrategy; -import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Map; - -public class JsonLitmusExperimentCollectionDeserializer implements JsonDeserializer { - - private static final Type PARAMS_TYPE = new TypeToken>() { - }.getType(); - private static final Type FEATURE_COLLECTION_TYPE = - new TypeToken>() { - }.getType(); - - @Override - public @Nullable - LitmusExperimentCollection deserialize( - JsonElement rootElement, Type type, JsonDeserializationContext context) - throws JsonParseException { - - int version = getVersion(rootElement); - - switch (version) { - case 0: - return deserializeVersion0(rootElement, context); - case 1: - default: - return deserializeVersion1(rootElement, context); - } - } - - static @Nullable - LitmusExperimentCollection deserializeVersion0( - JsonElement rootElement, JsonDeserializationContext context) { - if (!rootElement.getAsJsonObject().has("features")) { - return null; - } - - Collection litmusExperiments = new ArrayList<>(); - - JsonArray features = rootElement.getAsJsonObject().getAsJsonArray("features"); - - features.forEach( - elm -> { - JsonObject featureObj = elm.getAsJsonObject(); - - String name = featureObj.get("name").getAsString(); - boolean enabled = featureObj.get("enabled").getAsBoolean(); - String strategyName = featureObj.get("strategy").getAsString(); - Map strategyParams = - context.deserialize(featureObj.get("parameters"), PARAMS_TYPE); - - ActivationStrategy strategy = - new ActivationStrategy(strategyName, strategyParams); - litmusExperiments.add( - new LitmusExperiment(name, enabled, singletonList(strategy))); - }); - - return new LitmusExperimentCollection(litmusExperiments); - } - - static @Nullable - LitmusExperimentCollection deserializeVersion1( - JsonElement rootElement, JsonDeserializationContext context) { - if (!rootElement.getAsJsonObject().has("features")) { - return null; - } - - JsonArray featureArray = rootElement.getAsJsonObject().getAsJsonArray("features"); - - Collection featureTgggles = - context.deserialize(featureArray, FEATURE_COLLECTION_TYPE); - return new LitmusExperimentCollection(featureTgggles); - } - - private int getVersion(JsonElement rootElement) { - if (!rootElement.getAsJsonObject().has("version")) { - return 0; - } else { - return rootElement.getAsJsonObject().get("version").getAsInt(); - } - } -} diff --git a/litmus-client/src/main/java/com/navi/medici/util/JsonLitmusExperimentParser.java b/litmus-client/src/main/java/com/navi/medici/util/JsonLitmusExperimentParser.java deleted file mode 100644 index b749d0a..0000000 --- a/litmus-client/src/main/java/com/navi/medici/util/JsonLitmusExperimentParser.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.navi.medici.util; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.navi.medici.response.LitmusExperimentCollection; -import java.io.Reader; - -public class JsonLitmusExperimentParser { - - private JsonLitmusExperimentParser() { - } - - public static String toJsonString(LitmusExperimentCollection litmusExperimentCollection) { - Gson gson = new GsonBuilder().create(); - return gson.toJson(litmusExperimentCollection); - } - - public static LitmusExperimentCollection fromJson(Reader reader) throws IllegalStateException { - Gson gson = - new GsonBuilder() - .registerTypeAdapter( - LitmusExperimentCollection.class, new JsonLitmusExperimentCollectionDeserializer()) - .create(); - LitmusExperimentCollection gsonCollection = gson.fromJson(reader, LitmusExperimentCollection.class); - if (gsonCollection == null) { - throw new IllegalStateException("Could not extract litmus experiments from json"); - } - return new LitmusExperimentCollection(gsonCollection.getLitmusExperiments()); - } - -} diff --git a/litmus-client/src/main/java/com/navi/medici/util/LitmusURLs.java b/litmus-client/src/main/java/com/navi/medici/util/LitmusURLs.java index b76609d..7e8b36d 100644 --- a/litmus-client/src/main/java/com/navi/medici/util/LitmusURLs.java +++ b/litmus-client/src/main/java/com/navi/medici/util/LitmusURLs.java @@ -7,11 +7,13 @@ import java.net.URL; public class LitmusURLs { private final URL fetchExperimentsURL; + private final URL segmentIdURL; public LitmusURLs(URI litmusAPI) { try { String litmusAPIstr = litmusAPI.toString(); fetchExperimentsURL = URI.create(litmusAPIstr + "/experiments").normalize().toURL(); + segmentIdURL = URI.create(litmusAPIstr + "/segments/exists").normalize().toURL(); } catch (MalformedURLException ex) { throw new IllegalArgumentException("Litmus API is not a valid URL: " + litmusAPI); @@ -35,6 +37,15 @@ public class LitmusURLs { } } + public URL getSegmentIdURL() { + try { + return URI.create(segmentIdURL.toString()).normalize().toURL(); + } catch (IllegalArgumentException | MalformedURLException e) { + throw new IllegalArgumentException( + "getSegmentIdURL Url [" + segmentIdURL + "] was not URL friendly.", e); + } + } + private void appendParam(StringBuilder suffix, String key, @Nullable String value) { if (value == null) return; if (suffix.length() == 0) { diff --git a/litmus-core/src/main/java/com/navi/medici/controller/v1/ExperimentController.java b/litmus-core/src/main/java/com/navi/medici/controller/v1/ExperimentController.java index 70d33fe..db4c985 100644 --- a/litmus-core/src/main/java/com/navi/medici/controller/v1/ExperimentController.java +++ b/litmus-core/src/main/java/com/navi/medici/controller/v1/ExperimentController.java @@ -1,29 +1,39 @@ package com.navi.medici.controller.v1; +import com.navi.medici.request.v1.LitmusExperiment; +import com.navi.medici.response.LitmusExperimentCollection; +import com.navi.medici.service.experiment.ExperimentService; +import java.util.List; import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequiredArgsConstructor @RequestMapping("/v1/experiments") +@Log4j2 public class ExperimentController { + private final ExperimentService experimentService; @PostMapping(value = "/create", consumes = MediaType.APPLICATION_JSON_VALUE) - public void createExperiment() { - + public void createExperiment(@RequestBody LitmusExperiment litmusExperimentRequest) { + experimentService.create(litmusExperimentRequest); } @GetMapping - public String fetchExperiments() { - String result = "{\"features\":[{\"name\":\"test-offer\",\"enabled\":true,\"strategies\":[{\"name\":\"flexibleRollout\"," - + "\"parameters\":{\"rollout\":\"24\",\"stickiness\":\"random\",\"groupId\":\"test-offer\"},\"constraints\":[]}],\"variants\":[]},{\"name\":\"test-v1\",\"enabled\":true,\"strategies\":[{\"name\":\"default\",\"parameters\":{}}],\"variants\":[]}],\"version\":1}"; + public LitmusExperimentCollection fetchExperiments() { - return result; + return experimentService.fetchAllExperiments(); +// String result = "{\"features\":[{\"name\":\"test-offer\",\"enabled\":true,\"strategies\":[{\"name\":\"flexibleRollout\"," +// + "\"parameters\":{\"rollout\":\"24\",\"stickiness\":\"random\",\"groupId\":\"test-offer\"},\"constraints\":[]}],\"variants\":[]},{\"name\":\"test-v1\",\"enabled\":true,\"strategies\":[{\"name\":\"default\",\"parameters\":{}}],\"variants\":[]}],\"version\":1}"; + +// return result; } diff --git a/litmus-core/src/main/java/com/navi/medici/controller/v1/SegmentController.java b/litmus-core/src/main/java/com/navi/medici/controller/v1/SegmentController.java index a45bc6f..b324a75 100644 --- a/litmus-core/src/main/java/com/navi/medici/controller/v1/SegmentController.java +++ b/litmus-core/src/main/java/com/navi/medici/controller/v1/SegmentController.java @@ -1,9 +1,13 @@ package com.navi.medici.controller.v1; import com.navi.medici.request.v1.SegmentRequest; -import com.navi.medici.service.SegmentService; +import com.navi.medici.response.LitmusResponse; +import com.navi.medici.service.segment.SegmentService; +import java.time.LocalDateTime; import lombok.RequiredArgsConstructor; import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; @@ -25,4 +29,19 @@ public class SegmentController { segmentService.createSegment(file, segmentRequest); } + @GetMapping(value = "/exists", produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity userIdExistInSegment(@RequestParam(value = "segment_name") String segmentName, + @RequestParam(value = "id") String id) { + + var result = segmentService.segmentIdExist(segmentName, id); + + var litmusResponse = LitmusResponse.builder() + .code(200) + .data(result) + .timestamp(LocalDateTime.now()) + .build(); + + return ResponseEntity.status(litmusResponse.getCode()).body(litmusResponse); + } + } diff --git a/litmus-core/src/main/java/com/navi/medici/service/experiment/ExperimentService.java b/litmus-core/src/main/java/com/navi/medici/service/experiment/ExperimentService.java new file mode 100644 index 0000000..d1d44e5 --- /dev/null +++ b/litmus-core/src/main/java/com/navi/medici/service/experiment/ExperimentService.java @@ -0,0 +1,11 @@ +package com.navi.medici.service.experiment; + +import com.navi.medici.request.v1.LitmusExperiment; +import com.navi.medici.response.LitmusExperimentCollection; +import java.util.List; + +public interface ExperimentService { + void create(LitmusExperiment litmusExperimentRequest); + + LitmusExperimentCollection fetchAllExperiments(); +} diff --git a/litmus-core/src/main/java/com/navi/medici/service/experiment/ExperimentServiceImpl.java b/litmus-core/src/main/java/com/navi/medici/service/experiment/ExperimentServiceImpl.java new file mode 100644 index 0000000..84ed94d --- /dev/null +++ b/litmus-core/src/main/java/com/navi/medici/service/experiment/ExperimentServiceImpl.java @@ -0,0 +1,66 @@ +package com.navi.medici.service.experiment; + +import com.navi.medici.entity.ExperimentEntity; +import com.navi.medici.query.experiment.IExperimentQuery; +import com.navi.medici.request.v1.LitmusExperiment; +import com.navi.medici.response.LitmusExperimentCollection; +import com.navi.medici.strategy.ActivationStrategy; +import com.navi.medici.util.JacksonUtils; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.springframework.stereotype.Service; + +@Service +@Log4j2 +@RequiredArgsConstructor +public class ExperimentServiceImpl implements ExperimentService { + private final IExperimentQuery experimentQuery; + private final JacksonUtils jacksonUtils; + + @Override + public void create(LitmusExperiment litmusExperimentRequest) { + ExperimentEntity experiment = ExperimentEntity.builder() + .name(litmusExperimentRequest.getName()) + .experimentId(UUID.randomUUID().toString()) + .enabled(litmusExperimentRequest.getEnabled()) + .description(litmusExperimentRequest.getDescription()) + .archived(litmusExperimentRequest.getArchived()) + .strategies(jacksonUtils.objectToString(litmusExperimentRequest.getStrategies())) + .variants(jacksonUtils.objectToString(litmusExperimentRequest.getVariants())) + .type(litmusExperimentRequest.getType()) + .startTime(litmusExperimentRequest.getStartTime()) + .endTime(litmusExperimentRequest.getEndTime()) + .build(); + + experimentQuery.save(experiment); + + } + + @Override + public LitmusExperimentCollection fetchAllExperiments() { + List experimentEntities = experimentQuery.findByEnabled(true); + List litmusExperiments = experimentEntities.stream() + .map(this::build) + .collect(Collectors.toList()); + return LitmusExperimentCollection.builder() + .litmusExperiments(litmusExperiments) + .build(); + } + + private LitmusExperiment build(ExperimentEntity experimentEntity) { + return LitmusExperiment.builder() + .experimentId(experimentEntity.getExperimentId()) + .name(experimentEntity.getName()) + .description(experimentEntity.getDescription()) + .enabled(experimentEntity.getEnabled()) + .archived(experimentEntity.getArchived()) + .strategies(jacksonUtils.stringToListObject(experimentEntity.getStrategies(), ActivationStrategy.class)) + .type(experimentEntity.getType()) + .startTime(experimentEntity.getStartTime()) + .endTime(experimentEntity.getEndTime()) + .build(); + } +} diff --git a/litmus-core/src/main/java/com/navi/medici/service/SegmentService.java b/litmus-core/src/main/java/com/navi/medici/service/segment/SegmentService.java similarity index 68% rename from litmus-core/src/main/java/com/navi/medici/service/SegmentService.java rename to litmus-core/src/main/java/com/navi/medici/service/segment/SegmentService.java index f9bf958..ab7f433 100644 --- a/litmus-core/src/main/java/com/navi/medici/service/SegmentService.java +++ b/litmus-core/src/main/java/com/navi/medici/service/segment/SegmentService.java @@ -1,4 +1,4 @@ -package com.navi.medici.service; +package com.navi.medici.service.segment; import com.navi.medici.request.v1.SegmentRequest; import org.springframework.web.multipart.MultipartFile; @@ -6,6 +6,5 @@ import org.springframework.web.multipart.MultipartFile; public interface SegmentService { void createSegment(MultipartFile file, SegmentRequest segmentRequest); - void segmentIdExist(String segmentName, String id); - + Boolean segmentIdExist(String segmentName, String id); } diff --git a/litmus-core/src/main/java/com/navi/medici/service/SegmentServiceImpl.java b/litmus-core/src/main/java/com/navi/medici/service/segment/SegmentServiceImpl.java similarity index 94% rename from litmus-core/src/main/java/com/navi/medici/service/SegmentServiceImpl.java rename to litmus-core/src/main/java/com/navi/medici/service/segment/SegmentServiceImpl.java index 5ae7b74..35b981b 100644 --- a/litmus-core/src/main/java/com/navi/medici/service/SegmentServiceImpl.java +++ b/litmus-core/src/main/java/com/navi/medici/service/segment/SegmentServiceImpl.java @@ -1,4 +1,4 @@ -package com.navi.medici.service; +package com.navi.medici.service.segment; import com.navi.medici.command.CacheCommands; import com.navi.medici.entity.SegmentEntity; @@ -52,9 +52,11 @@ public class SegmentServiceImpl implements SegmentService { } @Override - public void segmentIdExist(String segmentName, String id) { + public Boolean segmentIdExist(String segmentName, String id) { var bloomFilter = cacheCommands.getBf(segmentName); var result = cacheCommands.contains(bloomFilter, id); + + return result; } } diff --git a/litmus-core/src/main/resources/application.properties b/litmus-core/src/main/resources/application.properties index dbce67a..c415da0 100644 --- a/litmus-core/src/main/resources/application.properties +++ b/litmus-core/src/main/resources/application.properties @@ -23,5 +23,7 @@ redis.port=6379 redis.expected.insertions=99999 redis.false.probability=0.001 +server.port = 12000 + segment.s3.bucket=navi-test report.s3.region=temp \ No newline at end of file diff --git a/litmus-db/src/main/java/com/navi/medici/entity/ExperimentEntity.java b/litmus-db/src/main/java/com/navi/medici/entity/ExperimentEntity.java index 1cb28fb..2ea3845 100644 --- a/litmus-db/src/main/java/com/navi/medici/entity/ExperimentEntity.java +++ b/litmus-db/src/main/java/com/navi/medici/entity/ExperimentEntity.java @@ -55,7 +55,7 @@ public class ExperimentEntity { @Column(name = "variants") String variants; - @Column(name = "type") + @Column(name = "experiment_type") @Enumerated(EnumType.STRING) ExperimentType type; diff --git a/litmus-db/src/main/java/com/navi/medici/query/experiment/ExperimentQueryImpl.java b/litmus-db/src/main/java/com/navi/medici/query/experiment/ExperimentQueryImpl.java index ab5d70e..910bf9c 100644 --- a/litmus-db/src/main/java/com/navi/medici/query/experiment/ExperimentQueryImpl.java +++ b/litmus-db/src/main/java/com/navi/medici/query/experiment/ExperimentQueryImpl.java @@ -26,4 +26,9 @@ public class ExperimentQueryImpl implements IExperimentQuery { public Optional findByExperimentId(String experimentId) { return experimentRepository.findByExperimentId(experimentId); } + + @Override + public void save(ExperimentEntity experiment) { + experimentRepository.save(experiment); + } } diff --git a/litmus-db/src/main/java/com/navi/medici/query/experiment/IExperimentQuery.java b/litmus-db/src/main/java/com/navi/medici/query/experiment/IExperimentQuery.java index 384953a..0a05d53 100644 --- a/litmus-db/src/main/java/com/navi/medici/query/experiment/IExperimentQuery.java +++ b/litmus-db/src/main/java/com/navi/medici/query/experiment/IExperimentQuery.java @@ -10,4 +10,6 @@ public interface IExperimentQuery { Optional findByName(String name); Optional findByExperimentId(String experimentId); + + void save(ExperimentEntity experiment); } diff --git a/litmus-liquibase/src/main/resources/db/changelog/202110061546-create-experiment-table.sql b/litmus-liquibase/src/main/resources/db/changelog/202110061546-create-experiment-table.sql new file mode 100644 index 0000000..acd12c8 --- /dev/null +++ b/litmus-liquibase/src/main/resources/db/changelog/202110061546-create-experiment-table.sql @@ -0,0 +1,23 @@ +--liquibase formatted sql + +--changeset author:chandresh id:202110061546 +CREATE TABLE experiments ( + id SERIAL PRIMARY KEY, + experiment_id varchar(36) NOT NULL, + name varchar(100) NOT NULL, + description text NOT NULL, + enabled boolean, + archived boolean, + strategies jsonb, + variants jsonb, + experiment_type varchar(100) NOT NULL, + start_time timestamp, + end_time timestamp, + version BIGINT, + created_at timestamp, + updated_at timestamp +); + + +CREATE INDEX idx_experiment_id ON experiments(experiment_id); +CREATE INDEX idx_experiment_name ON experiments(name); diff --git a/litmus-mock/src/main/java/com/navi/medici/container/CustomLitmusContextProvider.java b/litmus-mock/src/main/java/com/navi/medici/container/CustomLitmusContextProvider.java new file mode 100644 index 0000000..b3e7588 --- /dev/null +++ b/litmus-mock/src/main/java/com/navi/medici/container/CustomLitmusContextProvider.java @@ -0,0 +1,21 @@ +package com.navi.medici.container; + +import com.navi.medici.context.LitmusContext; +import com.navi.medici.provider.LitmusContextProvider; +import java.util.Random; +import lombok.extern.log4j.Log4j2; +import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.RequestScope; + +@Component +@RequestScope +@Log4j2 +public class CustomLitmusContextProvider implements LitmusContextProvider { + + @Override + public LitmusContext getContext() { + return LitmusContext.builder() + .userId("test1") + .build(); + } +} \ No newline at end of file diff --git a/litmus-mock/src/main/java/com/navi/medici/container/MockContainer.java b/litmus-mock/src/main/java/com/navi/medici/container/MockContainer.java index eab925b..e7a84e2 100644 --- a/litmus-mock/src/main/java/com/navi/medici/container/MockContainer.java +++ b/litmus-mock/src/main/java/com/navi/medici/container/MockContainer.java @@ -12,9 +12,10 @@ public class MockContainer { @Bean public Litmus litmus() { var litmusConfig = LitmusConfig.builder() - .litmusAPI("http://localhost:8080/v1") + .litmusAPI("http://localhost:12000/v1") .appName("litmus-mock") .instanceId("test-instance") + .litmusContextProvider(new CustomLitmusContextProvider()) .build(); Litmus unleash = new DefaultLitmus(litmusConfig); diff --git a/litmus-model/pom.xml b/litmus-model/pom.xml index 4ea32cf..78c6d0d 100644 --- a/litmus-model/pom.xml +++ b/litmus-model/pom.xml @@ -52,6 +52,13 @@ 5.5.2 + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + 2.13.0 + + + diff --git a/litmus-model/src/main/java/com/navi/medici/context/LitmusContext.java b/litmus-model/src/main/java/com/navi/medici/context/LitmusContext.java index e782dc1..bd537d3 100644 --- a/litmus-model/src/main/java/com/navi/medici/context/LitmusContext.java +++ b/litmus-model/src/main/java/com/navi/medici/context/LitmusContext.java @@ -1,8 +1,10 @@ package com.navi.medici.context; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import java.util.HashMap; import java.util.Map; import java.util.Optional; +import javax.annotation.Nullable; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Builder; @@ -12,23 +14,19 @@ import lombok.Setter; import lombok.experimental.FieldDefaults; @AllArgsConstructor -@NoArgsConstructor -@Builder @Getter @Setter @JsonIgnoreProperties(ignoreUnknown = true) @FieldDefaults(level = AccessLevel.PRIVATE) public class LitmusContext { - - Optional environment; Optional appName; + Optional environment; Optional userId; Optional sessionId; Optional remoteAddress; Optional appVersionCode; Optional osType; Optional deviceId; - Map properties; public Optional getByName(String contextName) { @@ -53,4 +51,88 @@ public class LitmusContext { return Optional.ofNullable(properties.get(contextName)); } } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + @Nullable private String appName; + @Nullable private String environment; + @Nullable private String userId; + @Nullable private String sessionId; + @Nullable private String remoteAddress; + @Nullable private String appVersionCode; + @Nullable private String osType; + @Nullable private String deviceId; + + private final Map properties = new HashMap<>(); + + public Builder() {} + + public Builder(LitmusContext context) { + context.appName.ifPresent(val -> this.appName = val); + context.environment.ifPresent(val -> this.environment = val); + context.userId.ifPresent(val -> this.userId = val); + context.sessionId.ifPresent(val -> this.sessionId = val); + context.remoteAddress.ifPresent(val -> this.remoteAddress = val); + context.appVersionCode.ifPresent(val -> this.appVersionCode = val); + context.osType.ifPresent(val -> this.osType = val); + context.deviceId.ifPresent(val -> this.deviceId = val); + this.properties.putAll(context.properties); + } + + public Builder appName(String appName) { + this.appName = appName; + return this; + } + + public Builder environment(String environment) { + this.environment = environment; + return this; + } + + public Builder userId(String userId) { + this.userId = userId; + return this; + } + + public Builder sessionId(String sessionId) { + this.sessionId = sessionId; + return this; + } + + public Builder remoteAddress(String remoteAddress) { + this.remoteAddress = remoteAddress; + return this; + } + + public Builder appVersionCode(String appVersionCode) { + this.appVersionCode = appVersionCode; + return this; + } + + public Builder osType(String osType) { + this.osType = osType; + return this; + } + + public Builder deviceId(String deviceId) { + this.deviceId = deviceId; + return this; + } + + public Builder addProperty(String name, String value) { + properties.put(name, value); + return this; + } + + public LitmusContext build() { + return new LitmusContext( + Optional.ofNullable(environment), Optional.ofNullable(appName),Optional.ofNullable(userId), + Optional.ofNullable(sessionId), Optional.ofNullable( remoteAddress), Optional.ofNullable( appVersionCode), + Optional.ofNullable(osType), Optional.ofNullable(deviceId), properties); + } + } + } diff --git a/litmus-model/src/main/java/com/navi/medici/experiment/LitmusExperiment.java b/litmus-model/src/main/java/com/navi/medici/experiment/LitmusExperiment.java deleted file mode 100644 index 0131812..0000000 --- a/litmus-model/src/main/java/com/navi/medici/experiment/LitmusExperiment.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.navi.medici.experiment; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.navi.medici.strategy.ActivationStrategy; -import java.util.List; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; -import lombok.experimental.FieldDefaults; - -@AllArgsConstructor -@NoArgsConstructor -@Builder -@Getter -@Setter -@JsonIgnoreProperties(ignoreUnknown = true) -@FieldDefaults(level = AccessLevel.PRIVATE) -public class LitmusExperiment { - - String name; - boolean enabled; - List strategies; -// @Nullable -// List variants; -} diff --git a/litmus-model/src/main/java/com/navi/medici/request/v1/ExperimentRequest.java b/litmus-model/src/main/java/com/navi/medici/request/v1/LitmusExperiment.java similarity index 74% rename from litmus-model/src/main/java/com/navi/medici/request/v1/ExperimentRequest.java rename to litmus-model/src/main/java/com/navi/medici/request/v1/LitmusExperiment.java index 1792c5e..9612844 100644 --- a/litmus-model/src/main/java/com/navi/medici/request/v1/ExperimentRequest.java +++ b/litmus-model/src/main/java/com/navi/medici/request/v1/LitmusExperiment.java @@ -2,7 +2,10 @@ package com.navi.medici.request.v1; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +import com.navi.medici.enums.ExperimentType; +import com.navi.medici.strategy.ActivationStrategy; import java.time.LocalDateTime; +import java.util.List; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Builder; @@ -18,7 +21,10 @@ import lombok.experimental.FieldDefaults; @Setter @JsonIgnoreProperties(ignoreUnknown = true) @FieldDefaults(level = AccessLevel.PRIVATE) -public class ExperimentRequest { +public class LitmusExperiment { + @JsonProperty("experiment_id") + String experimentId; + @JsonProperty("name") String name; @@ -32,17 +38,21 @@ public class ExperimentRequest { Boolean archived; @JsonProperty("strategies") - String strategies; + List strategies; @JsonProperty("variants") String variants; @JsonProperty("type") - String type; + ExperimentType type; @JsonProperty("start_time") LocalDateTime startTime; @JsonProperty("end_time") LocalDateTime endTime; + + public boolean isEnabled() { + return enabled; + } } diff --git a/litmus-model/src/main/java/com/navi/medici/response/LitmusExperimentCollection.java b/litmus-model/src/main/java/com/navi/medici/response/LitmusExperimentCollection.java index aa7a27d..36857c3 100644 --- a/litmus-model/src/main/java/com/navi/medici/response/LitmusExperimentCollection.java +++ b/litmus-model/src/main/java/com/navi/medici/response/LitmusExperimentCollection.java @@ -1,26 +1,41 @@ package com.navi.medici.response; -import com.navi.medici.experiment.LitmusExperiment; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.navi.medici.request.v1.LitmusExperiment; import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import javax.annotation.Nullable; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.experimental.FieldDefaults; +@AllArgsConstructor +@NoArgsConstructor +@Builder +@Getter +@Setter +@JsonIgnoreProperties(ignoreUnknown = true) +@FieldDefaults(level = AccessLevel.PRIVATE) public final class LitmusExperimentCollection { - private final Collection litmusExperiments; - private final int version = 1; // required for serialization - private final transient Map cache; + Collection litmusExperiments; + int version = 1; // required for serialization +// Map cache; - public LitmusExperimentCollection(final Collection litmusExperiments) { - this.litmusExperiments = ensureNotNull(litmusExperiments); - this.cache = new ConcurrentHashMap<>(); - for (LitmusExperiment litmusExperiment : this.litmusExperiments) { - cache.put(litmusExperiment.getName(), litmusExperiment); - } - } +// public LitmusExperimentCollection(final Collection litmusExperiments) { +// this.litmusExperiments = ensureNotNull(litmusExperiments); +// this.cache = new ConcurrentHashMap<>(); +// for (LitmusExperiment litmusExperiment : this.litmusExperiments) { +// cache.put(litmusExperiment.getName(), litmusExperiment); +// } +// } private Collection ensureNotNull(@Nullable Collection litmusExperiments) { if (litmusExperiments == null) { @@ -33,7 +48,5 @@ public final class LitmusExperimentCollection { return Collections.unmodifiableCollection(litmusExperiments); } - public LitmusExperiment getLitmusExperiment(final String name) { - return cache.get(name); - } + } \ No newline at end of file diff --git a/litmus-model/src/main/java/com/navi/medici/response/LitmusExperimentResponse.java b/litmus-model/src/main/java/com/navi/medici/response/LitmusExperimentResponse.java index 1130130..19c8e98 100644 --- a/litmus-model/src/main/java/com/navi/medici/response/LitmusExperimentResponse.java +++ b/litmus-model/src/main/java/com/navi/medici/response/LitmusExperimentResponse.java @@ -1,8 +1,9 @@ package com.navi.medici.response; -import com.navi.medici.experiment.LitmusExperiment; +import com.navi.medici.request.v1.LitmusExperiment; import java.util.Collections; import java.util.List; +import java.util.concurrent.ConcurrentHashMap; import javax.annotation.Nullable; public class LitmusExperimentResponse { @@ -29,7 +30,10 @@ public class LitmusExperimentResponse { this.status = status; this.httpStatusCode = httpStatusCode; List emptyList = Collections.emptyList(); - this.experimentCollection = new LitmusExperimentCollection(emptyList); + this.experimentCollection = LitmusExperimentCollection.builder() + .litmusExperiments(emptyList) + .build(); + } public LitmusExperimentResponse(Status status, int httpStatusCode, @Nullable String location) { diff --git a/litmus-model/src/main/java/com/navi/medici/response/LitmusResponse.java b/litmus-model/src/main/java/com/navi/medici/response/LitmusResponse.java new file mode 100644 index 0000000..066ecb4 --- /dev/null +++ b/litmus-model/src/main/java/com/navi/medici/response/LitmusResponse.java @@ -0,0 +1,34 @@ +package com.navi.medici.response; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; +import java.time.LocalDateTime; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.experimental.FieldDefaults; + +@AllArgsConstructor +@NoArgsConstructor +@Builder +@Getter +@Setter +@JsonIgnoreProperties(ignoreUnknown = true) +@FieldDefaults(level = AccessLevel.PRIVATE) +public class LitmusResponse { + int code; + + T data; + + String message; + + @JsonSerialize(using = LocalDateTimeSerializer.class) + @JsonDeserialize(using = LocalDateTimeDeserializer.class) + LocalDateTime timestamp; +} diff --git a/litmus-model/src/main/java/com/navi/medici/strategy/ActivationStrategy.java b/litmus-model/src/main/java/com/navi/medici/strategy/ActivationStrategy.java index 0c916d4..b2f584d 100644 --- a/litmus-model/src/main/java/com/navi/medici/strategy/ActivationStrategy.java +++ b/litmus-model/src/main/java/com/navi/medici/strategy/ActivationStrategy.java @@ -1,37 +1,29 @@ package com.navi.medici.strategy; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.navi.medici.constraint.Constraint; import java.util.Collections; import java.util.List; import java.util.Map; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.experimental.FieldDefaults; -public final class ActivationStrategy { +@AllArgsConstructor +@NoArgsConstructor +@Builder +@Getter +@Setter +@JsonIgnoreProperties(ignoreUnknown = true) +@FieldDefaults(level = AccessLevel.PRIVATE) +public class ActivationStrategy { - private final String name; - private final Map parameters; - private final List constraints; - - public ActivationStrategy(String name, Map parameters) { - this(name, parameters, Collections.emptyList()); - } - - public ActivationStrategy( - String name, Map parameters, List constraints) { - this.name = name; - this.parameters = parameters; - this.constraints = constraints; - } - - public String getName() { - return name; - } - - public Map getParameters() { - return parameters; - } - - public List getConstraints() { - return constraints; - } + String name; + Map parameters; + List constraints = Collections.emptyList(); }