Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11

def wasmResourcePath = "$projectDir/src/main/resources"
def wasmVersion = "1.41.0"
def wasmVersion = "1.42.1"
def wasmUrl = "https://unpkg.com/@devcycle/bucketing-assembly-script@$wasmVersion/build/bucketing-lib.release.wasm"
task downloadDVCBucketingWASM(type: Download) {
src wasmUrl
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -382,5 +382,15 @@ private int getSDKKeyAddress(String sdkKey) {

return sdkKeyAddresses.get(sdkKey);
}

public String getConfigMetadata(String sdkKey) {
int sdkKeyAddress = getSDKKeyAddress(sdkKey);
Func getConfigMetadataPtr = linker.get(store, "", "getConfigMetadata").get().func();
WasmFunctions.Function1<Integer, Integer> getConfigMetadata = WasmFunctions.func(
store, getConfigMetadataPtr, I32, I32);

int resultAddress = getConfigMetadata.call(sdkKeyAddress);
return readWasmString(resultAddress);
}
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mostly reverting back to the code from before config metadata was added

Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ public final class EnvironmentConfigManager {
private final DevCycleLocalOptions options;

private ProjectConfig config;
private ConfigMetadata configMetadata;
private String configETag = "";
private String configLastModified = "";

private final String sdkKey;
private final int pollingIntervalMS;
Expand Down Expand Up @@ -73,9 +74,7 @@ public void run() {
}
} catch (DevCycleException e) {
DevCycleLogger.error("Failed to load config: " + e.getMessage(), e);
} catch (Exception e) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed as it was added through cursor while fixing an error

DevCycleLogger.error("Unexpected error during config fetch: " + e.getMessage(), e);
}
}
}
};

Expand All @@ -84,15 +83,7 @@ public boolean isConfigInitialized() {
}

