diff --git a/README.md b/README.md
index 80472995..1acc7518 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@ The recommended method for obtaining the SDK is via Gradle or Maven through the
### Gradle
```groovy
-compile "com.smartcar.sdk:java-sdk:3.6.0"
+compile "com.smartcar.sdk:java-sdk:3.7.0"
```
### Maven
@@ -18,16 +18,16 @@ compile "com.smartcar.sdk:java-sdk:3.6.0"
com.smartcar.sdk
java-sdk
- 3.6.0
+ 3.7.0
```
### Jar Direct Download
-* [java-sdk-3.6.0.jar](https://search.maven.org/remotecontent?filepath=com/smartcar/sdk/java-sdk/3.6.0/java-sdk-3.6.0.jar)
-* [java-sdk-3.6.0-sources.jar](https://search.maven.org/remotecontent?filepath=com/smartcar/sdk/java-sdk/3.6.0/java-sdk-3.6.0-sources.jar)
-* [java-sdk-3.6.0-javadoc.jar](https://search.maven.org/remotecontent?filepath=com/smartcar/sdk/java-sdk/3.6.0/java-sdk-3.6.0-javadoc.jar)
+* [java-sdk-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/smartcar/sdk/java-sdk/3.7.0/java-sdk-3.7.0.jar)
+* [java-sdk-3.7.0-sources.jar](https://search.maven.org/remotecontent?filepath=com/smartcar/sdk/java-sdk/3.7.0/java-sdk-3.7.0-sources.jar)
+* [java-sdk-3.7.0-javadoc.jar](https://search.maven.org/remotecontent?filepath=com/smartcar/sdk/java-sdk/3.7.0/java-sdk-3.7.0-javadoc.jar)
-Signatures and other downloads available at [Maven Central](https://search.maven.org/artifact/com.smartcar.sdk/java-sdk/3.6.0/jar).
+Signatures and other downloads available at [Maven Central](https://search.maven.org/artifact/com.smartcar.sdk/java-sdk/3.7.0/jar).
## Usage
@@ -136,5 +136,5 @@ In accordance with the Semantic Versioning specification, the addition of suppor
[ci-url]: https://travis-ci.com/smartcar/java-sdk
[coverage-image]: https://codecov.io/gh/smartcar/java-sdk/branch/master/graph/badge.svg?token=nZAITx7w3X
[coverage-url]: https://codecov.io/gh/smartcar/java-sdk
-[javadoc-image]: https://img.shields.io/badge/javadoc-3.6.0-brightgreen.svg
+[javadoc-image]: https://img.shields.io/badge/javadoc-3.7.0-brightgreen.svg
[javadoc-url]: https://smartcar.github.io/java-sdk
diff --git a/gradle.properties b/gradle.properties
index ec61dc22..0130598b 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,4 +1,4 @@
libGroup=com.smartcar.sdk
libName=java-sdk
-libVersion=3.6.0
+libVersion=3.7.0
libDescription=Smartcar Java SDK
diff --git a/src/integration/java/com/smartcar/sdk/SmartcarTest.java b/src/integration/java/com/smartcar/sdk/SmartcarTest.java
index 0df001e3..d66f827d 100644
--- a/src/integration/java/com/smartcar/sdk/SmartcarTest.java
+++ b/src/integration/java/com/smartcar/sdk/SmartcarTest.java
@@ -1,9 +1,6 @@
package com.smartcar.sdk;
-import com.smartcar.sdk.data.Compatibility;
-import com.smartcar.sdk.data.RequestPaging;
-import com.smartcar.sdk.data.User;
-import com.smartcar.sdk.data.VehicleIds;
+import com.smartcar.sdk.data.*;
import com.smartcar.sdk.helpers.AuthHelpers;
import org.testng.Assert;
import org.testng.annotations.BeforeSuite;
@@ -14,8 +11,10 @@
public class SmartcarTest {
private String accessToken;
+ private VehicleIds vehicleIds;
private String clientId = System.getenv("E2E_SMARTCAR_CLIENT_ID");
private String clientSecret = System.getenv("E2E_SMARTCAR_CLIENT_SECRET");
+ private String amt = System.getenv("E2E_SMARTCAR_AMT");
private AuthClient client;
private String authorizeUrl;
private String[] scope = {"read_odometer"};
@@ -26,6 +25,8 @@ public void beforeSuite() throws Exception {
this.authorizeUrl = client.authUrlBuilder(new String[]{"read_vehicle_info"}).build();
String code = AuthHelpers.runAuthFlow(client.authUrlBuilder(this.scope).build());
this.accessToken = client.exchangeCode(code).getAccessToken();
+ VehicleIds vehicleIds = Smartcar.getVehicles(this.accessToken);
+ this.vehicleIds = vehicleIds;
}
@Test
@@ -78,4 +79,26 @@ public void testGetCompatibility() throws Exception {
}
Assert.assertFalse((capable));
}
+
+ @Test
+ public void testGetConnections() throws SmartcarException {
+ String testVehicleId = this.vehicleIds.getVehicleIds()[0];
+ ConnectionsFilter filter = new ConnectionsFilter
+ .Builder()
+ .vehicleId(testVehicleId)
+ .build();
+ GetConnections connections = Smartcar.getConnections(this.amt, filter);
+ Assert.assertEquals(connections.getConnections().length, 1);
+ }
+
+ @Test
+ public void testDeleteConnections() throws SmartcarException {
+ String testVehicleId = this.vehicleIds.getVehicleIds()[0];
+ ConnectionsFilter filter = new ConnectionsFilter
+ .Builder()
+ .vehicleId(testVehicleId)
+ .build();
+ DeleteConnections connections = Smartcar.deleteConnections(this.amt, filter);
+ Assert.assertEquals(connections.getConnections().length, 1);
+ }
}
diff --git a/src/main/java/com/smartcar/sdk/Smartcar.java b/src/main/java/com/smartcar/sdk/Smartcar.java
index 7c19618e..98b59cd8 100644
--- a/src/main/java/com/smartcar/sdk/Smartcar.java
+++ b/src/main/java/com/smartcar/sdk/Smartcar.java
@@ -12,7 +12,8 @@
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
-import javax.json.JsonObject;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@@ -20,6 +21,7 @@
public class Smartcar {
public static String API_VERSION = "2.0";
public static String API_ORIGIN = "https://api.smartcar.com";
+ public static String MANAGEMENT_API_ORIGIN = "https://management.smartcar.com";
/**
* Sets the Smartcar API version
@@ -40,7 +42,6 @@ static String getApiUrl() {
}
/**
- *
* @return Smartcar API origin
*/
static String getApiOrigin() {
@@ -51,6 +52,17 @@ static String getApiOrigin() {
return apiOrigin;
}
+ /**
+ * @return Smartcar Management API origin
+ */
+ static String getManagementApiOrigin() {
+ String managementApiOrigin = System.getenv("SMARTCAR_MANAGEMENT_API_ORIGIN");
+ if (managementApiOrigin == null) {
+ return MANAGEMENT_API_ORIGIN;
+ }
+ return managementApiOrigin;
+ }
+
/**
* Retrieves the user ID of the user authenticated with the specified access token.
*
@@ -72,7 +84,7 @@ public static User getUser(String accessToken) throws SmartcarException {
* Retrieves all vehicles associated with the authenticated user.
*
* @param accessToken a valid access token
- * @param paging paging parameters
+ * @param paging paging parameters
* @return the requested vehicle IDs
* @throws SmartcarException if the request is unsuccessful
*/
@@ -111,7 +123,7 @@ public static VehicleIds getVehicles(String accessToken)
* Convenience method for determining if an auth token expiration has passed.
*
* @param expiration the expiration date of the token
- * @return whether or not the token has expired
+ * @return whether the token has expired
*/
public static boolean isExpired(Date expiration) {
return !expiration.after(new Date());
@@ -129,7 +141,7 @@ public static boolean isExpired(Date expiration) {
*
* @param compatibilityRequest with options for this request. See Smartcar.SmartcarCompatibilityRequest
* @return A Compatibility object with isCompatible set to false if the vehicle is not compatible in the specified country and true if the vehicle is
- * likely compatible.
+ * likely compatible.
* @throws SmartcarException when the request is unsuccessful
*/
public static Compatibility getCompatibility(SmartcarCompatibilityRequest compatibilityRequest) throws SmartcarException {
@@ -144,7 +156,6 @@ public static Compatibility getCompatibility(SmartcarCompatibilityRequest compat
.addQueryParameter("country", compatibilityRequest.getCountry());
-
if (compatibilityRequest.getFlags() != null) {
urlBuilder.addQueryParameter("flags", compatibilityRequest.getFlags());
}
@@ -168,6 +179,7 @@ public static Compatibility getCompatibility(SmartcarCompatibilityRequest compat
/**
* Performs a HmacSHA256 hash on a challenge string using the key provided
+ *
* @param key
* @param challenge
* @return String digest
@@ -186,13 +198,146 @@ public static String hashChallenge(String key, String challenge) throws Smartcar
/**
* Verifies as HmacSHA256 signature
+ *
* @param applicationManagementToken
* @param signature
* @param payload
- * @return boolean whether or not the signature was verified
+ * @return boolean whether the signature was verified
* @throws SmartcarException
*/
public static boolean verifyPayload(String applicationManagementToken, String signature, String payload) throws SmartcarException {
return Smartcar.hashChallenge(applicationManagementToken, payload).equals(signature);
}
+
+ /**
+ * Returns a paged list of all the vehicles that are connected to the application associated
+ * with the management API token used sorted in descending order by connection date.
+ *
+ * @param applicationManagementToken
+ * @param filter
+ * @param paging
+ * @return connections
+ * @throws SmartcarException if the request is unsuccessful
+ */
+ public static GetConnections getConnections(String applicationManagementToken, ConnectionsFilter filter, RequestPagingCursor paging)
+ throws SmartcarException {
+ // Build Request
+ HttpUrl.Builder urlBuilder = HttpUrl
+ .parse(Smartcar.getManagementApiOrigin() + "/v" + Smartcar.API_VERSION + "/management/connections")
+ .newBuilder();
+
+ if (filter != null) {
+ if (filter.getUserId() != null) {
+ urlBuilder
+ .addQueryParameter("user_id", String.valueOf(filter.getUserId()));
+ }
+ if (filter.getVehicleId() != null) {
+ urlBuilder
+ .addQueryParameter("vehicle_id", String.valueOf(filter.getVehicleId()));
+ }
+ }
+ if (paging != null) {
+ if (paging.getCursor() != null) {
+ urlBuilder
+ .addQueryParameter("cursor", String.valueOf(paging.getCursor()));
+ }
+ if (paging.getLimit() != null) {
+ urlBuilder
+ .addQueryParameter("limit", String.valueOf(paging.getLimit()));
+ }
+ }
+
+ HttpUrl url = urlBuilder.build();
+ Map headers = new HashMap<>();
+ headers.put("Authorization", Credentials.basic(
+ "default",
+ applicationManagementToken
+ ));
+ Request request = ApiClient.buildRequest(url, "GET", null, headers);
+
+ return ApiClient.execute(request, GetConnections.class);
+ }
+
+ /**
+ * Returns a paged list of all the vehicles that are connected to the application associated
+ * with the management API token used sorted in descending order by connection date.
+ *
+ * @param applicationManagementToken
+ * @param filter
+ * @throws SmartcarException if the request is unsuccessful
+ */
+ public static GetConnections getConnections(String applicationManagementToken, ConnectionsFilter filter)
+ throws SmartcarException {
+ return Smartcar.getConnections(applicationManagementToken, filter, null);
+ }
+
+ /**
+ * Returns a paged list of all the vehicles that are connected to the application associated
+ * with the management API token used sorted in descending order by connection date.
+ *
+ * @param applicationManagementToken
+ * @throws SmartcarException if the request is unsuccessful
+ */
+ public static GetConnections getConnections(String applicationManagementToken)
+ throws SmartcarException {
+ return Smartcar.getConnections(applicationManagementToken, null, null);
+ }
+
+ /**
+ * Deletes all the connections by vehicle or user ID and returns a list of all connections that were deleted.
+ *
+ * @param applicationManagementToken
+ * @param filter
+ * @return connections
+ * @throws SmartcarException if the request is unsuccessful
+ */
+ public static DeleteConnections deleteConnections(String applicationManagementToken, ConnectionsFilter filter)
+ throws SmartcarException {
+ // Build Request
+ HttpUrl.Builder urlBuilder = HttpUrl
+ .parse(Smartcar.getManagementApiOrigin() + "/v" + Smartcar.API_VERSION + "/management/connections")
+ .newBuilder();
+
+ if (filter != null) {
+ String userId = filter.getUserId();
+ String vehicleId = filter.getVehicleId();
+ if (userId != null && vehicleId != null) {
+ throw new SmartcarException
+ .Builder()
+ .type("SDK_ERROR")
+ .description("Filter can contain EITHER user_id OR vehicle_id, not both")
+ .build();
+ }
+ if (userId != null) {
+ urlBuilder
+ .addQueryParameter("user_id", String.valueOf(userId));
+ }
+ if (vehicleId != null) {
+ urlBuilder
+ .addQueryParameter("vehicle_id", String.valueOf(vehicleId));
+ }
+ }
+
+ HttpUrl url = urlBuilder.build();
+ Map headers = new HashMap<>();
+ headers.put("Authorization", Credentials.basic(
+ "default",
+ applicationManagementToken
+ ));
+ Request request = ApiClient.buildRequest(url, "GET", null, headers);
+
+ return ApiClient.execute(request, DeleteConnections.class);
+ }
+
+ private static String getManagementToken(String applicationManagementToken, String username) {
+ String credentials = username + ":" + applicationManagementToken;
+ byte[] credentialsBytes = credentials.getBytes(StandardCharsets.UTF_8);
+ return Base64.getEncoder().encodeToString(credentialsBytes);
+ }
+
+ private static String getManagementToken(String applicationManagementToken) {
+ String credentials = "default:" + applicationManagementToken;
+ byte[] credentialsBytes = credentials.getBytes(StandardCharsets.UTF_8);
+ return Base64.getEncoder().encodeToString(credentialsBytes);
+ }
}
diff --git a/src/main/java/com/smartcar/sdk/data/Connection.java b/src/main/java/com/smartcar/sdk/data/Connection.java
new file mode 100644
index 00000000..a2713f98
--- /dev/null
+++ b/src/main/java/com/smartcar/sdk/data/Connection.java
@@ -0,0 +1,28 @@
+package com.smartcar.sdk.data;
+
+public class Connection {
+ private String vehicleId;
+ private String userId;
+ private String connectedAt;
+
+ public String getVehicleId() {
+ return vehicleId;
+ }
+
+ public String getUserId() {
+ return userId;
+ }
+
+ public String getConnectedAt() {
+ return connectedAt;
+ }
+
+ @Override
+ public String toString() {
+ return "Connection{" +
+ "vehicleId='" + vehicleId + '\'' +
+ ", userId='" + userId + '\'' +
+ ", createdAt='" + connectedAt + '\'' +
+ '}';
+ }
+}
diff --git a/src/main/java/com/smartcar/sdk/data/ConnectionsFilter.java b/src/main/java/com/smartcar/sdk/data/ConnectionsFilter.java
new file mode 100644
index 00000000..7100b601
--- /dev/null
+++ b/src/main/java/com/smartcar/sdk/data/ConnectionsFilter.java
@@ -0,0 +1,46 @@
+package com.smartcar.sdk.data;
+
+public class ConnectionsFilter {
+ private String userId;
+ private String vehicleId;
+
+ private ConnectionsFilter(Builder builder) {
+ this.userId = builder.userId;
+ this.vehicleId = builder.vehicleId;
+ }
+
+ public String getUserId() {
+ return userId;
+ }
+
+ public String getVehicleId() {
+ return vehicleId;
+ }
+
+ public static class Builder {
+ private String userId;
+ private String vehicleId;
+
+ public ConnectionsFilter.Builder userId(String userId) {
+ this.userId = userId;
+ return this;
+ }
+
+ public ConnectionsFilter.Builder vehicleId(String vehicleId) {
+ this.vehicleId = vehicleId;
+ return this;
+ }
+
+ public ConnectionsFilter build() {
+ return new ConnectionsFilter(this);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "ConnectionsFilter{" +
+ "userId='" + userId + '\'' +
+ ", vehicleId='" + vehicleId + '\'' +
+ '}';
+ }
+}
diff --git a/src/main/java/com/smartcar/sdk/data/DeleteConnections.java b/src/main/java/com/smartcar/sdk/data/DeleteConnections.java
new file mode 100644
index 00000000..9b28f356
--- /dev/null
+++ b/src/main/java/com/smartcar/sdk/data/DeleteConnections.java
@@ -0,0 +1,21 @@
+package com.smartcar.sdk.data;
+
+import java.util.Arrays;
+
+public class DeleteConnections extends ApiData {
+ private Connection[] connections;
+
+ public DeleteConnections(final Connection[] connections) {
+ this.connections = connections;
+ }
+
+ public Connection[] getConnections() {
+ return connections;
+ }
+
+ @Override public String toString() {
+ return "DeleteConnections{" +
+ "connections=" + Arrays.toString(connections) +
+ '}';
+ }
+}
diff --git a/src/main/java/com/smartcar/sdk/data/GetConnections.java b/src/main/java/com/smartcar/sdk/data/GetConnections.java
new file mode 100644
index 00000000..1d69ad58
--- /dev/null
+++ b/src/main/java/com/smartcar/sdk/data/GetConnections.java
@@ -0,0 +1,29 @@
+package com.smartcar.sdk.data;
+
+import java.util.Arrays;
+
+public class GetConnections extends ApiData {
+ private Connection[] connections;
+
+ private ResponsePagingCursor paging;
+ public GetConnections(final Connection[] connections, final ResponsePagingCursor paging) {
+ this.connections = connections;
+ this.paging = paging;
+ }
+
+ public Connection[] getConnections() {
+ return connections;
+ }
+
+ public ResponsePagingCursor getPaging() {
+ return paging;
+ }
+
+ @Override
+ public String toString() {
+ return "GetConnections{" +
+ "connections=" + Arrays.toString(connections) +
+ ", paging=" + paging +
+ '}';
+ }
+}
diff --git a/src/main/java/com/smartcar/sdk/data/RequestPagingCursor.java b/src/main/java/com/smartcar/sdk/data/RequestPagingCursor.java
new file mode 100644
index 00000000..805d5103
--- /dev/null
+++ b/src/main/java/com/smartcar/sdk/data/RequestPagingCursor.java
@@ -0,0 +1,47 @@
+package com.smartcar.sdk.data;
+
+/** Builder class for setting request paging w/cursor options */
+public class RequestPagingCursor {
+ private Integer limit;
+ private String cursor;
+
+ private RequestPagingCursor(Builder builder) {
+ this.limit = builder.limit;
+ this.cursor = builder.cursor;
+ }
+
+ public Integer getLimit() {
+ return limit;
+ }
+
+ public String getCursor() {
+ return cursor;
+ }
+
+ public static class Builder {
+ private Integer limit;
+ private String cursor;
+
+ public Builder limit(Integer limit) {
+ this.limit = limit;
+ return this;
+ }
+
+ public Builder cursor(String cursor) {
+ this.cursor = cursor;
+ return this;
+ }
+
+ public RequestPagingCursor build() {
+ return new RequestPagingCursor(this);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "RequestPagingCursor{" +
+ "limit=" + limit +
+ ", cursor='" + cursor + '\'' +
+ '}';
+ }
+}
diff --git a/src/main/java/com/smartcar/sdk/data/ResponsePagingCursor.java b/src/main/java/com/smartcar/sdk/data/ResponsePagingCursor.java
new file mode 100644
index 00000000..f91cca9b
--- /dev/null
+++ b/src/main/java/com/smartcar/sdk/data/ResponsePagingCursor.java
@@ -0,0 +1,21 @@
+package com.smartcar.sdk.data;
+
+/** POJO for the response paging w/cursor object */
+public class ResponsePagingCursor extends ApiData {
+ private String cursor;
+
+ /**
+ * Returns the response cursor
+ *
+ * @return response cursor
+ */
+ public String getCursor() {
+ return this.cursor;
+ }
+
+ /** @return a stringified representation of ResponsePagingCursor */
+ @Override
+ public String toString() {
+ return this.getClass().getName() + "{" + "cursor=" + cursor + '}';
+ }
+}
diff --git a/src/test/resources/DeleteConnections.json b/src/test/resources/DeleteConnections.json
new file mode 100644
index 00000000..22685059
--- /dev/null
+++ b/src/test/resources/DeleteConnections.json
@@ -0,0 +1,8 @@
+{
+ "connections": [
+ {
+ "vehicleId": "bcf1b859-4bd1-41c5-80bc-b8abfe3623fc",
+ "userId": "d2f64075-d0e1-45d4-aee0-72bd39088947"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/test/resources/GetConnections.json b/src/test/resources/GetConnections.json
new file mode 100644
index 00000000..3127ff44
--- /dev/null
+++ b/src/test/resources/GetConnections.json
@@ -0,0 +1,13 @@
+{
+ "connections": [
+ {
+ "connectedAt": "2023-08-10T15:00:00.000Z",
+ "vehicleId": "bcf1b859-4bd1-41c5-80bc-b8abfe3623fc",
+ "userId": "d2f64075-d0e1-45d4-aee0-72bd39088947"
+ }
+ ],
+ "paging": {
+ "cursor": "AES-256-CBC::base64::utf8::zLBjfq8xCCTKr3grfBFNXg==Oh0pX00AHjxsloC6vHMyKw==",
+ "count": 1
+ }
+}
\ No newline at end of file