package org.bremersee.security.authentication;

import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Optional;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Positive;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.cache.Cache;
import org.springframework.cache.concurrent.ConcurrentMapCache;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.util.Assert;

/**
 * The access token cache implementation.
 */
@Slf4j
public class AccessTokenCacheImpl implements AccessTokenCache, DisposableBean {

  private ConcurrentMapCache internalCache;

  private Timer internalCacheTimer;

  private boolean internalCacheTimerCanceled = false;

  private final JwtDecoder jwtDecoder;

  private final Cache cache;

  @Setter
  @Positive
  private long expirationToleranceAmount = 30L;

  @Setter
  @NotNull
  private ChronoUnit expirationToleranceUnit = ChronoUnit.SECONDS;

  /**
   * Instantiates a new access token cache.
   *
   * @param jwtDecoder the jwt decoder
   */
  public AccessTokenCacheImpl(JwtDecoder jwtDecoder) {
    this(jwtDecoder, null);
  }

  /**
   * Instantiates a new access token cache.
   *
   * @param jwtDecoder the jwt decoder
   * @param cache the external cache
   */
  public AccessTokenCacheImpl(JwtDecoder jwtDecoder, Cache cache) {
    Assert.notNull(jwtDecoder, "Jwt decoder must not be present.");
    this.jwtDecoder = jwtDecoder;
    if (cache != null) {
      log.info("Creating a jwt cache with given cache.");
      this.cache = cache;
    } else {
      log.info("Creating a jwt cache with internal in memory cache.");
      this.cache = getInternalCache();
    }
  }

  private ConcurrentMapCache getInternalCache() {
    synchronized (CACHE_NAME) {
      if (internalCache == null) {
        final long period = 1000L * 60L * 30L;
        internalCache = new ConcurrentMapCache(CACHE_NAME);
        internalCacheTimer = new Timer();
        internalCacheTimer.schedule(new TimerTask() {
          @Override
          public void run() {
            Set<Object> keys = internalCache.getNativeCache().keySet();
            log.trace("Removing obsolete jwt entries from internal cache (sze = {}).", keys.size());
            keys.forEach(key -> findJwt(String.valueOf(key))
                .ifPresent(jwt -> {
                  if (jwt.getExpiresAt() == null || jwt.getExpiresAt().isBefore(Instant.now())) {
                    internalCache.evict(key);
                  }
                }));
          }
        }, period, period);
      }
      return internalCache;
    }
  }

  @Override
  public Optional<String> findAccessToken(String key) {
    return findJwt(key).map(Jwt::getTokenValue);
  }

  @Override
  public Optional<Jwt> findJwt(String key) {
    return Optional.ofNullable(cache.get(key, String.class))
        .map(jwtDecoder::decode)
        .filter(jwt -> jwt.getExpiresAt() != null && jwt.getExpiresAt()
            .isAfter(Instant.now().plus(expirationToleranceAmount, expirationToleranceUnit)));
  }

  @Override
  public void putAccessToken(String key, String accessToken) {
    putJwt(key, jwtDecoder.decode(accessToken));
  }

  @Override
  public void putJwt(String key, Jwt jwt) {
    if (jwt.getExpiresAt() != null) {
      cache.put(key, jwt.getTokenValue());
    }
  }

  @Override
  public void destroy() {
    synchronized (CACHE_NAME) {
      if (!internalCacheTimerCanceled && internalCacheTimer != null) {
        internalCacheTimerCanceled = true;
        internalCacheTimer.cancel();
      }
    }
  }

}

@@ -808,145 +808,8 @@
  %7B%0A%0A
-  private ConcurrentMapCache internalCache;%0A%0A  private Timer internalCacheTimer;%0A%0A  private boolean internalCacheTimerCanceled = false;%0A%0A
   pr
@@ -874,16 +874,53 @@
 cache;%0A%0A
+  private Timer internalCacheTimer;%0A%0A
   @Sette
@@ -1517,12 +1517,8 @@
 ust 
-not 
 be p
@@ -1774,19 +1774,22 @@
 cache = 
-get
+create
 Internal
@@ -1837,19 +1837,22 @@
 apCache 
-get
+create
 Internal
@@ -1865,79 +1865,8 @@
 ) %7B%0A
-    synchronized (CACHE_NAME) %7B%0A      if (internalCache == null) %7B%0A    
     
@@ -1904,27 +1904,42 @@
  * 30L;%0A    
-   
+ConcurrentMapCache
  internalCac
@@ -1979,20 +1979,16 @@
 _NAME);%0A
-    
     inte
@@ -2017,28 +2017,24 @@
 imer();%0A    
-   
- 
 internalCach
@@ -2073,20 +2073,16 @@
 %7B%0A      
-    
 @Overrid
@@ -2083,20 +2083,16 @@
 verride%0A
-    
       pu
@@ -2117,20 +2117,16 @@
         
-  
-  
 Set%3CObje
@@ -2177,20 +2177,16 @@
 ySet();%0A
-    
         
@@ -2282,20 +2282,16 @@
         
-    
 keys.for
@@ -2343,20 +2343,16 @@
         
- 
-   
 .ifPrese
@@ -2363,20 +2363,16 @@
 wt -%3E %7B%0A
-    
         
@@ -2469,28 +2469,24 @@
             
-    
 internalCach
@@ -2509,34 +2509,26 @@
             
-    %7D%0A    
+%7D%0A
             
@@ -2534,34 +2534,26 @@
  %7D));%0A      
-   
- 
 %7D%0A
-    
     %7D, perio
@@ -2564,26 +2564,16 @@
 eriod);%0A
-      %7D%0A  
     retu
@@ -2590,22 +2590,16 @@
 lCache;%0A
-    %7D%0A
   %7D%0A%0A  @
@@ -3363,152 +3363,42 @@
     
-synchronized (CACHE_NAME) %7B%0A      if (!internalCacheTimerCanceled && internalCacheTimer != null) %7B%0A        internalCacheTimerCanceled = true;%0A  
+if (internalCacheTimer != null) %7B%0A
     
@@ -3428,24 +3428,16 @@
 ncel();%0A
-      %7D%0A
     %7D%0A  

Version 2.1.3-SNAPSHOT