diff --git a/checkstyle.xml b/checkstyle.xml index d7ff94b0..da314b9a 100644 --- a/checkstyle.xml +++ b/checkstyle.xml @@ -16,5 +16,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - \ No newline at end of file + diff --git a/pom.xml b/pom.xml index 7c7121b5..c9e7f47f 100644 --- a/pom.xml +++ b/pom.xml @@ -200,7 +200,7 @@ false true - **/generated/** + **/generated/**,**/logs/** diff --git a/src/main/java/com/skyflow/VaultClient.java b/src/main/java/com/skyflow/VaultClient.java index b6974f5d..b89bdad9 100644 --- a/src/main/java/com/skyflow/VaultClient.java +++ b/src/main/java/com/skyflow/VaultClient.java @@ -808,9 +808,9 @@ private TokenTypeMapping buildTokenType(TokenFormat tokenFormat, private FileDataDeidentifyAudioDataFormat mapAudioDataFormat(String dataFormat) throws SkyflowException { switch (dataFormat) { - case "mp3": + case Constants.FileFormatType.MP3: return FileDataDeidentifyAudioDataFormat.MP_3; - case "wav": + case Constants.FileFormatType.WAV: return FileDataDeidentifyAudioDataFormat.WAV; default: throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.InvalidAudioFileType.getMessage()); diff --git a/src/main/java/com/skyflow/errors/ErrorMessage.java b/src/main/java/com/skyflow/errors/ErrorMessage.java index ac6adfd0..c20df57c 100644 --- a/src/main/java/com/skyflow/errors/ErrorMessage.java +++ b/src/main/java/com/skyflow/errors/ErrorMessage.java @@ -2,6 +2,7 @@ import com.skyflow.utils.Constants; +@SuppressWarnings("checkstyle:MultipleStringLiterals") public enum ErrorMessage { // Client initialization VaultIdAlreadyInConfigList("%s0 Validation error. VaultId is present in an existing config. Specify a new vaultId in config."), diff --git a/src/main/java/com/skyflow/errors/SkyflowException.java b/src/main/java/com/skyflow/errors/SkyflowException.java index 32c71f39..849ce3c5 100644 --- a/src/main/java/com/skyflow/errors/SkyflowException.java +++ b/src/main/java/com/skyflow/errors/SkyflowException.java @@ -57,7 +57,7 @@ public SkyflowException(int httpCode, Throwable cause, Map> private void setResponseBody(String responseBody, Map> responseHeaders) { this.responseBody = JsonParser.parseString(responseBody).getAsJsonObject(); - if (this.responseBody.get("error") != null) { + if (this.responseBody.get(Constants.JsonFieldNames.ERROR) != null) { setGrpcCode(); setHttpStatus(); setMessage(); @@ -75,17 +75,17 @@ private void setRequestId(Map> responseHeaders) { } private void setMessage() { - JsonElement messageElement = ((JsonObject) responseBody.get("error")).get("message"); + JsonElement messageElement = ((JsonObject) responseBody.get(Constants.JsonFieldNames.ERROR)).get(Constants.JsonFieldNames.MESSAGE); this.message = messageElement == null ? null : messageElement.getAsString(); } private void setGrpcCode() { - JsonElement grpcElement = ((JsonObject) responseBody.get("error")).get("grpc_code"); + JsonElement grpcElement = ((JsonObject) responseBody.get(Constants.JsonFieldNames.ERROR)).get(Constants.JsonFieldNames.GRPC_CODE); this.grpcCode = grpcElement == null ? null : grpcElement.getAsInt(); } private void setHttpStatus() { - JsonElement statusElement = ((JsonObject) responseBody.get("error")).get("http_status"); + JsonElement statusElement = ((JsonObject) responseBody.get(Constants.JsonFieldNames.ERROR)).get(Constants.JsonFieldNames.HTTP_STATUS); this.httpStatus = statusElement == null ? null : statusElement.getAsString(); } @@ -98,7 +98,7 @@ public JsonArray getDetails() { } private void setDetails(Map> responseHeaders) { - JsonElement detailsElement = ((JsonObject) responseBody.get("error")).get("details"); + JsonElement detailsElement = ((JsonObject) responseBody.get(Constants.JsonFieldNames.ERROR)).get(Constants.JsonFieldNames.DETAILS); List errorFromClientHeader = responseHeaders.get(Constants.ERROR_FROM_CLIENT_HEADER_KEY); if (detailsElement != null) { this.details = detailsElement.getAsJsonArray(); diff --git a/src/main/java/com/skyflow/logs/InfoLogs.java b/src/main/java/com/skyflow/logs/InfoLogs.java index f71fc416..18363670 100644 --- a/src/main/java/com/skyflow/logs/InfoLogs.java +++ b/src/main/java/com/skyflow/logs/InfoLogs.java @@ -14,7 +14,7 @@ public enum InfoLogs { // Bearer token generation EMPTY_BEARER_TOKEN("Bearer token is empty."), - BEARER_TOKEN_EXPIRED("Bearer token is expired."), + BEARER_TOKEN_EXPIRED("Bearer token is invalid or expired."), GET_BEARER_TOKEN_TRIGGERED("getBearerToken method triggered."), GET_BEARER_TOKEN_SUCCESS("Bearer token generated."), GET_SIGNED_DATA_TOKENS_TRIGGERED("getSignedDataTokens method triggered."), diff --git a/src/main/java/com/skyflow/serviceaccount/util/BearerToken.java b/src/main/java/com/skyflow/serviceaccount/util/BearerToken.java index 21000f0f..a2bfcdc3 100644 --- a/src/main/java/com/skyflow/serviceaccount/util/BearerToken.java +++ b/src/main/java/com/skyflow/serviceaccount/util/BearerToken.java @@ -92,25 +92,25 @@ private static V1GetAuthTokenResponse getBearerTokenFromCredentials( JsonObject credentials, String context, ArrayList roles ) throws SkyflowException { try { - JsonElement privateKey = credentials.get("privateKey"); + JsonElement privateKey = credentials.get(Constants.CredentialFields.PRIVATE_KEY); if (privateKey == null) { LogUtil.printErrorLog(ErrorLogs.PRIVATE_KEY_IS_REQUIRED.getLog()); throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.MissingPrivateKey.getMessage()); } - JsonElement clientID = credentials.get("clientID"); + JsonElement clientID = credentials.get(Constants.CredentialFields.CLIENT_ID); if (clientID == null) { LogUtil.printErrorLog(ErrorLogs.CLIENT_ID_IS_REQUIRED.getLog()); throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.MissingClientId.getMessage()); } - JsonElement keyID = credentials.get("keyID"); + JsonElement keyID = credentials.get(Constants.CredentialFields.KEY_ID); if (keyID == null) { LogUtil.printErrorLog(ErrorLogs.KEY_ID_IS_REQUIRED.getLog()); throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.MissingKeyId.getMessage()); } - JsonElement tokenURI = credentials.get("tokenURI"); + JsonElement tokenURI = credentials.get(Constants.CredentialFields.TOKEN_URI); if (tokenURI == null) { LogUtil.printErrorLog(ErrorLogs.TOKEN_URI_IS_REQUIRED.getLog()); throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.MissingTokenUri.getMessage()); @@ -123,7 +123,7 @@ private static V1GetAuthTokenResponse getBearerTokenFromCredentials( String basePath = Utils.getBaseURL(tokenURI.getAsString()); API_CLIENT_BUILDER.url(basePath); - ApiClient apiClient = API_CLIENT_BUILDER.token("token").build(); + ApiClient apiClient = API_CLIENT_BUILDER.token(Constants.ApiToken.TOKEN).build(); AuthenticationClient authenticationApi = apiClient.authentication(); V1GetAuthTokenRequest._FinalStage authTokenBuilder = V1GetAuthTokenRequest.builder().grantType(Constants.GRANT_TYPE).assertion(signedUserJWT); @@ -149,11 +149,11 @@ private static String getSignedToken( final Date createdDate = new Date(); final Date expirationDate = new Date(createdDate.getTime() + (3600 * 1000)); return Jwts.builder() - .claim("iss", clientID) - .claim("key", keyID) - .claim("aud", tokenURI) - .claim("sub", clientID) - .claim("ctx", context) + .claim(Constants.JwtClaims.ISS, clientID) + .claim(Constants.JwtClaims.KEY, keyID) + .claim(Constants.JwtClaims.AUD, tokenURI) + .claim(Constants.JwtClaims.SUB, clientID) + .claim(Constants.JwtClaims.CTX, context) .expiration(expirationDate) .signWith(pvtKey, Jwts.SIG.RS256) .compact(); @@ -163,7 +163,7 @@ private static String getScopeUsingRoles(ArrayList roles) { StringBuilder scope = new StringBuilder(); if (roles != null) { for (String role : roles) { - scope.append(" role:").append(role); + scope.append(Constants.ApiToken.ROLE_PREFIX).append(role); } } return scope.toString(); @@ -173,10 +173,10 @@ public synchronized String getBearerToken() throws SkyflowException { LogUtil.printInfoLog(InfoLogs.GET_BEARER_TOKEN_TRIGGERED.getLog()); V1GetAuthTokenResponse response; String accessToken = null; - if (this.credentialsFile != null && Objects.equals(this.credentialsType, "FILE")) { + if (this.credentialsFile != null && Objects.equals(this.credentialsType, Constants.CredentialTypeValues.FILE)) { response = generateBearerTokenFromCredentials(this.credentialsFile, this.ctx, this.roles); accessToken = response.getAccessToken().get(); - } else if (this.credentialsString != null && Objects.equals(this.credentialsType, "STRING")) { + } else if (this.credentialsString != null && Objects.equals(this.credentialsType, Constants.CredentialTypeValues.STRING)) { response = generateBearerTokenFromCredentialString(this.credentialsString, this.ctx, this.roles); accessToken = response.getAccessToken().get(); } @@ -200,13 +200,13 @@ private void setCredentialsType(String credentialsType) { } public BearerTokenBuilder setCredentials(File credentialsFile) { - setCredentialsType("FILE"); + setCredentialsType(Constants.CredentialTypeValues.FILE); this.credentialsFile = credentialsFile; return this; } public BearerTokenBuilder setCredentials(String credentialsString) { - setCredentialsType("STRING"); + setCredentialsType(Constants.CredentialTypeValues.STRING); this.credentialsString = credentialsString; return this; } diff --git a/src/main/java/com/skyflow/serviceaccount/util/SignedDataTokens.java b/src/main/java/com/skyflow/serviceaccount/util/SignedDataTokens.java index 70a5a330..883dfbc9 100644 --- a/src/main/java/com/skyflow/serviceaccount/util/SignedDataTokens.java +++ b/src/main/java/com/skyflow/serviceaccount/util/SignedDataTokens.java @@ -9,6 +9,7 @@ import com.skyflow.errors.SkyflowException; import com.skyflow.logs.ErrorLogs; import com.skyflow.logs.InfoLogs; +import com.skyflow.utils.Constants; import com.skyflow.utils.Utils; import com.skyflow.utils.logger.LogUtil; import io.jsonwebtoken.Jwts; @@ -93,19 +94,19 @@ private static List generateSignedTokensFromCredentials ) throws SkyflowException { List signedDataTokens = null; try { - JsonElement privateKey = credentials.get("privateKey"); + JsonElement privateKey = credentials.get(Constants.CredentialFields.PRIVATE_KEY); if (privateKey == null) { LogUtil.printErrorLog(ErrorLogs.PRIVATE_KEY_IS_REQUIRED.getLog()); throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.MissingPrivateKey.getMessage()); } - JsonElement clientID = credentials.get("clientID"); + JsonElement clientID = credentials.get(Constants.CredentialFields.CLIENT_ID); if (clientID == null) { LogUtil.printErrorLog(ErrorLogs.CLIENT_ID_IS_REQUIRED.getLog()); throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.MissingClientId.getMessage()); } - JsonElement keyID = credentials.get("keyID"); + JsonElement keyID = credentials.get(Constants.CredentialFields.KEY_ID); if (keyID == null) { LogUtil.printErrorLog(ErrorLogs.KEY_ID_IS_REQUIRED.getLog()); throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.MissingKeyId.getMessage()); @@ -136,12 +137,12 @@ private static List getSignedToken( List list = new ArrayList<>(); for (String dataToken : dataTokens) { String eachSignedDataToken = Jwts.builder() - .claim("iss", "sdk") - .claim("iat", (createdDate.getTime() / 1000)) - .claim("key", keyID) - .claim("sub", clientID) - .claim("ctx", context) - .claim("tok", dataToken) + .claim(Constants.JwtClaims.ISS, Constants.JwtClaims.SDK) + .claim(Constants.JwtClaims.IAT, (createdDate.getTime() / 1000)) + .claim(Constants.JwtClaims.KEY, keyID) + .claim(Constants.JwtClaims.SUB, clientID) + .claim(Constants.JwtClaims.CTX, context) + .claim(Constants.JwtClaims.TOK, dataToken) .expiration(expirationDate) .signWith(pvtKey, Jwts.SIG.RS256) .compact(); @@ -154,9 +155,9 @@ private static List getSignedToken( public synchronized List getSignedDataTokens() throws SkyflowException { LogUtil.printInfoLog(InfoLogs.GET_SIGNED_DATA_TOKENS_TRIGGERED.getLog()); List signedToken = new ArrayList<>(); - if (this.credentialsFile != null && Objects.equals(this.credentialsType, "FILE")) { + if (this.credentialsFile != null && Objects.equals(this.credentialsType, Constants.CredentialTypeValues.FILE)) { signedToken = generateSignedTokenFromCredentialsFile(this.credentialsFile, this.dataTokens, this.timeToLive, this.ctx); - } else if (this.credentialsString != null && Objects.equals(this.credentialsType, "STRING")) { + } else if (this.credentialsString != null && Objects.equals(this.credentialsType, Constants.CredentialTypeValues.STRING)) { signedToken = generateSignedTokensFromCredentialsString(this.credentialsString, this.dataTokens, this.timeToLive, this.ctx); } LogUtil.printInfoLog(InfoLogs.GET_SIGNED_DATA_TOKEN_SUCCESS.getLog()); @@ -179,13 +180,13 @@ private void setCredentialsType(String credentialsType) { } public SignedDataTokensBuilder setCredentials(File credentialsFile) { - setCredentialsType("FILE"); + setCredentialsType(Constants.CredentialTypeValues.FILE); this.credentialsFile = credentialsFile; return this; } public SignedDataTokensBuilder setCredentials(String credentialsString) { - setCredentialsType("STRING"); + setCredentialsType(Constants.CredentialTypeValues.STRING); this.credentialsString = credentialsString; return this; } diff --git a/src/main/java/com/skyflow/serviceaccount/util/Token.java b/src/main/java/com/skyflow/serviceaccount/util/Token.java index 90da0cf4..d015a9c8 100644 --- a/src/main/java/com/skyflow/serviceaccount/util/Token.java +++ b/src/main/java/com/skyflow/serviceaccount/util/Token.java @@ -8,6 +8,7 @@ import com.skyflow.errors.SkyflowException; import com.skyflow.logs.ErrorLogs; import com.skyflow.logs.InfoLogs; +import com.skyflow.utils.Constants; import com.skyflow.utils.logger.LogUtil; import org.apache.commons.codec.binary.Base64; @@ -25,7 +26,7 @@ public static boolean isExpired(String token) { } currentTime = new Date().getTime() / 1000; - expiryTime = decoded(token).get("exp").getAsLong(); + expiryTime = decoded(token).get(Constants.JsonFieldNames.EXP).getAsLong(); } catch (JsonSyntaxException | SkyflowException e) { LogUtil.printErrorLog(ErrorLogs.INVALID_BEARER_TOKEN.getLog()); diff --git a/src/main/java/com/skyflow/utils/Constants.java b/src/main/java/com/skyflow/utils/Constants.java index 6af03281..92a608ac 100644 --- a/src/main/java/com/skyflow/utils/Constants.java +++ b/src/main/java/com/skyflow/utils/Constants.java @@ -10,6 +10,7 @@ public final class Constants { public static final String STAGE_DOMAIN = ".vault.skyflowapis.tech"; public static final String SANDBOX_DOMAIN = ".vault.skyflowapis-preview.com"; public static final String PROD_DOMAIN = ".vault.skyflowapis.com"; + public static final String HTTPS_PROTOCOL = "https"; public static final String PKCS8_PRIVATE_HEADER = "-----BEGIN PRIVATE KEY-----"; public static final String PKCS8_PRIVATE_FOOTER = "-----END PRIVATE KEY-----"; public static final String GRANT_TYPE = "urn:ietf:params:oauth:grant-type:jwt-bearer"; @@ -30,9 +31,221 @@ public final class Constants { public static final String SDK_AUTH_HEADER_KEY = "x-skyflow-authorization"; public static final String SDK_METRICS_HEADER_KEY = "sky-metadata"; public static final String REQUEST_ID_HEADER_KEY = "x-request-id"; - public static final String PROCESSED_FILE_NAME_PREFIX = "processed-"; public static final String ERROR_FROM_CLIENT_HEADER_KEY = "error-from-client"; + public static final String PROCESSED_FILE_NAME_PREFIX = "processed-"; public static final String DEIDENTIFIED_FILE_PREFIX = "deidentified"; + public static final String CURLY_PLACEHOLDER = "{%s}"; + + public static final class HttpHeader { + public static final String CONTENT_TYPE = "content-type"; + public static final String CONTENT_TYPE_JSON = "application/json"; + public static final String CONTENT_TYPE_FORM_URLENCODED = "application/x-www-form-urlencoded"; + public static final String CONTENT_TYPE_MULTIPART = "multipart/form-data"; + public static final String ACCEPT = "Accept"; + public static final String ACCEPT_ALL = "*/*"; + public static final String CONTENT_DISPOSITION = "Content-Disposition"; + public static final String BOUNDARY_SEPARATOR = "; boundary="; + public static final String FORM_DATA_HEADER = ": form-data; name=\""; + + private HttpHeader() { + // Utility class constructor + } + } + + public static final class EncodingType { + public static final String BASE64 = "base64"; + public static final String UTF_8 = "utf-8"; + public static final String BINARY = "binary"; + public static final String UTF_8_CHARSET = "UTF-8"; + + private EncodingType() { + // Utility class constructor + } + } + public static final String EMPTY_STRING = ""; + public static final String QUOTE = "\""; + public static final class HttpUtilityExtra { + public static final String RAW_BODY_KEY = "__raw_body__"; + public static final String SDK_GENERATED_PREFIX = "SDK-Generated-"; + private HttpUtilityExtra() {} + } + + public static final class FileFormatType { + public static final String TXT = "txt"; + public static final String MP3 = "mp3"; + public static final String WAV = "wav"; + public static final String PDF = "pdf"; + public static final String JPG = "jpg"; + public static final String JPEG = "jpeg"; + public static final String PNG = "png"; + public static final String BMP = "bmp"; + public static final String TIF = "tif"; + public static final String TIFF = "tiff"; + public static final String PPT = "ppt"; + public static final String PPTX = "pptx"; + public static final String CSV = "csv"; + public static final String XLS = "xls"; + public static final String XLSX = "xlsx"; + public static final String DOC = "doc"; + public static final String DOCX = "docx"; + public static final String JSON = "json"; + public static final String XML = "xml"; + + private FileFormatType() { + // Utility class constructor + } + } + + public static final class DetectStatus { + public static final String SUCCESS = "SUCCESS"; + public static final String FAILED = "FAILED"; + public static final String IN_PROGRESS = "IN_PROGRESS"; + + private DetectStatus() { + // Utility class constructor + } + } + + public static final class FileProcessing { + public static final String ENTITIES = "entities"; + + private FileProcessing() { + // Utility class constructor + } + } + + public static final class FieldNames { + public static final String SKYFLOW_ID_CAMEL = "skyflowId"; + public static final String TOKENIZED_DATA = "tokenizedData"; + + private FieldNames() { + // Utility class constructor + } + } + + public static final class FormData { + public static final String BOUNDARY_SEPARATOR = "--"; + + private FormData() { + // Utility class constructor + } + } + + public static final class JsonFieldNames { + public static final String ERROR = "error"; + public static final String MESSAGE = "message"; + public static final String GRPC_CODE = "grpc_code"; + public static final String HTTP_STATUS = "http_status"; + public static final String DETAILS = "details"; + public static final String BODY = "Body"; + public static final String TOKENS = "tokens"; + public static final String RECORDS = "records"; + public static final String FIELDS = "fields"; + public static final String VALUE = "value"; + public static final String ERRORS = "errors"; + public static final String TYPE = "type"; + public static final String UPDATED_FIELD = "updatedField"; + public static final String REQUEST_INDEX = "requestIndex"; + public static final String HTTP_CODE = "httpCode"; + public static final String REQUEST_ID = "requestId"; + public static final String EXP = "exp"; + public static final String SKYFLOW_ID = "skyflow_id"; + + private JsonFieldNames() { + // Utility class constructor + } + } + + public static final class CredentialFields { + public static final String PRIVATE_KEY = "privateKey"; + public static final String CLIENT_ID = "clientID"; + public static final String KEY_ID = "keyID"; + public static final String TOKEN_URI = "tokenURI"; + + private CredentialFields() { + // Utility class constructor + } + } + + public static final class JwtClaims { + public static final String ISS = "iss"; + public static final String KEY = "key"; + public static final String AUD = "aud"; + public static final String SUB = "sub"; + public static final String CTX = "ctx"; + public static final String IAT = "iat"; + public static final String TOK = "tok"; + public static final String SDK = "sdk"; + + private JwtClaims() { + // Utility class constructor + } + } + + public static final class ApiToken { + public static final String TOKEN = "token"; + public static final String ROLE_PREFIX = " role:"; + + private ApiToken() { + // Utility class constructor + } + } + + public static final class CredentialTypeValues { + public static final String FILE = "FILE"; + public static final String STRING = "STRING"; + + private CredentialTypeValues() { + // Utility class constructor + } + } + + public static final class CryptoAlgorithm { + public static final String RSA = "RSA"; + + private CryptoAlgorithm() { + // Utility class constructor + } + } + + public static final class HttpUtility { + public static final String FORM_ENCODE_SEPARATOR = "="; + public static final String FORM_ENCODE_DELIMITER = "&"; + public static final String REQUEST_ID_DELIMITER = ","; + public static final String REQUEST_ID_PREFIX = " - requestId: "; + public static final String ERROR_DESCRIPTION = "replace with description"; + + private HttpUtility() { + // Utility class constructor + } + } + + public static final class SystemProperty { + public static final String OS_NAME = "os.name"; + public static final String OS_VERSION = "os.version"; + public static final String JAVA_VERSION = "java.version"; + public static final String JAVA_TEMP_DIR = "java.io.tmpdir"; + + private SystemProperty() { + // Utility class constructor + } + } + + public static final class UrlFormat { + public static final String PROTOCOL_HOST_FORMAT = "%s://%s"; + + private UrlFormat() { + // Utility class constructor + } + } + + public static final class FileExtension { + public static final String JSON = ".json"; + + private FileExtension() { + // Utility class constructor + } + } static { String sdkVersion; diff --git a/src/main/java/com/skyflow/utils/HttpUtility.java b/src/main/java/com/skyflow/utils/HttpUtility.java index b8e9283b..fd30ed26 100644 --- a/src/main/java/com/skyflow/utils/HttpUtility.java +++ b/src/main/java/com/skyflow/utils/HttpUtility.java @@ -7,11 +7,13 @@ import java.io.*; import java.net.HttpURLConnection; import java.net.URL; +import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.UUID; public final class HttpUtility { @@ -32,17 +34,20 @@ public static String sendRequest(String method, URL url, JsonObject params, Map< try { connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod(method); - connection.setRequestProperty("content-type", "application/json"); - connection.setRequestProperty("Accept", "*/*"); + connection.setRequestProperty(Constants.HttpHeader.ACCEPT, Constants.HttpHeader.ACCEPT_ALL); + boolean hasContentType = headers != null && headers.containsKey(Constants.HttpHeader.CONTENT_TYPE); + if (!hasContentType && params != null && !params.isEmpty()) { + connection.setRequestProperty(Constants.HttpHeader.CONTENT_TYPE, Constants.HttpHeader.CONTENT_TYPE_JSON); + } if (headers != null && !headers.isEmpty()) { for (Map.Entry entry : headers.entrySet()) connection.setRequestProperty(entry.getKey(), entry.getValue()); // append dynamic boundary if content-type is multipart/form-data - if (headers.containsKey("content-type")) { - if (Objects.equals(headers.get("content-type"), "multipart/form-data")) { - connection.setRequestProperty("content-type", "multipart/form-data; boundary=" + boundary); + if (headers.containsKey(Constants.HttpHeader.CONTENT_TYPE)) { + if (Objects.equals(headers.get(Constants.HttpHeader.CONTENT_TYPE), Constants.HttpHeader.CONTENT_TYPE_MULTIPART)) { + connection.setRequestProperty(Constants.HttpHeader.CONTENT_TYPE, Constants.HttpHeader.CONTENT_TYPE_MULTIPART + Constants.HttpHeader.BOUNDARY_SEPARATOR + boundary); } } } @@ -50,11 +55,14 @@ public static String sendRequest(String method, URL url, JsonObject params, Map< connection.setDoOutput(true); try (DataOutputStream wr = new DataOutputStream(connection.getOutputStream())) { byte[] input = null; - String requestContentType = connection.getRequestProperty("content-type"); + String requestContentType = connection.getRequestProperty(Constants.HttpHeader.CONTENT_TYPE); - if (requestContentType.contains("application/x-www-form-urlencoded")) { + // Check if this is a raw body (XML, plain text, etc.) + if (params.has(Constants.HttpUtilityExtra.RAW_BODY_KEY) && params.size() == 1) { + input = params.get(Constants.HttpUtilityExtra.RAW_BODY_KEY).getAsString().getBytes(StandardCharsets.UTF_8); + } else if (requestContentType != null && requestContentType.contains(Constants.HttpHeader.CONTENT_TYPE_FORM_URLENCODED)) { input = formatJsonToFormEncodedString(params).getBytes(StandardCharsets.UTF_8); - } else if (requestContentType.contains("multipart/form-data")) { + } else if (requestContentType != null && requestContentType.contains(Constants.HttpHeader.CONTENT_TYPE_MULTIPART)) { input = formatJsonToMultiPartFormDataString(params, boundary).getBytes(StandardCharsets.UTF_8); } else { input = params.toString().getBytes(StandardCharsets.UTF_8); @@ -66,15 +74,19 @@ public static String sendRequest(String method, URL url, JsonObject params, Map< } int httpCode = connection.getResponseCode(); - String requestID = connection.getHeaderField("x-request-id"); - HttpUtility.requestID = requestID.split(",")[0]; + String requestID = connection.getHeaderField(Constants.REQUEST_ID_HEADER_KEY); + if (requestID != null) { + HttpUtility.requestID = requestID.split(Constants.HttpUtility.REQUEST_ID_DELIMITER)[0]; + } else { + HttpUtility.requestID = Constants.HttpUtilityExtra.SDK_GENERATED_PREFIX + UUID.randomUUID(); + } Map> responseHeaders = connection.getHeaderFields(); Reader streamReader; if (httpCode > 299) { if (connection.getErrorStream() != null) streamReader = new InputStreamReader(connection.getErrorStream()); else { - String description = appendRequestId("replace with description", requestID); + String description = appendRequestId(Constants.HttpUtility.ERROR_DESCRIPTION, requestID); throw new SkyflowException(description); } } else { @@ -121,7 +133,7 @@ public static String formatJsonToMultiPartFormDataString(JsonObject requestBody, formEncodeString.append(makeFormDataKeyValuePair(currentEntry.getKey(), currentEntry.getValue(), boundary)); formEncodeString.append(LINE_FEED); - formEncodeString.append("--").append(boundary).append("--").append(LINE_FEED); + formEncodeString.append(Constants.FormData.BOUNDARY_SEPARATOR).append(boundary).append(Constants.FormData.BOUNDARY_SEPARATOR).append(LINE_FEED); return formEncodeString.toString(); } @@ -143,8 +155,8 @@ private static HashMap convertJsonToMap(JsonObject json, String private static String makeFormDataKeyValuePair(String key, String value, String boundary) { StringBuilder formDataTextField = new StringBuilder(); - formDataTextField.append("--").append(boundary).append(LINE_FEED); - formDataTextField.append("Content-Disposition: form-data; name=\"").append(key).append("\"").append(LINE_FEED); + formDataTextField.append(Constants.FormData.BOUNDARY_SEPARATOR).append(boundary).append(LINE_FEED); + formDataTextField.append(Constants.HttpHeader.CONTENT_DISPOSITION).append(Constants.HttpHeader.FORM_DATA_HEADER).append(key).append("\"").append(LINE_FEED); formDataTextField.append(LINE_FEED); formDataTextField.append(value).append(LINE_FEED); @@ -153,13 +165,19 @@ private static String makeFormDataKeyValuePair(String key, String value, String public static String appendRequestId(String message, String requestId) { if (requestId != null && !requestId.isEmpty()) { - message = message + " - requestId: " + requestId; + message = message + Constants.HttpUtility.REQUEST_ID_PREFIX + requestId; } return message; } private static String makeFormEncodeKeyValuePair(String key, String value) { - return key + "=" + value + "&"; + try { + String encodedKey = URLEncoder.encode(key, StandardCharsets.UTF_8.toString()); + String encodedValue = URLEncoder.encode(value, StandardCharsets.UTF_8.toString()); + return encodedKey + Constants.HttpUtility.FORM_ENCODE_SEPARATOR + encodedValue + Constants.HttpUtility.FORM_ENCODE_DELIMITER; + } catch (Exception e) { + return key + Constants.HttpUtility.FORM_ENCODE_SEPARATOR + value + Constants.HttpUtility.FORM_ENCODE_DELIMITER; + } } } diff --git a/src/main/java/com/skyflow/utils/Utils.java b/src/main/java/com/skyflow/utils/Utils.java index 165c6a80..c53685cc 100644 --- a/src/main/java/com/skyflow/utils/Utils.java +++ b/src/main/java/com/skyflow/utils/Utils.java @@ -17,6 +17,8 @@ import java.io.File; import java.net.MalformedURLException; import java.net.URL; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; @@ -77,10 +79,10 @@ public static PrivateKey getPrivateKeyFromPem(String pemKey) throws SkyflowExcep PrivateKey privateKey = null; if (pemKey.contains(PKCS8PrivateHeader)) { - privateKeyContent = privateKeyContent.replace(PKCS8PrivateHeader, ""); - privateKeyContent = privateKeyContent.replace(PKCS8PrivateFooter, ""); - privateKeyContent = privateKeyContent.replace("\n", ""); - privateKeyContent = privateKeyContent.replace("\r\n", ""); + privateKeyContent = privateKeyContent.replace(PKCS8PrivateHeader, Constants.EMPTY_STRING); + privateKeyContent = privateKeyContent.replace(PKCS8PrivateFooter, Constants.EMPTY_STRING); + privateKeyContent = privateKeyContent.replace("\n", Constants.EMPTY_STRING); + privateKeyContent = privateKeyContent.replace("\r\n", Constants.EMPTY_STRING); privateKey = parsePkcs8PrivateKey(Base64.decodeBase64(privateKeyContent)); } else { LogUtil.printErrorLog(ErrorLogs.JWT_INVALID_FORMAT.getLog()); @@ -93,7 +95,7 @@ public static String getBaseURL(String url) throws MalformedURLException { URL parsedUrl = new URL(url); String protocol = parsedUrl.getProtocol(); String host = parsedUrl.getHost(); - return String.format("%s://%s", protocol, host); + return String.format(Constants.UrlFormat.PROTOCOL_HOST_FORMAT, protocol, host); } public static String parameterizedString(String base, String... args) { @@ -110,7 +112,12 @@ public static String constructConnectionURL(ConnectionConfig config, InvokeConne for (Map.Entry entry : invokeConnectionRequest.getPathParams().entrySet()) { String key = entry.getKey(); String value = entry.getValue(); - filledURL = new StringBuilder(filledURL.toString().replace(String.format("{%s}", key), value)); + try { + String encodedValue = URLEncoder.encode(value, StandardCharsets.UTF_8.name()); + filledURL = new StringBuilder(filledURL.toString().replace(String.format(Constants.CURLY_PLACEHOLDER, key), encodedValue)); + } catch (Exception e) { + filledURL = new StringBuilder(filledURL.toString().replace(String.format(Constants.CURLY_PLACEHOLDER, key), value)); + } } } @@ -119,7 +126,13 @@ public static String constructConnectionURL(ConnectionConfig config, InvokeConne for (Map.Entry entry : invokeConnectionRequest.getQueryParams().entrySet()) { String key = entry.getKey(); String value = entry.getValue(); - filledURL.append(key).append("=").append(value).append("&"); + try { + String encodedKey = URLEncoder.encode(key, StandardCharsets.UTF_8.name()); + String encodedValue = URLEncoder.encode(value, StandardCharsets.UTF_8.name()); + filledURL.append(encodedKey).append(Constants.HttpUtility.FORM_ENCODE_SEPARATOR).append(encodedValue).append(Constants.HttpUtility.FORM_ENCODE_DELIMITER); + } catch (Exception e) { + filledURL.append(key).append(Constants.HttpUtility.FORM_ENCODE_SEPARATOR).append(value).append(Constants.HttpUtility.FORM_ENCODE_DELIMITER); + } } filledURL = new StringBuilder(filledURL.substring(0, filledURL.length() - 1)); } @@ -145,38 +158,38 @@ public static JsonObject getMetrics() { String javaVersion; // Retrieve device model try { - deviceModel = System.getProperty("os.name"); + deviceModel = System.getProperty(Constants.SystemProperty.OS_NAME); if (deviceModel == null) throw new Exception(); } catch (Exception e) { LogUtil.printInfoLog(parameterizedString( InfoLogs.UNABLE_TO_GENERATE_SDK_METRIC.getLog(), Constants.SDK_METRIC_CLIENT_DEVICE_MODEL )); - deviceModel = ""; + deviceModel = Constants.EMPTY_STRING; } // Retrieve OS details try { - osDetails = System.getProperty("os.version"); + osDetails = System.getProperty(Constants.SystemProperty.OS_VERSION); if (osDetails == null) throw new Exception(); } catch (Exception e) { LogUtil.printInfoLog(parameterizedString( InfoLogs.UNABLE_TO_GENERATE_SDK_METRIC.getLog(), Constants.SDK_METRIC_CLIENT_OS_DETAILS )); - osDetails = ""; + osDetails = Constants.EMPTY_STRING; } // Retrieve Java version details try { - javaVersion = System.getProperty("java.version"); + javaVersion = System.getProperty(Constants.SystemProperty.JAVA_VERSION); if (javaVersion == null) throw new Exception(); } catch (Exception e) { LogUtil.printInfoLog(parameterizedString( InfoLogs.UNABLE_TO_GENERATE_SDK_METRIC.getLog(), Constants.SDK_METRIC_RUNTIME_DETAILS )); - javaVersion = ""; + javaVersion = Constants.EMPTY_STRING; } details.addProperty(Constants.SDK_METRIC_NAME_VERSION, Constants.SDK_METRIC_NAME_VERSION_PREFIX + sdkVersion); details.addProperty(Constants.SDK_METRIC_CLIENT_DEVICE_MODEL, deviceModel); @@ -190,7 +203,7 @@ private static PrivateKey parsePkcs8PrivateKey(byte[] pkcs8Bytes) throws Skyflow PrivateKey privateKey = null; try { PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(pkcs8Bytes); - keyFactory = KeyFactory.getInstance("RSA"); + keyFactory = KeyFactory.getInstance(Constants.CryptoAlgorithm.RSA); privateKey = keyFactory.generatePrivate(keySpec); } catch (NoSuchAlgorithmException e) { LogUtil.printErrorLog(ErrorLogs.INVALID_ALGORITHM.getLog()); diff --git a/src/main/java/com/skyflow/utils/validations/Validations.java b/src/main/java/com/skyflow/utils/validations/Validations.java index 0372749a..78077867 100644 --- a/src/main/java/com/skyflow/utils/validations/Validations.java +++ b/src/main/java/com/skyflow/utils/validations/Validations.java @@ -1,6 +1,7 @@ package com.skyflow.utils.validations; import com.google.gson.Gson; +import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.skyflow.config.ConnectionConfig; import com.skyflow.config.Credentials; @@ -137,12 +138,27 @@ public static void validateInvokeConnectionRequest(InvokeConnectionRequest invok } if (requestBody != null) { - Gson gson = new Gson(); - JsonObject bodyObject = gson.toJsonTree(requestBody).getAsJsonObject(); - if (bodyObject.isEmpty()) { - LogUtil.printErrorLog(Utils.parameterizedString( - ErrorLogs.EMPTY_REQUEST_BODY.getLog(), InterfaceName.INVOKE_CONNECTION.getName())); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyRequestBody.getMessage()); + if (requestBody.getClass().equals(Object.class)) { + return; + } + if (requestBody instanceof String) { + String bodyStr = (String) requestBody; + if (bodyStr.trim().isEmpty()) { + LogUtil.printErrorLog(Utils.parameterizedString( + ErrorLogs.EMPTY_REQUEST_BODY.getLog(), InterfaceName.INVOKE_CONNECTION.getName())); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyRequestBody.getMessage()); + } + } else { + Gson gson = new Gson(); + JsonElement bodyElement = gson.toJsonTree(requestBody); + if (bodyElement.isJsonObject()) { + JsonObject bodyObject = bodyElement.getAsJsonObject(); + if (bodyObject.isEmpty()) { + LogUtil.printErrorLog(Utils.parameterizedString( + ErrorLogs.EMPTY_REQUEST_BODY.getLog(), InterfaceName.INVOKE_CONNECTION.getName())); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyRequestBody.getMessage()); + } + } } } } @@ -513,17 +529,17 @@ public static void validateUpdateRequest(UpdateRequest updateRequest) throws Sky ErrorLogs.EMPTY_DATA.getLog(), InterfaceName.UPDATE.getName() )); throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyData.getMessage()); - } else if (!data.containsKey("skyflow_id")) { + } else if (!data.containsKey(Constants.JsonFieldNames.SKYFLOW_ID)) { LogUtil.printErrorLog(Utils.parameterizedString( ErrorLogs.SKYFLOW_ID_IS_REQUIRED.getLog(), InterfaceName.UPDATE.getName() )); throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.SkyflowIdKeyError.getMessage()); - } else if (!(data.get("skyflow_id") instanceof String)) { + } else if (!(data.get(Constants.JsonFieldNames.SKYFLOW_ID) instanceof String)) { LogUtil.printErrorLog(Utils.parameterizedString( ErrorLogs.INVALID_SKYFLOW_ID_TYPE.getLog(), InterfaceName.UPDATE.getName() )); throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.InvalidSkyflowIdType.getMessage()); - } else if (data.get("skyflow_id").toString().trim().isEmpty()) { + } else if (data.get(Constants.JsonFieldNames.SKYFLOW_ID).toString().trim().isEmpty()) { LogUtil.printErrorLog(Utils.parameterizedString( ErrorLogs.EMPTY_SKYFLOW_ID.getLog(), InterfaceName.UPDATE.getName() )); @@ -800,7 +816,7 @@ public static void validateReidentifyTextRequest(ReidentifyTextRequest reidentif private static boolean isInvalidURL(String configURL) { try { URL url = new URL(configURL); - if (!url.getProtocol().equals("https")) throw new Exception(); + if (!url.getProtocol().equals(Constants.HTTPS_PROTOCOL)) throw new Exception(); } catch (Exception e) { return true; } diff --git a/src/main/java/com/skyflow/vault/controller/ConnectionController.java b/src/main/java/com/skyflow/vault/controller/ConnectionController.java index 4a9334d4..06d806c9 100644 --- a/src/main/java/com/skyflow/vault/controller/ConnectionController.java +++ b/src/main/java/com/skyflow/vault/controller/ConnectionController.java @@ -51,22 +51,52 @@ public InvokeConnectionResponse invoke(InvokeConnectionRequest invokeConnectionR headers.put(Constants.SDK_METRICS_HEADER_KEY, Utils.getMetrics().toString()); RequestMethod requestMethod = invokeConnectionRequest.getMethod(); - JsonObject requestBody = null; Object requestBodyObject = invokeConnectionRequest.getRequestBody(); + String requestContentType = headers + .getOrDefault(Constants.HttpHeader.CONTENT_TYPE, Constants.HttpHeader.CONTENT_TYPE_JSON) + .toLowerCase(); + boolean isJsonRequest = requestContentType.contains(Constants.HttpHeader.CONTENT_TYPE_JSON); - if (requestBodyObject != null) { - try { - requestBody = convertObjectToJson(requestBodyObject); - } catch (Exception e) { - LogUtil.printErrorLog(ErrorLogs.INVALID_REQUEST_HEADERS.getLog()); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.InvalidRequestBody.getMessage()); + Object processedRequestBody; + + try { + if (requestBodyObject instanceof String) { + if (isJsonRequest) { + JsonObject jsonWrapper = new JsonObject(); + jsonWrapper.addProperty( + Constants.HttpUtilityExtra.RAW_BODY_KEY, + (String) requestBodyObject + ); + processedRequestBody = jsonWrapper; + } else { + processedRequestBody = requestBodyObject; + } + } else if (requestBodyObject instanceof JsonObject || requestBodyObject == null) { + processedRequestBody = requestBodyObject; + } else { + processedRequestBody = convertObjectToJson(requestBodyObject); } + } catch (Exception e) { + LogUtil.printErrorLog(ErrorLogs.INVALID_REQUEST_HEADERS.getLog()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.InvalidRequestBody.getMessage()); + } + JsonObject payloadToSend; + if (processedRequestBody instanceof JsonObject || processedRequestBody == null) { + payloadToSend = (JsonObject) processedRequestBody; + } else { + payloadToSend = new JsonObject(); + payloadToSend.addProperty(Constants.HttpUtilityExtra.RAW_BODY_KEY, processedRequestBody.toString()); } - String response = HttpUtility.sendRequest(requestMethod.name(), new URL(filledURL), requestBody, headers); - JsonObject data = JsonParser.parseString(response).getAsJsonObject(); + String response = HttpUtility.sendRequest(requestMethod.name(), new URL(filledURL), payloadToSend, headers); + Object data = response; + try { + data = JsonParser.parseString(response).getAsJsonObject(); + } catch (Exception e) { + LogUtil.printErrorLog(ErrorLogs.INVOKE_CONNECTION_REQUEST_REJECTED.getLog()); + } HashMap metadata = new HashMap<>(); - metadata.put("requestId", HttpUtility.getRequestID()); + metadata.put(Constants.JsonFieldNames.REQUEST_ID, HttpUtility.getRequestID()); connectionResponse = new InvokeConnectionResponse(data, metadata, null); LogUtil.printInfoLog(InfoLogs.INVOKE_CONNECTION_REQUEST_RESOLVED.getLog()); } catch (IOException e) { @@ -84,7 +114,7 @@ private JsonObject convertObjectToJson(Object object) { return jsonElement.getAsJsonObject(); } else { JsonObject wrapper = new JsonObject(); - wrapper.add("value", jsonElement); + wrapper.add(Constants.JsonFieldNames.VALUE, jsonElement); return wrapper; } } diff --git a/src/main/java/com/skyflow/vault/controller/DetectController.java b/src/main/java/com/skyflow/vault/controller/DetectController.java index 5801d688..7a67c6dd 100644 --- a/src/main/java/com/skyflow/vault/controller/DetectController.java +++ b/src/main/java/com/skyflow/vault/controller/DetectController.java @@ -161,7 +161,7 @@ public DeidentifyFileResponse deidentifyFile(DeidentifyFileRequest request) thro String outputDir = request.getOutputDirectory(); if (entityBase64 != null) { byte[] entityDecodedBytes = Base64.getDecoder().decode(entityBase64); - String entityFileName = Constants.PROCESSED_FILE_NAME_PREFIX + fileName.substring(0, fileName.lastIndexOf('.')) + ".json"; + String entityFileName = Constants.PROCESSED_FILE_NAME_PREFIX + fileName.substring(0, fileName.lastIndexOf('.')) + Constants.FileExtension.JSON; File entityFile; if (outputDir != null && !outputDir.isEmpty()) { entityFile = new File(outputDir, entityFileName); @@ -284,7 +284,7 @@ private static synchronized DeidentifyFileResponse parseDeidentifyFileResponse(D byte[] decodedBytes = Base64.getDecoder().decode(processedFileBase64.get()); String suffix = "." + processedFileExtension.get(); String fileName = Constants.DEIDENTIFIED_FILE_PREFIX + suffix; - processedFileObject = new File(System.getProperty("java.io.tmpdir"), fileName); + processedFileObject = new File(System.getProperty(Constants.SystemProperty.JAVA_TEMP_DIR), fileName); Files.write(processedFileObject.toPath(), decodedBytes); fileInfo = new FileInfo(processedFileObject); processedFileObject.deleteOnExit(); @@ -350,54 +350,54 @@ private String extractBodyAsString(ApiClientApiException e) { private com.skyflow.generated.rest.types.DeidentifyFileResponse processFileByType(String fileExtension, String base64Content, DeidentifyFileRequest request, String vaultId) throws SkyflowException { switch (fileExtension.toLowerCase()) { - case "txt": + case Constants.FileFormatType.TXT: com.skyflow.generated.rest.resources.files.requests.DeidentifyFileRequestDeidentifyText textFileRequest = super.getDeidentifyTextFileRequest(request, vaultId, base64Content); return super.getDetectFileAPi().deidentifyText(textFileRequest); - case "mp3": - case "wav": + case Constants.FileFormatType.MP3: + case Constants.FileFormatType.WAV: DeidentifyFileAudioRequestDeidentifyAudio audioRequest = super.getDeidentifyAudioRequest(request, vaultId, base64Content, fileExtension); return super.getDetectFileAPi().deidentifyAudio(audioRequest); - case "pdf": + case Constants.FileFormatType.PDF: DeidentifyFileDocumentPdfRequestDeidentifyPdf pdfRequest = super.getDeidentifyPdfRequest(request, vaultId, base64Content); return super.getDetectFileAPi().deidentifyPdf(pdfRequest); - case "jpg": - case "jpeg": - case "png": - case "bmp": - case "tif": - case "tiff": + case Constants.FileFormatType.JPG: + case Constants.FileFormatType.JPEG: + case Constants.FileFormatType.PNG: + case Constants.FileFormatType.BMP: + case Constants.FileFormatType.TIF: + case Constants.FileFormatType.TIFF: DeidentifyFileImageRequestDeidentifyImage imageRequest = super.getDeidentifyImageRequest(request, vaultId, base64Content, fileExtension); return super.getDetectFileAPi().deidentifyImage(imageRequest); - case "ppt": - case "pptx": + case Constants.FileFormatType.PPT: + case Constants.FileFormatType.PPTX: DeidentifyFileRequestDeidentifyPresentation presentationRequest = super.getDeidentifyPresentationRequest(request, vaultId, base64Content, fileExtension); return super.getDetectFileAPi().deidentifyPresentation(presentationRequest); - case "csv": - case "xls": - case "xlsx": + case Constants.FileFormatType.CSV: + case Constants.FileFormatType.XLS: + case Constants.FileFormatType.XLSX: DeidentifyFileRequestDeidentifySpreadsheet spreadsheetRequest = super.getDeidentifySpreadsheetRequest(request, vaultId, base64Content, fileExtension); return super.getDetectFileAPi().deidentifySpreadsheet(spreadsheetRequest); - case "doc": - case "docx": + case Constants.FileFormatType.DOC: + case Constants.FileFormatType.DOCX: DeidentifyFileRequestDeidentifyDocument documentRequest = super.getDeidentifyDocumentRequest(request, vaultId, base64Content, fileExtension); return super.getDetectFileAPi().deidentifyDocument(documentRequest); - case "json": - case "xml": + case Constants.FileFormatType.JSON: + case Constants.FileFormatType.XML: DeidentifyFileRequestDeidentifyStructuredText structuredTextRequest = super.getDeidentifyStructuredTextRequest(request, vaultId, base64Content, fileExtension); return super.getDetectFileAPi().deidentifyStructuredText(structuredTextRequest); diff --git a/src/main/java/com/skyflow/vault/controller/VaultController.java b/src/main/java/com/skyflow/vault/controller/VaultController.java index 3a237470..1bb618ba 100644 --- a/src/main/java/com/skyflow/vault/controller/VaultController.java +++ b/src/main/java/com/skyflow/vault/controller/VaultController.java @@ -41,15 +41,15 @@ public VaultController(VaultConfig vaultConfig, Credentials credentials) { private static synchronized HashMap getFormattedBatchInsertRecord(Object record, Integer requestIndex) { HashMap insertRecord = new HashMap<>(); String jsonString = GSON.toJson(record); - JsonObject bodyObject = JsonParser.parseString(jsonString).getAsJsonObject().get("Body").getAsJsonObject(); - JsonArray records = bodyObject.getAsJsonArray("records"); - JsonPrimitive error = bodyObject.getAsJsonPrimitive("error"); + JsonObject bodyObject = JsonParser.parseString(jsonString).getAsJsonObject().get(Constants.JsonFieldNames.BODY).getAsJsonObject(); + JsonArray records = bodyObject.getAsJsonArray(Constants.JsonFieldNames.RECORDS); + JsonPrimitive error = bodyObject.getAsJsonPrimitive(Constants.JsonFieldNames.ERROR); if (records != null) { for (JsonElement recordElement : records) { JsonObject recordObject = recordElement.getAsJsonObject(); - insertRecord.put("skyflowId", recordObject.get("skyflow_id").getAsString()); - JsonElement tokensElement = recordObject.get("tokens"); + insertRecord.put(Constants.FieldNames.SKYFLOW_ID_CAMEL, recordObject.get(Constants.JsonFieldNames.SKYFLOW_ID).getAsString()); + JsonElement tokensElement = recordObject.get(Constants.JsonFieldNames.TOKENS); if (tokensElement != null) { insertRecord.putAll(tokensElement.getAsJsonObject().asMap()); } @@ -57,16 +57,16 @@ private static synchronized HashMap getFormattedBatchInsertRecor } if (error != null) { - insertRecord.put("error", error.getAsString()); + insertRecord.put(Constants.JsonFieldNames.ERROR, error.getAsString()); } - insertRecord.put("requestIndex", requestIndex); + insertRecord.put(Constants.JsonFieldNames.REQUEST_INDEX, requestIndex); return insertRecord; } private static synchronized HashMap getFormattedBulkInsertRecord(V1RecordMetaProperties record) { HashMap insertRecord = new HashMap<>(); if (record.getSkyflowId().isPresent()) { - insertRecord.put("skyflowId", record.getSkyflowId().get()); + insertRecord.put(Constants.FieldNames.SKYFLOW_ID_CAMEL, record.getSkyflowId().get()); } if (record.getTokens().isPresent()) { @@ -93,7 +93,7 @@ private static synchronized HashMap getFormattedGetRecord(V1Fiel private static synchronized HashMap getFormattedUpdateRecord(V1UpdateRecordResponse record) { HashMap updateTokens = new HashMap<>(); - record.getSkyflowId().ifPresent(skyflowId -> updateTokens.put("skyflowId", skyflowId)); + record.getSkyflowId().ifPresent(skyflowId -> updateTokens.put(Constants.FieldNames.SKYFLOW_ID_CAMEL, skyflowId)); record.getTokens().ifPresent(tokensMap -> updateTokens.putAll(tokensMap)); @@ -134,11 +134,11 @@ public InsertResponse insert(InsertRequest insertRequest) throws SkyflowExceptio Map record = recordList.get(index); HashMap insertRecord = getFormattedBatchInsertRecord(record, index); - if (insertRecord.containsKey("skyflowId")) { + if (insertRecord.containsKey(Constants.FieldNames.SKYFLOW_ID_CAMEL)) { insertedFields.add(insertRecord); } else { - insertRecord.put("requestId", batchInsertResult.headers().get(Constants.REQUEST_ID_HEADER_KEY).get(0)); - insertRecord.put("httpCode", ErrorCode.INVALID_INPUT.getCode()); + insertRecord.put(Constants.JsonFieldNames.REQUEST_ID, batchInsertResult.headers().get(Constants.REQUEST_ID_HEADER_KEY).get(0)); + insertRecord.put(Constants.JsonFieldNames.HTTP_CODE, ErrorCode.INVALID_INPUT.getCode()); errorFields.add(insertRecord); } } @@ -281,7 +281,7 @@ public UpdateResponse update(UpdateRequest updateRequest) throws SkyflowExceptio result = super.getRecordsApi().recordServiceUpdateRecord( super.getVaultConfig().getVaultId(), updateRequest.getTable(), - updateRequest.getData().remove("skyflow_id").toString(), + updateRequest.getData().remove(Constants.JsonFieldNames.SKYFLOW_ID).toString(), updateBody, requestOptions ); diff --git a/src/main/java/com/skyflow/vault/data/DeleteResponse.java b/src/main/java/com/skyflow/vault/data/DeleteResponse.java index f34d219a..0f243aa4 100644 --- a/src/main/java/com/skyflow/vault/data/DeleteResponse.java +++ b/src/main/java/com/skyflow/vault/data/DeleteResponse.java @@ -1,6 +1,7 @@ package com.skyflow.vault.data; import com.google.gson.Gson; +import com.skyflow.utils.Constants; import com.google.gson.JsonObject; import com.google.gson.JsonParser; @@ -21,7 +22,7 @@ public List getDeletedIds() { public String toString() { Gson gson = new Gson(); JsonObject responseObject = JsonParser.parseString(gson.toJson(this)).getAsJsonObject(); - responseObject.add("errors", null); + responseObject.add(Constants.JsonFieldNames.ERRORS, null); return responseObject.toString(); } } diff --git a/src/main/java/com/skyflow/vault/data/QueryResponse.java b/src/main/java/com/skyflow/vault/data/QueryResponse.java index 7a1bca51..5a9b735c 100644 --- a/src/main/java/com/skyflow/vault/data/QueryResponse.java +++ b/src/main/java/com/skyflow/vault/data/QueryResponse.java @@ -1,6 +1,7 @@ package com.skyflow.vault.data; import com.google.gson.*; +import com.skyflow.utils.Constants; import java.util.ArrayList; import java.util.HashMap; @@ -21,12 +22,12 @@ public ArrayList> getFields() { public String toString() { Gson gson = new GsonBuilder().serializeNulls().create(); JsonObject responseObject = gson.toJsonTree(this).getAsJsonObject(); - JsonArray fieldsArray = responseObject.get("fields").getAsJsonArray(); + JsonArray fieldsArray = responseObject.get(Constants.JsonFieldNames.FIELDS).getAsJsonArray(); for (JsonElement fieldElement : fieldsArray) { - fieldElement.getAsJsonObject().add("tokenizedData", new JsonObject()); + fieldElement.getAsJsonObject().add(Constants.FieldNames.TOKENIZED_DATA, new JsonObject()); } - responseObject.add("errors", null); - responseObject.remove("tokenizedData"); + responseObject.add(Constants.JsonFieldNames.ERRORS, null); + responseObject.remove(Constants.FieldNames.TOKENIZED_DATA); return responseObject.toString(); } } diff --git a/src/main/java/com/skyflow/vault/data/UpdateResponse.java b/src/main/java/com/skyflow/vault/data/UpdateResponse.java index 068b29be..0120edaa 100644 --- a/src/main/java/com/skyflow/vault/data/UpdateResponse.java +++ b/src/main/java/com/skyflow/vault/data/UpdateResponse.java @@ -1,6 +1,7 @@ package com.skyflow.vault.data; import com.google.gson.Gson; +import com.skyflow.utils.Constants; import com.google.gson.JsonArray; import com.google.gson.JsonObject; import com.google.gson.JsonParser; @@ -28,13 +29,13 @@ public HashMap getTokens() { public String toString() { Gson gson = new Gson(); JsonObject responseObject = JsonParser.parseString(gson.toJson(this)).getAsJsonObject(); - JsonObject tokensObject = responseObject.remove("tokens").getAsJsonObject(); + JsonObject tokensObject = responseObject.remove(Constants.JsonFieldNames.TOKENS).getAsJsonObject(); for (String key : tokensObject.keySet()) { responseObject.add(key, tokensObject.get(key)); } JsonObject finalResponseObject = new JsonObject(); - finalResponseObject.add("updatedField", responseObject); - finalResponseObject.add("errors", null); + finalResponseObject.add(Constants.JsonFieldNames.UPDATED_FIELD, responseObject); + finalResponseObject.add(Constants.JsonFieldNames.ERRORS, null); return finalResponseObject.toString(); } } diff --git a/src/main/java/com/skyflow/vault/tokens/DetokenizeResponse.java b/src/main/java/com/skyflow/vault/tokens/DetokenizeResponse.java index bd1d7cde..d00fc8d2 100644 --- a/src/main/java/com/skyflow/vault/tokens/DetokenizeResponse.java +++ b/src/main/java/com/skyflow/vault/tokens/DetokenizeResponse.java @@ -1,6 +1,7 @@ package com.skyflow.vault.tokens; import com.google.gson.ExclusionStrategy; +import com.skyflow.utils.Constants; import com.google.gson.FieldAttributes; import com.google.gson.Gson; import com.google.gson.GsonBuilder; @@ -32,18 +33,18 @@ public String toString() { DetokenizeRecordResponse.class, (com.google.gson.JsonSerializer) (src, typeOfSrc, context) -> { com.google.gson.JsonObject obj = new com.google.gson.JsonObject(); - obj.addProperty("token", src.getToken()); + obj.addProperty(Constants.ApiToken.TOKEN, src.getToken()); if (src.getValue() != null) { - obj.addProperty("value", src.getValue()); + obj.addProperty(Constants.JsonFieldNames.VALUE, src.getValue()); } if (src.getType() != null) { - obj.addProperty("type", src.getType()); + obj.addProperty(Constants.JsonFieldNames.TYPE, src.getType()); } if (src.getError() != null) { - obj.add("error", context.serialize(src.getError())); + obj.add(Constants.JsonFieldNames.ERROR, context.serialize(src.getError())); } if (src.getRequestId() != null) { - obj.addProperty("requestId", src.getRequestId()); + obj.addProperty(Constants.JsonFieldNames.REQUEST_ID, src.getRequestId()); } return obj; } diff --git a/src/main/java/com/skyflow/vault/tokens/TokenizeResponse.java b/src/main/java/com/skyflow/vault/tokens/TokenizeResponse.java index d8d8072b..29b5e2bb 100644 --- a/src/main/java/com/skyflow/vault/tokens/TokenizeResponse.java +++ b/src/main/java/com/skyflow/vault/tokens/TokenizeResponse.java @@ -1,6 +1,7 @@ package com.skyflow.vault.tokens; import com.google.gson.*; +import com.skyflow.utils.Constants; import java.util.List; @@ -19,15 +20,15 @@ public List getTokens() { public String toString() { Gson gson = new Gson(); JsonObject responseObject = JsonParser.parseString(gson.toJson(this)).getAsJsonObject(); - JsonArray tokensArray = responseObject.remove("tokens").getAsJsonArray(); + JsonArray tokensArray = responseObject.remove(Constants.JsonFieldNames.TOKENS).getAsJsonArray(); JsonArray newTokensArray = new JsonArray(); for (JsonElement token : tokensArray) { JsonObject jsonObject = new JsonObject(); - jsonObject.addProperty("token", token.getAsString()); + jsonObject.addProperty(Constants.ApiToken.TOKEN, token.getAsString()); newTokensArray.add(jsonObject); } - responseObject.add("tokens", newTokensArray); - responseObject.add("errors", null); + responseObject.add(Constants.JsonFieldNames.TOKENS, newTokensArray); + responseObject.add(Constants.JsonFieldNames.ERRORS, null); return responseObject.toString(); } } diff --git a/src/test/java/com/skyflow/utils/HttpUtilityTests.java b/src/test/java/com/skyflow/utils/HttpUtilityTests.java index f7214690..5f7271e1 100644 --- a/src/test/java/com/skyflow/utils/HttpUtilityTests.java +++ b/src/test/java/com/skyflow/utils/HttpUtilityTests.java @@ -124,4 +124,71 @@ public void testSendRequestError() { fail(INVALID_EXCEPTION_THROWN); } } + + // New test cases for content-type handling changes + + @Test + @PrepareForTest({URL.class, HttpURLConnection.class}) + public void testSendRequestWithRawBody() { + try { + given(mockConnection.getRequestProperty("content-type")).willReturn("application/xml"); + Map headers = new HashMap<>(); + headers.put("content-type", "application/xml"); + JsonObject params = new JsonObject(); + params.addProperty("__raw_body__", "test"); + String response = httpUtility.sendRequest("POST", url, params, headers); + Assert.assertEquals(expected, response); + } catch (Exception e) { + fail(INVALID_EXCEPTION_THROWN); + } + } + + @Test + @PrepareForTest({URL.class, HttpURLConnection.class}) + public void testSendRequestWithoutContentTypeHeader() { + try { + given(mockConnection.getRequestProperty("content-type")).willReturn("application/json"); + JsonObject params = new JsonObject(); + params.addProperty("key", "value"); + String response = httpUtility.sendRequest("POST", url, params, null); + Assert.assertEquals(expected, response); + } catch (Exception e) { + fail(INVALID_EXCEPTION_THROWN); + } + } + + @Test + @PrepareForTest({URL.class, HttpURLConnection.class}) + public void testSendRequestWithNullRequestId() { + try { + given(mockConnection.getHeaderField(anyString())).willReturn(null); + given(mockConnection.getRequestProperty("content-type")).willReturn("application/json"); + Map headers = new HashMap<>(); + headers.put("content-type", "application/json"); + JsonObject params = new JsonObject(); + params.addProperty("key", "value"); + String response = httpUtility.sendRequest("GET", url, params, headers); + Assert.assertEquals(expected, response); + Assert.assertNotNull(HttpUtility.getRequestID()); + } catch (Exception e) { + fail(INVALID_EXCEPTION_THROWN); + } + } + + @Test + @PrepareForTest({URL.class, HttpURLConnection.class}) + public void testSendRequestFormURLEncodedWithSpecialCharacters() { + try { + given(mockConnection.getRequestProperty("content-type")).willReturn("application/x-www-form-urlencoded"); + Map headers = new HashMap<>(); + headers.put("content-type", "application/x-www-form-urlencoded"); + JsonObject params = new JsonObject(); + params.addProperty("key", "value with spaces"); + params.addProperty("special", "test@email.com"); + String response = httpUtility.sendRequest("POST", url, params, headers); + Assert.assertEquals(expected, response); + } catch (Exception e) { + fail(INVALID_EXCEPTION_THROWN); + } + } } diff --git a/src/test/java/com/skyflow/vault/connection/InvokeConnectionTests.java b/src/test/java/com/skyflow/vault/connection/InvokeConnectionTests.java index 63717b5c..e87d7877 100644 --- a/src/test/java/com/skyflow/vault/connection/InvokeConnectionTests.java +++ b/src/test/java/com/skyflow/vault/connection/InvokeConnectionTests.java @@ -437,4 +437,145 @@ public void testInvokeConnectionResponse() { Assert.fail(INVALID_EXCEPTION_THROWN); } } + + // New test cases for content-type handling changes + + @Test + public void testValidStringRequestBodyInInvokeConnectionRequestValidations() { + try { + String xmlBody = "test"; + requestHeaders.put("Content-Type", "application/xml"); + InvokeConnectionRequest request = InvokeConnectionRequest.builder() + .method(RequestMethod.POST) + .requestHeaders(requestHeaders) + .requestBody(xmlBody) + .build(); + Validations.validateInvokeConnectionRequest(request); + Assert.assertTrue(request.getRequestBody() instanceof String); + Assert.assertEquals(xmlBody, request.getRequestBody()); + } catch (SkyflowException e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + + @Test + public void testEmptyStringRequestBodyInInvokeConnectionRequestValidations() { + try { + String emptyBody = ""; + requestHeaders.put("Content-Type", "application/xml"); + InvokeConnectionRequest request = InvokeConnectionRequest.builder() + .method(RequestMethod.POST) + .requestHeaders(requestHeaders) + .requestBody(emptyBody) + .build(); + Validations.validateInvokeConnectionRequest(request); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); + Assert.assertEquals(ErrorMessage.EmptyRequestBody.getMessage(), e.getMessage()); + } + } + + @Test + public void testWhitespaceOnlyStringRequestBodyInInvokeConnectionRequestValidations() { + try { + String whitespaceBody = " \n\t "; + requestHeaders.put("Content-Type", "application/xml"); + InvokeConnectionRequest request = InvokeConnectionRequest.builder() + .method(RequestMethod.POST) + .requestHeaders(requestHeaders) + .requestBody(whitespaceBody) + .build(); + Validations.validateInvokeConnectionRequest(request); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); + Assert.assertEquals(ErrorMessage.EmptyRequestBody.getMessage(), e.getMessage()); + } + } + + @Test + public void testXMLStringRequestBodyInInvokeConnectionRequestValidations() { + try { + String xmlBody = "4111111111111111"; + requestHeaders.put("Content-Type", "application/xml"); + InvokeConnectionRequest request = InvokeConnectionRequest.builder() + .method(RequestMethod.POST) + .requestHeaders(requestHeaders) + .requestBody(xmlBody) + .build(); + Validations.validateInvokeConnectionRequest(request); + Assert.assertTrue(request.getRequestBody() instanceof String); + } catch (SkyflowException e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + + @Test + public void testPlainTextRequestBodyInInvokeConnectionRequestValidations() { + try { + String plainText = "This is a plain text request body with some data"; + requestHeaders.put("Content-Type", "text/plain"); + InvokeConnectionRequest request = InvokeConnectionRequest.builder() + .method(RequestMethod.POST) + .requestHeaders(requestHeaders) + .requestBody(plainText) + .build(); + Validations.validateInvokeConnectionRequest(request); + Assert.assertTrue(request.getRequestBody() instanceof String); + } catch (SkyflowException e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + + @Test + public void testHTMLRequestBodyInInvokeConnectionRequestValidations() { + try { + String htmlBody = "

Processing Payment

"; + requestHeaders.put("Content-Type", "text/html"); + InvokeConnectionRequest request = InvokeConnectionRequest.builder() + .method(RequestMethod.POST) + .requestHeaders(requestHeaders) + .requestBody(htmlBody) + .build(); + Validations.validateInvokeConnectionRequest(request); + Assert.assertTrue(request.getRequestBody() instanceof String); + } catch (SkyflowException e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + + @Test + public void testInvokeConnectionResponseWithStringData() { + try { + String xmlData = "success"; + HashMap metadata = new HashMap<>(); + metadata.put("requestId", "xml-12345"); + InvokeConnectionResponse connectionResponse = new InvokeConnectionResponse(xmlData, metadata, null); + + Assert.assertNotNull(connectionResponse.getData()); + Assert.assertTrue(connectionResponse.getData() instanceof String); + Assert.assertEquals(xmlData, connectionResponse.getData()); + Assert.assertEquals("xml-12345", connectionResponse.getMetadata().get("requestId")); + } catch (Exception e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + + @Test + public void testRequestBodyWithSpecialCharacters() { + try { + String bodyWithSpecialChars = "card_number=4111111111111111&exp_month=12&exp_year=2025&cvv=123"; + requestHeaders.put("Content-Type", "application/x-www-form-urlencoded"); + InvokeConnectionRequest request = InvokeConnectionRequest.builder() + .method(RequestMethod.POST) + .requestHeaders(requestHeaders) + .requestBody(bodyWithSpecialChars) + .build(); + Validations.validateInvokeConnectionRequest(request); + Assert.assertTrue(request.getRequestBody() instanceof String); + } catch (SkyflowException e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } } diff --git a/src/test/java/com/skyflow/vault/controller/ConnectionControllerTests.java b/src/test/java/com/skyflow/vault/controller/ConnectionControllerTests.java index b121280a..5ded96b2 100644 --- a/src/test/java/com/skyflow/vault/controller/ConnectionControllerTests.java +++ b/src/test/java/com/skyflow/vault/controller/ConnectionControllerTests.java @@ -1,19 +1,36 @@ package com.skyflow.vault.controller; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; import com.skyflow.Skyflow; import com.skyflow.config.ConnectionConfig; import com.skyflow.config.Credentials; import com.skyflow.enums.LogLevel; +import com.skyflow.enums.RequestMethod; import com.skyflow.errors.ErrorCode; import com.skyflow.errors.ErrorMessage; import com.skyflow.errors.SkyflowException; +import com.skyflow.utils.HttpUtility; import com.skyflow.vault.connection.InvokeConnectionRequest; +import com.skyflow.vault.connection.InvokeConnectionResponse; import org.junit.Assert; +import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; +import java.net.URL; import java.util.HashMap; +import java.util.Map; +import static org.mockito.ArgumentMatchers.*; +import static org.powermock.api.mockito.PowerMockito.when; + +@RunWith(PowerMockRunner.class) +@PrepareForTest({HttpUtility.class}) public class ConnectionControllerTests { private static final String INVALID_EXCEPTION_THROWN = "Should not have thrown any exception"; private static final String EXCEPTION_NOT_THROWN = "Should have thrown an exception"; @@ -36,6 +53,12 @@ public static void setup() { connectionConfig.setCredentials(credentials); } + @Before + public void setupMocks() throws Exception { + PowerMockito.mockStatic(HttpUtility.class); + when(HttpUtility.getRequestID()).thenReturn("test-request-id"); + } + @Test public void testInvalidRequestInInvokeConnectionMethod() { try { @@ -49,4 +72,369 @@ public void testInvalidRequestInInvokeConnectionMethod() { Assert.assertEquals(ErrorMessage.EmptyRequestBody.getMessage(), e.getMessage()); } } + + // New test cases for content-type handling changes + + @Test + public void testInvokeConnectionWithStringRequestBody() { + try { + String xmlBody = "test"; + HashMap headers = new HashMap<>(); + headers.put("Content-Type", "application/xml"); + InvokeConnectionRequest connectionRequest = InvokeConnectionRequest.builder() + .requestBody(xmlBody) + .requestHeaders(headers) + .build(); + Assert.assertNotNull(connectionRequest.getRequestBody()); + Assert.assertTrue(connectionRequest.getRequestBody() instanceof String); + } catch (Exception e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + + @Test + public void testInvokeConnectionWithEmptyStringRequestBody() { + try { + String emptyBody = ""; + HashMap headers = new HashMap<>(); + headers.put("Content-Type", "application/xml"); + InvokeConnectionRequest connectionRequest = InvokeConnectionRequest.builder() + .requestBody(emptyBody) + .requestHeaders(headers) + .build(); + Skyflow skyflowClient = Skyflow.builder().setLogLevel(LogLevel.DEBUG).addConnectionConfig(connectionConfig).build(); + skyflowClient.connection().invoke(connectionRequest); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); + Assert.assertEquals(ErrorMessage.EmptyRequestBody.getMessage(), e.getMessage()); + } + } + + @Test + public void testInvokeConnectionWithXMLContentType() { + try { + String xmlBody = "4111111111111111"; + HashMap headers = new HashMap<>(); + headers.put("Content-Type", "application/xml"); + InvokeConnectionRequest connectionRequest = InvokeConnectionRequest.builder() + .requestBody(xmlBody) + .requestHeaders(headers) + .build(); + Assert.assertEquals("application/xml", connectionRequest.getRequestHeaders().get("Content-Type")); + Assert.assertTrue(connectionRequest.getRequestBody() instanceof String); + } catch (Exception e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + + @Test + public void testInvokeConnectionWithPlainTextContentType() { + try { + String plainTextBody = "This is a plain text request body"; + HashMap headers = new HashMap<>(); + headers.put("Content-Type", "text/plain"); + InvokeConnectionRequest connectionRequest = InvokeConnectionRequest.builder() + .requestBody(plainTextBody) + .requestHeaders(headers) + .build(); + Assert.assertEquals("text/plain", connectionRequest.getRequestHeaders().get("Content-Type")); + Assert.assertTrue(connectionRequest.getRequestBody() instanceof String); + } catch (Exception e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + + @Test + public void testInvokeConnectionWithHTMLContentType() { + try { + String htmlBody = "

Test

"; + HashMap headers = new HashMap<>(); + headers.put("Content-Type", "text/html"); + InvokeConnectionRequest connectionRequest = InvokeConnectionRequest.builder() + .requestBody(htmlBody) + .requestHeaders(headers) + .build(); + Assert.assertEquals("text/html", connectionRequest.getRequestHeaders().get("Content-Type")); + Assert.assertTrue(connectionRequest.getRequestBody() instanceof String); + } catch (Exception e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + + // Tests for new content-type handling logic with actual controller invocation + + @Test + public void testInvokeConnectionWithStringBodyAndJsonContentType() throws Exception { + String jsonResponse = "{\"success\":true}"; + when(HttpUtility.sendRequest(anyString(), any(URL.class), any(JsonObject.class), anyMap())) + .thenReturn(jsonResponse); + + Skyflow client = Skyflow.builder() + .setLogLevel(LogLevel.DEBUG) + .addConnectionConfig(connectionConfig) + .build(); + + String stringBody = "{\"key\":\"value\"}"; + Map headers = new HashMap<>(); + headers.put("content-type", "application/json"); + + InvokeConnectionRequest request = InvokeConnectionRequest.builder() + .method(RequestMethod.POST) + .requestBody(stringBody) + .requestHeaders(headers) + .build(); + + InvokeConnectionResponse response = client.connection().invoke(request); + Assert.assertNotNull(response); + Assert.assertTrue(response.getData() instanceof JsonObject); + } + + @Test + public void testInvokeConnectionWithStringBodyAndXmlContentType() throws Exception { + String xmlResponse = "success"; + when(HttpUtility.sendRequest(anyString(), any(URL.class), any(JsonObject.class), anyMap())) + .thenReturn(xmlResponse); + + Skyflow client = Skyflow.builder() + .setLogLevel(LogLevel.DEBUG) + .addConnectionConfig(connectionConfig) + .build(); + + String xmlBody = "test"; + Map headers = new HashMap<>(); + headers.put("content-type", "application/xml"); + + InvokeConnectionRequest request = InvokeConnectionRequest.builder() + .method(RequestMethod.POST) + .requestBody(xmlBody) + .requestHeaders(headers) + .build(); + + InvokeConnectionResponse response = client.connection().invoke(request); + Assert.assertNotNull(response); + // When response is not valid JSON, it should be returned as String + Assert.assertTrue(response.getData() instanceof String); + Assert.assertEquals(xmlResponse, response.getData()); + } + + @Test + public void testInvokeConnectionWithJsonObjectBody() throws Exception { + String jsonResponse = "{\"result\":\"ok\"}"; + when(HttpUtility.sendRequest(anyString(), any(URL.class), any(JsonObject.class), anyMap())) + .thenReturn(jsonResponse); + + Skyflow client = Skyflow.builder() + .setLogLevel(LogLevel.DEBUG) + .addConnectionConfig(connectionConfig) + .build(); + + JsonObject jsonBody = new JsonObject(); + jsonBody.addProperty("test", "value"); + + InvokeConnectionRequest request = InvokeConnectionRequest.builder() + .method(RequestMethod.POST) + .requestBody(jsonBody) + .build(); + + InvokeConnectionResponse response = client.connection().invoke(request); + Assert.assertNotNull(response); + Assert.assertTrue(response.getData() instanceof JsonObject); + } + + @Test + public void testInvokeConnectionWithNullRequestBody() throws Exception { + String jsonResponse = "{\"status\":\"success\"}"; + when(HttpUtility.sendRequest(anyString(), any(URL.class), isNull(), anyMap())) + .thenReturn(jsonResponse); + + Skyflow client = Skyflow.builder() + .setLogLevel(LogLevel.DEBUG) + .addConnectionConfig(connectionConfig) + .build(); + + InvokeConnectionRequest request = InvokeConnectionRequest.builder() + .method(RequestMethod.GET) + .requestBody(null) + .build(); + + InvokeConnectionResponse response = client.connection().invoke(request); + Assert.assertNotNull(response); + Assert.assertTrue(response.getData() instanceof JsonObject); + } + + @Test + public void testInvokeConnectionWithNonJsonResponse() throws Exception { + String plainTextResponse = "This is a plain text response"; + when(HttpUtility.sendRequest(anyString(), any(URL.class), any(JsonObject.class), anyMap())) + .thenReturn(plainTextResponse); + + Skyflow client = Skyflow.builder() + .setLogLevel(LogLevel.DEBUG) + .addConnectionConfig(connectionConfig) + .build(); + + JsonObject jsonBody = new JsonObject(); + jsonBody.addProperty("key", "value"); + + InvokeConnectionRequest request = InvokeConnectionRequest.builder() + .method(RequestMethod.POST) + .requestBody(jsonBody) + .build(); + + InvokeConnectionResponse response = client.connection().invoke(request); + Assert.assertNotNull(response); + // Non-JSON response should be returned as String + Assert.assertTrue(response.getData() instanceof String); + Assert.assertEquals(plainTextResponse, response.getData()); + } + + @Test + public void testInvokeConnectionWithContentTypeCaseInsensitive() throws Exception { + String jsonResponse = "{\"data\":\"test\"}"; + when(HttpUtility.sendRequest(anyString(), any(URL.class), any(JsonObject.class), anyMap())) + .thenReturn(jsonResponse); + + Skyflow client = Skyflow.builder() + .setLogLevel(LogLevel.DEBUG) + .addConnectionConfig(connectionConfig) + .build(); + + String stringBody = "test data"; + Map headers = new HashMap<>(); + // Test with uppercase Content-Type + headers.put("Content-Type", "APPLICATION/JSON"); + + InvokeConnectionRequest request = InvokeConnectionRequest.builder() + .method(RequestMethod.POST) + .requestBody(stringBody) + .requestHeaders(headers) + .build(); + + InvokeConnectionResponse response = client.connection().invoke(request); + Assert.assertNotNull(response); + Assert.assertTrue(response.getData() instanceof JsonObject); + } + + @Test + public void testInvokeConnectionWithMixedCaseContentType() throws Exception { + String xmlResponse = "data"; + when(HttpUtility.sendRequest(anyString(), any(URL.class), any(JsonObject.class), anyMap())) + .thenReturn(xmlResponse); + + Skyflow client = Skyflow.builder() + .setLogLevel(LogLevel.DEBUG) + .addConnectionConfig(connectionConfig) + .build(); + + String stringBody = "test"; + Map headers = new HashMap<>(); + headers.put("content-type", "Application/Xml"); + + InvokeConnectionRequest request = InvokeConnectionRequest.builder() + .method(RequestMethod.POST) + .requestBody(stringBody) + .requestHeaders(headers) + .build(); + + InvokeConnectionResponse response = client.connection().invoke(request); + Assert.assertNotNull(response); + Assert.assertTrue(response.getData() instanceof String); + } + + @Test + public void testInvokeConnectionWithMapRequestBody() throws Exception { + String jsonResponse = "{\"created\":true}"; + when(HttpUtility.sendRequest(anyString(), any(URL.class), any(JsonObject.class), anyMap())) + .thenReturn(jsonResponse); + + Skyflow client = Skyflow.builder() + .setLogLevel(LogLevel.DEBUG) + .addConnectionConfig(connectionConfig) + .build(); + + Map mapBody = new HashMap<>(); + mapBody.put("name", "John"); + mapBody.put("age", "30"); + + InvokeConnectionRequest request = InvokeConnectionRequest.builder() + .method(RequestMethod.POST) + .requestBody(mapBody) + .build(); + + InvokeConnectionResponse response = client.connection().invoke(request); + Assert.assertNotNull(response); + Assert.assertTrue(response.getData() instanceof JsonObject); + } + + @Test + public void testInvokeConnectionWithDefaultContentType() throws Exception { + String jsonResponse = "{\"message\":\"success\"}"; + when(HttpUtility.sendRequest(anyString(), any(URL.class), any(JsonObject.class), anyMap())) + .thenReturn(jsonResponse); + + Skyflow client = Skyflow.builder() + .setLogLevel(LogLevel.DEBUG) + .addConnectionConfig(connectionConfig) + .build(); + + String stringBody = "string data"; + // No content-type header - should default to application/json + + InvokeConnectionRequest request = InvokeConnectionRequest.builder() + .method(RequestMethod.POST) + .requestBody(stringBody) + .build(); + + InvokeConnectionResponse response = client.connection().invoke(request); + Assert.assertNotNull(response); + Assert.assertTrue(response.getData() instanceof JsonObject); + } + + @Test + public void testInvokeConnectionWithFormUrlEncodedContentType() throws Exception { + String jsonResponse = "{\"submitted\":true}"; + when(HttpUtility.sendRequest(anyString(), any(URL.class), any(JsonObject.class), anyMap())) + .thenReturn(jsonResponse); + + Skyflow client = Skyflow.builder() + .setLogLevel(LogLevel.DEBUG) + .addConnectionConfig(connectionConfig) + .build(); + + String formBody = "param1=value1¶m2=value2"; + Map headers = new HashMap<>(); + headers.put("content-type", "application/x-www-form-urlencoded"); + + InvokeConnectionRequest request = InvokeConnectionRequest.builder() + .method(RequestMethod.POST) + .requestBody(formBody) + .requestHeaders(headers) + .build(); + + InvokeConnectionResponse response = client.connection().invoke(request); + Assert.assertNotNull(response); + Assert.assertTrue(response.getData() instanceof JsonObject); + } + + @Test + public void testInvokeConnectionMetadataContainsRequestId() throws Exception { + String jsonResponse = "{\"data\":\"value\"}"; + when(HttpUtility.sendRequest(anyString(), any(URL.class), any(JsonObject.class), anyMap())) + .thenReturn(jsonResponse); + + Skyflow client = Skyflow.builder() + .setLogLevel(LogLevel.DEBUG) + .addConnectionConfig(connectionConfig) + .build(); + + InvokeConnectionRequest request = InvokeConnectionRequest.builder() + .method(RequestMethod.GET) + .build(); + + InvokeConnectionResponse response = client.connection().invoke(request); + Assert.assertNotNull(response); + Assert.assertNotNull(response.getMetadata()); + Assert.assertEquals("test-request-id", response.getMetadata().get("requestId")); + } }