private ProjectConfig getConfig() throws DevCycleException {
// Handle initial request where configMetadata might be null
String etag = null;
String lastModified = null;
if (this.configMetadata != null) {
etag = this.configMetadata.configETag;
lastModified = this.configMetadata.configLastModified;
}

Call<ProjectConfig> config = this.configApiClient.getConfig(this.sdkKey, etag, lastModified);
Call<ProjectConfig> config = this.configApiClient.getConfig(this.sdkKey, this.configETag, this.configLastModified);
ProjectConfig fetchedConfig = getResponseWithRetries(config, 1);
this.config = fetchedConfig;

Expand Down Expand Up @@ -204,16 +195,13 @@ private ProjectConfig getConfigResponse(Call<ProjectConfig> call) throws DevCycl
String currentETag = response.headers().get("ETag");
String headerLastModified = response.headers().get("Last-Modified");

// Check if we should skip this config due to older timestamp (only if configMetadata exists)
if (this.configMetadata != null &&
!this.configMetadata.configLastModified.isEmpty() &&
headerLastModified != null && !headerLastModified.isEmpty()) {
if (!this.configLastModified.isEmpty() && headerLastModified != null && !headerLastModified.isEmpty()) {
ZonedDateTime parsedLastModified = ZonedDateTime.parse(
headerLastModified,
DateTimeFormatter.RFC_1123_DATE_TIME
);
ZonedDateTime configLastModified = ZonedDateTime.parse(
this.configMetadata.configLastModified,
this.configLastModified,
DateTimeFormatter.RFC_1123_DATE_TIME
);

Expand All @@ -229,23 +217,18 @@ private ProjectConfig getConfigResponse(Call<ProjectConfig> call) throws DevCycl
localBucketing.storeConfig(sdkKey, mapper.writeValueAsString(config));
} catch (JsonProcessingException e) {
if (this.config != null) {
String currentConfigInfo = (this.configMetadata != null) ?
" etag " + this.configMetadata.configETag + " last-modified: " + this.configMetadata.configLastModified :
"";
DevCycleLogger.error("Unable to parse config with etag: " + currentETag + ". Using cache," + currentConfigInfo);
DevCycleLogger.error("Unable to parse config with etag: " + currentETag + ". Using cache, etag " + this.configETag + " last-modified: " + this.configLastModified);
return this.config;
} else {
errorResponse.setMessage(e.getMessage());
throw new DevCycleException(HttpResponseCode.SERVER_ERROR, errorResponse);
}
}
this.configMetadata = new ConfigMetadata(currentETag, headerLastModified, config.getProject(), config.getEnvironment());
this.configETag = currentETag;
this.configLastModified = headerLastModified;
return response.body();
} else if (httpResponseCode == HttpResponseCode.NOT_MODIFIED) {
String cacheInfo = (this.configMetadata != null) ?
" etag: " + this.configMetadata.configETag + " last-modified: " + this.configMetadata.configLastModified :
" (no metadata available)";
DevCycleLogger.debug("Config not modified, using cache," + cacheInfo);
DevCycleLogger.debug("Config not modified, using cache, etag: " + this.configETag + " last-modified: " + this.configLastModified);
return this.config;
} else {
if (response.errorBody() != null) {
Expand Down Expand Up @@ -291,6 +274,12 @@ public void cleanup() {
}

public ConfigMetadata getConfigMetadata() {
return configMetadata;
String configMetadata = localBucketing.getConfigMetadata(this.sdkKey);
try {
return OBJECT_MAPPER.readValue(configMetadata, ConfigMetadata.class);
} catch (JsonProcessingException e) {
DevCycleLogger.warning("Unable to parse config metadata: " + e.getMessage());
return new ConfigMetadata();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
package com.devcycle.sdk.server.local.model;

import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

@AllArgsConstructor
@NoArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
public class ConfigMetadata {

public final String configETag;
public final String configLastModified;
public final ProjectMetadata project;
public final EnvironmentMetadata environment;
public ProjectMetadata project;
public EnvironmentMetadata environment;

public ConfigMetadata(String currentETag, String headerLastModified, Project project, Environment environment) {
this.configETag = currentETag;
this.configLastModified = headerLastModified;
this.project = new ProjectMetadata(project._id, project.key);
this.environment = new EnvironmentMetadata(environment._id, environment.key);
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
package com.devcycle.sdk.server.local.model;

public class EnvironmentMetadata {
public final String id;
public final String key;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

public EnvironmentMetadata(String id, String key) {
this.id = id;
this.key = key;
}
@AllArgsConstructor
@NoArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
public class EnvironmentMetadata {
@JsonProperty("id")
public String id;
@JsonProperty("key")
public String key;
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
package com.devcycle.sdk.server.local.model;

public class ProjectMetadata {
public final String id;
public final String key;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;

public ProjectMetadata(String id, String key) {
this.id = id;
this.key = key;
}
@AllArgsConstructor
@NoArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
public class ProjectMetadata {
@JsonProperty("id")
public String id;
@JsonProperty("key")
public String key;
}
Binary file modified src/main/resources/bucketing-lib.release.wasm
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@
import com.devcycle.sdk.server.local.model.ConfigMetadata;
import com.devcycle.sdk.server.local.model.DevCycleLocalOptions;
import com.devcycle.sdk.server.local.model.Environment;
import com.devcycle.sdk.server.local.model.EnvironmentMetadata;
import com.devcycle.sdk.server.local.model.Project;
import com.devcycle.sdk.server.local.model.ProjectMetadata;

@RunWith(MockitoJUnitRunner.class)
public class DevCycleLocalClientTest {
Expand Down Expand Up @@ -1035,14 +1037,9 @@ public void after(HookContext<String> ctx, Variable<String> variable) {

// Check that config metadata has the expected structure
ConfigMetadata metadata = ctx.getMetadata();
Assert.assertNotNull("Config ETag should not be null", metadata.configETag);
Assert.assertNotNull("Config last modified should not be null", metadata.configLastModified);
Assert.assertNotNull("Project metadata should not be null", metadata.project);
Assert.assertNotNull("Environment metadata should not be null", metadata.environment);

// Verify basic metadata structure is present
Assert.assertFalse("Config ETag should not be empty", metadata.configETag.isEmpty());
Assert.assertFalse("Config last modified should not be empty", metadata.configLastModified.isEmpty());

metadataChecked[0] = true;
}
Expand Down Expand Up @@ -1089,11 +1086,9 @@ public void onFinally(HookContext<String> ctx, Optional<Variable<String>> variab

// Verify metadata is consistent across all hook stages
Assert.assertEquals("Before and after metadata should be the same",
capturedMetadata[0].configETag, capturedMetadata[1].configETag);
capturedMetadata[0], capturedMetadata[1]);
Assert.assertEquals("Before and finally metadata should be the same",
capturedMetadata[0].configETag, capturedMetadata[2].configETag);
Assert.assertEquals("Metadata timestamps should be consistent",
capturedMetadata[0].configLastModified, capturedMetadata[1].configLastModified);
capturedMetadata[0], capturedMetadata[2]);
}

@Test
Expand All @@ -1115,8 +1110,6 @@ public void error(HookContext<String> ctx, Throwable error) {
// Verify metadata is accessible even in error hook
Assert.assertNotNull("Metadata should be accessible in error hook", ctx.getMetadata());
ConfigMetadata metadata = ctx.getMetadata();
Assert.assertNotNull("Config ETag should not be null in error hook", metadata.configETag);
Assert.assertNotNull("Config last modified should not be null in error hook", metadata.configLastModified);
Assert.assertNotNull("Project metadata should not be null in error hook", metadata.project);
Assert.assertNotNull("Environment metadata should not be null in error hook", metadata.environment);
metadataCheckedInError[0] = true;
Expand Down Expand Up @@ -1152,15 +1145,10 @@ public void after(HookContext<String> ctx, Variable<String> variable) {
ConfigMetadata directMetadata = client.getMetadata();
Assert.assertNotNull("Direct metadata should not be null", directMetadata);

// The metadata in hooks should match the current client metadata
Assert.assertEquals("Hook metadata ETag should match current metadata",
directMetadata.configETag, capturedMetadata[0].configETag);
Assert.assertEquals("Hook metadata timestamp should match current metadata",
directMetadata.configLastModified, capturedMetadata[0].configLastModified);
Assert.assertEquals("Hook metadata project should match current metadata",
directMetadata.project, capturedMetadata[0].project);
directMetadata.project.id, capturedMetadata[0].project.id);
Assert.assertEquals("Hook metadata environment should match current metadata",
directMetadata.environment, capturedMetadata[0].environment);
directMetadata.environment.id, capturedMetadata[0].environment.id);
}

@Test
Expand All @@ -1177,7 +1165,6 @@ public void variable_withMultipleHooks_allReceiveMetadata() throws DevCycleExcep
public void after(HookContext<String> ctx, Variable<String> variable) {
Assert.assertNotNull("First hook should receive metadata", ctx.getMetadata());
Assert.assertNotNull("First hook metadata should have project", ctx.getMetadata().project);
Assert.assertNotNull("First hook metadata should have config ETag", ctx.getMetadata().configETag);
metadataChecked[0] = true;
}
});
Expand All @@ -1188,7 +1175,6 @@ public void after(HookContext<String> ctx, Variable<String> variable) {
public void after(HookContext<String> ctx, Variable<String> variable) {
Assert.assertNotNull("Second hook should receive metadata", ctx.getMetadata());
Assert.assertNotNull("Second hook metadata should have environment", ctx.getMetadata().environment);
Assert.assertNotNull("Second hook metadata should have last modified", ctx.getMetadata().configLastModified);
metadataChecked[1] = true;
}
});
Expand All @@ -1212,16 +1198,12 @@ public void configMetadata_canBeConstructedWithMockData() {

// Test ConfigMetadata construction
ConfigMetadata metadata = new ConfigMetadata(
"test-etag-12345",
"2023-10-01T12:00:00Z",
mockProject,
mockEnvironment
new ProjectMetadata(mockProject._id, mockProject.key),
new EnvironmentMetadata(mockEnvironment._id, mockEnvironment.key)
);

// Verify metadata is properly constructed
Assert.assertNotNull("Metadata should not be null", metadata);
Assert.assertEquals("Config ETag should match", "test-etag-12345", metadata.configETag);
Assert.assertEquals("Config last modified should match", "2023-10-01T12:00:00Z", metadata.configLastModified);
Assert.assertNotNull("Project metadata should not be null", metadata.project);
Assert.assertNotNull("Environment metadata should not be null", metadata.environment);

Expand All @@ -1231,6 +1213,5 @@ public void configMetadata_canBeConstructedWithMockData() {

Assert.assertNotNull("HookContext should not be null", contextWithMetadata);
Assert.assertEquals("Metadata should be accessible from context", metadata, contextWithMetadata.getMetadata());
Assert.assertEquals("Config ETag should be accessible", "test-etag-12345", contextWithMetadata.getMetadata().configETag);
}
}
Loading