1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.forgerock.http.oauth2.resolver;
18
19 import static org.forgerock.util.promise.Promises.newExceptionPromise;
20 import static org.forgerock.util.time.Duration.duration;
21
22 import java.util.concurrent.Callable;
23 import java.util.concurrent.ExecutionException;
24 import java.util.concurrent.TimeUnit;
25
26 import org.forgerock.http.oauth2.AccessTokenException;
27 import org.forgerock.http.oauth2.AccessTokenInfo;
28 import org.forgerock.http.oauth2.AccessTokenResolver;
29 import org.forgerock.services.context.Context;
30 import org.forgerock.util.AsyncFunction;
31 import org.forgerock.util.Function;
32 import org.forgerock.util.PerItemEvictionStrategyCache;
33 import org.forgerock.util.promise.Promise;
34 import org.forgerock.util.time.Duration;
35 import org.forgerock.util.time.TimeService;
36
37
38
39
40
41 public class CachingAccessTokenResolver implements AccessTokenResolver {
42
43 private final AccessTokenResolver resolver;
44 private final PerItemEvictionStrategyCache<String, Promise<AccessTokenInfo, AccessTokenException>> cache;
45 private final AsyncFunction<Promise<AccessTokenInfo, AccessTokenException>, Duration, Exception> expires;
46
47
48
49
50
51
52
53
54
55
56
57
58 public CachingAccessTokenResolver(final TimeService time,
59 final AccessTokenResolver resolver,
60 final PerItemEvictionStrategyCache
61 <String, Promise<AccessTokenInfo, AccessTokenException>> cache) {
62 this.resolver = resolver;
63 this.cache = cache;
64 this.expires = new AccessTokenExpirationFunction(time);
65 }
66
67 @Override
68 public Promise<AccessTokenInfo, AccessTokenException> resolve(final Context context, final String token) {
69 try {
70 return cache.getValue(token, resolveToken(context, token), expires);
71 } catch (InterruptedException e) {
72 return newExceptionPromise(
73 new AccessTokenException("Timed out retrieving OAuth2 access token information", e));
74 } catch (ExecutionException e) {
75 return newExceptionPromise(
76 new AccessTokenException("Initial token resolution has failed", e));
77 }
78 }
79
80 private Callable<Promise<AccessTokenInfo, AccessTokenException>> resolveToken(final Context context,
81 final String token) {
82 return new Callable<Promise<AccessTokenInfo, AccessTokenException>>() {
83 @Override
84 public Promise<AccessTokenInfo, AccessTokenException> call() throws Exception {
85 return resolver.resolve(context, token);
86 }
87 };
88 }
89
90
91 private static class AccessTokenExpirationFunction
92 implements AsyncFunction<Promise<AccessTokenInfo, AccessTokenException>, Duration, Exception> {
93
94
95 private static final Function<AccessTokenException, Duration, AccessTokenException> TIMEOUT_ZERO =
96 new Function<AccessTokenException, Duration, AccessTokenException>() {
97 @Override
98 public Duration apply(AccessTokenException e) {
99
100 return Duration.ZERO;
101 }
102 };
103
104
105 private final Function<AccessTokenInfo, Duration, AccessTokenException> computeTtl;
106
107 public AccessTokenExpirationFunction(final TimeService time) {
108
109 this.computeTtl = new Function<AccessTokenInfo, Duration, AccessTokenException>() {
110 @Override
111 public Duration apply(AccessTokenInfo accessToken) {
112 if (accessToken.getExpiresAt() == AccessTokenInfo.NEVER_EXPIRES) {
113 return Duration.UNLIMITED;
114 }
115 long expires = accessToken.getExpiresAt() - time.now();
116 if (expires <= 0) {
117
118 return Duration.ZERO;
119 }
120
121 return duration(expires, TimeUnit.MILLISECONDS);
122 }
123 };
124
125 }
126
127 @Override
128 public Promise<? extends Duration, ? extends Exception> apply(
129 Promise<AccessTokenInfo, AccessTokenException> accessTokenPromise) throws Exception {
130 return accessTokenPromise.then(computeTtl, TIMEOUT_ZERO);
131 }
132
133 }
134
135 }