vaultClientsMap = new LinkedHashMap<>();
+ // Client-wide HTTP config. Resolution per vault, most specific first:
+ // VaultConfig value -> the value set here -> SDK default (60s call timeout, 0 retries).
+ // null here means "not set", so the SDK default applies to vaults that don't override it.
+ // Only null means inherit: an explicit 0 is a real value and wins over the level below.
+ private Integer timeout;
+ private Integer connectTimeout;
+ private Integer readTimeout;
+ private Integer writeTimeout;
+ private Integer maxRetries;
@Override
protected void validateVaultConfig(VaultConfig vaultConfig) throws SkyflowException {
@@ -54,13 +63,26 @@ protected void validateVaultConfig(VaultConfig vaultConfig) throws SkyflowExcept
@Override
protected void onVaultConfigAdded(VaultConfig vaultConfig) throws SkyflowException {
- this.vaultClientsMap.put(vaultConfig.getVaultId(), new VaultController(vaultConfig, this.skyflowCredentials));
+ VaultController controller = new VaultController(vaultConfig, this.skyflowCredentials);
+ controller.setCommonHttpConfig(this.timeout, this.connectTimeout, this.readTimeout,
+ this.writeTimeout, this.maxRetries);
+ this.vaultClientsMap.put(vaultConfig.getVaultId(), controller);
LogUtil.printInfoLog(Utils.parameterizedString(InfoLogs.VAULT_CONTROLLER_INITIALIZED.getLog(), vaultConfig.getVaultId()));
}
@Override
protected void onVaultConfigUpdated(VaultConfig updatedConfig) throws SkyflowException {
- this.vaultClientsMap.put(updatedConfig.getVaultId(), new VaultController(updatedConfig, this.skyflowCredentials));
+ // Update the existing controller in place — replacing it would leave any VaultController
+ // reference the caller already holds pointing at the previous config.
+ VaultController updated = this.vaultClientsMap.get(updatedConfig.getVaultId());
+ if (updated == null) {
+ updated = new VaultController(updatedConfig, this.skyflowCredentials);
+ this.vaultClientsMap.put(updatedConfig.getVaultId(), updated);
+ } else {
+ updated.setVaultConfig(updatedConfig);
+ }
+ updated.setCommonHttpConfig(this.timeout, this.connectTimeout, this.readTimeout,
+ this.writeTimeout, this.maxRetries);
}
@Override
@@ -89,9 +111,48 @@ public SkyflowClientBuilder addVaultConfig(VaultConfig vaultConfig) throws Skyfl
@Override
public SkyflowClientBuilder updateVaultConfig(VaultConfig vaultConfig) throws SkyflowException {
super.updateVaultConfig(vaultConfig);
+ carryVaultOverrides(vaultConfig);
return this;
}
+ /**
+ * BaseSkyflow.mergeVaultConfig() only carries env, clusterId and credentials across, so the
+ * flowvault-specific fields on an incoming update — vaultUrl and the HTTP settings — would
+ * be dropped silently. Apply them to the merged config the new controller is holding. A null
+ * on the incoming config means "leave as is", matching how the base class merges every
+ * other field.
+ */
+ private void carryVaultOverrides(VaultConfig incoming) throws SkyflowException {
+ VaultConfig merged = this.vaultConfigMap.get(incoming.getVaultId());
+ if (merged == null || merged == incoming) {
+ return;
+ }
+ if (incoming.getTimeout() != null) {
+ merged.setTimeout(incoming.getTimeout());
+ }
+ if (incoming.getConnectTimeout() != null) {
+ merged.setConnectTimeout(incoming.getConnectTimeout());
+ }
+ if (incoming.getReadTimeout() != null) {
+ merged.setReadTimeout(incoming.getReadTimeout());
+ }
+ if (incoming.getWriteTimeout() != null) {
+ merged.setWriteTimeout(incoming.getWriteTimeout());
+ }
+ if (incoming.getMaxRetries() != null) {
+ merged.setMaxRetries(incoming.getMaxRetries());
+ }
+ // The HTTP settings above are resolved lazily on the next request, but the URL is
+ // resolved once in the VaultClient constructor — which already ran with the old value.
+ if (incoming.getVaultUrl() != null) {
+ merged.setVaultUrl(incoming.getVaultUrl());
+ VaultController controller = this.vaultClientsMap.get(incoming.getVaultId());
+ if (controller != null) {
+ controller.refreshVaultUrl();
+ }
+ }
+ }
+
@Override
public SkyflowClientBuilder removeVaultConfig(String vaultId) throws SkyflowException {
super.removeVaultConfig(vaultId);
@@ -110,6 +171,75 @@ public SkyflowClientBuilder setLogLevel(LogLevel logLevel) {
return this;
}
+ /**
+ * Overall call timeout in seconds, including retries. Default 60.
+ *
+ * Precedence: a vault that sets {@link VaultConfig#setTimeout(Integer)} wins; this
+ * value applies only to vaults that leave it unset.
+ */
+ public SkyflowClientBuilder timeout(int timeout) {
+ this.timeout = timeout;
+ propagateHttpConfig();
+ return this;
+ }
+
+ /**
+ * Per-attempt connection-establishment timeout in seconds. Unset => HTTP client default (10s).
+ *
+ * Precedence: a vault that sets {@link VaultConfig#setConnectTimeout(Integer)} wins;
+ * this value applies only to vaults that leave it unset.
+ */
+ public SkyflowClientBuilder connectTimeout(int connectTimeout) {
+ this.connectTimeout = connectTimeout;
+ propagateHttpConfig();
+ return this;
+ }
+
+ /**
+ * Per-attempt response-read timeout in seconds. Unset => HTTP client default (10s).
+ *
+ * Precedence: a vault that sets {@link VaultConfig#setReadTimeout(Integer)} wins;
+ * this value applies only to vaults that leave it unset.
+ */
+ public SkyflowClientBuilder readTimeout(int readTimeout) {
+ this.readTimeout = readTimeout;
+ propagateHttpConfig();
+ return this;
+ }
+
+ /**
+ * Per-attempt request-write timeout in seconds. Unset => HTTP client default (10s).
+ *
+ * Precedence: a vault that sets {@link VaultConfig#setWriteTimeout(Integer)} wins;
+ * this value applies only to vaults that leave it unset.
+ */
+ public SkyflowClientBuilder writeTimeout(int writeTimeout) {
+ this.writeTimeout = writeTimeout;
+ propagateHttpConfig();
+ return this;
+ }
+
+ /**
+ * Retry attempts after the first failure. Default 0 — retries are opt-in so non-idempotent
+ * bulk writes are not replayed automatically.
+ *
+ * Precedence: a vault that sets {@link VaultConfig#setMaxRetries(Integer)} wins;
+ * this value applies only to vaults that leave it unset.
+ */
+ public SkyflowClientBuilder maxRetries(int maxRetries) {
+ this.maxRetries = maxRetries;
+ propagateHttpConfig();
+ return this;
+ }
+
+ /** Push the current client-wide HTTP settings onto every vault controller built so far. */
+ private void propagateHttpConfig() {
+ for (VaultController vault : this.vaultClientsMap.values()) {
+ vault.setCommonHttpConfig(this.timeout, this.connectTimeout, this.readTimeout,
+ this.writeTimeout, this.maxRetries);
+ }
+ }
+
public Skyflow build() {
return new Skyflow(this);
}
diff --git a/flowvault/src/main/java/com/skyflow/VaultClient.java b/flowvault/src/main/java/com/skyflow/VaultClient.java
index 9033e642..a8a2e2d7 100644
--- a/flowvault/src/main/java/com/skyflow/VaultClient.java
+++ b/flowvault/src/main/java/com/skyflow/VaultClient.java
@@ -5,19 +5,67 @@
import com.skyflow.errors.SkyflowException;
import com.skyflow.generated.rest.ApiClient;
import com.skyflow.generated.rest.ApiClientBuilder;
+import com.skyflow.generated.rest.core.RetryInterceptor;
import com.skyflow.generated.rest.resources.flowservice.FlowserviceClient;
import com.skyflow.generated.rest.resources.records.RecordsClient;
import com.skyflow.utils.Utils;
+import java.util.concurrent.TimeUnit;
+
+import okhttp3.ConnectionPool;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+
public class VaultClient extends BaseVaultClient {
private final ApiClientBuilder apiClientBuilder;
private ApiClient apiClient;
+ // Client-wide (Skyflow builder) HTTP config; null => fall back to the SDK defaults below.
+ private Integer commonTimeout;
+ private Integer commonConnectTimeout;
+ private Integer commonReadTimeout;
+ private Integer commonWriteTimeout;
+ private Integer commonMaxRetries;
+ // SDK defaults, used when neither the vault-level nor the client-wide value is set.
+ private static final int DEFAULT_TIMEOUT_SECONDS = 60;
+ // Retries OFF by default (opt-in) so non-idempotent bulk writes aren't replayed automatically.
+ private static final int DEFAULT_MAX_RETRIES = 0;
protected VaultClient(VaultConfig vaultConfig, Credentials credentials) throws SkyflowException {
super(vaultConfig, credentials);
this.apiClientBuilder = new ApiClientBuilder();
this.apiClient = null;
- updateVaultURL();
+ updateVaultUrl();
+ }
+
+ /**
+ * Applies the client-wide HTTP settings from the Skyflow builder. Discards the cached HTTP
+ * client and ApiClient so the next call rebuilds them with the new values.
+ */
+ protected void setCommonHttpConfig(Integer timeout, Integer connectTimeout, Integer readTimeout,
+ Integer writeTimeout, Integer maxRetries) {
+ this.commonTimeout = timeout;
+ this.commonConnectTimeout = connectTimeout;
+ this.commonReadTimeout = readTimeout;
+ this.commonWriteTimeout = writeTimeout;
+ this.commonMaxRetries = maxRetries;
+ this.sharedHttpClient = null;
+ this.apiClient = null;
+ }
+
+ /** Resolve a setting: vault-level override, else client-wide default, else the SDK default. */
+ private static int resolveInt(Integer vaultLevel, Integer clientLevel, int defaultValue) {
+ if (vaultLevel != null) {
+ return vaultLevel;
+ }
+ return clientLevel != null ? clientLevel : defaultValue;
+ }
+
+ /**
+ * Resolve an optional setting: vault-level override, else client-wide default, else null.
+ * Null means "not configured" — the caller leaves the underlying HTTP client default in place.
+ */
+ private static Integer resolveNullableInt(Integer vaultLevel, Integer clientLevel) {
+ return vaultLevel != null ? vaultLevel : clientLevel;
}
protected FlowserviceClient getRecordsApi() {
@@ -41,29 +89,79 @@ protected synchronized void setBearerToken() throws SkyflowException {
}
}
- private void updateVaultURL() throws SkyflowException {
- // Fetch vaultURL from ENV
- String vaultURL = Utils.getEnvVaultURL();
+ /**
+ * Adopts an updated config in place, so a VaultController reference the caller already holds
+ * keeps working instead of silently serving the previous config. Discards the cached HTTP and
+ * API clients; the bearer token is re-resolved by setBearerToken, which drops it when the
+ * effective credentials changed.
+ */
+ protected void setVaultConfig(VaultConfig vaultConfig) throws SkyflowException {
+ this.vaultConfig = vaultConfig;
+ this.sharedHttpClient = null;
+ this.apiClient = null;
+ updateVaultUrl();
+ }
+
+ /**
+ * Re-resolves the vault URL from the current config. The constructor resolves it once, so a
+ * vaultUrl supplied later through updateVaultConfig would otherwise never take effect.
+ */
+ protected void refreshVaultUrl() throws SkyflowException {
+ updateVaultUrl();
+ }
+
+ private void updateVaultUrl() throws SkyflowException {
+ // Fetch vaultUrl from ENV
+ String vaultUrl = Utils.getEnvVaultUrl();
- // If vaultURL from ENV is null or empty, fetch vaultURL from vault config
- if (vaultURL == null || vaultURL.isEmpty()) {
- vaultURL = this.vaultConfig.getVaultURL();
+ // If vaultUrl from ENV is null or empty, fetch vaultUrl from vault config
+ if (vaultUrl == null || vaultUrl.isEmpty()) {
+ vaultUrl = this.vaultConfig.getVaultUrl();
}
- // If vaultURL from vault config is also null or empty, construct vaultURL from clusterId passed in vault config
- if (vaultURL == null || vaultURL.isEmpty()) {
- vaultURL = Utils.getVaultURL(this.vaultConfig.getClusterId(), this.vaultConfig.getEnv());
+ // If vaultUrl from vault config is also null or empty, construct vaultUrl from clusterId passed in vault config
+ if (vaultUrl == null || vaultUrl.isEmpty()) {
+ vaultUrl = Utils.getVaultUrl(this.vaultConfig.getClusterId(), this.vaultConfig.getEnv());
}
- this.apiClientBuilder.url(vaultURL);
- if (!vaultURL.equals(this.currentVaultURL)) {
- this.currentVaultURL = vaultURL;
+ this.apiClientBuilder.url(vaultUrl);
+ if (!vaultUrl.equals(this.currentVaultURL)) {
+ this.currentVaultURL = vaultUrl;
this.apiClient = null;
}
}
protected void updateExecutorInHTTP() {
if (sharedHttpClient == null) {
- sharedHttpClient = buildSharedHttpClient(() -> this.token);
+ int timeoutSeconds = resolveInt(vaultConfig.getTimeout(), commonTimeout, DEFAULT_TIMEOUT_SECONDS);
+ int maxRetries = resolveInt(vaultConfig.getMaxRetries(), commonMaxRetries, DEFAULT_MAX_RETRIES);
+ // Per-attempt timeouts: null => leave OkHttp's built-in default (backward compatible).
+ Integer connectTimeout = resolveNullableInt(vaultConfig.getConnectTimeout(), commonConnectTimeout);
+ Integer readTimeout = resolveNullableInt(vaultConfig.getReadTimeout(), commonReadTimeout);
+ Integer writeTimeout = resolveNullableInt(vaultConfig.getWriteTimeout(), commonWriteTimeout);
+
+ OkHttpClient.Builder httpBuilder = new OkHttpClient.Builder()
+ .connectionPool(new ConnectionPool(10, 1, TimeUnit.MINUTES))
+ // Overall ceiling; bounds the whole call including retries.
+ .callTimeout(timeoutSeconds, TimeUnit.SECONDS)
+ // OUTER: retries. Must wrap the auth interceptor so each attempt re-reads the
+ // (possibly refreshed) bearer token rather than replaying a stale one.
+ .addInterceptor(new RetryInterceptor(maxRetries))
+ .addInterceptor(chain -> { // INNER: auth
+ Request requestWithAuth = chain.request().newBuilder()
+ .header("Authorization", "Bearer " + this.token)
+ .build();
+ return chain.proceed(requestWithAuth);
+ });
+ if (connectTimeout != null) {
+ httpBuilder.connectTimeout(connectTimeout, TimeUnit.SECONDS);
+ }
+ if (readTimeout != null) {
+ httpBuilder.readTimeout(readTimeout, TimeUnit.SECONDS);
+ }
+ if (writeTimeout != null) {
+ httpBuilder.writeTimeout(writeTimeout, TimeUnit.SECONDS);
+ }
+ sharedHttpClient = httpBuilder.build();
apiClientBuilder.httpClient(sharedHttpClient);
}
}
diff --git a/flowvault/src/main/java/com/skyflow/config/VaultConfig.java b/flowvault/src/main/java/com/skyflow/config/VaultConfig.java
index 8966d29f..b14ed6b5 100644
--- a/flowvault/src/main/java/com/skyflow/config/VaultConfig.java
+++ b/flowvault/src/main/java/com/skyflow/config/VaultConfig.java
@@ -1,20 +1,117 @@
package com.skyflow.config;
+/**
+ * Per-vault configuration.
+ *
+ * The HTTP timeout and retry settings below are vault-level overrides. Each one resolves
+ * most-specific-first: the value set here, else the client-wide value set on
+ * {@code Skyflow.builder()}, else the SDK default. So when the same setting is supplied at both
+ * levels, the value on this VaultConfig takes precedence and the client-wide value is
+ * ignored for this vault.
+ *
+ * Only {@code null} means "inherit" — an explicit {@code 0} is a real value and wins over the
+ * client-wide setting.
+ */
public class VaultConfig extends BaseVaultConfig {
- private String vaultURL;
+ private String vaultUrl;
+ // HTTP timeout & retry config (vault-level overrides). null => inherit client-wide default, then SDK default.
+ private Integer timeout; // overall call timeout, in seconds
+ private Integer connectTimeout; // per-attempt connection-establishment timeout, in seconds
+ private Integer readTimeout; // per-attempt response-read timeout, in seconds
+ private Integer writeTimeout; // per-attempt request-write timeout, in seconds
+ private Integer maxRetries; // retry attempts after the first failure
public VaultConfig() {
super();
- this.vaultURL = null;
+ this.vaultUrl = null;
+ this.timeout = null;
+ this.connectTimeout = null;
+ this.readTimeout = null;
+ this.writeTimeout = null;
+ this.maxRetries = null;
}
- public String getVaultURL() {
- return vaultURL;
+ public String getVaultUrl() {
+ return vaultUrl;
}
- public void setVaultURL(String vaultURL) {
- this.vaultURL = vaultURL;
+ public void setVaultUrl(String vaultUrl) {
+ this.vaultUrl = vaultUrl;
+ }
+
+ public Integer getTimeout() {
+ return timeout;
+ }
+
+ /**
+ * Overall call timeout in seconds for this vault, including retries.
+ *
+ * Takes precedence over the client-wide {@code Skyflow.builder().timeout(...)}. Leave unset
+ * (null) to inherit that value, or the SDK default of 60s if it is also unset.
+ */
+ public void setTimeout(Integer timeout) {
+ this.timeout = timeout;
+ }
+
+ public Integer getConnectTimeout() {
+ return connectTimeout;
+ }
+
+ /**
+ * Per-attempt connection-establishment timeout in seconds for this vault.
+ *
+ * Takes precedence over the client-wide {@code Skyflow.builder().connectTimeout(...)}. Leave
+ * unset (null) to inherit that value; if neither is set, the underlying HTTP client default
+ * (10s) applies. Note the overall {@code timeout} still bounds the whole call, including retries.
+ */
+ public void setConnectTimeout(Integer connectTimeout) {
+ this.connectTimeout = connectTimeout;
+ }
+
+ public Integer getReadTimeout() {
+ return readTimeout;
+ }
+
+ /**
+ * Per-attempt response-read timeout in seconds for this vault.
+ *
+ * Takes precedence over the client-wide {@code Skyflow.builder().readTimeout(...)}. Leave
+ * unset (null) to inherit that value; if neither is set, the underlying HTTP client default
+ * (10s) applies. Note the overall {@code timeout} still bounds the whole call, including retries.
+ */
+ public void setReadTimeout(Integer readTimeout) {
+ this.readTimeout = readTimeout;
+ }
+
+ public Integer getWriteTimeout() {
+ return writeTimeout;
+ }
+
+ /**
+ * Per-attempt request-write timeout in seconds for this vault.
+ *
+ * Takes precedence over the client-wide {@code Skyflow.builder().writeTimeout(...)}. Leave
+ * unset (null) to inherit that value; if neither is set, the underlying HTTP client default
+ * (10s) applies. Note the overall {@code timeout} still bounds the whole call, including retries.
+ */
+ public void setWriteTimeout(Integer writeTimeout) {
+ this.writeTimeout = writeTimeout;
+ }
+
+ public Integer getMaxRetries() {
+ return maxRetries;
+ }
+
+ /**
+ * Retry attempts after the first failure for this vault.
+ *
+ * Takes precedence over the client-wide {@code Skyflow.builder().maxRetries(...)}. Leave unset
+ * (null) to inherit that value, or the SDK default of 0 if it is also unset — retries are
+ * opt-in, so non-idempotent bulk writes are not replayed silently.
+ */
+ public void setMaxRetries(Integer maxRetries) {
+ this.maxRetries = maxRetries;
}
}
diff --git a/flowvault/src/main/java/com/skyflow/enums/UpsertType.java b/flowvault/src/main/java/com/skyflow/enums/UpsertType.java
deleted file mode 100644
index fa2350d2..00000000
--- a/flowvault/src/main/java/com/skyflow/enums/UpsertType.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package com.skyflow.enums;
-
-public enum UpsertType {
- UPDATE("UPDATE"),
-
- REPLACE("REPLACE");
-
- private final String value;
-
- UpsertType(String value) {
- this.value = value;
- }
-
- @Override
- public String toString() {
- return this.value;
- }
-}
diff --git a/flowvault/src/main/java/com/skyflow/utils/Constants.java b/flowvault/src/main/java/com/skyflow/utils/Constants.java
index 983e82b3..acbd471f 100644
--- a/flowvault/src/main/java/com/skyflow/utils/Constants.java
+++ b/flowvault/src/main/java/com/skyflow/utils/Constants.java
@@ -10,6 +10,7 @@ public final class Constants extends BaseConstants {
public static final String VAULT_DOMAIN = ".skyvault.";
public static final String SDK_PREFIX;
public static final String SDK_METRIC_NAME_VERSION_PREFIX = "skyflow-flowvault-java@";
+ public static final Integer MAX_BULK_DATA_SIZE = 10000;
public static final Integer INSERT_BATCH_SIZE = 50;
public static final Integer MAX_INSERT_BATCH_SIZE = 1000;
public static final Integer INSERT_CONCURRENCY_LIMIT = 1;
diff --git a/flowvault/src/main/java/com/skyflow/utils/Utils.java b/flowvault/src/main/java/com/skyflow/utils/Utils.java
index e26286c0..a88b3b15 100644
--- a/flowvault/src/main/java/com/skyflow/utils/Utils.java
+++ b/flowvault/src/main/java/com/skyflow/utils/Utils.java
@@ -1,41 +1,64 @@
package com.skyflow.utils;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
import com.google.gson.JsonObject;
-import com.skyflow.config.Credentials;
import com.skyflow.config.VaultConfig;
import com.skyflow.enums.Env;
-import com.skyflow.enums.UpsertType;
import com.skyflow.errors.ErrorCode;
import com.skyflow.errors.ErrorMessage;
import com.skyflow.errors.SkyflowException;
import com.skyflow.generated.rest.core.ApiClientApiException;
import com.skyflow.generated.rest.resources.flowservice.requests.V1FlowDetokenizeRequest;
-import com.skyflow.generated.rest.resources.flowservice.requests.V1GetRequest;
import com.skyflow.generated.rest.resources.flowservice.requests.V1InsertRequest;
-import com.skyflow.generated.rest.resources.records.requests.V1ExecuteQueryRequest;
-import com.skyflow.generated.rest.types.*;
+import com.skyflow.generated.rest.types.FlowEnumUpdateType;
+import com.skyflow.generated.rest.types.FlowTokenizeResponseObjectToken;
+import com.skyflow.generated.rest.types.V1DeleteTokenResponseObject;
+import com.skyflow.generated.rest.types.V1FlowDeleteTokenResponse;
+import com.skyflow.generated.rest.types.V1FlowDetokenizeResponse;
+import com.skyflow.generated.rest.types.V1FlowDetokenizeResponseObject;
+import com.skyflow.generated.rest.types.V1FlowTokenizeRequestObject;
+import com.skyflow.generated.rest.types.V1FlowTokenizeResponse;
+import com.skyflow.generated.rest.types.V1FlowTokenizeResponseObject;
+import com.skyflow.generated.rest.types.V1InsertRecordData;
+import com.skyflow.generated.rest.types.V1InsertResponse;
+import com.skyflow.generated.rest.types.V1RecordResponseObject;
+import com.skyflow.generated.rest.types.V1TokenGroupRedactions;
+import com.skyflow.generated.rest.types.V1Upsert;
import com.skyflow.logs.ErrorLogs;
-import com.skyflow.logs.InfoLogs;
-import com.skyflow.logs.WarningLogs;
-import com.skyflow.serviceaccount.util.BearerToken;
-import com.skyflow.serviceaccount.util.Token;
import com.skyflow.utils.logger.LogUtil;
-import com.skyflow.vault.data.*;
+import com.skyflow.vault.data.BulkDeleteTokensRequest;
+import com.skyflow.vault.data.BulkDeleteTokensResponse;
+import com.skyflow.vault.data.BulkDetokenizeRequest;
+import com.skyflow.vault.data.BulkDetokenizeResponse;
+import com.skyflow.vault.data.BulkDetokenizeResponseRecord;
+import com.skyflow.vault.data.BulkInsertRequest;
+import com.skyflow.vault.data.BulkInsertResponse;
+import com.skyflow.vault.data.BulkInsertResponseRecord;
+import com.skyflow.vault.data.BulkTokenizeRecord;
+import com.skyflow.vault.data.BulkTokenizeRequest;
+import com.skyflow.vault.data.BulkTokenizeResponse;
+import com.skyflow.vault.data.DeleteTokensSuccess;
+import com.skyflow.vault.data.ErrorRecord;
+import com.skyflow.vault.data.InsertRequest;
+import com.skyflow.vault.data.InsertRequestRecord;
+import com.skyflow.vault.data.TokenGroupRedactions;
+import com.skyflow.vault.data.TokenizeSuccess;
+import com.skyflow.vault.data.UpsertOptions;
+
import io.github.cdimascio.dotenv.Dotenv;
import io.github.cdimascio.dotenv.DotenvException;
-import java.io.File;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
public final class Utils extends BaseUtils {
- public static String getVaultURL(String clusterId, Env env) {
- return getVaultURL(clusterId, env, Constants.VAULT_DOMAIN);
+ public static String getVaultUrl(String clusterId, Env env) {
+ // The 3-arg overload is inherited from common's BaseUtils, which keeps the older
+ // getVaultURL spelling (shared with v2), so it is qualified rather than renamed here.
+ return BaseUtils.getVaultURL(clusterId, env, Constants.VAULT_DOMAIN);
}
public static JsonObject getMetrics() {
@@ -46,27 +69,27 @@ public static JsonObject getMetrics() {
}
- public static String getEnvVaultURL() throws SkyflowException {
+ public static String getEnvVaultUrl() throws SkyflowException {
try {
- String vaultURL = System.getenv("VAULT_URL");
- if (vaultURL == null) {
+ String vaultUrl = System.getenv("VAULT_URL");
+ if (vaultUrl == null) {
Dotenv dotenv = Dotenv.load();
- vaultURL = dotenv.get("VAULT_URL");
+ vaultUrl = dotenv.get("VAULT_URL");
}
- if (vaultURL != null && vaultURL.trim().isEmpty()) {
+ if (vaultUrl != null && vaultUrl.trim().isEmpty()) {
LogUtil.printErrorLog(ErrorLogs.EMPTY_VAULT_URL.getLog());
throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyVaultUrl.getMessage());
- } else if (vaultURL != null && !isValidURL(vaultURL)) {
+ } else if (vaultUrl != null && !isValidUrl(vaultUrl)) {
LogUtil.printErrorLog(ErrorLogs.INVALID_VAULT_URL_FORMAT.getLog());
throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.InvalidVaultUrlFormat.getMessage());
}
- return vaultURL;
+ return vaultUrl;
} catch (DotenvException e) {
return null;
}
}
- public static boolean isValidURL(String url) {
+ public static boolean isValidUrl(String url) {
URL parsedUrl;
try {
parsedUrl = new URL(url);
@@ -82,57 +105,40 @@ public static boolean isValidURL(String url) {
}
- public static String generateBearerToken(Credentials credentials) throws SkyflowException {
- if (credentials.getPath() != null) {
- BearerToken.BearerTokenBuilder builder = BearerToken.builder()
- .setCredentials(new File(credentials.getPath()))
- .setRoles(credentials.getRoles());
- Object ctx = credentials.getContext();
- if (ctx instanceof String) {
- builder.setCtx((String) ctx);
- } else if (ctx instanceof Map) {
- builder.setCtx((Map) ctx);
- }
- return builder.build().getBearerToken();
- } else if (credentials.getCredentialsString() != null) {
- BearerToken.BearerTokenBuilder builder = BearerToken.builder()
- .setCredentials(credentials.getCredentialsString())
- .setRoles(credentials.getRoles());
- Object ctx = credentials.getContext();
- if (ctx instanceof String) {
- builder.setCtx((String) ctx);
- } else if (ctx instanceof Map) {
- builder.setCtx((Map) ctx);
+ // Mirrors the "present" test used by the request validators: null and blank both count as absent.
+ private static boolean hasText(String value) {
+ return value != null && !value.trim().isEmpty();
+ }
+
+ private static V1Upsert toV1Upsert(UpsertOptions upsert) {
+ V1Upsert.Builder builder = V1Upsert.builder().uniqueColumns(upsert.getUniqueColumns());
+ // updateType is a String on the request; the legal values come from the wire enum itself
+ // so there is a single source of truth. Validations rejects anything that does not match.
+ String updateType = upsert.getUpdateType();
+ for (FlowEnumUpdateType type : FlowEnumUpdateType.values()) {
+ if (type.toString().equalsIgnoreCase(updateType)) {
+ builder.updateType(type);
+ break;
}
- return builder.build().getBearerToken();
- } else {
- return credentials.getToken();
}
+ return builder.build();
}
- public static V1InsertRequest getBulkInsertRequestBody(InsertRequest request, VaultConfig config) {
- ArrayList records = request.getRecords();
+ public static V1InsertRequest getInsertRequestBody(InsertRequest request, VaultConfig config) {
+ List records = request.getRecords();
List insertRecordDataList = new ArrayList<>();
- for (InsertRecord record : records) {
- V1InsertRecordData.Builder data = V1InsertRecordData.builder();
- data.data(record.getData());
- if (record.getTable() != null && !record.getTable().isEmpty()) {
- data.tableName(record.getTable());
- }
- if (record.getUpsert() != null && !record.getUpsert().isEmpty()) {
- if (record.getUpsertType() != null) {
- FlowEnumUpdateType updateType = null;
- if (record.getUpsertType() == UpsertType.REPLACE) {
- updateType = FlowEnumUpdateType.REPLACE;
- } else if (record.getUpsertType() == UpsertType.UPDATE) {
- updateType = FlowEnumUpdateType.UPDATE;
- }
- V1Upsert upsert = V1Upsert.builder().uniqueColumns(record.getUpsert()).updateType(updateType).build();
- data.upsert(upsert);
- } else {
- V1Upsert upsert = V1Upsert.builder().uniqueColumns(record.getUpsert()).build();
- data.upsert(upsert);
- }
+ for (InsertRequestRecord record : records) {
+ V1InsertRecordData.Builder data = V1InsertRecordData.builder()
+ .data(record.getData())
+ // A blank record-level table name counts as absent, matching
+ // validateInsertRequest, so it falls back to the request-level one.
+ .tableName(hasText(record.getTableName()) ? record.getTableName() : request.getTableName());
+ if (record.getTokens() != null && !record.getTokens().isEmpty()) {
+ data.tokens(record.getTokens());
+ }
+ UpsertOptions upsert = record.getUpsert() != null ? record.getUpsert() : request.getUpsert();
+ if (upsert != null && upsert.getUniqueColumns() != null && !upsert.getUniqueColumns().isEmpty()) {
+ data.upsert(toV1Upsert(upsert));
}
insertRecordDataList.add(data.build());
}
@@ -140,249 +146,23 @@ public static V1InsertRequest getBulkInsertRequestBody(InsertRequest request, Va
V1InsertRequest.Builder builder = V1InsertRequest.builder()
.vaultId(config.getVaultId())
.records(insertRecordDataList);
-
- if (request.getTable() != null && !request.getTable().isEmpty()) {
- builder.tableName(request.getTable());
- }
-
- if (request.getUpsert() != null && !request.getUpsert().isEmpty()) {
- if (request.getUpsertType() != null) {
- FlowEnumUpdateType updateType = null;
- if (request.getUpsertType() == UpsertType.REPLACE) {
- updateType = FlowEnumUpdateType.REPLACE;
- } else if (request.getUpsertType() == UpsertType.UPDATE) {
- updateType = FlowEnumUpdateType.UPDATE;
- }
- V1Upsert upsert = V1Upsert.builder().uniqueColumns(request.getUpsert()).updateType(updateType).build();
- builder.upsert(upsert);
- } else {
- V1Upsert upsert = V1Upsert.builder().uniqueColumns(request.getUpsert()).build();
- builder.upsert(upsert);
- }
+ if (hasText(request.getTableName())) {
+ builder.tableName(request.getTableName());
}
return builder.build();
-
}
- public static InsertResponse buildInsertResponse(V1InsertResponse res) {
- ArrayList> insertedFields = new ArrayList<>();
- ArrayList> errors = new ArrayList<>();
-
- if (res != null && res.getRecords().isPresent()) {
- for (V1RecordResponseObject record : res.getRecords().get()) {
- if (record.getError().isPresent()) {
- HashMap errorRecord = new HashMap<>();
- record.getSkyflowId().ifPresent(skyflowId -> errorRecord.put("skyflowId", skyflowId));
- record.getTableName().ifPresent(tableName -> errorRecord.put("tableName", tableName));
- errorRecord.put("error", record.getError().get());
- record.getHttpCode().ifPresent(httpCode -> errorRecord.put("httpCode", httpCode));
- errors.add(errorRecord);
- } else {
- HashMap insertedRecord = new HashMap<>();
- record.getSkyflowId().ifPresent(skyflowId -> insertedRecord.put("skyflowId", skyflowId));
- record.getTokens().ifPresent(insertedRecord::putAll);
- insertedFields.add(insertedRecord);
- }
- }
- }
- return new InsertResponse(insertedFields, errors);
- }
-
- public static com.skyflow.generated.rest.resources.flowservice.requests.V1FlowDetokenizeRequest getDetokenizeRequestBody(DetokenizeRequest request, String vaultid) {
- List detokenizeData = request.getDetokenizeData();
- List tokens = new ArrayList<>();
- for(int i = 0; i< detokenizeData.size(); i++){
- tokens.add(detokenizeData.get(i).getToken());
- }
- com.skyflow.generated.rest.resources.flowservice.requests.V1FlowDetokenizeRequest.Builder builder =
- com.skyflow.generated.rest.resources.flowservice.requests.V1FlowDetokenizeRequest.builder()
- .vaultId(vaultid)
- .tokens(tokens);
- if (request.getTokenGroupRedactions() != null) {
- List tokenGroupRedactionsList = new ArrayList<>();
- for (com.skyflow.vault.data.TokenGroupRedactions tokenGroupRedactions : request.getTokenGroupRedactions()) {
- com.skyflow.generated.rest.types.V1TokenGroupRedactions redactions =
- com.skyflow.generated.rest.types.V1TokenGroupRedactions.builder()
- .tokenGroupName(tokenGroupRedactions.getTokenGroupName())
- .redaction(tokenGroupRedactions.getRedaction())
- .build();
- tokenGroupRedactionsList.add(redactions);
- }
-
- builder.tokenGroupRedactions(tokenGroupRedactionsList);
- }
- return builder.build();
- }
-
- public static DetokenizeResponse buildDetokenizeResponse(V1FlowDetokenizeResponse res) {
- ArrayList detokenizedFields = new ArrayList<>();
- ArrayList errors = new ArrayList<>();
-
- if (res != null && res.getResponse().isPresent()) {
- for (V1FlowDetokenizeResponseObject record : res.getResponse().get()) {
- String token = record.getToken().orElse(null);
- String tokenGroupName = record.getTokenGroupName().orElse(null);
- Map metadata = record.getMetadata().orElse(null);
- if (record.getError().isPresent()) {
- errors.add(new DetokenizeRecordResponse(token, null, record.getError().get(), tokenGroupName, metadata));
- } else {
- Object value = record.getValue().orElse(null);
- detokenizedFields.add(new DetokenizeRecordResponse(token, value, null, tokenGroupName, metadata));
- }
- }
- }
- return new DetokenizeResponse(detokenizedFields, errors);
- }
-
- public static com.skyflow.generated.rest.resources.flowservice.requests.V1FlowDeleteTokenRequest getDeleteTokensRequestBody(DeleteTokensRequest request, String vaultid) {
- return com.skyflow.generated.rest.resources.flowservice.requests.V1FlowDeleteTokenRequest.builder()
- .vaultId(vaultid)
- .tokens(request.getTokens())
- .build();
- }
private static String extractRequestId(Map> headers) {
if (headers == null) return null;
List ids = headers.get(BaseConstants.REQUEST_ID_HEADER_KEY);
return (ids == null || ids.isEmpty()) ? null : ids.get(0);
}
- public static DeleteTokensResponse buildDeleteTokensResponse(V1FlowDeleteTokenResponse res, Map> headers, int requestedTokenCount) {
- ArrayList deletedTokens = new ArrayList<>();
- ArrayList> errors = new ArrayList<>();
- String requestId = extractRequestId(headers);
- if (res != null && res.getTokens().isPresent()) {
- for (V1DeleteTokenResponseObject record : res.getTokens().get()) {
- if (record.getError().isPresent()) {
- HashMap errorRecord = new HashMap<>();
- errorRecord.put("error", record.getError().get());
- record.getHttpCode().ifPresent(httpCode -> errorRecord.put("httpCode", httpCode));
- errorRecord.put("requestId", requestId);
- errors.add(errorRecord);
- } else {
- record.getValue().ifPresent(deletedTokens::add);
- }
- }
- if (deletedTokens.size() + errors.size() != requestedTokenCount) {
- LogUtil.printWarningLog(WarningLogs.INCOMPLETE_DELETE_TOKENS_RESPONSE.getLog());
- }
- } else {
- LogUtil.printWarningLog(WarningLogs.EMPTY_DELETE_TOKENS_RESPONSE.getLog());
- }
- return new DeleteTokensResponse(deletedTokens, errors);
- }
-
- public static com.skyflow.generated.rest.resources.flowservice.requests.V1FlowTokenizeRequest getTokenizeRequestBody(TokenizeRequest request, String vaultid) {
- List dataList = new ArrayList<>();
- for (TokenizeRecord record : request.getData()) {
- V1FlowTokenizeRequestObject obj = V1FlowTokenizeRequestObject.builder()
- .value(record.getValue())
- .tokenGroupNames(record.getTokenGroupNames())
- .build();
- dataList.add(obj);
- }
- return com.skyflow.generated.rest.resources.flowservice.requests.V1FlowTokenizeRequest.builder()
- .vaultId(vaultid)
- .data(dataList)
- .build();
- }
-
- public static TokenizeResponse buildTokenizeResponse(V1FlowTokenizeResponse res, Map> headers, int requestedRecordCount) {
- List tokenizedData = new ArrayList<>();
- ArrayList> errors = new ArrayList<>();
- String requestId = extractRequestId(headers);
- if (res != null && res.getResponse().isPresent()) {
- List records = res.getResponse().get();
- int indexNumber = 0;
- for (V1FlowTokenizeResponseObject record : records) {
- Object value = record.getValue().orElse(null);
- TokenizeData tokenizeData = new TokenizeData(value, indexNumber);
- boolean hasAnySuccess = false;
- if (record.getTokens().isPresent()) {
- for (FlowTokenizeResponseObjectToken tokenObj : record.getTokens().get()) {
- if (tokenObj.getError().isPresent()) {
- HashMap errorRecord = new HashMap<>();
- errorRecord.put("error", tokenObj.getError().get());
- tokenObj.getHttpCode().ifPresent(httpCode -> errorRecord.put("httpCode", httpCode));
- tokenObj.getTokenGroupName().ifPresent(name -> errorRecord.put("tokenGroupName", name));
- errorRecord.put("index", indexNumber);
- errorRecord.put("requestId", requestId);
- errors.add(errorRecord);
- } else if (tokenObj.getTokenGroupName().isPresent() && tokenObj.getToken().isPresent()) {
- tokenizeData.addToken(tokenObj.getTokenGroupName().get(), tokenObj.getToken().get());
- hasAnySuccess = true;
- }
- }
- }
- if (hasAnySuccess) {
- tokenizedData.add(tokenizeData);
- }
- indexNumber++;
- }
- if (indexNumber != requestedRecordCount) {
- LogUtil.printWarningLog(WarningLogs.INCOMPLETE_TOKENIZE_RESPONSE.getLog());
- }
- } else {
- LogUtil.printWarningLog(WarningLogs.EMPTY_TOKENIZE_RESPONSE.getLog());
- }
- TokenizeResponse tokenizeResponse = new TokenizeResponse(errors);
- tokenizeResponse.setTokenizedData(tokenizedData);
- return tokenizeResponse;
- }
-
// ── Bulk (batched/concurrent) request-body builders ──────────────────────
+ // BulkInsertRequest is an InsertRequest, so the bulk body is built exactly the same way.
public static com.skyflow.generated.rest.resources.flowservice.requests.V1InsertRequest getBulkInsertRequestBody(BulkInsertRequest request, VaultConfig config) {
- ArrayList records = request.getRecords();
- List insertRecordDataList = new ArrayList<>();
- for (BulkInsertRecord record : records) {
- V1InsertRecordData.Builder data = V1InsertRecordData.builder();
- data.data(record.getData());
- if (record.getTable() != null && !record.getTable().isEmpty()) {
- data.tableName(record.getTable());
- }
- if (record.getUpsert() != null && !record.getUpsert().isEmpty()) {
- if (record.getUpsertType() != null) {
- FlowEnumUpdateType updateType = null;
- if (record.getUpsertType() == UpsertType.REPLACE) {
- updateType = FlowEnumUpdateType.REPLACE;
- } else if (record.getUpsertType() == UpsertType.UPDATE) {
- updateType = FlowEnumUpdateType.UPDATE;
- }
- V1Upsert upsert = V1Upsert.builder().uniqueColumns(record.getUpsert()).updateType(updateType).build();
- data.upsert(upsert);
- } else {
- V1Upsert upsert = V1Upsert.builder().uniqueColumns(record.getUpsert()).build();
- data.upsert(upsert);
- }
- }
- insertRecordDataList.add(data.build());
- }
-
- com.skyflow.generated.rest.resources.flowservice.requests.V1InsertRequest.Builder builder =
- com.skyflow.generated.rest.resources.flowservice.requests.V1InsertRequest.builder()
- .vaultId(config.getVaultId())
- .records(insertRecordDataList);
-
- if (request.getTable() != null && !request.getTable().isEmpty()) {
- builder.tableName(request.getTable());
- }
-
- if (request.getUpsert() != null && !request.getUpsert().isEmpty()) {
- if (request.getUpsertType() != null) {
- FlowEnumUpdateType updateType = null;
- if (request.getUpsertType() == UpsertType.REPLACE) {
- updateType = FlowEnumUpdateType.REPLACE;
- } else if (request.getUpsertType() == UpsertType.UPDATE) {
- updateType = FlowEnumUpdateType.UPDATE;
- }
- V1Upsert upsert = V1Upsert.builder().uniqueColumns(request.getUpsert()).updateType(updateType).build();
- builder.upsert(upsert);
- } else {
- V1Upsert upsert = V1Upsert.builder().uniqueColumns(request.getUpsert()).build();
- builder.upsert(upsert);
- }
- }
- return builder.build();
+ return getInsertRequestBody(request, config);
}
public static V1FlowDetokenizeRequest getBulkDetokenizeRequestBody(BulkDetokenizeRequest request, String vaultId) {
@@ -391,7 +171,7 @@ public static V1FlowDetokenizeRequest getBulkDetokenizeRequestBody(BulkDetokeniz
.tokens(request.getTokens());
if (request.getTokenGroupRedactions() != null && !request.getTokenGroupRedactions().isEmpty()) {
List tokenGroupRedactionsList = new ArrayList<>();
- for (BulkTokenGroupRedactions tokenGroupRedactions : request.getTokenGroupRedactions()) {
+ for (TokenGroupRedactions tokenGroupRedactions : request.getTokenGroupRedactions()) {
tokenGroupRedactionsList.add(V1TokenGroupRedactions.builder()
.tokenGroupName(tokenGroupRedactions.getTokenGroupName())
.redaction(tokenGroupRedactions.getRedaction())
@@ -486,6 +266,59 @@ public static List recordMap, int indexNumber, String requestId) {
+ BulkInsertResponseRecord err = null;
+ if (recordMap != null) {
+ int code = 500;
+ if (recordMap.containsKey("http_code")) {
+ code = (Integer) recordMap.get("http_code");
+ } else if (recordMap.containsKey("httpCode")) {
+ code = (Integer) recordMap.get("httpCode");
+ } else if (recordMap.containsKey("statusCode")) {
+ code = (Integer) recordMap.get("statusCode");
+ }
+ // check if skyflowID is present
+ String skyflowID = null;
+ if (recordMap.containsKey("skyflowID")) {
+ skyflowID = recordMap.get("skyflowID").toString();
+ }
+ String tableName = null;
+ if (recordMap.containsKey("tableName")) {
+ tableName = recordMap.get("tableName").toString();
+ }
+ String message = recordMap.containsKey("error") ? (String) recordMap.get("error") :
+ recordMap.containsKey("message") ? (String) recordMap.get("message") : "Unknown error";
+ err = new BulkInsertResponseRecord(indexNumber, tableName, skyflowID, null, null, code, message, requestId);
+ }
+ return err;
+ }
+
+ public static BulkDetokenizeResponseRecord createDetokenizeErrorRecord(Map recordMap, int indexNumber, String requestId) {
+ BulkDetokenizeResponseRecord err = null;
+ if (recordMap != null) {
+ int code = 500;
+ if (recordMap.containsKey("http_code")) {
+ code = (Integer) recordMap.get("http_code");
+ } else if (recordMap.containsKey("httpCode")) {
+ code = (Integer) recordMap.get("httpCode");
+ } else if (recordMap.containsKey("statusCode")) {
+ code = (Integer) recordMap.get("statusCode");
+ }
+ // the failing token is echoed back so the caller can tell which one it was
+ String token = null;
+ if (recordMap.containsKey("token")) {
+ token = recordMap.get("token").toString();
+ }
+ String tokenGroupName = null;
+ if (recordMap.containsKey("tokenGroupName")) {
+ tokenGroupName = recordMap.get("tokenGroupName").toString();
+ }
+ String message = recordMap.containsKey("error") ? (String) recordMap.get("error") :
+ recordMap.containsKey("message") ? (String) recordMap.get("message") : "Unknown error";
+ err = new BulkDetokenizeResponseRecord(indexNumber, token, null, tokenGroupName, null, code, message, requestId);
+ }
+ return err;
+ }
public static ErrorRecord createErrorRecord(Map recordMap, int indexNumber, String requestId) {
ErrorRecord err = null;
@@ -505,10 +338,12 @@ public static ErrorRecord createErrorRecord(Map recordMap, int i
return err;
}
- public static List handleBulkInsertBatchException(
+ // Errors are parsed into ErrorRecord (shared with the other bulk ops), then projected onto
+ // the unified BulkInsertResponseRecord shape that bulk insert now returns.
+ public static List handleBulkInsertBatchException(
Throwable ex, List batch, int batchNumber, int batchSize
) {
- List errorRecords = new ArrayList<>();
+ List allRecords = new ArrayList<>();
Throwable cause = ex.getCause();
if (cause instanceof ApiClientApiException) {
ApiClientApiException apiException = (ApiClientApiException) cause;
@@ -524,8 +359,8 @@ public static List handleBulkInsertBatchException(
for (Object record : recordsList) {
if (record instanceof Map) {
Map recordMap = (Map) record;
- ErrorRecord err = createErrorRecord(recordMap, indexNumber, requestId);
- errorRecords.add(err);
+ BulkInsertResponseRecord err = createInsertErrorRecord(recordMap, indexNumber, requestId);
+ allRecords.add(err);
indexNumber++;
}
}
@@ -535,35 +370,48 @@ public static List handleBulkInsertBatchException(
Map recordMap = (errField instanceof Map) ? (Map) errField : null;
String fallbackMsg = (errField instanceof String) ? (String) errField : null;
for (int j = 0; j < batch.size(); j++) {
- ErrorRecord err = (recordMap != null)
- ? createErrorRecord(recordMap, indexNumber, requestId)
- : new ErrorRecord(indexNumber, fallbackMsg != null ? fallbackMsg : apiException.getMessage(), apiException.statusCode(), requestId);
- errorRecords.add(err);
+ BulkInsertResponseRecord err = null;
+ if(recordMap != null){
+ err = createInsertErrorRecord(recordMap, indexNumber, requestId);
+ } else {
+ String errorMessage = null;
+ if (fallbackMsg != null){
+ errorMessage = fallbackMsg;
+ } else {
+ errorMessage = apiException.getMessage();
+ }
+ err = new BulkInsertResponseRecord(indexNumber, null, null, null, null, apiException.statusCode(), errorMessage, requestId);
+
+ }
+ allRecords.add(err);
indexNumber++;
}
}
}
- if (errorRecords.isEmpty()) {
+
+ if (allRecords.isEmpty()) {
for (int j = 0; j < batch.size(); j++) {
- errorRecords.add(new ErrorRecord(indexNumber, apiException.getMessage(), apiException.statusCode(), requestId));
+ allRecords.add(new BulkInsertResponseRecord(indexNumber, null, null, null, null, apiException.statusCode(), apiException.getMessage(), requestId));
indexNumber++;
}
}
} else {
int indexNumber = batchNumber > 0 ? batchNumber * batchSize : 0;
for (int j = 0; j < batch.size(); j++) {
- ErrorRecord err = new ErrorRecord(indexNumber, ex.getMessage(), 500);
- errorRecords.add(err);
+ BulkInsertResponseRecord err = new BulkInsertResponseRecord(indexNumber, null, null, null, null, 500, ex.getMessage(), null);
+ allRecords.add(err);
indexNumber++;
}
}
- return errorRecords;
+ return allRecords;
}
- public static List handleBulkDetokenizeBatchException(
+ // Errors are parsed into ErrorRecord (shared with the other bulk ops), then projected onto
+ // the unified BulkDetokenizeResponseRecord shape that bulk detokenize now returns.
+ public static List handleBulkDetokenizeBatchException(
Throwable ex, V1FlowDetokenizeRequest batch, int batchNumber, int batchSize
) {
- List errorRecords = new ArrayList<>();
+ List allRecords = new ArrayList<>();
Throwable cause = ex.getCause();
if (cause instanceof ApiClientApiException) {
ApiClientApiException apiException = (ApiClientApiException) cause;
@@ -571,6 +419,7 @@ public static List handleBulkDetokenizeBatchException(
Object rawBody = apiException.body();
Map responseBody = (rawBody instanceof Map) ? (Map) rawBody : null;
int indexNumber = batchNumber * batchSize;
+ int tokenCount = batch.getTokens().isPresent() ? batch.getTokens().get().size() : 0;
if (responseBody != null) {
if (responseBody.containsKey("response")) {
Object recordss = responseBody.get("response");
@@ -579,8 +428,8 @@ public static List handleBulkDetokenizeBatchException(
for (Object record : recordsList) {
if (record instanceof Map) {
Map recordMap = (Map) record;
- ErrorRecord err = createErrorRecord(recordMap, indexNumber, requestId);
- errorRecords.add(err);
+ BulkDetokenizeResponseRecord err = createDetokenizeErrorRecord(recordMap, indexNumber, requestId);
+ allRecords.add(err);
indexNumber++;
}
}
@@ -589,32 +438,40 @@ public static List handleBulkDetokenizeBatchException(
Object errField = responseBody.get("error");
Map recordMap = (errField instanceof Map) ? (Map) errField : null;
String fallbackMsg = (errField instanceof String) ? (String) errField : null;
- int tokenCount = batch.getTokens().isPresent() ? batch.getTokens().get().size() : 0;
for (int j = 0; j < tokenCount; j++) {
- ErrorRecord err = (recordMap != null)
- ? createErrorRecord(recordMap, indexNumber, requestId)
- : new ErrorRecord(indexNumber, fallbackMsg != null ? fallbackMsg : apiException.getMessage(), apiException.statusCode(), requestId);
- errorRecords.add(err);
+ BulkDetokenizeResponseRecord err = null;
+ if (recordMap != null) {
+ err = createDetokenizeErrorRecord(recordMap, indexNumber, requestId);
+ } else {
+ String errorMessage = null;
+ if (fallbackMsg != null) {
+ errorMessage = fallbackMsg;
+ } else {
+ errorMessage = apiException.getMessage();
+ }
+ err = new BulkDetokenizeResponseRecord(indexNumber, null, null, null, null, apiException.statusCode(), errorMessage, requestId);
+ }
+ allRecords.add(err);
indexNumber++;
}
}
}
- if (errorRecords.isEmpty()) {
- int tokenCount = batch.getTokens().isPresent() ? batch.getTokens().get().size() : 0;
+
+ if (allRecords.isEmpty()) {
for (int j = 0; j < tokenCount; j++) {
- errorRecords.add(new ErrorRecord(indexNumber, apiException.getMessage(), apiException.statusCode(), requestId));
+ allRecords.add(new BulkDetokenizeResponseRecord(indexNumber, null, null, null, null, apiException.statusCode(), apiException.getMessage(), requestId));
indexNumber++;
}
}
} else {
int indexNumber = batchNumber * batchSize;
for (int j = 0; j < batch.getTokens().get().size(); j++) {
- ErrorRecord err = new ErrorRecord(indexNumber, ex.getMessage(), 500);
- errorRecords.add(err);
+ BulkDetokenizeResponseRecord err = new BulkDetokenizeResponseRecord(indexNumber, null, null, null, null, 500, ex.getMessage(), null);
+ allRecords.add(err);
indexNumber++;
}
}
- return errorRecords;
+ return allRecords;
}
public static List handleBulkDeleteTokensBatchException(
@@ -738,68 +595,49 @@ public static List handleBulkTokenizeBatchException(
public static BulkInsertResponse formatBulkInsertResponse(V1InsertResponse response, int batch, int batchSize, Map> headers) {
BulkInsertResponse formattedResponse = null;
- List successRecords = new ArrayList<>();
- List errorRecords = new ArrayList<>();
+ List records = new ArrayList<>();
if (response != null && response.getRecords().isPresent()) {
- String requestId = extractRequestId(headers);
List record = response.getRecords().get();
int indexNumber = batch * batchSize;
int recordsSize = record.size();
for (int index = 0; index < recordsSize; index++) {
- if (record.get(index).getError().isPresent()) {
- ErrorRecord errorRecord = new ErrorRecord(indexNumber, record.get(index).getError().get(), record.get(index).getHttpCode().orElse(500), requestId);
- errorRecords.add(errorRecord);
- } else {
- Map> tokensMap = null;
- if (record.get(index).getTokens().isPresent()) {
- tokensMap = new HashMap<>();
- Map tok = record.get(index).getTokens().get();
- for (Map.Entry entry : tok.entrySet()) {
- String key = entry.getKey();
- Object value = entry.getValue();
- List tokenList = new ArrayList<>();
- if (value instanceof List) {
- List> valueList = (List>) value;
- for (Object item : valueList) {
- if (item instanceof Map) {
- Map tokenMap = (Map) item;
- com.skyflow.vault.data.Token token = new com.skyflow.vault.data.Token((String) tokenMap.get("token"), (String) tokenMap.get("tokenGroupName"));
- tokenList.add(token);
- }
- }
- }
- tokensMap.put(key, tokenList);
- }
- }
- Success success = new Success(indexNumber, record.get(index).getSkyflowId().orElse(null), tokensMap, record.get(index).getData().isPresent() ? record.get(index).getData().get() : null, record.get(index).getTableName().isPresent() ? record.get(index).getTableName().get() : null);
- successRecords.add(success);
- }
+ V1RecordResponseObject current = record.get(index);
+ records.add(new BulkInsertResponseRecord(
+ indexNumber,
+ current.getTableName().orElse(null),
+ current.getSkyflowId().orElse(null),
+ current.getTokens().orElse(null),
+ current.getHashedData().orElse(null),
+ current.getHttpCode().orElse(current.getError().isPresent() ? 500 : 200),
+ current.getError().orElse(null),
+ null));
indexNumber++;
}
- formattedResponse = new BulkInsertResponse(successRecords, errorRecords);
+ formattedResponse = new BulkInsertResponse(records);
}
return formattedResponse;
}
public static BulkDetokenizeResponse formatBulkDetokenizeResponse(V1FlowDetokenizeResponse response, int batch, int batchSize, Map> headers) {
if (response != null && response.getResponse().isPresent()) {
- String requestId = extractRequestId(headers);
List record = response.getResponse().get();
- List errorRecords = new ArrayList<>();
- List successRecords = new ArrayList<>();
+ List records = new ArrayList<>();
int indexNumber = batch * batchSize;
int recordsSize = record.size();
for (int index = 0; index < recordsSize; index++) {
- if (record.get(index).getError().isPresent()) {
- ErrorRecord errorRecord = new ErrorRecord(indexNumber, record.get(index).getError().get(), record.get(index).getHttpCode().orElse(500), requestId);
- errorRecords.add(errorRecord);
- } else {
- DetokenizeResponseObject success = new DetokenizeResponseObject(indexNumber, record.get(index).getToken().orElse(null), record.get(index).getValue().orElse(null), record.get(index).getTokenGroupName().orElse(null), record.get(index).getError().orElse(null), record.get(index).getMetadata().orElse(null));
- successRecords.add(success);
- }
+ V1FlowDetokenizeResponseObject current = record.get(index);
+ records.add(new BulkDetokenizeResponseRecord(
+ indexNumber,
+ current.getToken().orElse(null),
+ current.getValue().orElse(null),
+ current.getTokenGroupName().orElse(null),
+ current.getMetadata().orElse(null),
+ current.getHttpCode().orElse(current.getError().isPresent() ? 500 : 200),
+ current.getError().orElse(null),
+ null));
indexNumber++;
}
- return new BulkDetokenizeResponse(successRecords, errorRecords);
+ return new BulkDetokenizeResponse(records);
}
return null;
}
@@ -867,120 +705,4 @@ public static BulkTokenizeResponse formatBulkTokenizeResponse(
return null;
}
- public static V1ExecuteQueryRequest getQueryRequestBody(QueryRequest request, String vaultId) {
- return V1ExecuteQueryRequest.builder()
- .vaultId(vaultId)
- .query(request.getQuery())
- .build();
- }
-
- public static QueryResponse buildQueryResponse(V1ExecuteQueryResponse res) {
- ArrayList> fields = new ArrayList<>();
- if (res != null && res.getRecords().isPresent()) {
- for (V1ExecuteQueryRecordResponse record : res.getRecords().get()) {
- HashMap fieldMap = new HashMap<>();
- if (record.getData().isPresent()) {
- fieldMap.putAll(record.getData().get());
- }
- fields.add(fieldMap);
- }
- }
- return new QueryResponse(fields);
- }
-
- private static List buildColumnRedactions(List columnRedactions) {
- List columnRedactionsList = new ArrayList<>();
- for (ColumnRedaction columnRedaction : columnRedactions) {
- columnRedactionsList.add(V1ColumnRedactions.builder()
- .columnName(columnRedaction.getColumnName())
- .redaction(columnRedaction.getRedaction())
- .build());
- }
- return columnRedactionsList;
- }
-
- private static List buildUniqueValues(List