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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 42 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@ And then add the artifact `incognia-api-client` **or** `incognia-api-client-shad
<dependency>
<groupId>com.incognia</groupId>
<artifactId>incognia-api-client</artifactId>
<version>3.16.0</version>
<version>3.17.0</version>
</dependency>
```
```xml
<dependency>
<groupId>com.incognia</groupId>
<artifactId>incognia-api-client-shaded</artifactId>
<version>3.16.0</version>
<version>3.17.0</version>
</dependency>
```

Expand All @@ -47,13 +47,13 @@ repositories {
And then add the dependency
```gradle
dependencies {
implementation 'com.incognia:incognia-api-client:3.16.0'
implementation 'com.incognia:incognia-api-client:3.17.0'
}
```
OR
```gradle
dependencies {
implementation 'com.incognia:incognia-api-client-shaded:3.16.0'
implementation 'com.incognia:incognia-api-client-shaded:3.17.0'
}
```

Expand Down Expand Up @@ -137,9 +137,45 @@ The implementation is based on the [Incognia API Reference](https://dash.incogni

#### Authentication

Authentication is done transparently, so you don't need to worry about it.
Authentication is handled automatically by default, including refreshing expired tokens during API calls.

If you are curious about how we handle it, you can check the `TokenAwareNetworkingClient` class
For latency-sensitive services, you can take control of when refresh happens by creating a `ManualRefreshTokenProvider` and passing it through `CustomOptions`. The library does not create background threads for token refresh, so your application stays in full control of scheduling.

```java
CustomOptions sharedOptions =
CustomOptions.builder()
.timeoutMillis(5_000L)
.maxConnections(10)
.keepAliveSeconds(60L)
.build();

ManualRefreshTokenProvider tokenProvider =
new ManualRefreshTokenProvider("client-id", "client-secret", sharedOptions);

IncogniaAPI api =
IncogniaAPI.init(
"client-id",
"client-secret",
sharedOptions.toBuilder()
.tokenProvider(tokenProvider)
.build());

ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
executor.scheduleAtFixedRate(
() -> {
try {
Token token = tokenProvider.refresh();
System.out.println("token refreshed until " + token.getExpiresAt());
} catch (IncogniaException e) {
System.out.println("could not refresh token");
}
Comment thread
figueredo marked this conversation as resolved.
},
0,
1,
TimeUnit.MINUTES);
```

If you are curious about how we handle it, you can check the `TokenAwareNetworkingClient` class.

#### Registering Signup

Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ plugins {
}

group = "com.incognia"
version = "3.16.0"
version = "3.17.0"

task createProjectVersionFile {
def projectVersionDir = "$projectDir/src/main/java/com/incognia/api"
Expand Down
31 changes: 18 additions & 13 deletions src/main/java/com/incognia/api/IncogniaAPI.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package com.incognia.api;

import com.incognia.api.clients.AutoRefreshTokenProvider;
import com.incognia.api.clients.NetworkingClient;
import com.incognia.api.clients.TokenAwareNetworkingClient;
import com.incognia.api.clients.TokenProvider;
import com.incognia.common.Address;
import com.incognia.common.exceptions.IncogniaAPIException;
import com.incognia.common.exceptions.IncogniaException;
Expand Down Expand Up @@ -62,20 +65,22 @@ public class IncogniaAPI {
IncogniaAPI(String clientId, String clientSecret, CustomOptions options, String apiUrl) {
Asserts.assertNotEmpty(clientId, "client id");
Asserts.assertNotEmpty(clientSecret, "client secret");
Asserts.assertNotNull(options, "custom options");
Asserts.assertNotEmpty(apiUrl, "api url");
tokenAwareNetworkingClient =
new TokenAwareNetworkingClient(
new OkHttpClient.Builder()
.callTimeout(options.getTimeoutMillis(), TimeUnit.MILLISECONDS)
.connectionPool(
new ConnectionPool(
options.getMaxConnections(),
options.getKeepAliveSeconds(),
TimeUnit.SECONDS))
.build(),
apiUrl,
clientId,
clientSecret);
OkHttpClient httpClient =
new OkHttpClient.Builder()
.callTimeout(options.getTimeoutMillis(), TimeUnit.MILLISECONDS)
.connectionPool(
new ConnectionPool(
options.getMaxConnections(), options.getKeepAliveSeconds(), TimeUnit.SECONDS))
.build();
TokenProvider tokenProvider = options.getTokenProvider();
if (tokenProvider == null) {
tokenProvider =
new AutoRefreshTokenProvider(
clientId, clientSecret, new NetworkingClient(httpClient, apiUrl));
}
tokenAwareNetworkingClient = new TokenAwareNetworkingClient(httpClient, apiUrl, tokenProvider);
}

/**
Expand Down
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Basically the previous TokenProvider with a new name

Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.incognia.api.clients;

import com.incognia.common.Token;
import com.incognia.common.exceptions.IncogniaException;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.concurrent.locks.ReentrantLock;

public class AutoRefreshTokenProvider implements TokenProvider {
private static final int TOKEN_REFRESH_BEFORE_SECONDS = 10;

private final ReentrantLock lock = new ReentrantLock();
private final TokenRequester tokenRequester;
private volatile Token token;

public AutoRefreshTokenProvider(
String clientId, String clientSecret, NetworkingClient networkingClient) {
this.tokenRequester = new TokenRequester(clientId, clientSecret, networkingClient);
}

@Override
public Token getToken() throws IncogniaException {
refreshTokenIfNeeded();
return token;
}

private boolean needsRefresh(Token token) {
return token == null
|| Instant.now().until(token.getExpiresAt(), ChronoUnit.SECONDS)
<= TOKEN_REFRESH_BEFORE_SECONDS;
}

private void refreshTokenIfNeeded() throws IncogniaException {
Token currentToken = token;
if (needsRefresh(currentToken)) {
lock.lock();
try {
if (needsRefresh(token)) {
token = tokenRequester.requestToken();
}
} finally {
lock.unlock();
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package com.incognia.api.clients;

import com.incognia.common.Token;
import com.incognia.common.exceptions.IncogniaException;
import com.incognia.common.exceptions.TokenExpiredException;
import com.incognia.common.exceptions.TokenNotFoundException;
import com.incognia.common.utils.Asserts;
import com.incognia.common.utils.CustomOptions;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import okhttp3.ConnectionPool;
import okhttp3.OkHttpClient;

public class ManualRefreshTokenProvider implements TokenProvider {
private static final String API_URL = "https://api.incognia.com";

private final ReentrantLock lock = new ReentrantLock();
private final TokenRequester tokenRequester;
private volatile Token token;

public ManualRefreshTokenProvider(String clientId, String clientSecret) {
this(clientId, clientSecret, CustomOptions.builder().build());
}

public ManualRefreshTokenProvider(String clientId, String clientSecret, CustomOptions options) {
this(clientId, clientSecret, createNetworkingClient(options));
}

ManualRefreshTokenProvider(
String clientId, String clientSecret, NetworkingClient networkingClient) {
Asserts.assertNotEmpty(clientId, "client id");
Asserts.assertNotEmpty(clientSecret, "client secret");
Asserts.assertNotNull(networkingClient, "networking client");
this.tokenRequester = new TokenRequester(clientId, clientSecret, networkingClient);
}
Comment thread
figueredo marked this conversation as resolved.

@Override
public Token getToken() throws IncogniaException {
Token currentToken = token;
if (currentToken == null) {
throw new TokenNotFoundException();
}

if (currentToken.isExpired()) {
throw new TokenExpiredException();
}

return currentToken;
}

public Token refresh() throws IncogniaException {
lock.lock();
try {
token = tokenRequester.requestToken();
return token;
} finally {
lock.unlock();
}
}

private static NetworkingClient createNetworkingClient(CustomOptions options) {
Asserts.assertNotNull(options, "custom options");
OkHttpClient httpClient =
new OkHttpClient.Builder()
.callTimeout(options.getTimeoutMillis(), TimeUnit.MILLISECONDS)
.connectionPool(
new ConnectionPool(
options.getMaxConnections(), options.getKeepAliveSeconds(), TimeUnit.SECONDS))
.build();
return new NetworkingClient(httpClient, API_URL);
Comment thread
figueredo marked this conversation as resolved.
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.incognia.api.clients;

import com.incognia.common.Token;
import com.incognia.common.exceptions.IncogniaException;
import java.util.HashMap;
import java.util.Map;
Expand All @@ -26,44 +27,67 @@ public class TokenAwareNetworkingClient {

public TokenAwareNetworkingClient(
OkHttpClient httpClient, String baseUrl, String clientId, String clientSecret) {
this.networkingClient = new NetworkingClient(httpClient, baseUrl);
this.tokenProvider = new TokenProvider(clientId, clientSecret, networkingClient);
this(
httpClient,
baseUrl,
new AutoRefreshTokenProvider(
clientId, clientSecret, new NetworkingClient(httpClient, baseUrl)));
}

private Map<String, String> buildHeaders() throws IncogniaException {
Map<String, String> headers = new HashMap<>();
headers.put(USER_AGENT_HEADER, USER_AGENT_HEADER_CONTENT);
headers.put(AUTHORIZATION_HEADER, tokenProvider.buildAuthorizationHeader());
Long latency = lastLatency.get();
if (latency != null) {
headers.put(LATENCY_HEADER, Long.toString(latency));
}
return headers;
public TokenAwareNetworkingClient(
OkHttpClient httpClient, String baseUrl, TokenProvider tokenProvider) {
this.networkingClient = new NetworkingClient(httpClient, baseUrl);
this.tokenProvider = tokenProvider;
}

public <T, U> U doPost(
String path, T body, Class<U> responseType, Map<String, String> queryParameters)
throws IncogniaException {
tokenProvider.getToken();
Token token = tokenProvider.getToken();
long start = System.nanoTime();
U result = networkingClient.doPost(path, body, responseType, buildHeaders(), queryParameters);
U result =
networkingClient.doPost(path, body, responseType, buildHeaders(token), queryParameters);
lastLatency.set(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start));
return result;
}

public <T, U> U doPost(String path, T body, Class<U> responseType) throws IncogniaException {
tokenProvider.getToken();
Token token = tokenProvider.getToken();
long start = System.nanoTime();
U result = networkingClient.doPost(path, body, responseType, buildHeaders());
U result = networkingClient.doPost(path, body, responseType, buildHeaders(token));
lastLatency.set(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start));
return result;
}

public <T> void doPost(String path, T body, Map<String, String> queryParameters)
throws IncogniaException {
tokenProvider.getToken();
Token token = tokenProvider.getToken();
long start = System.nanoTime();
networkingClient.doPost(path, body, buildHeaders(), queryParameters);
networkingClient.doPost(path, body, buildHeaders(token), queryParameters);
lastLatency.set(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start));
}

private Map<String, String> buildHeaders(Token token) throws IncogniaException {
validateToken(token);
Map<String, String> headers = new HashMap<>();
headers.put(USER_AGENT_HEADER, USER_AGENT_HEADER_CONTENT);
headers.put(AUTHORIZATION_HEADER, token.getTokenType() + " " + token.getAccessToken());
Long latency = lastLatency.get();
if (latency != null) {
headers.put(LATENCY_HEADER, Long.toString(latency));
}
return headers;
}

private void validateToken(Token token) throws IncogniaException {
if (token == null) {
throw new IncogniaException("token provider returned a null token");
}
if (token.getTokenType() == null || token.getTokenType().isEmpty()) {
throw new IncogniaException("token provider returned a token without token type");
}
if (token.getAccessToken() == null || token.getAccessToken().isEmpty()) {
throw new IncogniaException("token provider returned a token without access token");
}
}
}
Loading
Loading