View Javadoc
1   /*
2    * The contents of this file are subject to the terms of the Common Development and
3    * Distribution License (the License). You may not use this file except in compliance with the
4    * License.
5    *
6    * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
7    * specific language governing permission and limitations under the License.
8    *
9    * When distributing Covered Software, include this CDDL Header Notice in each file and include
10   * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
11   * Header, with the fields enclosed by brackets [] replaced by your own identifying
12   * information: "Portions copyright [year] [name of copyright owner]".
13   *
14   * Copyright 2014-2016 ForgeRock AS.
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   * A {@link CachingAccessTokenResolver} is a delegating {@link AccessTokenResolver} that uses a write-through cache
39   * to enable fast {@link AccessTokenInfo} resolution.
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       * Builds a {@link CachingAccessTokenResolver} delegating to the given {@link AccessTokenResolver} using the given
49       * (pre-configured) cache.
50       *
51       * @param time
52       *         Time service used to compute the token cache time-to-live
53       * @param resolver
54       *         resolver to delegates to
55       * @param cache
56       *         access token cache
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      /** A function that will compute the access token's timeout. */
91      private static class AccessTokenExpirationFunction
92              implements AsyncFunction<Promise<AccessTokenInfo, AccessTokenException>, Duration, Exception> {
93  
94          // @Checkstyle:off
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                          // Do not cache the AccessToken if there was a problem while resolving it
100                         return Duration.ZERO;
101                     }
102                 };
103         // @Checkstyle:on
104 
105         private final Function<AccessTokenInfo, Duration, AccessTokenException> computeTtl;
106 
107         public AccessTokenExpirationFunction(final TimeService time) {
108             // @Checkstyle:off
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                         // The token is already expired
118                         return Duration.ZERO;
119                     }
120 
121                     return duration(expires, TimeUnit.MILLISECONDS);
122                 }
123             };
124             // @Checkstyle:on
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 }