diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8534bd5..72ef906 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,10 @@
# Changelog
All notable changes to this project will be documented in this file.
+## [1.5.0] - 2022-04-12
+
+### Added
+- support for application/x-www-form-urlencoded and multipart/form-data content-type's in connections.
## [1.4.1] - 2022-03-29
diff --git a/README.md b/README.md
index 7405925..1a76fcb 100644
--- a/README.md
+++ b/README.md
@@ -37,7 +37,7 @@ This Java SDK is designed to help developers easily implement Skyflow into their
Add this dependency to your project's build file:
```
-implementation 'com.skyflow:skyflow-java:1.4.1'
+implementation 'com.skyflow:skyflow-java:1.5.0'
```
#### Maven users
@@ -47,7 +47,7 @@ Add this dependency to your project's POM:
com.skyflow
skyflow-java
- 1.4.1
+ 1.5.0
```
---
diff --git a/pom.xml b/pom.xml
index 646a28e..0b57200 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
com.skyflow
skyflow-java
- 1.4.1
+ 1.5.0
jar
${project.groupId}:${project.artifactId}
diff --git a/src/main/java/com/skyflow/common/utils/Helpers.java b/src/main/java/com/skyflow/common/utils/Helpers.java
index 982fd10..7cdc1a6 100644
--- a/src/main/java/com/skyflow/common/utils/Helpers.java
+++ b/src/main/java/com/skyflow/common/utils/Helpers.java
@@ -5,19 +5,19 @@
import com.skyflow.entities.InsertRecordInput;
import com.skyflow.errors.ErrorCode;
import com.skyflow.errors.SkyflowException;
+import com.skyflow.logs.DebugLogs;
+
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
public final class Helpers {
+ private static final String LINE_FEED = "\r\n";
public static JSONObject constructInsertRequest(InsertInput recordsInput, InsertOptions options) throws SkyflowException {
JSONObject finalRequest = new JSONObject();
List requestBodyContent = new ArrayList();
@@ -161,4 +161,56 @@ public static String appendRequestIdToErrorObj(int status, String error, String
return error;
}
+ public static String formatJsonToFormEncodedString(JSONObject requestBody){
+ LogUtil.printDebugLog(DebugLogs.FormatRequestBodyFormUrlFormEncoded.getLog());
+ StringBuilder formEncodeString = new StringBuilder();
+ HashMap jsonMap = convertJsonToMap(requestBody,"");
+
+ for (Map.Entry currentEntry : jsonMap.entrySet())
+ formEncodeString.append(makeFormEncodeKeyValuePair(currentEntry.getKey(),currentEntry.getValue()));
+
+ return formEncodeString.substring(0,formEncodeString.length()-1);
+ }
+
+ public static String formatJsonToMultiPartFormDataString(JSONObject requestBody,String boundary){
+ LogUtil.printDebugLog(DebugLogs.FormatRequestBodyFormData.getLog());
+ StringBuilder formEncodeString = new StringBuilder();
+ HashMap jsonMap = convertJsonToMap(requestBody,"");
+
+ for (Map.Entry currentEntry : jsonMap.entrySet())
+ formEncodeString.append(makeFormDataKeyValuePair(currentEntry.getKey(),currentEntry.getValue(),boundary));
+
+ formEncodeString.append(LINE_FEED);
+ formEncodeString.append("--").append(boundary).append("--").append(LINE_FEED);
+
+ return formEncodeString.toString();
+ }
+
+ private static HashMap convertJsonToMap(JSONObject json,String rootKey){
+ HashMap currentMap = new HashMap<>();
+ for (Object key : json.keySet()) {
+ Object currentValue = json.get(key);
+ String currentKey = rootKey.length() != 0 ? rootKey + '[' + key.toString() + ']' : rootKey + key.toString();
+ if(currentValue instanceof JSONObject){
+ currentMap.putAll(convertJsonToMap((JSONObject) currentValue, currentKey));
+ }else {
+ currentMap.put(currentKey,currentValue.toString());
+ }
+ }
+ return currentMap;
+ }
+
+ private static String makeFormEncodeKeyValuePair(String key, String value){
+ return key+"="+value+"&";
+ }
+
+ 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(LINE_FEED);
+ formDataTextField.append(value).append(LINE_FEED);
+
+ return formDataTextField.toString();
+ }
}
diff --git a/src/main/java/com/skyflow/common/utils/HttpUtility.java b/src/main/java/com/skyflow/common/utils/HttpUtility.java
index a30a0b0..ba019a4 100644
--- a/src/main/java/com/skyflow/common/utils/HttpUtility.java
+++ b/src/main/java/com/skyflow/common/utils/HttpUtility.java
@@ -10,28 +10,48 @@
import java.nio.charset.StandardCharsets;
import java.util.Map;
+import static com.skyflow.common.utils.Helpers.formatJsonToFormEncodedString;
+import static com.skyflow.common.utils.Helpers.formatJsonToMultiPartFormDataString;
+
public final class HttpUtility {
public static String sendRequest(String method, String requestUrl, JSONObject params, Map headers) throws IOException, SkyflowException {
HttpURLConnection connection = null;
BufferedReader in = null;
StringBuffer response = null;
+ String boundary = String.valueOf(System.currentTimeMillis());
try {
URL url = new URL(requestUrl);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod(method);
connection.setRequestProperty("content-type", "application/json");
+ connection.setRequestProperty("Accept", "*/*");
if (headers != null && headers.size() > 0) {
- for (Map.Entry entry : headers.entrySet()) {
+ 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 (headers.get("content-type") == "multipart/form-data") {
+ connection.setRequestProperty("content-type", "multipart/form-data; boundary=" + boundary);
+ }
}
}
-
if (params != null && params.size() > 0) {
connection.setDoOutput(true);
try (DataOutputStream wr = new DataOutputStream(connection.getOutputStream())) {
- byte[] input = params.toString().getBytes(StandardCharsets.UTF_8);
+ byte[] input = null;
+ String requestContentType = connection.getRequestProperty("content-type");
+
+ if (requestContentType.contains("application/x-www-form-urlencoded")) {
+ input = formatJsonToFormEncodedString(params).getBytes(StandardCharsets.UTF_8);
+ } else if (requestContentType.contains("multipart/form-data")) {
+ input = formatJsonToMultiPartFormDataString(params, boundary).getBytes(StandardCharsets.UTF_8);
+ }else {
+ input = params.toString().getBytes(StandardCharsets.UTF_8);
+ }
+
wr.write(input, 0, input.length);
wr.flush();
}
diff --git a/src/main/java/com/skyflow/logs/DebugLogs.java b/src/main/java/com/skyflow/logs/DebugLogs.java
new file mode 100644
index 0000000..9e4d467
--- /dev/null
+++ b/src/main/java/com/skyflow/logs/DebugLogs.java
@@ -0,0 +1,16 @@
+package com.skyflow.logs;
+
+public enum DebugLogs {
+
+ FormatRequestBodyFormUrlFormEncoded("Formatting request body for form-urlencoded content-type"),
+ FormatRequestBodyFormData("Formatting request body for form-data content-type");
+ private final String log;
+
+ DebugLogs(String log) {
+ this.log = log;
+ }
+
+ public String getLog() {
+ return log;
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/skyflow/common/utils/HelpersTest.java b/src/test/java/com/skyflow/common/utils/HelpersTest.java
new file mode 100644
index 0000000..6bc447d
--- /dev/null
+++ b/src/test/java/com/skyflow/common/utils/HelpersTest.java
@@ -0,0 +1,38 @@
+package com.skyflow.common.utils;
+
+import org.json.simple.JSONObject;
+import org.junit.Test;
+
+public class HelpersTest {
+
+ @Test
+ public void testFormatJsonToFormEncodedString(){
+ JSONObject testJson = new JSONObject();
+ testJson.put("key1","value1");
+ JSONObject nestedObj = new JSONObject();
+ nestedObj.put("key2","value2");
+ testJson.put("nest",nestedObj);
+
+ String testResponse = Helpers.formatJsonToFormEncodedString(testJson);
+ System.out.println(testResponse);
+ assert testResponse.contains("key1=value1");
+ assert testResponse.contains("nest[key2]=value2");
+ }
+
+ @Test
+ public void testFormatJsonToMultiPartFormDataString(){
+ JSONObject testJson = new JSONObject();
+ testJson.put("key1","value1");
+ JSONObject nestedObj = new JSONObject();
+ nestedObj.put("key2","value2");
+ testJson.put("nest",nestedObj);
+ String testBoundary = "123";
+ String testResponse = Helpers.formatJsonToMultiPartFormDataString(testJson,testBoundary);
+ assert testResponse.contains("--"+testBoundary);
+ assert testResponse.contains("--"+testBoundary+"--");
+ assert testResponse.contains("Content-Disposition: form-data; name=\"key1\"");
+ assert testResponse.contains("value1");
+ assert testResponse.contains("Content-Disposition: form-data; name=\"nest[key2]\"");
+ assert testResponse.contains("value2");
+ }
+}
diff --git a/src/test/java/com/skyflow/serviceaccount/util/TokenTest.java b/src/test/java/com/skyflow/serviceaccount/util/TokenTest.java
index 56aa7c0..c12fd19 100644
--- a/src/test/java/com/skyflow/serviceaccount/util/TokenTest.java
+++ b/src/test/java/com/skyflow/serviceaccount/util/TokenTest.java
@@ -23,7 +23,7 @@ public class TokenTest {
@Test
public void testInvalidFilePath() {
try {
- Token.generateBearerToken("");
+ Token.GenerateToken("");
} catch (SkyflowException exception) {
assertEquals(exception.getMessage(), ErrorCode.EmptyFilePath.getDescription());
}
diff --git a/src/test/java/com/skyflow/vault/InvokeConnectionTest.java b/src/test/java/com/skyflow/vault/InvokeConnectionTest.java
index e8594c3..e70c795 100644
--- a/src/test/java/com/skyflow/vault/InvokeConnectionTest.java
+++ b/src/test/java/com/skyflow/vault/InvokeConnectionTest.java
@@ -20,6 +20,7 @@
import java.io.IOException;
+import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.anyString;
@@ -34,6 +35,7 @@ public String getBearerToken() throws Exception {
@RunWith(PowerMockRunner.class)
@PrepareForTest(fullyQualifiedNames = "com.skyflow.common.utils.TokenUtils")
public class InvokeConnectionTest {
+ private static final String INVALID_EXCEPTION_THROWN = "Should not have thrown any exception";
private JSONObject testConfig;
private static Skyflow skyflowClient;
@@ -84,8 +86,10 @@ public void testInvokeConnectionValidInput() {
Assert.assertEquals(gatewayResponse.toJSONString(), mockResponse);
} catch (SkyflowException exception) {
Assert.assertNull(exception);
+ fail(INVALID_EXCEPTION_THROWN);
} catch (IOException exception) {
exception.printStackTrace();
+ fail(INVALID_EXCEPTION_THROWN);
}
@@ -158,4 +162,78 @@ public void testInvokeConnectionInvalidMethodName() {
}
}
+ @Test
+ @PrepareForTest(fullyQualifiedNames = {"com.skyflow.common.utils.HttpUtility", "com.skyflow.common.utils.TokenUtils"})
+ public void testInvokeConnectionWithFormEncoded() {
+ JSONObject testConfig = new JSONObject();
+ testConfig.put("connectionURL", "https://testgatewayurl.com/");
+ testConfig.put("methodName", RequestMethod.POST);
+
+ JSONObject requestHeadersJson = new JSONObject();
+ requestHeadersJson.put("content-type", "application/x-www-form-urlencoded");
+ testConfig.put("requestHeader", requestHeadersJson);
+
+ JSONObject testJson = new JSONObject();
+ testJson.put("key1","value1");
+ JSONObject nestedObj = new JSONObject();
+ nestedObj.put("key2","value2");
+ testJson.put("nest",nestedObj);
+
+ testConfig.put("requestBody", testJson);
+
+ try {
+ PowerMockito.mockStatic(HttpUtility.class);
+ String mockResponse = "{\"id\":\"12345\"}";
+ PowerMockito.when(HttpUtility.sendRequest(anyString(), anyString(), ArgumentMatchers.any(), ArgumentMatchers.anyMap())).thenReturn(mockResponse);
+ JSONObject gatewayResponse = skyflowClient.invokeConnection(testConfig);
+
+ Assert.assertNotNull(gatewayResponse);
+ Assert.assertEquals(gatewayResponse.toJSONString(), mockResponse);
+ } catch (SkyflowException exception) {
+ Assert.assertNull(exception);
+ fail(INVALID_EXCEPTION_THROWN);
+ } catch (IOException exception) {
+ exception.printStackTrace();
+ fail(INVALID_EXCEPTION_THROWN);
+ }
+
+ }
+
+ @Test
+ @PrepareForTest(fullyQualifiedNames = {"com.skyflow.common.utils.HttpUtility", "com.skyflow.common.utils.TokenUtils"})
+ public void testInvokeConnectionWithMultipartFormData() {
+ JSONObject testConfig = new JSONObject();
+ testConfig.put("connectionURL", "https://testgatewayurl.com/");
+ testConfig.put("methodName", RequestMethod.POST);
+
+ JSONObject requestHeadersJson = new JSONObject();
+ requestHeadersJson.put("content-type", "multipart/form-data");
+ testConfig.put("requestHeader", requestHeadersJson);
+
+ JSONObject testJson = new JSONObject();
+ testJson.put("key1","value1");
+ JSONObject nestedObj = new JSONObject();
+ nestedObj.put("key2","value2");
+ testJson.put("nest",nestedObj);
+
+ testConfig.put("requestBody", testJson);
+
+ try {
+ PowerMockito.mockStatic(HttpUtility.class);
+ String mockResponse = "{\"id\":\"12345\"}";
+ PowerMockito.when(HttpUtility.sendRequest(anyString(), anyString(), ArgumentMatchers.any(), ArgumentMatchers.anyMap())).thenReturn(mockResponse);
+ JSONObject gatewayResponse = skyflowClient.invokeConnection(testConfig);
+
+ Assert.assertNotNull(gatewayResponse);
+ Assert.assertEquals(gatewayResponse.toJSONString(), mockResponse);
+ } catch (SkyflowException exception) {
+ Assert.assertNull(exception);
+ fail(INVALID_EXCEPTION_THROWN);
+ } catch (IOException exception) {
+ exception.printStackTrace();
+ fail(INVALID_EXCEPTION_THROWN);
+ }
+
+ }
+
}