Skip to content

Commit 0ce9d78

Browse files
committed
Use Duration to track token expiration
Easier to work with than a Date. (cherry picked from commit 6277348)
1 parent 116f570 commit 0ce9d78

10 files changed

+95
-96
lines changed

src/main/java/com/rabbitmq/client/impl/AMQConnection.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) 2007-Present Pivotal Software, Inc. All rights reserved.
1+
// Copyright (c) 2007-2019 Pivotal Software, Inc. All rights reserved.
22
//
33
// This software, the RabbitMQ Java client library, is triple-licensed under the
44
// Mozilla Public License 1.1 ("MPL"), the GNU General Public License version 2
@@ -341,8 +341,8 @@ public void start()
341341
String username = credentialsProvider.getUsername();
342342
String password = credentialsProvider.getPassword();
343343

344-
if (credentialsProvider.getExpiration() != null) {
345-
if (this.credentialsRefreshService.needRefresh(credentialsProvider.getExpiration())) {
344+
if (credentialsProvider.getTimeBeforeExpiration() != null) {
345+
if (this.credentialsRefreshService.needRefresh(credentialsProvider.getTimeBeforeExpiration())) {
346346
credentialsProvider.refresh();
347347
username = credentialsProvider.getUsername();
348348
password = credentialsProvider.getPassword();
@@ -426,7 +426,7 @@ public void start()
426426
throw AMQChannel.wrap(sse);
427427
}
428428

429-
if (this.credentialsProvider.getExpiration() != null) {
429+
if (this.credentialsProvider.getTimeBeforeExpiration() != null) {
430430
String registrationId = this.credentialsRefreshService.register(credentialsProvider, () -> {
431431
// return false if connection is closed, so refresh service can get rid of this registration
432432
if (!isOpen()) {

src/main/java/com/rabbitmq/client/impl/CredentialsProvider.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
package com.rabbitmq.client.impl;
1717

18-
import java.util.Date;
18+
import java.time.Duration;
1919

2020
/**
2121
* Provider interface for establishing credentials for connecting to the broker. Especially useful
@@ -42,16 +42,15 @@ public interface CredentialsProvider {
4242
String getPassword();
4343

4444
/**
45-
* The expiration date of the credentials, if any.
45+
* The time before the credentials expire, if they do expire.
4646
* <p>
4747
* If credentials do not expire, must return null. Default
4848
* behavior is to return null, assuming credentials never
4949
* expire.
5050
*
51-
* @return credentials expiration date
51+
* @return time before expiration
5252
*/
53-
default Date getExpiration() {
54-
// no expiration by default
53+
default Duration getTimeBeforeExpiration() {
5554
return null;
5655
}
5756

src/main/java/com/rabbitmq/client/impl/CredentialsRefreshService.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
package com.rabbitmq.client.impl;
1717

18-
import java.util.Date;
18+
import java.time.Duration;
1919
import java.util.concurrent.Callable;
2020

2121
/**
@@ -65,9 +65,9 @@ public interface CredentialsRefreshService {
6565
/**
6666
* Provide a hint about whether credentials should be renewed.
6767
*
68-
* @param expiration
68+
* @param timeBeforeExpiration
6969
* @return true if credentials should be renewed, false otherwise
7070
*/
71-
boolean needRefresh(Date expiration);
71+
boolean needRefresh(Duration timeBeforeExpiration);
7272

7373
}

src/main/java/com/rabbitmq/client/impl/DefaultCredentialsRefreshService.java

Lines changed: 24 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import org.slf4j.LoggerFactory;
2020

2121
import java.time.Duration;
22-
import java.util.Date;
2322
import java.util.Iterator;
2423
import java.util.Map;
2524
import java.util.UUID;
@@ -35,7 +34,7 @@
3534
* <p>
3635
* This implementation keeps track of entities (typically AMQP connections) that need
3736
* to renew credentials. Token renewal is scheduled based on token expiration, using
38-
* a <code>Function<Date, Long> refreshDelayStrategy</code>. Once credentials
37+
* a <code>Function<Duration, Long> refreshDelayStrategy</code>. Once credentials
3938
* for a {@link CredentialsProvider} have been renewed, the callback registered
4039
* by each entity/connection is performed. This callback typically propagates
4140
* the new credentials in the entity state, e.g. sending the new password to the
@@ -51,11 +50,11 @@ public class DefaultCredentialsRefreshService implements CredentialsRefreshServi
5150

5251
private final boolean privateScheduler;
5352

54-
private final Function<Date, Long> refreshDelayStrategy;
53+
private final Function<Duration, Long> refreshDelayStrategy;
5554

56-
private final Function<Date, Boolean> needRefreshStrategy;
55+
private final Function<Duration, Boolean> needRefreshStrategy;
5756

58-
public DefaultCredentialsRefreshService(ScheduledExecutorService scheduler, Function<Date, Long> refreshDelayStrategy, Function<Date, Boolean> needRefreshStrategy) {
57+
public DefaultCredentialsRefreshService(ScheduledExecutorService scheduler, Function<Duration, Long> refreshDelayStrategy, Function<Duration, Boolean> needRefreshStrategy) {
5958
this.refreshDelayStrategy = refreshDelayStrategy;
6059
this.needRefreshStrategy = needRefreshStrategy;
6160
if (scheduler == null) {
@@ -76,7 +75,7 @@ public DefaultCredentialsRefreshService(ScheduledExecutorService scheduler, Func
7675
* @param duration
7776
* @return
7877
*/
79-
public static Function<Date, Long> fixedDelayBeforeExpirationRefreshDelayStrategy(Duration duration) {
78+
public static Function<Duration, Long> fixedDelayBeforeExpirationRefreshDelayStrategy(Duration duration) {
8079
return new FixedDelayBeforeExpirationRefreshDelayStrategy(duration.toMillis());
8180
}
8281

@@ -86,20 +85,21 @@ public static Function<Date, Long> fixedDelayBeforeExpirationRefreshDelayStrateg
8685
* @param limitBeforeExpiration
8786
* @return
8887
*/
89-
public static Function<Date, Boolean> fixedTimeNeedRefreshStrategy(Duration limitBeforeExpiration) {
88+
public static Function<Duration, Boolean> fixedTimeNeedRefreshStrategy(Duration limitBeforeExpiration) {
9089
return new FixedTimeNeedRefreshStrategy(limitBeforeExpiration.toMillis());
9190
}
9291

9392
// TODO add a delay refresh strategy that bases the time on a percentage of the TTL, use it as default with 80% TTL
9493

9594
private static Runnable refresh(ScheduledExecutorService scheduler, CredentialsProviderState credentialsProviderState,
96-
Function<Date, Long> refreshDelayStrategy) {
95+
Function<Duration, Long> refreshDelayStrategy) {
9796
return () -> {
9897
LOGGER.debug("Refreshing token");
9998
credentialsProviderState.refresh();
10099

101-
Date expirationAfterRefresh = credentialsProviderState.credentialsProvider.getExpiration();
102-
long newDelay = refreshDelayStrategy.apply(expirationAfterRefresh);
100+
Duration timeBeforeExpiration = credentialsProviderState.credentialsProvider.getTimeBeforeExpiration();
101+
102+
long newDelay = refreshDelayStrategy.apply(timeBeforeExpiration);
103103

104104
LOGGER.debug("Scheduling refresh in {} milliseconds", newDelay);
105105

@@ -122,8 +122,7 @@ public String register(CredentialsProvider credentialsProvider, Callable<Boolean
122122
credentialsProviderState.add(registration);
123123

124124
credentialsProviderState.maybeSetRefreshTask(() -> {
125-
Date expiration = credentialsProvider.getExpiration();
126-
long delay = refreshDelayStrategy.apply(expiration);
125+
long delay = refreshDelayStrategy.apply(credentialsProvider.getTimeBeforeExpiration());
127126
LOGGER.debug("Scheduling refresh in {} milliseconds", delay);
128127
return scheduler.schedule(refresh(scheduler, credentialsProviderState, refreshDelayStrategy), delay, TimeUnit.MILLISECONDS);
129128
});
@@ -140,8 +139,8 @@ public void unregister(CredentialsProvider credentialsProvider, String registrat
140139
}
141140

142141
@Override
143-
public boolean needRefresh(Date expiration) {
144-
return this.needRefreshStrategy.apply(expiration);
142+
public boolean needRefresh(Duration timeBeforeExpiration) {
143+
return this.needRefreshStrategy.apply(timeBeforeExpiration);
145144
}
146145

147146
public void close() {
@@ -150,7 +149,7 @@ public void close() {
150149
}
151150
}
152151

153-
private static class FixedTimeNeedRefreshStrategy implements Function<Date, Boolean> {
152+
private static class FixedTimeNeedRefreshStrategy implements Function<Duration, Boolean> {
154153

155154
private final long limitBeforeExpiration;
156155

@@ -159,13 +158,12 @@ private FixedTimeNeedRefreshStrategy(long limitBeforeExpiration) {
159158
}
160159

161160
@Override
162-
public Boolean apply(Date expiration) {
163-
long ttl = expiration.getTime() - new Date().getTime();
164-
return ttl <= limitBeforeExpiration;
161+
public Boolean apply(Duration timeBeforeExpiration) {
162+
return timeBeforeExpiration.toMillis() <= limitBeforeExpiration;
165163
}
166164
}
167165

168-
private static class FixedDelayBeforeExpirationRefreshDelayStrategy implements Function<Date, Long> {
166+
private static class FixedDelayBeforeExpirationRefreshDelayStrategy implements Function<Duration, Long> {
169167

170168
private final long delay;
171169

@@ -174,11 +172,10 @@ private FixedDelayBeforeExpirationRefreshDelayStrategy(long delay) {
174172
}
175173

176174
@Override
177-
public Long apply(Date expiration) {
178-
long ttl = expiration.getTime() - new Date().getTime();
179-
long refreshTimeBeforeExpiration = ttl - delay;
175+
public Long apply(Duration timeBeforeExpiration) {
176+
long refreshTimeBeforeExpiration = timeBeforeExpiration.toMillis() - delay;
180177
if (refreshTimeBeforeExpiration < 0) {
181-
return ttl;
178+
return timeBeforeExpiration.toMillis();
182179
} else {
183180
return refreshTimeBeforeExpiration;
184181
}
@@ -279,21 +276,21 @@ public static class DefaultCredentialsRefreshServiceBuilder {
279276

280277
private ScheduledExecutorService scheduler;
281278

282-
private Function<Date, Long> refreshDelayStrategy = fixedDelayBeforeExpirationRefreshDelayStrategy(Duration.ofSeconds(60));
279+
private Function<Duration, Long> refreshDelayStrategy = fixedDelayBeforeExpirationRefreshDelayStrategy(Duration.ofSeconds(60));
283280

284-
private Function<Date, Boolean> needRefreshStrategy = fixedTimeNeedRefreshStrategy(Duration.ofSeconds(60));
281+
private Function<Duration, Boolean> needRefreshStrategy = fixedTimeNeedRefreshStrategy(Duration.ofSeconds(60));
285282

286283
public DefaultCredentialsRefreshServiceBuilder scheduler(ScheduledThreadPoolExecutor scheduler) {
287284
this.scheduler = scheduler;
288285
return this;
289286
}
290287

291-
public DefaultCredentialsRefreshServiceBuilder refreshDelayStrategy(Function<Date, Long> refreshDelayStrategy) {
288+
public DefaultCredentialsRefreshServiceBuilder refreshDelayStrategy(Function<Duration, Long> refreshDelayStrategy) {
292289
this.refreshDelayStrategy = refreshDelayStrategy;
293290
return this;
294291
}
295292

296-
public DefaultCredentialsRefreshServiceBuilder needRefreshStrategy(Function<Date, Boolean> needRefreshStrategy) {
293+
public DefaultCredentialsRefreshServiceBuilder needRefreshStrategy(Function<Duration, Boolean> needRefreshStrategy) {
297294
this.needRefreshStrategy = needRefreshStrategy;
298295
return this;
299296
}

src/main/java/com/rabbitmq/client/impl/OAuth2ClientCredentialsGrantCredentialsProvider.java

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@
2525
import java.net.URL;
2626
import java.net.URLEncoder;
2727
import java.nio.charset.StandardCharsets;
28+
import java.time.Duration;
29+
import java.time.Instant;
30+
import java.time.temporal.ChronoUnit;
2831
import java.util.*;
2932

3033
/**
@@ -107,9 +110,8 @@ protected Token parseToken(String response) {
107110
try {
108111
Map<?, ?> map = objectMapper.readValue(response, Map.class);
109112
int expiresIn = ((Number) map.get("expires_in")).intValue();
110-
Calendar calendar = Calendar.getInstance();
111-
calendar.add(Calendar.SECOND, expiresIn);
112-
return new Token(map.get("access_token").toString(), calendar.getTime());
113+
Instant receivedAt = Instant.now();
114+
return new Token(map.get("access_token").toString(), expiresIn, receivedAt);
113115
} catch (IOException e) {
114116
throw new OAuthTokenManagementException("Error while parsing OAuth 2 token", e);
115117
}
@@ -177,8 +179,8 @@ protected String passwordFromToken(Token token) {
177179
}
178180

179181
@Override
180-
protected Date expirationFromToken(Token token) {
181-
return token.getExpiration();
182+
protected Duration timeBeforeExpiration(Token token) {
183+
return token.getTimeBeforeExpiration();
182184
}
183185

184186
protected void configureHttpConnection(HttpURLConnection connection) {
@@ -212,20 +214,33 @@ public static class Token {
212214

213215
private final String access;
214216

215-
private final Date expiration;
217+
private final int expiresIn;
216218

217-
public Token(String access, Date expiration) {
218-
this.access = access;
219-
this.expiration = expiration;
220-
}
219+
private final Instant receivedAt;
221220

222-
public Date getExpiration() {
223-
return expiration;
221+
public Token(String access, int expiresIn, Instant receivedAt) {
222+
this.access = access;
223+
this.expiresIn = expiresIn;
224+
this.receivedAt = receivedAt;
224225
}
225226

226227
public String getAccess() {
227228
return access;
228229
}
230+
231+
public int getExpiresIn() {
232+
return expiresIn;
233+
}
234+
235+
public Instant getReceivedAt() {
236+
return receivedAt;
237+
}
238+
239+
public Duration getTimeBeforeExpiration() {
240+
Instant now = Instant.now();
241+
long age = receivedAt.until(now, ChronoUnit.SECONDS);
242+
return Duration.ofSeconds(expiresIn - age);
243+
}
229244
}
230245

231246
public static class OAuth2ClientCredentialsGrantCredentialsProviderBuilder {

src/main/java/com/rabbitmq/client/impl/RefreshProtectedCredentialsProvider.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
import org.slf4j.Logger;
1919
import org.slf4j.LoggerFactory;
2020

21-
import java.util.Date;
21+
import java.time.Duration;
2222
import java.util.concurrent.CountDownLatch;
2323
import java.util.concurrent.atomic.AtomicBoolean;
2424
import java.util.concurrent.atomic.AtomicReference;
@@ -35,8 +35,8 @@
3535
* is already being renewed.
3636
* <p>
3737
* Subclasses need to provide the actual token retrieval (whether is a first retrieval or a renewal is
38-
* a implementation detail) and how to extract information (username, password, expiration date) from the retrieved
39-
* token.
38+
* a implementation detail) and how to extract information (username, password, time before expiration)
39+
* from the retrieved token.
4040
*
4141
* @param <T> the type of token (usually specified by the subclass)
4242
*/
@@ -67,11 +67,11 @@ public String getPassword() {
6767
}
6868

6969
@Override
70-
public Date getExpiration() {
70+
public Duration getTimeBeforeExpiration() {
7171
if (token.get() == null) {
7272
refresh();
7373
}
74-
return expirationFromToken(token.get());
74+
return timeBeforeExpiration(token.get());
7575
}
7676

7777
@Override
@@ -109,5 +109,5 @@ public void refresh() {
109109

110110
protected abstract String passwordFromToken(T token);
111111

112-
protected abstract Date expirationFromToken(T token);
112+
protected abstract Duration timeBeforeExpiration(T token);
113113
}

src/test/java/com/rabbitmq/client/impl/DefaultCredentialsRefreshServiceTest.java

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,6 @@
2323
import org.mockito.stubbing.Answer;
2424

2525
import java.time.Duration;
26-
import java.util.Calendar;
27-
import java.util.Date;
2826
import java.util.List;
2927
import java.util.concurrent.Callable;
3028
import java.util.concurrent.CopyOnWriteArrayList;
@@ -63,11 +61,7 @@ public void scheduling() throws Exception {
6361
AtomicInteger passwordSequence = new AtomicInteger(0);
6462
when(credentialsProvider.getPassword()).thenAnswer(
6563
(Answer<String>) invocation -> "password-" + passwordSequence.get());
66-
when(credentialsProvider.getExpiration()).thenAnswer((Answer<Date>) invocation -> {
67-
Calendar calendar = Calendar.getInstance();
68-
calendar.add(Calendar.SECOND, 5);
69-
return calendar.getTime();
70-
});
64+
when(credentialsProvider.getTimeBeforeExpiration()).thenAnswer((Answer<Duration>) invocation -> Duration.ofSeconds(5));
7165
doAnswer(invocation -> {
7266
passwordSequence.incrementAndGet();
7367
return null;
@@ -88,11 +82,7 @@ public void scheduling() throws Exception {
8882
AtomicInteger passwordSequence2 = new AtomicInteger(0);
8983
CredentialsProvider credentialsProvider2 = mock(CredentialsProvider.class);
9084
when(credentialsProvider2.getPassword()).thenAnswer((Answer<String>) invocation -> "password2-" + passwordSequence2.get());
91-
when(credentialsProvider2.getExpiration()).thenAnswer((Answer<Date>) invocation -> {
92-
Calendar calendar = Calendar.getInstance();
93-
calendar.add(Calendar.SECOND, 4);
94-
return calendar.getTime();
95-
});
85+
when(credentialsProvider2.getTimeBeforeExpiration()).thenAnswer((Answer<Duration>) invocation -> Duration.ofSeconds(4));
9686
doAnswer(invocation -> {
9787
passwordSequence2.incrementAndGet();
9888
return null;

0 commit comments

Comments
 (0)