From 66b16718d7e5e30473e161db9c73768a18b1df26 Mon Sep 17 00:00:00 2001 From: Jamie Sinn Date: Mon, 17 Jun 2024 15:39:28 -0400 Subject: [PATCH] autoformat --- .../devcycle/examples/OpenFeatureExample.java | 4 +- .../cloud/api/DevCycleCloudApiClient.java | 63 ++- .../server/cloud/api/DevCycleCloudClient.java | 3 +- .../cloud/model/DevCycleCloudOptions.java | 4 +- .../sdk/server/common/api/APIUtils.java | 10 +- .../sdk/server/common/api/IDevCycleApi.java | 120 ++--- .../sdk/server/common/api/IRestOptions.java | 3 +- .../AuthorizationHeaderInterceptor.java | 2 +- .../interceptor/CustomHeaderInterceptor.java | 6 +- .../server/common/logging/DevCycleLogger.java | 20 +- .../common/logging/IDevCycleLogger.java | 4 + .../common/logging/SimpleDevCycleLogger.java | 29 +- .../sdk/server/common/model/BaseVariable.java | 4 +- .../server/common/model/DevCycleEvent.java | 22 +- .../server/common/model/DevCycleResponse.java | 6 +- .../common/model/DevCycleUserAndEvents.java | 64 +-- .../server/common/model/ErrorResponse.java | 14 +- .../sdk/server/common/model/Feature.java | 87 ++-- .../server/common/model/HttpResponseCode.java | 8 +- .../server/common/model/IDevCycleOptions.java | 3 +- .../sdk/server/common/model/PlatformData.java | 48 +- .../server/common/model/ProjectConfig.java | 24 +- .../sdk/server/common/model/Variable.java | 87 ++-- .../local/api/DevCycleLocalApiClient.java | 76 ++- .../server/local/api/DevCycleLocalClient.java | 460 +++++++++--------- .../api/DevCycleLocalEventsApiClient.java | 10 +- .../local/bucketing/LocalBucketing.java | 54 +- .../local/managers/DaemonThreadFactory.java | 4 +- .../managers/EnvironmentConfigManager.java | 293 ++++++----- .../local/managers/EventQueueManager.java | 35 +- .../local/model/BucketedUserConfig.java | 2 +- .../local/model/DevCycleLocalOptions.java | 24 +- .../sdk/server/local/model/FlushPayload.java | 6 +- .../sdk/server/local/model/Project.java | 10 +- .../sdk/server/local/model/RequestEvent.java | 1 - .../local/utils/ByteConversionUtils.java | 1 + .../utils/LongTimestampDeserializer.java | 1 + .../sdk/server/local/utils/ProtobufUtils.java | 12 +- .../server/cloud/DevCycleCloudClientTest.java | 29 +- .../sdk/server/common/api/DVCApiMock.java | 5 +- .../sdk/server/helpers/LocalConfigServer.java | 10 +- .../sdk/server/helpers/TestDataFixtures.java | 6 +- .../sdk/server/helpers/TestResponse.java | 12 +- .../server/local/DevCycleLocalClientTest.java | 59 ++- .../sdk/server/local/LocalBucketingTest.java | 33 +- .../DevCycleProviderLocalSDKTest.java | 2 +- .../server/utils/ByteConversionUtilsTest.java | 7 +- src/test/resources/fixture_small_config.json | 2 +- ...xture_small_config_special_characters.json | 2 +- 49 files changed, 882 insertions(+), 909 deletions(-) diff --git a/src/examples/java/com/devcycle/examples/OpenFeatureExample.java b/src/examples/java/com/devcycle/examples/OpenFeatureExample.java index 9e671ccf..ceb23d0f 100644 --- a/src/examples/java/com/devcycle/examples/OpenFeatureExample.java +++ b/src/examples/java/com/devcycle/examples/OpenFeatureExample.java @@ -45,12 +45,12 @@ public static void main(String[] args) throws InterruptedException { context.add("deviceModel", "Macbook"); // Add Devcycle Custom Data values - Map customData = new LinkedHashMap<>(); + Map customData = new LinkedHashMap<>(); customData.put("custom", "value"); context.add("customData", Structure.mapToStructure(customData)); // Add Devcycle Private Custom Data values - Map privateCustomData = new LinkedHashMap<>(); + Map privateCustomData = new LinkedHashMap<>(); privateCustomData.put("private", "data"); context.add("privateCustomData", Structure.mapToStructure(privateCustomData)); diff --git a/src/main/java/com/devcycle/sdk/server/cloud/api/DevCycleCloudApiClient.java b/src/main/java/com/devcycle/sdk/server/cloud/api/DevCycleCloudApiClient.java index 91c8b839..ef40c75e 100755 --- a/src/main/java/com/devcycle/sdk/server/cloud/api/DevCycleCloudApiClient.java +++ b/src/main/java/com/devcycle/sdk/server/cloud/api/DevCycleCloudApiClient.java @@ -4,10 +4,9 @@ import com.devcycle.sdk.server.common.api.APIUtils; import com.devcycle.sdk.server.common.api.IDevCycleApi; import com.devcycle.sdk.server.common.interceptor.AuthorizationHeaderInterceptor; -import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.annotation.JsonInclude; - -import okhttp3.*; +import com.fasterxml.jackson.databind.ObjectMapper; +import okhttp3.OkHttpClient; import retrofit2.Retrofit; import retrofit2.converter.jackson.JacksonConverterFactory; @@ -15,43 +14,41 @@ public final class DevCycleCloudApiClient { - private final OkHttpClient.Builder okBuilder; - private final Retrofit.Builder adapterBuilder; + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private static final String BUCKETING_URL = "https://bucketing-api.devcycle.com/"; + private final OkHttpClient.Builder okBuilder; + private final Retrofit.Builder adapterBuilder; + private String bucketingUrl; - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + public DevCycleCloudApiClient(String apiKey, DevCycleCloudOptions options) { + OBJECT_MAPPER.setSerializationInclusion(JsonInclude.Include.NON_NULL); + okBuilder = new OkHttpClient.Builder(); - private static final String BUCKETING_URL = "https://bucketing-api.devcycle.com/"; - private String bucketingUrl; + APIUtils.applyRestOptions(options.getRestOptions(), okBuilder); - public DevCycleCloudApiClient(String apiKey, DevCycleCloudOptions options) { - OBJECT_MAPPER.setSerializationInclusion(JsonInclude.Include.NON_NULL); - okBuilder = new OkHttpClient.Builder(); + okBuilder.addInterceptor(new AuthorizationHeaderInterceptor(apiKey)); - APIUtils.applyRestOptions(options.getRestOptions(), okBuilder); + if (!isStringNullOrEmpty(options.getBaseURLOverride())) { + bucketingUrl = options.getBaseURLOverride(); + } else { + bucketingUrl = BUCKETING_URL; + } - okBuilder.addInterceptor(new AuthorizationHeaderInterceptor(apiKey)); + bucketingUrl = bucketingUrl.endsWith("/") ? bucketingUrl : bucketingUrl + "/"; - if (!isStringNullOrEmpty(options.getBaseURLOverride())) { - bucketingUrl = options.getBaseURLOverride(); - } else { - bucketingUrl = BUCKETING_URL; + adapterBuilder = new Retrofit.Builder() + .baseUrl(bucketingUrl) + .addConverterFactory(JacksonConverterFactory.create()); } - bucketingUrl = bucketingUrl.endsWith("/") ? bucketingUrl : bucketingUrl + "/"; - - adapterBuilder = new Retrofit.Builder() - .baseUrl(bucketingUrl) - .addConverterFactory(JacksonConverterFactory.create()); - } - - public IDevCycleApi initialize() { - return adapterBuilder - .client(okBuilder.build()) - .build() - .create(IDevCycleApi.class); - } + public IDevCycleApi initialize() { + return adapterBuilder + .client(okBuilder.build()) + .build() + .create(IDevCycleApi.class); + } - private Boolean isStringNullOrEmpty(String stringToCheck) { - return Objects.isNull(stringToCheck) || Objects.equals(stringToCheck, ""); - } + private Boolean isStringNullOrEmpty(String stringToCheck) { + return Objects.isNull(stringToCheck) || Objects.equals(stringToCheck, ""); + } } diff --git a/src/main/java/com/devcycle/sdk/server/cloud/api/DevCycleCloudClient.java b/src/main/java/com/devcycle/sdk/server/cloud/api/DevCycleCloudClient.java index 2f887415..c45e3b01 100755 --- a/src/main/java/com/devcycle/sdk/server/cloud/api/DevCycleCloudClient.java +++ b/src/main/java/com/devcycle/sdk/server/cloud/api/DevCycleCloudClient.java @@ -23,12 +23,11 @@ public final class DevCycleCloudClient implements IDevCycleClient { + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); private final IDevCycleApi api; private final DevCycleCloudOptions dvcOptions; private final DevCycleProvider openFeatureProvider; - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); - public DevCycleCloudClient(String sdkKey) { this(sdkKey, DevCycleCloudOptions.builder().build()); } diff --git a/src/main/java/com/devcycle/sdk/server/cloud/model/DevCycleCloudOptions.java b/src/main/java/com/devcycle/sdk/server/cloud/model/DevCycleCloudOptions.java index dcee707e..3c31c15d 100644 --- a/src/main/java/com/devcycle/sdk/server/cloud/model/DevCycleCloudOptions.java +++ b/src/main/java/com/devcycle/sdk/server/cloud/model/DevCycleCloudOptions.java @@ -3,7 +3,6 @@ import com.devcycle.sdk.server.common.api.IRestOptions; import com.devcycle.sdk.server.common.logging.IDevCycleLogger; import com.devcycle.sdk.server.common.model.IDevCycleOptions; - import lombok.Builder; import lombok.Data; @@ -22,5 +21,6 @@ public class DevCycleCloudOptions { @Builder.Default private IRestOptions restOptions = null; - public static class DevCycleCloudOptionsBuilder implements IDevCycleOptions { } + public static class DevCycleCloudOptionsBuilder implements IDevCycleOptions { + } } diff --git a/src/main/java/com/devcycle/sdk/server/common/api/APIUtils.java b/src/main/java/com/devcycle/sdk/server/common/api/APIUtils.java index 326ea5b0..536860a7 100644 --- a/src/main/java/com/devcycle/sdk/server/common/api/APIUtils.java +++ b/src/main/java/com/devcycle/sdk/server/common/api/APIUtils.java @@ -4,15 +4,13 @@ import okhttp3.OkHttpClient; public class APIUtils { - public static void applyRestOptions(IRestOptions restOptions, OkHttpClient.Builder builder) - { - if(restOptions != null) - { - if(restOptions.getHostnameVerifier() != null){ + public static void applyRestOptions(IRestOptions restOptions, OkHttpClient.Builder builder) { + if (restOptions != null) { + if (restOptions.getHostnameVerifier() != null) { builder.hostnameVerifier(restOptions.getHostnameVerifier()); } - if(restOptions.getSocketFactory() != null && restOptions.getTrustManager() != null){ + if (restOptions.getSocketFactory() != null && restOptions.getTrustManager() != null) { builder.sslSocketFactory(restOptions.getSocketFactory(), restOptions.getTrustManager()); } builder.addInterceptor(new CustomHeaderInterceptor(restOptions)); diff --git a/src/main/java/com/devcycle/sdk/server/common/api/IDevCycleApi.java b/src/main/java/com/devcycle/sdk/server/common/api/IDevCycleApi.java index 1808f64d..98ec779f 100755 --- a/src/main/java/com/devcycle/sdk/server/common/api/IDevCycleApi.java +++ b/src/main/java/com/devcycle/sdk/server/common/api/IDevCycleApi.java @@ -8,70 +8,70 @@ import java.util.Map; public interface IDevCycleApi { - /** - * Get all features by key for user data - * - * @param user (required) - * @param enableEdgeDB (required) - * @return Call<Map<String, Feature>> - */ - @Headers({"Content-Type:application/json"}) - @POST("v1/features") - Call> getFeatures(@Body DevCycleUser user, @Query("enableEdgeDB") Boolean enableEdgeDB); + /** + * Get all features by key for user data + * + * @param user (required) + * @param enableEdgeDB (required) + * @return Call<Map<String, Feature>> + */ + @Headers({"Content-Type:application/json"}) + @POST("v1/features") + Call> getFeatures(@Body DevCycleUser user, @Query("enableEdgeDB") Boolean enableEdgeDB); - /** - * Get variable by key for user data - * - * @param user (required) - * @param key Variable key (required) - * @param enableEdgeDB (required) - * @return Call<Variable> - */ - @Headers({"Content-Type:application/json"}) - @POST("v1/variables/{key}") - Call getVariableByKey(@Body DevCycleUser user, @Path("key") String key, @Query("enableEdgeDB") Boolean enableEdgeDB); + /** + * Get variable by key for user data + * + * @param user (required) + * @param key Variable key (required) + * @param enableEdgeDB (required) + * @return Call<Variable> + */ + @Headers({"Content-Type:application/json"}) + @POST("v1/variables/{key}") + Call getVariableByKey(@Body DevCycleUser user, @Path("key") String key, @Query("enableEdgeDB") Boolean enableEdgeDB); - /** - * Get all variables by key for user data - * - * @param user (required) - * @param enableEdgeDB (required) - * @return Call<Map<String, Variable>> - */ - @Headers({"Content-Type:application/json"}) - @POST("v1/variables") - Call> getVariables(@Body DevCycleUser user, @Query("enableEdgeDB") Boolean enableEdgeDB); + /** + * Get all variables by key for user data + * + * @param user (required) + * @param enableEdgeDB (required) + * @return Call<Map<String, Variable>> + */ + @Headers({"Content-Type:application/json"}) + @POST("v1/variables") + Call> getVariables(@Body DevCycleUser user, @Query("enableEdgeDB") Boolean enableEdgeDB); - /** - * Post events to DevCycle for user - * - * @param userAndEvents (required) - * @param enableEdgeDB (required) - * @return Call<DevCycleResponse> - */ - @Headers({"Content-Type:application/json"}) - @POST("v1/track") - Call track(@Body DevCycleUserAndEvents userAndEvents, @Query("enableEdgeDB") Boolean enableEdgeDB); + /** + * Post events to DevCycle for user + * + * @param userAndEvents (required) + * @param enableEdgeDB (required) + * @return Call<DevCycleResponse> + */ + @Headers({"Content-Type:application/json"}) + @POST("v1/track") + Call track(@Body DevCycleUserAndEvents userAndEvents, @Query("enableEdgeDB") Boolean enableEdgeDB); - /** - * Get DevCycle project Config - * - * @param sdkToken (required) - * @param etag (nullable) - * @return Call<ProjectConfig> - */ - @Headers({"Content-Type:application/json"}) - @GET("config/v1/server/{sdkToken}.json") - Call getConfig(@Path("sdkToken") String sdkToken, @Header("If-None-Match") String etag); + /** + * Get DevCycle project Config + * + * @param sdkToken (required) + * @param etag (nullable) + * @return Call<ProjectConfig> + */ + @Headers({"Content-Type:application/json"}) + @GET("config/v1/server/{sdkToken}.json") + Call getConfig(@Path("sdkToken") String sdkToken, @Header("If-None-Match") String etag); - /** - * Post events to DevCycle for user - * - * @param eventsBatch (required) - * @return Call<DevCycleResponse> - */ - @Headers({"Content-Type:application/json"}) - @POST("v1/events/batch") - Call publishEvents(@Body EventsBatch eventsBatch); + /** + * Post events to DevCycle for user + * + * @param eventsBatch (required) + * @return Call<DevCycleResponse> + */ + @Headers({"Content-Type:application/json"}) + @POST("v1/events/batch") + Call publishEvents(@Body EventsBatch eventsBatch); } diff --git a/src/main/java/com/devcycle/sdk/server/common/api/IRestOptions.java b/src/main/java/com/devcycle/sdk/server/common/api/IRestOptions.java index d8735c41..ccc444df 100644 --- a/src/main/java/com/devcycle/sdk/server/common/api/IRestOptions.java +++ b/src/main/java/com/devcycle/sdk/server/common/api/IRestOptions.java @@ -13,7 +13,7 @@ public interface IRestOptions { /** * @return A set of HTTP request headers that should be incorporated into all outgoing requests. return null if no headers are needed. */ - Map getHeaders(); + Map getHeaders(); /** * @return A custom SSLSocketFactory to use when making requests. Return null if the default SSLSocket factory can be used @@ -21,7 +21,6 @@ public interface IRestOptions { SSLSocketFactory getSocketFactory(); /** - * * @return Provide a trust manager to handle custom certificates. Return null if the default trust manager can be used */ X509TrustManager getTrustManager(); diff --git a/src/main/java/com/devcycle/sdk/server/common/interceptor/AuthorizationHeaderInterceptor.java b/src/main/java/com/devcycle/sdk/server/common/interceptor/AuthorizationHeaderInterceptor.java index 36fbd0cc..a644a243 100755 --- a/src/main/java/com/devcycle/sdk/server/common/interceptor/AuthorizationHeaderInterceptor.java +++ b/src/main/java/com/devcycle/sdk/server/common/interceptor/AuthorizationHeaderInterceptor.java @@ -24,7 +24,7 @@ public Response intercept(Chain chain) throws IOException { // Is there already an authorization header on the request? // If so, we need to rename ours to avoid conflicts - if(request.header(AUTHORIZATION_HEADER) != null) { + if (request.header(AUTHORIZATION_HEADER) != null) { headerName = "DevCycle-" + AUTHORIZATION_HEADER; } diff --git a/src/main/java/com/devcycle/sdk/server/common/interceptor/CustomHeaderInterceptor.java b/src/main/java/com/devcycle/sdk/server/common/interceptor/CustomHeaderInterceptor.java index 32255be8..e135bf5a 100644 --- a/src/main/java/com/devcycle/sdk/server/common/interceptor/CustomHeaderInterceptor.java +++ b/src/main/java/com/devcycle/sdk/server/common/interceptor/CustomHeaderInterceptor.java @@ -13,7 +13,7 @@ * implementation */ public final class CustomHeaderInterceptor implements Interceptor { - private IRestOptions restOptions; + private final IRestOptions restOptions; public CustomHeaderInterceptor(IRestOptions restOptions) { this.restOptions = restOptions; @@ -23,11 +23,11 @@ public CustomHeaderInterceptor(IRestOptions restOptions) { public Response intercept(Chain chain) throws IOException { Request request = chain.request(); - if(restOptions != null) { + if (restOptions != null) { Request.Builder builder = request.newBuilder(); for (Map.Entry entry : restOptions.getHeaders().entrySet()) { - if(entry.getValue() != null) { + if (entry.getValue() != null) { builder.addHeader(entry.getKey(), entry.getValue()); } } diff --git a/src/main/java/com/devcycle/sdk/server/common/logging/DevCycleLogger.java b/src/main/java/com/devcycle/sdk/server/common/logging/DevCycleLogger.java index 3a7b353a..0acbeb6d 100644 --- a/src/main/java/com/devcycle/sdk/server/common/logging/DevCycleLogger.java +++ b/src/main/java/com/devcycle/sdk/server/common/logging/DevCycleLogger.java @@ -1,42 +1,44 @@ package com.devcycle.sdk.server.common.logging; + /** * DevCycleLogger is a simple central entrypoint for the SDK to log messages without pinning the SDK to a * specific logging framework. By default it logs to stdout but can e overriden by calling setCustomLogger() */ public class DevCycleLogger { private static IDevCycleLogger logger = new SimpleDevCycleLogger(SimpleDevCycleLogger.Level.INFO); + public static void setCustomLogger(IDevCycleLogger logger) { DevCycleLogger.logger = logger; } public static void debug(String message) { - if(logger != null){ + if (logger != null) { logger.debug(message); } } public static void info(String message) { - if(logger != null) { + if (logger != null) { logger.info(message); } } public static void warning(String message) { - if(logger != null) { + if (logger != null) { logger.warning(message); } } public static void error(String message) { - if(logger != null) { - logger.error(message); - } + if (logger != null) { + logger.error(message); + } } public static void error(String message, Throwable t) { - if(logger != null) { - logger.error(message, t); - } + if (logger != null) { + logger.error(message, t); + } } } diff --git a/src/main/java/com/devcycle/sdk/server/common/logging/IDevCycleLogger.java b/src/main/java/com/devcycle/sdk/server/common/logging/IDevCycleLogger.java index 31c3b468..0b4c3068 100644 --- a/src/main/java/com/devcycle/sdk/server/common/logging/IDevCycleLogger.java +++ b/src/main/java/com/devcycle/sdk/server/common/logging/IDevCycleLogger.java @@ -6,8 +6,12 @@ */ public interface IDevCycleLogger { void debug(String message); + void info(String message); + void warning(String message); + void error(String message); + void error(String message, Throwable t); } diff --git a/src/main/java/com/devcycle/sdk/server/common/logging/SimpleDevCycleLogger.java b/src/main/java/com/devcycle/sdk/server/common/logging/SimpleDevCycleLogger.java index fe87b0d7..5a1f3295 100644 --- a/src/main/java/com/devcycle/sdk/server/common/logging/SimpleDevCycleLogger.java +++ b/src/main/java/com/devcycle/sdk/server/common/logging/SimpleDevCycleLogger.java @@ -4,51 +4,52 @@ * Basic implementation of IDevCycleLogger that logs to stdout with some basic log level filtering. */ public class SimpleDevCycleLogger implements IDevCycleLogger { - public enum Level { - DEBUG, - INFO, - WARNING, - ERROR, - OFF, - } - private Level level; + private final Level level; + public SimpleDevCycleLogger(Level level) { this.level = level; } @Override public void debug(String message) { - if(this.level.ordinal() == Level.DEBUG.ordinal()) - { + if (this.level.ordinal() == Level.DEBUG.ordinal()) { System.out.println(message); } } @Override public void info(String message) { - if(this.level.ordinal() <= Level.INFO.ordinal()) { + if (this.level.ordinal() <= Level.INFO.ordinal()) { System.out.println(message); } } @Override public void warning(String message) { - if(this.level.ordinal() <= Level.WARNING.ordinal()) { + if (this.level.ordinal() <= Level.WARNING.ordinal()) { System.out.println(message); } } @Override public void error(String message) { - if(this.level.ordinal() <= Level.ERROR.ordinal()) { + if (this.level.ordinal() <= Level.ERROR.ordinal()) { System.out.println(message); } } @Override public void error(String message, Throwable t) { - if(this.level.ordinal() <= Level.ERROR.ordinal()) { + if (this.level.ordinal() <= Level.ERROR.ordinal()) { System.out.println(message); } } + + public enum Level { + DEBUG, + INFO, + WARNING, + ERROR, + OFF, + } } diff --git a/src/main/java/com/devcycle/sdk/server/common/model/BaseVariable.java b/src/main/java/com/devcycle/sdk/server/common/model/BaseVariable.java index c0dd2dbc..1c0055c2 100644 --- a/src/main/java/com/devcycle/sdk/server/common/model/BaseVariable.java +++ b/src/main/java/com/devcycle/sdk/server/common/model/BaseVariable.java @@ -18,10 +18,10 @@ public class BaseVariable { @Schema(required = true, description = "unique database id") @JsonProperty("_id") private String id; - + @Schema(required = true, description = "Unique key by Project, can be used in the SDK / API to reference by 'key' rather than _id.") private String key; - + @Schema(required = true, description = "Variable type") private TypeEnum type; diff --git a/src/main/java/com/devcycle/sdk/server/common/model/DevCycleEvent.java b/src/main/java/com/devcycle/sdk/server/common/model/DevCycleEvent.java index 8f13e074..51180d4c 100755 --- a/src/main/java/com/devcycle/sdk/server/common/model/DevCycleEvent.java +++ b/src/main/java/com/devcycle/sdk/server/common/model/DevCycleEvent.java @@ -3,7 +3,7 @@ * Documents the DevCycle Bucketing API which provides and API interface to User Bucketing and for generated SDKs. * * OpenAPI spec version: 1.0.0 - * + * * * NOTE: This class is auto generated by the swagger code generator program. * https://github.com/swagger-api/swagger-codegen.git @@ -26,18 +26,18 @@ @NoArgsConstructor public class DevCycleEvent { - @Schema(required = true, description = "Custom event type") - private String type; + @Schema(required = true, description = "Custom event type") + private String type; - @Schema(description = "Custom event target / subject of event. Contextual to event type") - private String target; + @Schema(description = "Custom event target / subject of event. Contextual to event type") + private String target; - @Schema(description = "Unix epoch time the event occurred according to client") - private Long date; + @Schema(description = "Unix epoch time the event occurred according to client") + private Long date; - @Schema(description = "Value for numerical events. Contextual to event type") - private BigDecimal value; + @Schema(description = "Value for numerical events. Contextual to event type") + private BigDecimal value; - @Schema(description = "Extra JSON metadata for event. Contextual to event type") - private Object metaData; + @Schema(description = "Extra JSON metadata for event. Contextual to event type") + private Object metaData; } diff --git a/src/main/java/com/devcycle/sdk/server/common/model/DevCycleResponse.java b/src/main/java/com/devcycle/sdk/server/common/model/DevCycleResponse.java index 457215d9..b7154f28 100755 --- a/src/main/java/com/devcycle/sdk/server/common/model/DevCycleResponse.java +++ b/src/main/java/com/devcycle/sdk/server/common/model/DevCycleResponse.java @@ -3,7 +3,7 @@ * Documents the DevCycle Bucketing API which provides and API interface to User Bucketing and for generated SDKs. * * OpenAPI spec version: 1.0.0 - * + * * * NOTE: This class is auto generated by the swagger code generator program. * https://github.com/swagger-api/swagger-codegen.git @@ -26,6 +26,6 @@ @JsonIgnoreProperties(ignoreUnknown = true) public class DevCycleResponse { - @Schema(required = false, description = "Response message") - private String message; + @Schema(required = false, description = "Response message") + private String message; } diff --git a/src/main/java/com/devcycle/sdk/server/common/model/DevCycleUserAndEvents.java b/src/main/java/com/devcycle/sdk/server/common/model/DevCycleUserAndEvents.java index 6f8312f1..e7a19271 100755 --- a/src/main/java/com/devcycle/sdk/server/common/model/DevCycleUserAndEvents.java +++ b/src/main/java/com/devcycle/sdk/server/common/model/DevCycleUserAndEvents.java @@ -3,7 +3,7 @@ * Documents the DevCycle Bucketing API which provides and API interface to User Bucketing and for generated SDKs. * * OpenAPI spec version: 1.0.0 - * + * * * NOTE: This class is auto generated by the swagger code generator program. * https://github.com/swagger-api/swagger-codegen.git @@ -20,46 +20,46 @@ @Data public class DevCycleUserAndEvents { - private List events; + private List events; - private DevCycleUser user; + private DevCycleUser user; - public DevCycleUserAndEvents(DevCycleUser user, List events) { - this.user = user; - this.events = events; - } + public DevCycleUserAndEvents(DevCycleUser user, List events) { + this.user = user; + this.events = events; + } - public DevCycleUserAndEvents addEventItem(DevCycleEvent eventItem) { - if (this.events == null) { - this.events = new ArrayList<>(); + public static DevCycleUserAndEvents.Builder builder() { + return new DevCycleUserAndEvents.Builder(); } - this.events.add(eventItem); - return this; - } - public static DevCycleUserAndEvents.Builder builder() { - return new DevCycleUserAndEvents.Builder(); - } + public DevCycleUserAndEvents addEventItem(DevCycleEvent eventItem) { + if (this.events == null) { + this.events = new ArrayList<>(); + } + this.events.add(eventItem); + return this; + } - public static class Builder { - private DevCycleUser user; - private List events; + public static class Builder { + private DevCycleUser user; + private List events; - Builder() { - } + Builder() { + } - public DevCycleUserAndEvents.Builder user(DevCycleUser user) { - this.user = user; - return this; - } + public DevCycleUserAndEvents.Builder user(DevCycleUser user) { + this.user = user; + return this; + } - public DevCycleUserAndEvents.Builder events(List events) { - this.events = events; - return this; - } + public DevCycleUserAndEvents.Builder events(List events) { + this.events = events; + return this; + } - public DevCycleUserAndEvents build() { - return new DevCycleUserAndEvents(user, events); + public DevCycleUserAndEvents build() { + return new DevCycleUserAndEvents(user, events); + } } - } } diff --git a/src/main/java/com/devcycle/sdk/server/common/model/ErrorResponse.java b/src/main/java/com/devcycle/sdk/server/common/model/ErrorResponse.java index 8e388ce2..24234421 100755 --- a/src/main/java/com/devcycle/sdk/server/common/model/ErrorResponse.java +++ b/src/main/java/com/devcycle/sdk/server/common/model/ErrorResponse.java @@ -3,7 +3,7 @@ * Documents the DevCycle Bucketing API which provides and API interface to User Bucketing and for generated SDKs. * * OpenAPI spec version: 1.0.0 - * + * * * NOTE: This class is auto generated by the swagger code generator program. * https://github.com/swagger-api/swagger-codegen.git @@ -25,12 +25,12 @@ @NoArgsConstructor @JsonIgnoreProperties(ignoreUnknown = true) public class ErrorResponse { - @Schema(required = true, description = "HTTP Status Code") - private int statusCode; + @Schema(required = true, description = "HTTP Status Code") + private int statusCode; - @Schema(required = true, description = "Error message") - private String message; + @Schema(required = true, description = "Error message") + private String message; - @Schema(description = "Additional error information detailing the error reasoning") - private Object data; + @Schema(description = "Additional error information detailing the error reasoning") + private Object data; } diff --git a/src/main/java/com/devcycle/sdk/server/common/model/Feature.java b/src/main/java/com/devcycle/sdk/server/common/model/Feature.java index b1d424a9..6e929079 100755 --- a/src/main/java/com/devcycle/sdk/server/common/model/Feature.java +++ b/src/main/java/com/devcycle/sdk/server/common/model/Feature.java @@ -3,7 +3,7 @@ * Documents the DevCycle Bucketing API which provides and API interface to User Bucketing and for generated SDKs. * * OpenAPI spec version: 1.0.0 - * + * * * NOTE: This class is auto generated by the swagger code generator program. * https://github.com/swagger-api/swagger-codegen.git @@ -27,59 +27,60 @@ @NoArgsConstructor @JsonIgnoreProperties(ignoreUnknown = true) public class Feature { - @Schema(required = true, description = "unique database id") - @JsonProperty("_id") - private String id; + @Schema(required = true, description = "unique database id") + @JsonProperty("_id") + private String id; - @Schema(required = true, description = "Unique key by Project, can be used in the SDK / API to reference by 'key' rather than _id.") - private String key; + @Schema(required = true, description = "Unique key by Project, can be used in the SDK / API to reference by 'key' rather than _id.") + private String key; - @Schema(required = true, description = "Feature type") - private TypeEnum type; + @Schema(required = true, description = "Feature type") + private TypeEnum type; - @Schema(required = true, description = "Bucketed feature variation") - @JsonProperty("_variation") - private String variation; + @Schema(required = true, description = "Bucketed feature variation") + @JsonProperty("_variation") + private String variation; - @Schema(required = true, description = "Bucketed feature variation key") - @JsonProperty("variationKey") - private String variationKey; + @Schema(required = true, description = "Bucketed feature variation key") + @JsonProperty("variationKey") + private String variationKey; - @Schema(required = true, description = "Bucketed feature variation name") - @JsonProperty("variationName") - private String variationName; + @Schema(required = true, description = "Bucketed feature variation name") + @JsonProperty("variationName") + private String variationName; - @Schema(description = "Evaluation reasoning") - private String evalReason; + @Schema(description = "Evaluation reasoning") + private String evalReason; - public enum TypeEnum { - RELEASE("release"), - EXPERIMENT("experiment"), - PERMISSION("permission"), - OPS("ops"); + public enum TypeEnum { + RELEASE("release"), + EXPERIMENT("experiment"), + PERMISSION("permission"), + OPS("ops"); - private final String value; + private final String value; - TypeEnum(String value) { - this.value = value; - } + TypeEnum(String value) { + this.value = value; + } - @JsonValue - public String getValue() { - return value; - } + public static TypeEnum fromValue(String text) { + for (TypeEnum b : TypeEnum.values()) { + if (String.valueOf(b.value).equals(text)) { + return b; + } + } + return null; + } - @Override - public String toString() { - return String.valueOf(value); - } - public static TypeEnum fromValue(String text) { - for (TypeEnum b : TypeEnum.values()) { - if (String.valueOf(b.value).equals(text)) { - return b; + @JsonValue + public String getValue() { + return value; + } + + @Override + public String toString() { + return String.valueOf(value); } - } - return null; } - } } diff --git a/src/main/java/com/devcycle/sdk/server/common/model/HttpResponseCode.java b/src/main/java/com/devcycle/sdk/server/common/model/HttpResponseCode.java index 6ccae082..50ac4919 100644 --- a/src/main/java/com/devcycle/sdk/server/common/model/HttpResponseCode.java +++ b/src/main/java/com/devcycle/sdk/server/common/model/HttpResponseCode.java @@ -20,13 +20,13 @@ public enum HttpResponseCode { this.code = code; } - public int code() { - return code; - } - public static HttpResponseCode byCode(int code) { return Arrays.stream(HttpResponseCode.values()) .filter(httpResponseCode -> httpResponseCode.code == code) .findFirst().orElse(HttpResponseCode.SERVER_ERROR); } + + public int code() { + return code; + } } diff --git a/src/main/java/com/devcycle/sdk/server/common/model/IDevCycleOptions.java b/src/main/java/com/devcycle/sdk/server/common/model/IDevCycleOptions.java index de7eb814..555b4fe6 100644 --- a/src/main/java/com/devcycle/sdk/server/common/model/IDevCycleOptions.java +++ b/src/main/java/com/devcycle/sdk/server/common/model/IDevCycleOptions.java @@ -1,3 +1,4 @@ package com.devcycle.sdk.server.common.model; -public interface IDevCycleOptions { } +public interface IDevCycleOptions { +} diff --git a/src/main/java/com/devcycle/sdk/server/common/model/PlatformData.java b/src/main/java/com/devcycle/sdk/server/common/model/PlatformData.java index 299a4a37..8c9a47c1 100644 --- a/src/main/java/com/devcycle/sdk/server/common/model/PlatformData.java +++ b/src/main/java/com/devcycle/sdk/server/common/model/PlatformData.java @@ -17,38 +17,34 @@ @Builder @JsonInclude(JsonInclude.Include.NON_NULL) public class PlatformData { - public PlatformData(String platform, String platformVersion, SdkTypeEnum sdkType, String sdkVersion, String hostname) { - this.platform = platform; - this.platformVersion = platformVersion; - this.sdkType = sdkType; - this.sdkVersion = sdkVersion; - try { - this.hostname = hostname != null ? hostname : InetAddress.getLocalHost().getHostName(); - } catch (UnknownHostException e) { - DevCycleLogger.warning("Error getting system hostname: " + e.getMessage()); - this.hostname = ""; - } - } - @Schema(description = "Platform the SDK is running on") @Builder.Default private String platform = "Java"; - @Schema(description = "Version of the platform the SDK is running on") @Builder.Default private String platformVersion = System.getProperty("java.version"); - @Schema(description = "DevCycle SDK type") @Builder.Default private PlatformData.SdkTypeEnum sdkType = PlatformData.SdkTypeEnum.SERVER; - @Schema(description = "DevCycle SDK Version") @Builder.Default private String sdkVersion = "2.2.0"; - @Schema(description = "Hostname where the SDK is running") private String hostname; + public PlatformData(String platform, String platformVersion, SdkTypeEnum sdkType, String sdkVersion, String hostname) { + this.platform = platform; + this.platformVersion = platformVersion; + this.sdkType = sdkType; + this.sdkVersion = sdkVersion; + try { + this.hostname = hostname != null ? hostname : InetAddress.getLocalHost().getHostName(); + } catch (UnknownHostException e) { + DevCycleLogger.warning("Error getting system hostname: " + e.getMessage()); + this.hostname = ""; + } + } + @Override public String toString() { ObjectMapper mapper = new ObjectMapper(); @@ -79,6 +75,15 @@ public enum SdkTypeEnum { this.value = value; } + public static SdkTypeEnum fromValue(String text) { + for (SdkTypeEnum b : SdkTypeEnum.values()) { + if (String.valueOf(b.value).equals(text)) { + return b; + } + } + return null; + } + @JsonValue public String getValue() { return value; @@ -88,14 +93,5 @@ public String getValue() { public String toString() { return String.valueOf(value); } - - public static SdkTypeEnum fromValue(String text) { - for (SdkTypeEnum b : SdkTypeEnum.values()) { - if (String.valueOf(b.value).equals(text)) { - return b; - } - } - return null; - } } } diff --git a/src/main/java/com/devcycle/sdk/server/common/model/ProjectConfig.java b/src/main/java/com/devcycle/sdk/server/common/model/ProjectConfig.java index f7857790..32ff1ca6 100644 --- a/src/main/java/com/devcycle/sdk/server/common/model/ProjectConfig.java +++ b/src/main/java/com/devcycle/sdk/server/common/model/ProjectConfig.java @@ -13,21 +13,21 @@ @NoArgsConstructor @JsonIgnoreProperties(ignoreUnknown = true) public class ProjectConfig { - @Schema(description = "Project Settings") - private Object project; + @Schema(description = "Project Settings") + private Object project; - @Schema(description = "Environment Key & ID") - private Object environment; + @Schema(description = "Environment Key & ID") + private Object environment; - @Schema(description = "List of Features in this Project") - private Object[] features; + @Schema(description = "List of Features in this Project") + private Object[] features; - @Schema(description = "List of Variables in this Project") - private Object[] variables; + @Schema(description = "List of Variables in this Project") + private Object[] variables; - @Schema(description = "Audiences in this Project indexed by ID") - private Object audiences; + @Schema(description = "Audiences in this Project indexed by ID") + private Object audiences; - @Schema(description = "Variable Hashes for all Variables in this Project") - private Object variableHashes; + @Schema(description = "Variable Hashes for all Variables in this Project") + private Object variableHashes; } \ No newline at end of file diff --git a/src/main/java/com/devcycle/sdk/server/common/model/Variable.java b/src/main/java/com/devcycle/sdk/server/common/model/Variable.java index 8cfcedc4..326842f6 100755 --- a/src/main/java/com/devcycle/sdk/server/common/model/Variable.java +++ b/src/main/java/com/devcycle/sdk/server/common/model/Variable.java @@ -1,10 +1,6 @@ package com.devcycle.sdk.server.common.model; -import java.util.HashMap; -import java.util.LinkedHashMap; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonValue; import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; @@ -12,61 +8,64 @@ import lombok.Data; import lombok.NoArgsConstructor; +import java.util.HashMap; +import java.util.LinkedHashMap; + @Data @Builder @AllArgsConstructor @NoArgsConstructor @JsonIgnoreProperties(ignoreUnknown = true) public class Variable { - @Schema(required = true, description = "Unique key by Project, can be used in the SDK / API to reference by 'key' rather than _id.") - private String key; + @Schema(required = true, description = "Unique key by Project, can be used in the SDK / API to reference by 'key' rather than _id.") + private String key; - @Schema(required = true, description = "Variable value can be a string, number, boolean, or JSON") - private T value; + @Schema(required = true, description = "Variable value can be a string, number, boolean, or JSON") + private T value; - @Schema(required = true, description = "Variable type") - private TypeEnum type; + @Schema(required = true, description = "Variable type") + private TypeEnum type; - @Schema(required = true, description = "Variable default value") - private T defaultValue; + @Schema(required = true, description = "Variable default value") + private T defaultValue; - @Builder.Default - private Boolean isDefaulted = false; + @Builder.Default + private Boolean isDefaulted = false; - public enum TypeEnum { - STRING("String"), - BOOLEAN("Boolean"), - NUMBER("Number"), - JSON("JSON"); + public enum TypeEnum { + STRING("String"), + BOOLEAN("Boolean"), + NUMBER("Number"), + JSON("JSON"); - private final String value; + private final String value; - TypeEnum(String value) { - this.value = value; - } + TypeEnum(String value) { + this.value = value; + } - @JsonValue - public String getValue() { - return value; - } + public static TypeEnum fromClass(Class clazz) { + if (clazz == LinkedHashMap.class || clazz == HashMap.class) { + return JSON; + } else if (clazz == Boolean.class) { + return BOOLEAN; + } else if (clazz == Integer.class || clazz == Double.class || clazz == Float.class) { + return NUMBER; + } else if (clazz == String.class) { + return STRING; + } else { + return null; + } + } - @Override - public String toString() { - return String.valueOf(value); - } + @JsonValue + public String getValue() { + return value; + } - public static TypeEnum fromClass(Class clazz) { - if (clazz == LinkedHashMap.class || clazz == HashMap.class) { - return JSON; - } else if (clazz == Boolean.class) { - return BOOLEAN; - } else if (clazz == Integer.class || clazz == Double.class || clazz == Float.class) { - return NUMBER; - } else if (clazz == String.class) { - return STRING; - } else { - return null; - } + @Override + public String toString() { + return String.valueOf(value); + } } - } } diff --git a/src/main/java/com/devcycle/sdk/server/local/api/DevCycleLocalApiClient.java b/src/main/java/com/devcycle/sdk/server/local/api/DevCycleLocalApiClient.java index abedf633..068f6cac 100755 --- a/src/main/java/com/devcycle/sdk/server/local/api/DevCycleLocalApiClient.java +++ b/src/main/java/com/devcycle/sdk/server/local/api/DevCycleLocalApiClient.java @@ -3,10 +3,9 @@ import com.devcycle.sdk.server.common.api.APIUtils; import com.devcycle.sdk.server.common.api.IDevCycleApi; import com.devcycle.sdk.server.local.model.DevCycleLocalOptions; -import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.annotation.JsonInclude; - -import okhttp3.*; +import com.fasterxml.jackson.databind.ObjectMapper; +import okhttp3.OkHttpClient; import retrofit2.Retrofit; import retrofit2.converter.jackson.JacksonConverterFactory; @@ -15,52 +14,49 @@ public final class DevCycleLocalApiClient { - private final OkHttpClient.Builder okBuilder; - private final Retrofit.Builder adapterBuilder; - - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private static final String CONFIG_URL = "https://config-cdn.devcycle.com/"; + private static final int DEFAULT_TIMEOUT_MS = 10000; + private static final int MIN_INTERVALS_MS = 1000; + private final OkHttpClient.Builder okBuilder; + private final Retrofit.Builder adapterBuilder; + private String configUrl; + private final int requestTimeoutMs; - private static final String CONFIG_URL = "https://config-cdn.devcycle.com/"; - private static final int DEFAULT_TIMEOUT_MS = 10000; - private static final int MIN_INTERVALS_MS = 1000; - - private String configUrl; - private int requestTimeoutMs; + private DevCycleLocalApiClient(DevCycleLocalOptions options) { - private DevCycleLocalApiClient(DevCycleLocalOptions options) { + OBJECT_MAPPER.setSerializationInclusion(JsonInclude.Include.NON_NULL); + okBuilder = new OkHttpClient.Builder(); - OBJECT_MAPPER.setSerializationInclusion(JsonInclude.Include.NON_NULL); - okBuilder = new OkHttpClient.Builder(); + APIUtils.applyRestOptions(options.getRestOptions(), okBuilder); - APIUtils.applyRestOptions(options.getRestOptions(), okBuilder); + String cdnUrlFromOptions = options.getConfigCdnBaseUrl(); + int configRequestTimeoutMs = options.getConfigRequestTimeoutMs(); - String cdnUrlFromOptions = options.getConfigCdnBaseUrl(); - int configRequestTimeoutMs = options.getConfigRequestTimeoutMs(); + configUrl = checkIfStringNullOrEmpty(cdnUrlFromOptions) ? CONFIG_URL : cdnUrlFromOptions; + requestTimeoutMs = configRequestTimeoutMs >= MIN_INTERVALS_MS ? configRequestTimeoutMs : DEFAULT_TIMEOUT_MS; - configUrl = checkIfStringNullOrEmpty(cdnUrlFromOptions) ? CONFIG_URL : cdnUrlFromOptions; - requestTimeoutMs = configRequestTimeoutMs >= MIN_INTERVALS_MS ? configRequestTimeoutMs : DEFAULT_TIMEOUT_MS; - - okBuilder.callTimeout(this.requestTimeoutMs, TimeUnit.MILLISECONDS); + okBuilder.callTimeout(this.requestTimeoutMs, TimeUnit.MILLISECONDS); - configUrl = configUrl.endsWith("/") ? configUrl : configUrl + "/"; + configUrl = configUrl.endsWith("/") ? configUrl : configUrl + "/"; - adapterBuilder = new Retrofit.Builder() - .baseUrl(configUrl) - .addConverterFactory(JacksonConverterFactory.create()); - } + adapterBuilder = new Retrofit.Builder() + .baseUrl(configUrl) + .addConverterFactory(JacksonConverterFactory.create()); + } - public DevCycleLocalApiClient(String sdkKey, DevCycleLocalOptions options) { - this(options); - } + public DevCycleLocalApiClient(String sdkKey, DevCycleLocalOptions options) { + this(options); + } - public IDevCycleApi initialize() { - return adapterBuilder - .client(okBuilder.build()) - .build() - .create(IDevCycleApi.class); - } + public IDevCycleApi initialize() { + return adapterBuilder + .client(okBuilder.build()) + .build() + .create(IDevCycleApi.class); + } - private Boolean checkIfStringNullOrEmpty(String stringToCheck) { - return Objects.isNull(stringToCheck) || Objects.equals(stringToCheck, ""); - } + private Boolean checkIfStringNullOrEmpty(String stringToCheck) { + return Objects.isNull(stringToCheck) || Objects.equals(stringToCheck, ""); + } } diff --git a/src/main/java/com/devcycle/sdk/server/local/api/DevCycleLocalClient.java b/src/main/java/com/devcycle/sdk/server/local/api/DevCycleLocalClient.java index 30d5afbb..c69afbcd 100755 --- a/src/main/java/com/devcycle/sdk/server/local/api/DevCycleLocalClient.java +++ b/src/main/java/com/devcycle/sdk/server/local/api/DevCycleLocalClient.java @@ -1,6 +1,7 @@ package com.devcycle.sdk.server.local.api; import com.devcycle.sdk.server.common.api.IDevCycleClient; +import com.devcycle.sdk.server.common.logging.DevCycleLogger; import com.devcycle.sdk.server.common.model.*; import com.devcycle.sdk.server.common.model.Variable.TypeEnum; import com.devcycle.sdk.server.local.bucketing.LocalBucketing; @@ -11,7 +12,6 @@ import com.devcycle.sdk.server.local.protobuf.SDKVariable_PB; import com.devcycle.sdk.server.local.protobuf.VariableForUserParams_PB; import com.devcycle.sdk.server.local.protobuf.VariableType_PB; -import com.devcycle.sdk.server.common.logging.DevCycleLogger; import com.devcycle.sdk.server.local.utils.ProtobufUtils; import com.devcycle.sdk.server.openfeature.DevCycleProvider; import com.fasterxml.jackson.core.JsonProcessingException; @@ -23,273 +23,267 @@ public final class DevCycleLocalClient implements IDevCycleClient { - private LocalBucketing localBucketing = new LocalBucketing(); + private final String sdkKey; + private final DevCycleProvider openFeatureProvider; + private final LocalBucketing localBucketing = new LocalBucketing(); + private final EnvironmentConfigManager configManager; + private EventQueueManager eventQueueManager; - private EnvironmentConfigManager configManager; + public DevCycleLocalClient(String sdkKey) { + this(sdkKey, DevCycleLocalOptions.builder().build()); + } - private final String sdkKey; + public DevCycleLocalClient(String sdkKey, DevCycleLocalOptions dvcOptions) { + if (sdkKey == null || sdkKey.equals("")) { + throw new IllegalArgumentException("Missing SDK key! Call initialize with a valid SDK key"); + } + if (!isValidServerKey(sdkKey)) { + throw new IllegalArgumentException("Invalid SDK key provided. Please call initialize with a valid server SDK key"); + } - private EventQueueManager eventQueueManager; + if (dvcOptions.getCustomLogger() != null) { + DevCycleLogger.setCustomLogger(dvcOptions.getCustomLogger()); + } - private final DevCycleProvider openFeatureProvider; - public DevCycleLocalClient(String sdkKey) { - this(sdkKey, DevCycleLocalOptions.builder().build()); - } + if (!isValidRuntime()) { + DevCycleLogger.warning("The DevCycleLocalClient requires a 64-bit, x86 or aarch64 runtime environment. This architecture may not be supported: " + System.getProperty("os.arch") + " | " + System.getProperty("sun.arch.data.model")); + } - public DevCycleLocalClient(String sdkKey, DevCycleLocalOptions dvcOptions) { - if(sdkKey == null || sdkKey.equals("")) { - throw new IllegalArgumentException("Missing SDK key! Call initialize with a valid SDK key"); - } - if(!isValidServerKey(sdkKey)) { - throw new IllegalArgumentException("Invalid SDK key provided. Please call initialize with a valid server SDK key"); - } + localBucketing.setPlatformData(PlatformData.builder().build().toString()); - if(dvcOptions.getCustomLogger() != null) { - DevCycleLogger.setCustomLogger(dvcOptions.getCustomLogger()); + configManager = new EnvironmentConfigManager(sdkKey, localBucketing, dvcOptions); + this.sdkKey = sdkKey; + try { + eventQueueManager = new EventQueueManager(sdkKey, localBucketing, dvcOptions); + } catch (Exception e) { + DevCycleLogger.error("Error creating event queue due to error: " + e.getMessage()); + } + this.openFeatureProvider = new DevCycleProvider(this); } - if(!isValidRuntime()){ - DevCycleLogger.warning("The DevCycleLocalClient requires a 64-bit, x86 or aarch64 runtime environment. This architecture may not be supported: " + System.getProperty("os.arch") + " | " + System.getProperty("sun.arch.data.model")); + /** + * @return true if the DevCycleLocalClient is fully initialized and has successfully loaded a configuration + */ + public boolean isInitialized() { + if (configManager != null) { + return configManager.isConfigInitialized(); + } + return false; } - localBucketing.setPlatformData(PlatformData.builder().build().toString()); - - configManager = new EnvironmentConfigManager(sdkKey, localBucketing, dvcOptions); - this.sdkKey = sdkKey; - try { - eventQueueManager = new EventQueueManager(sdkKey, localBucketing, dvcOptions); - } catch (Exception e) { - DevCycleLogger.error("Error creating event queue due to error: " + e.getMessage()); - } - this.openFeatureProvider = new DevCycleProvider(this); - } - - /** - * @return true if the DevCycleLocalClient is fully initialized and has successfully loaded a configuration - */ - public boolean isInitialized() { - if(configManager != null) { - return configManager.isConfigInitialized(); - } - return false; - } - - /** - * Get all features for user data - * - * @param user (required) - */ - public Map allFeatures(DevCycleUser user) { - if(!isInitialized()) { - return Collections.emptyMap(); - } + /** + * Get all features for user data + * + * @param user (required) + */ + public Map allFeatures(DevCycleUser user) { + if (!isInitialized()) { + return Collections.emptyMap(); + } - validateUser(user); - BucketedUserConfig bucketedUserConfig = null; + validateUser(user); + BucketedUserConfig bucketedUserConfig = null; - try { - bucketedUserConfig = localBucketing.generateBucketedConfig(sdkKey, user); - } catch (JsonProcessingException e) { - DevCycleLogger.error("Unable to parse JSON for allFeatures due to error: " + e.getMessage()); - return Collections.emptyMap(); - } - return bucketedUserConfig.features; - } - - /** - * Get variable value by key for user data - * - * @param user (required) - * @param key Feature key (required) - * @param defaultValue Default value to use if the variable could not be fetched - * (required) - * @return Variable value - */ - public T variableValue(DevCycleUser user, String key, T defaultValue) { - return variable(user, key, defaultValue).getValue(); - } - - /** - * Get variable by key for user data - * - * @param user (required) - * @param key Variable key (required) - * @param defaultValue Default value to use if the variable could not be fetched - * (required) - * @return Variable - */ - public Variable variable(DevCycleUser user, String key, T defaultValue) { - validateUser(user); - - if (key == null || key.equals("")) { - throw new IllegalArgumentException("Missing parameter: key"); + try { + bucketedUserConfig = localBucketing.generateBucketedConfig(sdkKey, user); + } catch (JsonProcessingException e) { + DevCycleLogger.error("Unable to parse JSON for allFeatures due to error: " + e.getMessage()); + return Collections.emptyMap(); + } + return bucketedUserConfig.features; } - if (defaultValue == null) { - throw new IllegalArgumentException("Missing parameter: defaultValue"); + /** + * Get variable value by key for user data + * + * @param user (required) + * @param key Feature key (required) + * @param defaultValue Default value to use if the variable could not be fetched + * (required) + * @return Variable value + */ + public T variableValue(DevCycleUser user, String key, T defaultValue) { + return variable(user, key, defaultValue).getValue(); } - TypeEnum variableType = TypeEnum.fromClass(defaultValue.getClass()); - Variable defaultVariable = (Variable) Variable.builder() - .key(key) - .type(variableType) - .value(defaultValue) - .defaultValue(defaultValue) - .isDefaulted(true) - .build(); - - if (!isInitialized()) { - DevCycleLogger.info("Variable called before DevCycleLocalClient has initialized, returning default value"); - try { - eventQueueManager.queueAggregateEvent(DevCycleEvent.builder().type("aggVariableDefaulted").target(key).build(), null); - } catch (Exception e) { - DevCycleLogger.error("Unable to parse aggVariableDefaulted event for Variable " + key + " due to error: " + e, e); - } - return defaultVariable; - } + /** + * Get variable by key for user data + * + * @param user (required) + * @param key Variable key (required) + * @param defaultValue Default value to use if the variable could not be fetched + * (required) + * @return Variable + */ + public Variable variable(DevCycleUser user, String key, T defaultValue) { + validateUser(user); + + if (key == null || key.equals("")) { + throw new IllegalArgumentException("Missing parameter: key"); + } - VariableType_PB pbVariableType = ProtobufUtils.convertTypeEnumToVariableType(variableType); - VariableForUserParams_PB params = VariableForUserParams_PB.newBuilder() - .setSdkKey(sdkKey) - .setUser(ProtobufUtils.createDVCUserPB(user)) - .setVariableKey(key) - .setVariableType(pbVariableType) - .setShouldTrackEvent(true) - .build(); + if (defaultValue == null) { + throw new IllegalArgumentException("Missing parameter: defaultValue"); + } - try { - byte[] paramsBuffer = params.toByteArray(); - byte[] variableData = localBucketing.getVariableForUserProtobuf(paramsBuffer); + TypeEnum variableType = TypeEnum.fromClass(defaultValue.getClass()); + Variable defaultVariable = (Variable) Variable.builder() + .key(key) + .type(variableType) + .value(defaultValue) + .defaultValue(defaultValue) + .isDefaulted(true) + .build(); + + if (!isInitialized()) { + DevCycleLogger.info("Variable called before DevCycleLocalClient has initialized, returning default value"); + try { + eventQueueManager.queueAggregateEvent(DevCycleEvent.builder().type("aggVariableDefaulted").target(key).build(), null); + } catch (Exception e) { + DevCycleLogger.error("Unable to parse aggVariableDefaulted event for Variable " + key + " due to error: " + e, e); + } + return defaultVariable; + } - if (variableData == null || variableData.length == 0) { - return defaultVariable; - } else { - SDKVariable_PB sdkVariable = SDKVariable_PB.parseFrom(variableData); - if(sdkVariable.getType() != pbVariableType) { - DevCycleLogger.warning("Variable type mismatch, returning default value"); - return defaultVariable; + VariableType_PB pbVariableType = ProtobufUtils.convertTypeEnumToVariableType(variableType); + VariableForUserParams_PB params = VariableForUserParams_PB.newBuilder() + .setSdkKey(sdkKey) + .setUser(ProtobufUtils.createDVCUserPB(user)) + .setVariableKey(key) + .setVariableType(pbVariableType) + .setShouldTrackEvent(true) + .build(); + + try { + byte[] paramsBuffer = params.toByteArray(); + byte[] variableData = localBucketing.getVariableForUserProtobuf(paramsBuffer); + + if (variableData == null || variableData.length == 0) { + return defaultVariable; + } else { + SDKVariable_PB sdkVariable = SDKVariable_PB.parseFrom(variableData); + if (sdkVariable.getType() != pbVariableType) { + DevCycleLogger.warning("Variable type mismatch, returning default value"); + return defaultVariable; + } + return ProtobufUtils.createVariable(sdkVariable, defaultValue); + } + } catch (Exception e) { + DevCycleLogger.error("Unable to evaluate Variable " + key + " due to error: " + e, e); } - return ProtobufUtils.createVariable(sdkVariable, defaultValue); - } - } catch (Exception e) { - DevCycleLogger.error("Unable to evaluate Variable " + key + " due to error: " + e, e); + return defaultVariable; } - return defaultVariable; - } - /** - * Get all variables by key for user data - * - * @param user (required) - */ - public Map allVariables(DevCycleUser user) { - validateUser(user); + /** + * Get all variables by key for user data + * + * @param user (required) + */ + public Map allVariables(DevCycleUser user) { + validateUser(user); - if (!isInitialized()) { - return Collections.emptyMap(); - } + if (!isInitialized()) { + return Collections.emptyMap(); + } - BucketedUserConfig bucketedUserConfig = null; - try { - bucketedUserConfig = localBucketing.generateBucketedConfig(sdkKey, user); - } catch (JsonProcessingException e) { - DevCycleLogger.error("Unable to parse JSON for allVariables due to error: " + e.getMessage()); - return Collections.emptyMap(); - } - return bucketedUserConfig.variables; - } - - /** - * Post events to DevCycle for user - * - * @param user (required) - * @param event (required) - */ - public void track(DevCycleUser user, DevCycleEvent event) { - validateUser(user); - - if (event == null || event.getType().equals("")) { - throw new IllegalArgumentException("Invalid DevCycleEvent"); + BucketedUserConfig bucketedUserConfig = null; + try { + bucketedUserConfig = localBucketing.generateBucketedConfig(sdkKey, user); + } catch (JsonProcessingException e) { + DevCycleLogger.error("Unable to parse JSON for allVariables due to error: " + e.getMessage()); + return Collections.emptyMap(); + } + return bucketedUserConfig.variables; } - try { - eventQueueManager.queueEvent(user, event); - } catch (Exception e) { - DevCycleLogger.error("Failed to queue event due to error: " + e.getMessage()); - } - } + /** + * Post events to DevCycle for user + * + * @param user (required) + * @param event (required) + */ + public void track(DevCycleUser user, DevCycleEvent event) { + validateUser(user); + + if (event == null || event.getType().equals("")) { + throw new IllegalArgumentException("Invalid DevCycleEvent"); + } - public void setClientCustomData(Map customData) { - if (!isInitialized()) { - DevCycleLogger.error("SetClientCustomData called before DevCycleClient has initialized"); - return; + try { + eventQueueManager.queueEvent(user, event); + } catch (Exception e) { + DevCycleLogger.error("Failed to queue event due to error: " + e.getMessage()); + } } - if (customData != null && !customData.isEmpty()) { - try { - ObjectMapper mapper = new ObjectMapper(); - String customDataJSON = mapper.writeValueAsString(customData); - localBucketing.setClientCustomData(this.sdkKey, customDataJSON); - } catch(Exception e) { - DevCycleLogger.error("Failed to set custom data due to error: " + e.getMessage(), e); - } + public void setClientCustomData(Map customData) { + if (!isInitialized()) { + DevCycleLogger.error("SetClientCustomData called before DevCycleClient has initialized"); + return; + } + + if (customData != null && !customData.isEmpty()) { + try { + ObjectMapper mapper = new ObjectMapper(); + String customDataJSON = mapper.writeValueAsString(customData); + localBucketing.setClientCustomData(this.sdkKey, customDataJSON); + } catch (Exception e) { + DevCycleLogger.error("Failed to set custom data due to error: " + e.getMessage(), e); + } + } } - } - - /** - * Gracefully close the client - * - */ - public void close() { - if (configManager != null) { - configManager.cleanup(); + + /** + * Gracefully close the client + */ + public void close() { + if (configManager != null) { + configManager.cleanup(); + } + if (eventQueueManager != null) { + eventQueueManager.cleanup(); + } } - if (eventQueueManager != null) { - eventQueueManager.cleanup(); + + /** + * @return the OpenFeature provider for this client. + */ + @Override + public FeatureProvider getOpenFeatureProvider() { + return this.openFeatureProvider; } - } - - /** - * @return the OpenFeature provider for this client. - */ - @Override - public FeatureProvider getOpenFeatureProvider() { - return this.openFeatureProvider; - } - - @Override - public String getSDKPlatform() { - return "Local"; - } - - private void validateUser(DevCycleUser user) { - if (user == null) { - throw new IllegalArgumentException("DevCycleUser cannot be null"); + + @Override + public String getSDKPlatform() { + return "Local"; } - if (user.getUserId().equals("")) { - throw new IllegalArgumentException("userId cannot be empty"); + + private void validateUser(DevCycleUser user) { + if (user == null) { + throw new IllegalArgumentException("DevCycleUser cannot be null"); + } + if (user.getUserId().equals("")) { + throw new IllegalArgumentException("userId cannot be empty"); + } } - } - - private boolean isValidServerKey(String serverKey) { - return serverKey.startsWith("server") || serverKey.startsWith("dvc_server"); - } - - private boolean isValidRuntime() { - String os = System.getProperty("os.name").toLowerCase(); - String arch = System.getProperty("os.arch").toLowerCase(); - String model = System.getProperty("sun.arch.data.model").toLowerCase(); - if (arch.contains("x86") && model.contains("64")) { - // Assume support for all x86_64 platforms - return true; + + private boolean isValidServerKey(String serverKey) { + return serverKey.startsWith("server") || serverKey.startsWith("dvc_server"); } - if (os.contains("mac os") || os.contains("darwin")) { - if (arch.equals("aarch64")) { - // Specific case of Apple Silicon - return true; - } + + private boolean isValidRuntime() { + String os = System.getProperty("os.name").toLowerCase(); + String arch = System.getProperty("os.arch").toLowerCase(); + String model = System.getProperty("sun.arch.data.model").toLowerCase(); + if (arch.contains("x86") && model.contains("64")) { + // Assume support for all x86_64 platforms + return true; + } + if (os.contains("mac os") || os.contains("darwin")) { + // Specific case of Apple Silicon + return arch.equals("aarch64"); + } + return false; } - return false; - } } diff --git a/src/main/java/com/devcycle/sdk/server/local/api/DevCycleLocalEventsApiClient.java b/src/main/java/com/devcycle/sdk/server/local/api/DevCycleLocalEventsApiClient.java index b658ba46..343aec88 100644 --- a/src/main/java/com/devcycle/sdk/server/local/api/DevCycleLocalEventsApiClient.java +++ b/src/main/java/com/devcycle/sdk/server/local/api/DevCycleLocalEventsApiClient.java @@ -4,9 +4,8 @@ import com.devcycle.sdk.server.common.api.IDevCycleApi; import com.devcycle.sdk.server.common.interceptor.AuthorizationHeaderInterceptor; import com.devcycle.sdk.server.local.model.DevCycleLocalOptions; -import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.annotation.JsonInclude; - +import com.fasterxml.jackson.databind.ObjectMapper; import okhttp3.OkHttpClient; import retrofit2.Retrofit; import retrofit2.converter.jackson.JacksonConverterFactory; @@ -15,13 +14,10 @@ public final class DevCycleLocalEventsApiClient { - private final OkHttpClient.Builder okBuilder; - private final Retrofit.Builder adapterBuilder; - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); - private static final String EVENTS_API_URL = "https://events.devcycle.com/"; - + private final OkHttpClient.Builder okBuilder; + private final Retrofit.Builder adapterBuilder; private String eventsApiUrl; public DevCycleLocalEventsApiClient(String sdkKey, DevCycleLocalOptions options) { diff --git a/src/main/java/com/devcycle/sdk/server/local/bucketing/LocalBucketing.java b/src/main/java/com/devcycle/sdk/server/local/bucketing/LocalBucketing.java index accd85ce..69b133d2 100644 --- a/src/main/java/com/devcycle/sdk/server/local/bucketing/LocalBucketing.java +++ b/src/main/java/com/devcycle/sdk/server/local/bucketing/LocalBucketing.java @@ -27,20 +27,16 @@ import static io.github.kawamuray.wasmtime.WasmValType.I32; public class LocalBucketing { - Store store; // WASM compilation environment - Linker linker; // used to read/write to WASM - AtomicReference memRef; // reference to start of WASM's memory private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); - - private Set pinnedAddresses; - private HashMap sdkKeyAddresses; - - private HashMap variableTypeMap = new HashMap(); - private final int WASM_OBJECT_ID_STRING = 1; private final int WASM_OBJECT_ID_UINT8ARRAY = 9; - - private Logger logger = Logger.getLogger(LocalBucketing.class.getName()); + Store store; // WASM compilation environment + Linker linker; // used to read/write to WASM + AtomicReference memRef; // reference to start of WASM's memory + private final Set pinnedAddresses; + private final HashMap sdkKeyAddresses; + private final HashMap variableTypeMap = new HashMap(); + private final Logger logger = Logger.getLogger(LocalBucketing.class.getName()); public LocalBucketing() { OBJECT_MAPPER.setSerializationInclusion(JsonInclude.Include.NON_NULL); @@ -134,8 +130,7 @@ private String readWasmString(int startAddress) { return result; } - private int newUint8ArrayParameter(byte[] paramData) - { + private int newUint8ArrayParameter(byte[] paramData) { int length = paramData.length; Func __newPtr = linker.get(store, "", "__new").get().func(); // get pointer to __new function @@ -143,8 +138,7 @@ private int newUint8ArrayParameter(byte[] paramData) store, __newPtr, I32, I32, I32); // load __new function int headerAddr = __new.call(12, WASM_OBJECT_ID_UINT8ARRAY); - try - { + try { pinParameter(headerAddr); int dataBufferAddr = __new.call(length, WASM_OBJECT_ID_STRING); @@ -152,8 +146,7 @@ private int newUint8ArrayParameter(byte[] paramData) byte[] bufferAddrBytes = ByteConversionUtils.intToBytesLittleEndian(dataBufferAddr); byte[] lengthBytes = ByteConversionUtils.intToBytesLittleEndian(length << 0); // Into the header need to write 12 bytes - for(int i = 0; i < 4; i++) - { + for (int i = 0; i < 4; i++) { // 0-3 = buffer address,little endian headerData[i] = bufferAddrBytes[i]; // 4-7 = buffer address again, little endian @@ -170,31 +163,25 @@ private int newUint8ArrayParameter(byte[] paramData) } // write the param data into WASM memory - for(int i = 0; i < length; i++) - { + for (int i = 0; i < length; i++) { buf.put(dataBufferAddr + i, paramData[i]); } - } - finally - { + } finally { unpinParameter(headerAddr); } return headerAddr; } - private byte[] readFromWasmMemory(int address, int length) - { + private byte[] readFromWasmMemory(int address, int length) { ByteBuffer buf = memRef.get().buffer(store); byte[] data = new byte[length]; - for(int i = 0; i < length; i++) - { + for (int i = 0; i < length; i++) { data[i] = buf.get(address + i); } return data; } - private byte[] readAssemblyScriptUint8Array(int address) - { + private byte[] readAssemblyScriptUint8Array(int address) { // The header is 12 bytes long, need to pull out the location of the array's data buffer // and the length of the data buffer byte[] bufferDataAddressBytes = readFromWasmMemory(address, 4); @@ -228,7 +215,7 @@ public synchronized void setPlatformData(String platformData) { public synchronized void setClientCustomData(String sdkKey, String customData) { unpinAll(); int sdkKeyAddress = getSDKKeyAddress(sdkKey); - int customDataAddress = newUint8ArrayParameter(customData.getBytes(StandardCharsets.UTF_8));; + int customDataAddress = newUint8ArrayParameter(customData.getBytes(StandardCharsets.UTF_8)); Func setCustomClientDataPtr = linker.get(store, "", "setClientCustomDataUTF8").get().func(); WasmFunctions.Consumer2 fn = WasmFunctions.consumer(store, setCustomClientDataPtr, I32, I32); fn.accept(sdkKeyAddress, customDataAddress); @@ -256,7 +243,7 @@ public synchronized BucketedUserConfig generateBucketedConfig(String sdkKey, Dev return config; } - public synchronized byte[] getVariableForUserProtobuf(byte[] serializedParams){ + public synchronized byte[] getVariableForUserProtobuf(byte[] serializedParams) { int paramsAddr = newUint8ArrayParameter(serializedParams); Func getVariablePtr = linker.get(store, "", "variableForUser_PB").get().func(); @@ -266,8 +253,7 @@ public synchronized byte[] getVariableForUserProtobuf(byte[] serializedParams){ int variableAddress = variableForUserPB.call(paramsAddr); byte[] varBytes = null; - if (variableAddress > 0) - { + if (variableAddress > 0) { varBytes = readAssemblyScriptUint8Array(variableAddress); } @@ -373,7 +359,7 @@ private void unpinParameter(int address) { } private void unpinAll() { - for(int address : pinnedAddresses) { + for (int address : pinnedAddresses) { unpinParameter(address); } pinnedAddresses.clear(); @@ -387,7 +373,7 @@ private int getPinnedParameter(String param) { } private int getSDKKeyAddress(String sdkKey) { - if(!sdkKeyAddresses.containsKey(sdkKey)) { + if (!sdkKeyAddresses.containsKey(sdkKey)) { int sdkKeyAddress = newWasmString(sdkKey); pinParameter(sdkKeyAddress); sdkKeyAddresses.put(sdkKey, sdkKeyAddress); diff --git a/src/main/java/com/devcycle/sdk/server/local/managers/DaemonThreadFactory.java b/src/main/java/com/devcycle/sdk/server/local/managers/DaemonThreadFactory.java index 29c439a3..f05bd09b 100644 --- a/src/main/java/com/devcycle/sdk/server/local/managers/DaemonThreadFactory.java +++ b/src/main/java/com/devcycle/sdk/server/local/managers/DaemonThreadFactory.java @@ -1,10 +1,10 @@ package com.devcycle.sdk.server.local.managers; -import java.util.concurrent.ThreadFactory; import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; public class DaemonThreadFactory implements ThreadFactory { - private ThreadFactory defaultThreadFactory = Executors.defaultThreadFactory(); + private final ThreadFactory defaultThreadFactory = Executors.defaultThreadFactory(); public Thread newThread(Runnable r) { Thread thread = defaultThreadFactory.newThread(r); diff --git a/src/main/java/com/devcycle/sdk/server/local/managers/EnvironmentConfigManager.java b/src/main/java/com/devcycle/sdk/server/local/managers/EnvironmentConfigManager.java index 616f9832..e6059eab 100644 --- a/src/main/java/com/devcycle/sdk/server/local/managers/EnvironmentConfigManager.java +++ b/src/main/java/com/devcycle/sdk/server/local/managers/EnvironmentConfigManager.java @@ -2,13 +2,13 @@ import com.devcycle.sdk.server.common.api.IDevCycleApi; import com.devcycle.sdk.server.common.exception.DevCycleException; +import com.devcycle.sdk.server.common.logging.DevCycleLogger; import com.devcycle.sdk.server.common.model.ErrorResponse; import com.devcycle.sdk.server.common.model.HttpResponseCode; import com.devcycle.sdk.server.common.model.ProjectConfig; import com.devcycle.sdk.server.local.api.DevCycleLocalApiClient; import com.devcycle.sdk.server.local.bucketing.LocalBucketing; import com.devcycle.sdk.server.local.model.DevCycleLocalOptions; -import com.devcycle.sdk.server.common.logging.DevCycleLogger; import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; @@ -21,170 +21,169 @@ import java.util.concurrent.TimeUnit; public final class EnvironmentConfigManager { - private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1, new DaemonThreadFactory()); - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); - private static final int DEFAULT_POLL_INTERVAL_MS = 30000; - private static final int MIN_INTERVALS_MS = 1000; + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private static final int DEFAULT_POLL_INTERVAL_MS = 30000; + private static final int MIN_INTERVALS_MS = 1000; + private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1, new DaemonThreadFactory()); + private final IDevCycleApi configApiClient; + private final LocalBucketing localBucketing; - private IDevCycleApi configApiClient; - private LocalBucketing localBucketing; + private ProjectConfig config; + private String configETag = ""; - private ProjectConfig config; - private String configETag = ""; + private final String sdkKey; + private final int pollingIntervalMS; + private boolean pollingEnabled = true; - private String sdkKey; - private int pollingIntervalMS; - private boolean pollingEnabled = true; + public EnvironmentConfigManager(String sdkKey, LocalBucketing localBucketing, DevCycleLocalOptions options) { + this.sdkKey = sdkKey; + this.localBucketing = localBucketing; - public EnvironmentConfigManager(String sdkKey, LocalBucketing localBucketing, DevCycleLocalOptions options) { - this.sdkKey = sdkKey; - this.localBucketing = localBucketing; + configApiClient = new DevCycleLocalApiClient(sdkKey, options).initialize(); - configApiClient = new DevCycleLocalApiClient(sdkKey, options).initialize(); + int configPollingIntervalMS = options.getConfigPollingIntervalMS(); + pollingIntervalMS = configPollingIntervalMS >= MIN_INTERVALS_MS ? configPollingIntervalMS + : DEFAULT_POLL_INTERVAL_MS; - int configPollingIntervalMS = options.getConfigPollingIntervalMS(); - pollingIntervalMS = configPollingIntervalMS >= MIN_INTERVALS_MS ? configPollingIntervalMS - : DEFAULT_POLL_INTERVAL_MS; + setupScheduler(); + } - setupScheduler(); - } + private void setupScheduler() { + Runnable getConfigRunnable = new Runnable() { + public void run() { + try { + if (pollingEnabled) { + getConfig(); + } + } catch (DevCycleException e) { + DevCycleLogger.error("Failed to load config: " + e.getMessage()); + } + } + }; + + scheduler.scheduleAtFixedRate(getConfigRunnable, 0, this.pollingIntervalMS, TimeUnit.MILLISECONDS); + } - private void setupScheduler() { - Runnable getConfigRunnable = new Runnable() { - public void run() { - try { - if (pollingEnabled) { - getConfig(); - } - } catch (DevCycleException e) { - DevCycleLogger.error("Failed to load config: " + e.getMessage()); - } - } - }; - - scheduler.scheduleAtFixedRate(getConfigRunnable, 0, this.pollingIntervalMS, TimeUnit.MILLISECONDS); - } - - public boolean isConfigInitialized() { - return config != null; - } - - private ProjectConfig getConfig() throws DevCycleException { - Call config = this.configApiClient.getConfig(this.sdkKey, this.configETag); - this.config = getResponseWithRetries(config, 1); - return this.config; - } - - private ProjectConfig getResponseWithRetries(Call call, int maxRetries) throws DevCycleException { - // attempt 0 is the initial request, attempt > 0 are all retries - int attempt = 0; - do { - try { - return getConfigResponse(call); - } catch (DevCycleException e) { - - attempt++; - - // if out of retries or this is an unauthorized error, throw up exception - if ( !e.isRetryable() || attempt > maxRetries) { - throw e; - } + public boolean isConfigInitialized() { + return config != null; + } - try { - // exponential backoff - long waitIntervalMS = (long) (10 * Math.pow(2, attempt)); - Thread.sleep(waitIntervalMS); - } catch (InterruptedException ex) { - // no-op - } + private ProjectConfig getConfig() throws DevCycleException { + Call config = this.configApiClient.getConfig(this.sdkKey, this.configETag); + this.config = getResponseWithRetries(config, 1); + return this.config; + } - // prep the call for a retry - call = call.clone(); - } - } while (attempt <= maxRetries && pollingEnabled); - - // getting here should not happen, but is technically possible - ErrorResponse errorResponse = ErrorResponse.builder().build(); - errorResponse.setMessage("Out of retry attempts"); - throw new DevCycleException(HttpResponseCode.SERVER_ERROR, errorResponse); - } - - private ProjectConfig getConfigResponse(Call call) throws DevCycleException { - ErrorResponse errorResponse = ErrorResponse.builder().build(); - Response response; - - try { - response = call.execute(); - } catch(JsonParseException badJsonExc) { - // Got a valid status code but the response body was not valid json, - // need to ignore this attempt and let the polling retry - errorResponse.setMessage(badJsonExc.getMessage()); - throw new DevCycleException(HttpResponseCode.NO_CONTENT, errorResponse); - } catch (IOException e) { - errorResponse.setMessage(e.getMessage()); - throw new DevCycleException(HttpResponseCode.byCode(500), errorResponse); + private ProjectConfig getResponseWithRetries(Call call, int maxRetries) throws DevCycleException { + // attempt 0 is the initial request, attempt > 0 are all retries + int attempt = 0; + do { + try { + return getConfigResponse(call); + } catch (DevCycleException e) { + + attempt++; + + // if out of retries or this is an unauthorized error, throw up exception + if (!e.isRetryable() || attempt > maxRetries) { + throw e; + } + + try { + // exponential backoff + long waitIntervalMS = (long) (10 * Math.pow(2, attempt)); + Thread.sleep(waitIntervalMS); + } catch (InterruptedException ex) { + // no-op + } + + // prep the call for a retry + call = call.clone(); + } + } while (attempt <= maxRetries && pollingEnabled); + + // getting here should not happen, but is technically possible + ErrorResponse errorResponse = ErrorResponse.builder().build(); + errorResponse.setMessage("Out of retry attempts"); + throw new DevCycleException(HttpResponseCode.SERVER_ERROR, errorResponse); } - HttpResponseCode httpResponseCode = HttpResponseCode.byCode(response.code()); - errorResponse.setMessage("Unknown error"); - - if (response.isSuccessful()) { - String currentETag = response.headers().get("ETag"); - ProjectConfig config = response.body(); - try { - ObjectMapper mapper = new ObjectMapper(); - localBucketing.storeConfig(sdkKey, mapper.writeValueAsString(config)); - } catch (JsonProcessingException e) { - if (this.config != null) { - DevCycleLogger.error("Unable to parse config with etag: " + currentETag + ". Using cache, etag " + this.configETag); - return this.config; - } else { - errorResponse.setMessage(e.getMessage()); - throw new DevCycleException(HttpResponseCode.SERVER_ERROR, errorResponse); - } - } - this.configETag = currentETag; - return response.body(); - } else if (httpResponseCode == HttpResponseCode.NOT_MODIFIED) { - DevCycleLogger.debug("Config not modified, using cache, etag: " + this.configETag); - return this.config; - } else { - if (response.errorBody() != null) { + private ProjectConfig getConfigResponse(Call call) throws DevCycleException { + ErrorResponse errorResponse = ErrorResponse.builder().build(); + Response response; + try { - errorResponse = OBJECT_MAPPER.readValue(response.errorBody().string(), ErrorResponse.class); - } catch (JsonProcessingException e) { - errorResponse.setMessage("Unable to parse error response: " + e.getMessage()); - throw new DevCycleException(httpResponseCode, errorResponse); + response = call.execute(); + } catch (JsonParseException badJsonExc) { + // Got a valid status code but the response body was not valid json, + // need to ignore this attempt and let the polling retry + errorResponse.setMessage(badJsonExc.getMessage()); + throw new DevCycleException(HttpResponseCode.NO_CONTENT, errorResponse); } catch (IOException e) { - errorResponse.setMessage(e.getMessage()); - throw new DevCycleException(httpResponseCode, errorResponse); + errorResponse.setMessage(e.getMessage()); + throw new DevCycleException(HttpResponseCode.byCode(500), errorResponse); } - throw new DevCycleException(httpResponseCode, errorResponse); - } - if (httpResponseCode == HttpResponseCode.UNAUTHORIZED || httpResponseCode == HttpResponseCode.FORBIDDEN) { - // SDK Key is no longer authorized or now blocked, stop polling for configs - errorResponse.setMessage("API Key is unauthorized"); - stopPolling(); - } else if (!response.message().equals("")) { - try { - errorResponse = OBJECT_MAPPER.readValue(response.message(), ErrorResponse.class); - } catch (JsonProcessingException e) { - errorResponse.setMessage(e.getMessage()); - throw new DevCycleException(httpResponseCode, errorResponse); + HttpResponseCode httpResponseCode = HttpResponseCode.byCode(response.code()); + errorResponse.setMessage("Unknown error"); + + if (response.isSuccessful()) { + String currentETag = response.headers().get("ETag"); + ProjectConfig config = response.body(); + try { + ObjectMapper mapper = new ObjectMapper(); + localBucketing.storeConfig(sdkKey, mapper.writeValueAsString(config)); + } catch (JsonProcessingException e) { + if (this.config != null) { + DevCycleLogger.error("Unable to parse config with etag: " + currentETag + ". Using cache, etag " + this.configETag); + return this.config; + } else { + errorResponse.setMessage(e.getMessage()); + throw new DevCycleException(HttpResponseCode.SERVER_ERROR, errorResponse); + } + } + this.configETag = currentETag; + return response.body(); + } else if (httpResponseCode == HttpResponseCode.NOT_MODIFIED) { + DevCycleLogger.debug("Config not modified, using cache, etag: " + this.configETag); + return this.config; + } else { + if (response.errorBody() != null) { + try { + errorResponse = OBJECT_MAPPER.readValue(response.errorBody().string(), ErrorResponse.class); + } catch (JsonProcessingException e) { + errorResponse.setMessage("Unable to parse error response: " + e.getMessage()); + throw new DevCycleException(httpResponseCode, errorResponse); + } catch (IOException e) { + errorResponse.setMessage(e.getMessage()); + throw new DevCycleException(httpResponseCode, errorResponse); + } + throw new DevCycleException(httpResponseCode, errorResponse); + } + + if (httpResponseCode == HttpResponseCode.UNAUTHORIZED || httpResponseCode == HttpResponseCode.FORBIDDEN) { + // SDK Key is no longer authorized or now blocked, stop polling for configs + errorResponse.setMessage("API Key is unauthorized"); + stopPolling(); + } else if (!response.message().equals("")) { + try { + errorResponse = OBJECT_MAPPER.readValue(response.message(), ErrorResponse.class); + } catch (JsonProcessingException e) { + errorResponse.setMessage(e.getMessage()); + throw new DevCycleException(httpResponseCode, errorResponse); + } + } + + throw new DevCycleException(httpResponseCode, errorResponse); } - } - - throw new DevCycleException(httpResponseCode, errorResponse); } - } - private void stopPolling() { - pollingEnabled = false; - scheduler.shutdown(); - } + private void stopPolling() { + pollingEnabled = false; + scheduler.shutdown(); + } - public void cleanup() { - stopPolling(); - } + public void cleanup() { + stopPolling(); + } } \ No newline at end of file diff --git a/src/main/java/com/devcycle/sdk/server/local/managers/EventQueueManager.java b/src/main/java/com/devcycle/sdk/server/local/managers/EventQueueManager.java index 2f610486..cc895a20 100644 --- a/src/main/java/com/devcycle/sdk/server/local/managers/EventQueueManager.java +++ b/src/main/java/com/devcycle/sdk/server/local/managers/EventQueueManager.java @@ -1,11 +1,16 @@ package com.devcycle.sdk.server.local.managers; import com.devcycle.sdk.server.common.api.IDevCycleApi; -import com.devcycle.sdk.server.common.model.*; +import com.devcycle.sdk.server.common.logging.DevCycleLogger; +import com.devcycle.sdk.server.common.model.DevCycleEvent; +import com.devcycle.sdk.server.common.model.DevCycleResponse; +import com.devcycle.sdk.server.common.model.DevCycleUser; import com.devcycle.sdk.server.local.api.DevCycleLocalEventsApiClient; import com.devcycle.sdk.server.local.bucketing.LocalBucketing; -import com.devcycle.sdk.server.local.model.*; -import com.devcycle.sdk.server.common.logging.DevCycleLogger; +import com.devcycle.sdk.server.local.model.BucketedUserConfig; +import com.devcycle.sdk.server.local.model.DevCycleLocalOptions; +import com.devcycle.sdk.server.local.model.EventsBatch; +import com.devcycle.sdk.server.local.model.FlushPayload; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.ObjectMapper; import retrofit2.Call; @@ -19,15 +24,15 @@ public class EventQueueManager { - private LocalBucketing localBucketing; - private final String sdkKey; private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); - private IDevCycleApi eventsApiClient; - private int eventFlushIntervalMS; + private final String sdkKey; private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1, new DaemonThreadFactory()); + private final LocalBucketing localBucketing; + private final IDevCycleApi eventsApiClient; + private final int eventFlushIntervalMS; private boolean isFlushingEvents = false; - private int flushEventQueueSize; - private int maxEventQueueSize; + private final int flushEventQueueSize; + private final int maxEventQueueSize; public EventQueueManager(String sdkKey, LocalBucketing localBucketing, DevCycleLocalOptions options) throws Exception { this.localBucketing = localBucketing; @@ -50,7 +55,7 @@ private void setupScheduler() { try { flushEvents(); } catch (Exception e) { - DevCycleLogger.error( "DevCycle Error flushing events: " + e.getMessage(), e); + DevCycleLogger.error("DevCycle Error flushing events: " + e.getMessage(), e); } }; @@ -71,7 +76,7 @@ public synchronized void flushEvents() throws Exception { try { flushPayloads = this.localBucketing.flushEventQueue(this.sdkKey); } catch (Exception e) { - DevCycleLogger.error( "DevCycle Error flushing event payloads: " + e.getMessage(), e); + DevCycleLogger.error("DevCycle Error flushing event payloads: " + e.getMessage(), e); } if (flushPayloads.length == 0) return; @@ -80,7 +85,7 @@ public synchronized void flushEvents() throws Exception { int eventCount = 0; isFlushingEvents = true; - for (FlushPayload payload: flushPayloads) { + for (FlushPayload payload : flushPayloads) { eventCount += payload.eventCount; publishEvents(this.sdkKey, payload); } @@ -135,7 +140,7 @@ private int getResponse(Call call) { try { response = call.execute(); } catch (IOException e) { - DevCycleLogger.error( "DevCycle Events error: " + e.getMessage(), e); + DevCycleLogger.error("DevCycle Events error: " + e.getMessage(), e); } if (response == null) { @@ -157,9 +162,7 @@ private boolean checkEventQueueSize() throws Exception { flushEvents(); } - if (queueSize >= maxEventQueueSize) { - return true; - } + return queueSize >= maxEventQueueSize; } return false; } diff --git a/src/main/java/com/devcycle/sdk/server/local/model/BucketedUserConfig.java b/src/main/java/com/devcycle/sdk/server/local/model/BucketedUserConfig.java index 40e27b28..cd9a8189 100644 --- a/src/main/java/com/devcycle/sdk/server/local/model/BucketedUserConfig.java +++ b/src/main/java/com/devcycle/sdk/server/local/model/BucketedUserConfig.java @@ -1,7 +1,7 @@ package com.devcycle.sdk.server.local.model; -import com.devcycle.sdk.server.common.model.Feature; import com.devcycle.sdk.server.common.model.BaseVariable; +import com.devcycle.sdk.server.common.model.Feature; import java.util.List; import java.util.Map; diff --git a/src/main/java/com/devcycle/sdk/server/local/model/DevCycleLocalOptions.java b/src/main/java/com/devcycle/sdk/server/local/model/DevCycleLocalOptions.java index ce53f6f1..23cd030a 100644 --- a/src/main/java/com/devcycle/sdk/server/local/model/DevCycleLocalOptions.java +++ b/src/main/java/com/devcycle/sdk/server/local/model/DevCycleLocalOptions.java @@ -1,9 +1,9 @@ package com.devcycle.sdk.server.local.model; import com.devcycle.sdk.server.common.api.IRestOptions; -import com.devcycle.sdk.server.common.model.IDevCycleOptions; import com.devcycle.sdk.server.common.logging.DevCycleLogger; import com.devcycle.sdk.server.common.logging.IDevCycleLogger; +import com.devcycle.sdk.server.common.model.IDevCycleOptions; import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Builder; import lombok.Data; @@ -30,19 +30,7 @@ public class DevCycleLocalOptions implements IDevCycleOptions { @JsonIgnore private IDevCycleLogger customLogger = null; - - public int getConfigPollingIntervalMS(int configPollingIntervalMs, int configPollingIntervalMS) { - if (configPollingIntervalMS > 0) { - return configPollingIntervalMS; - } else if (configPollingIntervalMs > 0) { - return configPollingIntervalMs; - } else { - return this.configPollingIntervalMS; - } - } - private boolean disableCustomEventLogging = false; - @JsonIgnore private IRestOptions restOptions = null; @@ -101,4 +89,14 @@ public DevCycleLocalOptions( this.maxEventQueueSize = 20000; } } + + public int getConfigPollingIntervalMS(int configPollingIntervalMs, int configPollingIntervalMS) { + if (configPollingIntervalMS > 0) { + return configPollingIntervalMS; + } else if (configPollingIntervalMs > 0) { + return configPollingIntervalMs; + } else { + return this.configPollingIntervalMS; + } + } } diff --git a/src/main/java/com/devcycle/sdk/server/local/model/FlushPayload.java b/src/main/java/com/devcycle/sdk/server/local/model/FlushPayload.java index 3175bb05..a709abac 100644 --- a/src/main/java/com/devcycle/sdk/server/local/model/FlushPayload.java +++ b/src/main/java/com/devcycle/sdk/server/local/model/FlushPayload.java @@ -19,6 +19,9 @@ public String toString() { } public static class Record { + public DevCycleUser user; + public RequestEvent[] events; + @Override public String toString() { return "Record{" + @@ -26,9 +29,6 @@ public String toString() { ", events=" + Arrays.toString(events) + '}'; } - - public DevCycleUser user; - public RequestEvent[] events; } } diff --git a/src/main/java/com/devcycle/sdk/server/local/model/Project.java b/src/main/java/com/devcycle/sdk/server/local/model/Project.java index e3d40080..7328639a 100644 --- a/src/main/java/com/devcycle/sdk/server/local/model/Project.java +++ b/src/main/java/com/devcycle/sdk/server/local/model/Project.java @@ -7,6 +7,9 @@ public class Project { public ProjectSettings settings; static class ProjectSettings { + public EdgeDBSettings edgeDB; + public OptInSettings optIn; + static class EdgeDBSettings { public Boolean enabled; } @@ -16,16 +19,13 @@ static class OptInSettings { public String title; public String description; public String imageURL; + public Colors colors; + public String poweredByAlignment; static class Colors { public String primary; public String secondary; } - public Colors colors; - public String poweredByAlignment; } - - public EdgeDBSettings edgeDB; - public OptInSettings optIn; } } diff --git a/src/main/java/com/devcycle/sdk/server/local/model/RequestEvent.java b/src/main/java/com/devcycle/sdk/server/local/model/RequestEvent.java index 0ebb1bd8..7336d80e 100644 --- a/src/main/java/com/devcycle/sdk/server/local/model/RequestEvent.java +++ b/src/main/java/com/devcycle/sdk/server/local/model/RequestEvent.java @@ -1,4 +1,3 @@ - package com.devcycle.sdk.server.local.model; import com.fasterxml.jackson.annotation.JsonFormat; diff --git a/src/main/java/com/devcycle/sdk/server/local/utils/ByteConversionUtils.java b/src/main/java/com/devcycle/sdk/server/local/utils/ByteConversionUtils.java index f107aebc..faa6d6c5 100644 --- a/src/main/java/com/devcycle/sdk/server/local/utils/ByteConversionUtils.java +++ b/src/main/java/com/devcycle/sdk/server/local/utils/ByteConversionUtils.java @@ -11,6 +11,7 @@ public static long getUnsignedInt(byte[] data) { return result; } + public static byte[] intToBytesLittleEndian(int value) { byte[] encodedValue = new byte[4]; encodedValue[3] = (byte) (value >> 8 * 3); diff --git a/src/main/java/com/devcycle/sdk/server/local/utils/LongTimestampDeserializer.java b/src/main/java/com/devcycle/sdk/server/local/utils/LongTimestampDeserializer.java index 28a4e56b..715fd679 100644 --- a/src/main/java/com/devcycle/sdk/server/local/utils/LongTimestampDeserializer.java +++ b/src/main/java/com/devcycle/sdk/server/local/utils/LongTimestampDeserializer.java @@ -13,6 +13,7 @@ public class LongTimestampDeserializer extends StdDeserializer { public LongTimestampDeserializer() { super(Long.class); } + @Override public Long deserialize(JsonParser parser, DeserializationContext ctxt) throws IOException { diff --git a/src/main/java/com/devcycle/sdk/server/local/utils/ProtobufUtils.java b/src/main/java/com/devcycle/sdk/server/local/utils/ProtobufUtils.java index 462f2ef6..56b94526 100644 --- a/src/main/java/com/devcycle/sdk/server/local/utils/ProtobufUtils.java +++ b/src/main/java/com/devcycle/sdk/server/local/utils/ProtobufUtils.java @@ -16,11 +16,10 @@ public static DVCUser_PB createDVCUserPB(DevCycleUser user) { double appBuild = Double.NaN; try { appBuild = Double.parseDouble(user.getAppBuild()); - } - catch(Exception e) { /* doesn't matter */ } + } catch (Exception e) { /* doesn't matter */ } return DVCUser_PB.newBuilder() - .setUserId( user.getUserId()) + .setUserId(user.getUserId()) .setEmail(createNullableString(user.getEmail())) .setName(createNullableString(user.getName())) .setLanguage(createNullableString(user.getLanguage())) @@ -43,7 +42,7 @@ public static DVCUser_PB createDVCUserPB(DevCycleUser user) { public static Variable createVariable(SDKVariable_PB sdkVariable, T defaultValue) throws JsonProcessingException { Variable variable; - switch(sdkVariable.getType()) { + switch (sdkVariable.getType()) { case Boolean: variable = (Variable) Variable.builder() .key(sdkVariable.getKey()) @@ -73,7 +72,8 @@ public static Variable createVariable(SDKVariable_PB sdkVariable, T defau break; case JSON: ObjectMapper mapper = new ObjectMapper(); - LinkedHashMap jsonObject = mapper.readValue(sdkVariable.getStringValue(), new TypeReference>() {}); + LinkedHashMap jsonObject = mapper.readValue(sdkVariable.getStringValue(), new TypeReference>() { + }); variable = (Variable) Variable.builder() .key(sdkVariable.getKey()) .type(Variable.TypeEnum.JSON) @@ -83,7 +83,7 @@ public static Variable createVariable(SDKVariable_PB sdkVariable, T defau .build(); break; default: - throw new IllegalArgumentException("Unknown variable type: "+sdkVariable.getType()); + throw new IllegalArgumentException("Unknown variable type: " + sdkVariable.getType()); } return variable; } diff --git a/src/test/java/com/devcycle/sdk/server/cloud/DevCycleCloudClientTest.java b/src/test/java/com/devcycle/sdk/server/cloud/DevCycleCloudClientTest.java index 86cd51ca..466d6fd5 100755 --- a/src/test/java/com/devcycle/sdk/server/cloud/DevCycleCloudClientTest.java +++ b/src/test/java/com/devcycle/sdk/server/cloud/DevCycleCloudClientTest.java @@ -1,14 +1,12 @@ package com.devcycle.sdk.server.cloud; -import static org.mockito.Mockito.when; - -import java.math.BigDecimal; -import java.time.Instant; -import java.util.Collections; -import java.util.Map; -import java.util.UUID; - import com.devcycle.sdk.server.cloud.api.DevCycleCloudClient; +import com.devcycle.sdk.server.cloud.model.DevCycleCloudOptions; +import com.devcycle.sdk.server.common.api.DVCApiMock; +import com.devcycle.sdk.server.common.api.IDevCycleApi; +import com.devcycle.sdk.server.common.exception.DevCycleException; +import com.devcycle.sdk.server.common.model.*; +import com.devcycle.sdk.server.helpers.WhiteBox; import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -16,12 +14,13 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; -import com.devcycle.sdk.server.cloud.model.DevCycleCloudOptions; -import com.devcycle.sdk.server.common.api.DVCApiMock; -import com.devcycle.sdk.server.common.api.IDevCycleApi; -import com.devcycle.sdk.server.common.exception.DevCycleException; -import com.devcycle.sdk.server.common.model.*; -import com.devcycle.sdk.server.helpers.WhiteBox; +import java.math.BigDecimal; +import java.time.Instant; +import java.util.Collections; +import java.util.Map; +import java.util.UUID; + +import static org.mockito.Mockito.when; /** * API tests for DevcycleApi @@ -165,7 +164,7 @@ public void variable_emptyDefaultValue_throwsException() { .build(); Assert.assertThrows("Missing parameter: defaultValue", - IllegalArgumentException.class, () -> api.variable(user, "wibble", null)); + IllegalArgumentException.class, () -> api.variable(user, "wibble", null)); } @Test diff --git a/src/test/java/com/devcycle/sdk/server/common/api/DVCApiMock.java b/src/test/java/com/devcycle/sdk/server/common/api/DVCApiMock.java index 4c3bd7ea..2d7a126e 100644 --- a/src/test/java/com/devcycle/sdk/server/common/api/DVCApiMock.java +++ b/src/test/java/com/devcycle/sdk/server/common/api/DVCApiMock.java @@ -1,13 +1,12 @@ package com.devcycle.sdk.server.common.api; -import java.util.Map; - import com.devcycle.sdk.server.common.model.*; import com.devcycle.sdk.server.helpers.TestResponse; import com.devcycle.sdk.server.local.model.EventsBatch; - import retrofit2.Call; +import java.util.Map; + public class DVCApiMock implements IDevCycleApi { @Override diff --git a/src/test/java/com/devcycle/sdk/server/helpers/LocalConfigServer.java b/src/test/java/com/devcycle/sdk/server/helpers/LocalConfigServer.java index a58ab9fc..201acb42 100644 --- a/src/test/java/com/devcycle/sdk/server/helpers/LocalConfigServer.java +++ b/src/test/java/com/devcycle/sdk/server/helpers/LocalConfigServer.java @@ -3,14 +3,16 @@ import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpServer; -import java.io.*; +import java.io.IOException; +import java.io.OutputStream; import java.net.InetSocketAddress; import java.nio.charset.StandardCharsets; public class LocalConfigServer { - private HttpServer server; + private final HttpServer server; private String configData = ""; - public LocalConfigServer(String configData, int port) throws IOException{ + + public LocalConfigServer(String configData, int port) throws IOException { this.configData = configData; InetSocketAddress address = new InetSocketAddress(port); server = HttpServer.create(address, 0); @@ -20,7 +22,7 @@ public LocalConfigServer(String configData, int port) throws IOException{ } public String getHostRootURL() { - return "http://localhost:" + server.getAddress().getPort() +"/"; + return "http://localhost:" + server.getAddress().getPort() + "/"; } public void handleConfigRequest(HttpExchange exchange) throws IOException { diff --git a/src/test/java/com/devcycle/sdk/server/helpers/TestDataFixtures.java b/src/test/java/com/devcycle/sdk/server/helpers/TestDataFixtures.java index e220d891..422c935e 100644 --- a/src/test/java/com/devcycle/sdk/server/helpers/TestDataFixtures.java +++ b/src/test/java/com/devcycle/sdk/server/helpers/TestDataFixtures.java @@ -1,4 +1,5 @@ package com.devcycle.sdk.server.helpers; + import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; @@ -8,10 +9,11 @@ public class TestDataFixtures { /** * Loads the config data from the file in the resources folder + * * @param fileName name of the file to load * @return the String contents of that resource file */ - private static String loadConfigData(String fileName) { + private static String loadConfigData(String fileName) { String configData = ""; ClassLoader classLoader = TestDataFixtures.class.getClassLoader(); try (InputStream inputStream = classLoader.getResourceAsStream(fileName); @@ -22,7 +24,7 @@ private static String loadConfigData(String fileName) { configData += line; } } catch (IOException e) { - System.out.println("Failed to load config data ["+fileName+"]: " + e.getMessage()); + System.out.println("Failed to load config data [" + fileName + "]: " + e.getMessage()); e.printStackTrace(); } return configData; diff --git a/src/test/java/com/devcycle/sdk/server/helpers/TestResponse.java b/src/test/java/com/devcycle/sdk/server/helpers/TestResponse.java index eac9fea4..7df4ec19 100644 --- a/src/test/java/com/devcycle/sdk/server/helpers/TestResponse.java +++ b/src/test/java/com/devcycle/sdk/server/helpers/TestResponse.java @@ -1,14 +1,16 @@ package com.devcycle.sdk.server.helpers; +import com.devcycle.sdk.server.common.model.BaseVariable; +import com.devcycle.sdk.server.common.model.DevCycleResponse; +import com.devcycle.sdk.server.common.model.Feature; +import com.devcycle.sdk.server.common.model.Variable; +import retrofit2.Call; +import retrofit2.mock.Calls; + import java.util.HashMap; import java.util.Map; import java.util.UUID; -import com.devcycle.sdk.server.common.model.*; - -import retrofit2.Call; -import retrofit2.mock.Calls; - public final class TestResponse { private TestResponse() { diff --git a/src/test/java/com/devcycle/sdk/server/local/DevCycleLocalClientTest.java b/src/test/java/com/devcycle/sdk/server/local/DevCycleLocalClientTest.java index cc20a1e2..95952c61 100644 --- a/src/test/java/com/devcycle/sdk/server/local/DevCycleLocalClientTest.java +++ b/src/test/java/com/devcycle/sdk/server/local/DevCycleLocalClientTest.java @@ -3,8 +3,8 @@ import com.devcycle.sdk.server.common.api.IRestOptions; import com.devcycle.sdk.server.common.logging.IDevCycleLogger; import com.devcycle.sdk.server.common.model.BaseVariable; -import com.devcycle.sdk.server.common.model.Feature; import com.devcycle.sdk.server.common.model.DevCycleUser; +import com.devcycle.sdk.server.common.model.Feature; import com.devcycle.sdk.server.common.model.Variable; import com.devcycle.sdk.server.helpers.LocalConfigServer; import com.devcycle.sdk.server.helpers.TestDataFixtures; @@ -26,10 +26,7 @@ @RunWith(MockitoJUnitRunner.class) public class DevCycleLocalClientTest { - private static DevCycleLocalClient client; - private static LocalConfigServer localConfigServer; static final String apiKey = String.format("server-%s", UUID.randomUUID()); - static IDevCycleLogger testLoggingWrapper = new IDevCycleLogger() { @Override public void debug(String message) { @@ -56,12 +53,11 @@ public void error(String message, Throwable throwable) { System.out.println("ERROR TEST: " + message); } }; - static IRestOptions restOptions = new IRestOptions() { @Override public Map getHeaders() { - Map headers = new HashMap<>(); + Map headers = new HashMap<>(); headers.put("Oauth-Token", "test-token"); headers.put("Custom-Meta-Data", "some information the developer wants send"); return headers; @@ -82,6 +78,8 @@ public HostnameVerifier getHostnameVerifier() { return null; } }; + private static DevCycleLocalClient client; + private static LocalConfigServer localConfigServer; @BeforeClass public static void setup() throws Exception { @@ -91,7 +89,7 @@ public static void setup() throws Exception { client = createClient(TestDataFixtures.SmallConfig()); } - private static DevCycleLocalClient createClient(String config){ + private static DevCycleLocalClient createClient(String config) { localConfigServer.setConfigData(config); DevCycleLocalOptions options = DevCycleLocalOptions.builder() @@ -104,17 +102,16 @@ private static DevCycleLocalClient createClient(String config){ DevCycleLocalClient client = new DevCycleLocalClient(apiKey, options); try { int loops = 0; - while(!client.isInitialized()) - { + while (!client.isInitialized()) { // wait for the client to load the config and initialize Thread.sleep(100); loops++; // wait a max 10 seconds to initialize client before failing completely - if(loops >= 100){ - throw new RuntimeException("Client failed to initialize in 10 seconds"); - } + if (loops >= 100) { + throw new RuntimeException("Client failed to initialize in 10 seconds"); + } } - }catch (InterruptedException e) { + } catch (InterruptedException e) { // no-op } return client; @@ -167,7 +164,7 @@ public void variableJsonValueTest() { user.setEmail("giveMeVariationOn@email.com"); DevCycleLocalClient myClient = createClient(TestDataFixtures.SmallConfig()); - Map defaultJSON = new HashMap(); + Map defaultJSON = new HashMap(); defaultJSON.put("displayText", "This variation is off"); defaultJSON.put("showDialog", false); defaultJSON.put("maxUsers", 0); @@ -175,14 +172,14 @@ public void variableJsonValueTest() { Variable var = myClient.variable(user, "json-var", defaultJSON); Assert.assertNotNull(var); Assert.assertFalse(var.getIsDefaulted()); - Map variableData = (Map)var.getValue(); + Map variableData = (Map) var.getValue(); Assert.assertEquals("This variation is on", variableData.get("displayText")); Assert.assertEquals(true, variableData.get("showDialog")); Assert.assertEquals(100, variableData.get("maxUsers")); } @Test - public void variableTestNotInitialized(){ + public void variableTestNotInitialized() { // NOTE - this test will generate some additional logging noise from the EventQueue // because it isn't initialized properly before the first call to variable() DevCycleLocalClient newClient = new DevCycleLocalClient(apiKey); @@ -193,17 +190,17 @@ public void variableTestNotInitialized(){ } @Test - public void variableTestWithCustomData(){ + public void variableTestWithCustomData() { DevCycleUser user = getUser(); user.setEmail("giveMeVariationOn@email.com"); - Map customData = new HashMap(); + Map customData = new HashMap(); customData.put("boolProp", true); customData.put("intProp", 123); customData.put("stringProp", "abc"); user.setCustomData(customData); - Map privateCustomData = new HashMap(); + Map privateCustomData = new HashMap(); privateCustomData.put("boolProp", false); privateCustomData.put("intProp", 789); privateCustomData.put("stringProp", "xyz"); @@ -214,14 +211,15 @@ public void variableTestWithCustomData(){ Assert.assertFalse(var.getIsDefaulted()); Assert.assertEquals("variationOn", var.getValue()); } + @Test - public void variableTestBucketingWithCustomData(){ + public void variableTestBucketingWithCustomData() { // Make sure we are properly sending custom data to the WASM so the user is bucketed correctly DevCycleLocalClient myClient = createClient(TestDataFixtures.SmallConfigWithCustomDataBucketing()); DevCycleUser user = getUser(); - Map customData = new HashMap(); + Map customData = new HashMap(); customData.put("should-bucket", true); user.setCustomData(customData); @@ -230,8 +228,9 @@ public void variableTestBucketingWithCustomData(){ Assert.assertFalse(var.getIsDefaulted()); Assert.assertEquals("↑↑↓↓←→←→BA 🤖", var.getValue()); } + @Test - public void variableTestUnknownVariableKey(){ + public void variableTestUnknownVariableKey() { Variable var = client.variable(getUser(), "some-var-that-doesnt-exist", true); Assert.assertNotNull(var); Assert.assertTrue(var.getIsDefaulted()); @@ -239,7 +238,7 @@ public void variableTestUnknownVariableKey(){ } @Test - public void variableTestTypeMismatch(){ + public void variableTestTypeMismatch() { Variable var = client.variable(getUser(), "string-var", true); Assert.assertNotNull(var); Assert.assertTrue(var.getIsDefaulted()); @@ -252,17 +251,17 @@ public void variableTestNoDefault() { try { Variable var = client.variable(user, "string-var", null); Assert.fail("Expected IllegalArgumentException for null default value"); - }catch(IllegalArgumentException e) { + } catch (IllegalArgumentException e) { // expected } } @Test public void variableTestNullUser() { - try{ + try { client.variable(null, "string-var", "default string"); Assert.fail("Expected IllegalArgumentException for null user"); - }catch(IllegalArgumentException e) { + } catch (IllegalArgumentException e) { // expected } } @@ -273,7 +272,7 @@ public void variableTestBadUserID() { try { client.variable(badUser, "string-var", "default string"); Assert.fail("Expected IllegalArgumentException for empty userID"); - }catch(IllegalArgumentException e) { + } catch (IllegalArgumentException e) { // expected } } @@ -320,7 +319,7 @@ public void allVariablesTest() { } @Test - public void setClientCustomDataWithBadMap(){ + public void setClientCustomDataWithBadMap() { // should be a no-op client.setClientCustomData(null); @@ -334,7 +333,7 @@ public void SetClientCustomDataWithBucketingTest() { DevCycleLocalClient myClient = createClient(TestDataFixtures.SmallConfigWithCustomDataBucketing()); // set the global custom data - Map customData = new HashMap(); + Map customData = new HashMap(); customData.put("should-bucket", true); myClient.setClientCustomData(customData); @@ -351,7 +350,7 @@ public void allFeaturesWithSpecialCharsTest() { DevCycleLocalClient myClient = createClient(TestDataFixtures.SmallConfigWithSpecialCharacters()); // make sure the user get bucketed correctly based on the global custom data DevCycleUser user = getUser(); - Map features = myClient.allFeatures(user); + Map features = myClient.allFeatures(user); Assert.assertNotNull(features); Assert.assertEquals(features.size(), 1); } diff --git a/src/test/java/com/devcycle/sdk/server/local/LocalBucketingTest.java b/src/test/java/com/devcycle/sdk/server/local/LocalBucketingTest.java index 206e98a0..6ce5f705 100644 --- a/src/test/java/com/devcycle/sdk/server/local/LocalBucketingTest.java +++ b/src/test/java/com/devcycle/sdk/server/local/LocalBucketingTest.java @@ -1,23 +1,22 @@ package com.devcycle.sdk.server.local; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -import com.devcycle.sdk.server.common.model.PlatformData; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.junit.MockitoJUnitRunner; - import com.devcycle.sdk.server.common.model.DevCycleEvent; import com.devcycle.sdk.server.common.model.DevCycleUser; +import com.devcycle.sdk.server.common.model.PlatformData; import com.devcycle.sdk.server.local.bucketing.LocalBucketing; import com.devcycle.sdk.server.local.model.FlushPayload; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; @RunWith(MockitoJUnitRunner.class) public class LocalBucketingTest { @@ -31,7 +30,7 @@ public class LocalBucketingTest { final ObjectMapper mapper = new ObjectMapper(); @Before - public void setup(){ + public void setup() { mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); localBucketing.setPlatformData(getUser().getPlatformData().toString()); localBucketing.storeConfig(apiKey, testConfigString); @@ -39,7 +38,7 @@ public void setup(){ @Test public void testSetClientCustomData() { - Map testData = new HashMap(); + Map testData = new HashMap(); testData.put("stringProp", "test"); testData.put("intProp", 1); testData.put("booleanProp", true); @@ -53,21 +52,21 @@ public void testSetClientCustomData() { } @Test - public void testSetPlatformData(){ + public void testSetPlatformData() { try { PlatformData platformData = PlatformData.builder().build(); String platformDataJSON = mapper.writeValueAsString(platformData); localBucketing.setPlatformData(platformDataJSON); - }catch(Exception e){ + } catch (Exception e) { Assert.fail("Unexpected exception: " + e.getMessage()); } } @Test - public void testStoreConfig(){ + public void testStoreConfig() { try { localBucketing.storeConfig(apiKey, testConfigString); - }catch(Exception e){ + } catch (Exception e) { Assert.fail("Unexpected exception: " + e.getMessage()); } } diff --git a/src/test/java/com/devcycle/sdk/server/openfeature/DevCycleProviderLocalSDKTest.java b/src/test/java/com/devcycle/sdk/server/openfeature/DevCycleProviderLocalSDKTest.java index 58c6f731..8cf14eca 100644 --- a/src/test/java/com/devcycle/sdk/server/openfeature/DevCycleProviderLocalSDKTest.java +++ b/src/test/java/com/devcycle/sdk/server/openfeature/DevCycleProviderLocalSDKTest.java @@ -20,9 +20,9 @@ * data conversions are working as expected */ public class DevCycleProviderLocalSDKTest { + static final String apiKey = String.format("server-%s", UUID.randomUUID()); private static DevCycleLocalClient client; private static LocalConfigServer localConfigServer; - static final String apiKey = String.format("server-%s", UUID.randomUUID()); @BeforeClass public static void setup() throws Exception { diff --git a/src/test/java/com/devcycle/sdk/server/utils/ByteConversionUtilsTest.java b/src/test/java/com/devcycle/sdk/server/utils/ByteConversionUtilsTest.java index 74bbcce5..62e6c617 100644 --- a/src/test/java/com/devcycle/sdk/server/utils/ByteConversionUtilsTest.java +++ b/src/test/java/com/devcycle/sdk/server/utils/ByteConversionUtilsTest.java @@ -7,7 +7,7 @@ public class ByteConversionUtilsTest { @Test public void getUnsignedIntTest() { - byte[] data = { (byte) 0x12, (byte) 0x34, (byte) 0x56, (byte) 0x78 }; + byte[] data = {(byte) 0x12, (byte) 0x34, (byte) 0x56, (byte) 0x78}; long expected = 0x12345678L; long result = ByteConversionUtils.getUnsignedInt(data); Assert.assertEquals(expected, result); @@ -21,13 +21,14 @@ public void testWriteInt32LittleEndian() { Assert.assertArrayEquals(expected, result); value = 123456789; - expected = new byte[]{ (byte) 0x15, (byte) 0xCD, (byte) 0x5B, (byte) 0x07 }; + expected = new byte[]{(byte) 0x15, (byte) 0xCD, (byte) 0x5B, (byte) 0x07}; result = ByteConversionUtils.intToBytesLittleEndian(value); Assert.assertArrayEquals(expected, result); } + @Test public void testReadInt32LittleEndian() { - byte[] encodedValue = { (byte) 0x15, (byte) 0xCD, (byte) 0x5B, (byte) 0x07 }; + byte[] encodedValue = {(byte) 0x15, (byte) 0xCD, (byte) 0x5B, (byte) 0x07}; int expected = 123456789; int result = ByteConversionUtils.bytesToIntLittleEndian(encodedValue); diff --git a/src/test/resources/fixture_small_config.json b/src/test/resources/fixture_small_config.json index c00d10ab..e2ce38e0 100644 --- a/src/test/resources/fixture_small_config.json +++ b/src/test/resources/fixture_small_config.json @@ -169,6 +169,6 @@ "a-cool-new-feature": 1868656757, "string-var": 2413071944, "json-var": 2763925441, - "num-var": 3071254410 + "num-var": 3071254410 } } \ No newline at end of file diff --git a/src/test/resources/fixture_small_config_special_characters.json b/src/test/resources/fixture_small_config_special_characters.json index d3b59dce..9494a2f6 100644 --- a/src/test/resources/fixture_small_config_special_characters.json +++ b/src/test/resources/fixture_small_config_special_characters.json @@ -166,6 +166,6 @@ "a-cool-new-feature": 1868656757, "string-var": 2413071944, "json-var": 2763925441, - "num-var": 3071254410 + "num-var": 3071254410 } } \ No newline at end of file