// 
// Decompiled by Procyon v0.6.0
// 

package com.nimbusds.jose.jwk.source;

import java.util.Objects;
import java.util.concurrent.TimeUnit;
import com.nimbusds.jose.util.events.Event;
import com.nimbusds.jose.KeySourceException;
import com.nimbusds.jose.util.cache.CachedObject;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.util.events.EventListener;
import java.util.concurrent.locks.ReentrantLock;
import com.nimbusds.jose.shaded.jcip.ThreadSafe;
import com.nimbusds.jose.proc.SecurityContext;

@ThreadSafe
public class CachingJWKSetSource<C extends SecurityContext> extends AbstractCachingJWKSetSource<C>
{
    private final ReentrantLock lock;
    private final long cacheRefreshTimeout;
    private final EventListener<CachingJWKSetSource<C>, C> eventListener;
    
    public CachingJWKSetSource(final JWKSetSource<C> source, final long timeToLive, final long cacheRefreshTimeout, final EventListener<CachingJWKSetSource<C>, C> eventListener) {
        super(source, timeToLive);
        this.lock = new ReentrantLock();
        this.cacheRefreshTimeout = cacheRefreshTimeout;
        this.eventListener = eventListener;
    }
    
    @Override
    public JWKSet getJWKSet(final JWKSetCacheRefreshEvaluator refreshEvaluator, final long currentTime, final C context) throws KeySourceException {
        final CachedObject<JWKSet> cache = this.getCachedJWKSet();
        if (cache == null) {
            return this.loadJWKSetBlocking(JWKSetCacheRefreshEvaluator.noRefresh(), currentTime, context);
        }
        final JWKSet jwkSet = cache.get();
        if (refreshEvaluator.requiresRefresh(jwkSet)) {
            return this.loadJWKSetBlocking(refreshEvaluator, currentTime, context);
        }
        if (cache.isExpired(currentTime)) {
            return this.loadJWKSetBlocking(JWKSetCacheRefreshEvaluator.referenceComparison(jwkSet), currentTime, context);
        }
        return cache.get();
    }
    
    public long getCacheRefreshTimeout() {
        return this.cacheRefreshTimeout;
    }
    
    JWKSet loadJWKSetBlocking(final JWKSetCacheRefreshEvaluator refreshEvaluator, final long currentTime, final C context) throws KeySourceException {
        try {
            CachedObject<JWKSet> cache;
            if (this.lock.tryLock()) {
                try {
                    final CachedObject<JWKSet> cachedJWKSet = this.getCachedJWKSet();
                    if (cachedJWKSet == null || refreshEvaluator.requiresRefresh(cachedJWKSet.get())) {
                        if (this.eventListener != null) {
                            this.eventListener.notify((Event<CachingJWKSetSource<C>, C>)new RefreshInitiatedEvent(this, this.lock.getQueueLength(), (SecurityContext)context));
                        }
                        final CachedObject<JWKSet> result = this.loadJWKSetNotThreadSafe(refreshEvaluator, currentTime, context);
                        if (this.eventListener != null) {
                            this.eventListener.notify((Event<CachingJWKSetSource<C>, C>)new RefreshCompletedEvent(this, (JWKSet)result.get(), this.lock.getQueueLength(), (SecurityContext)context));
                        }
                        cache = result;
                    }
                    else {
                        cache = cachedJWKSet;
                    }
                }
                finally {
                    this.lock.unlock();
                }
            }
            else {
                if (this.eventListener != null) {
                    this.eventListener.notify((Event<CachingJWKSetSource<C>, C>)new WaitingForRefreshEvent(this, this.lock.getQueueLength(), (SecurityContext)context));
                }
                if (!this.lock.tryLock(this.getCacheRefreshTimeout(), TimeUnit.MILLISECONDS)) {
                    if (this.eventListener != null) {
                        this.eventListener.notify((Event<CachingJWKSetSource<C>, C>)new RefreshTimedOutEvent(this, this.lock.getQueueLength(), (SecurityContext)context));
                    }
                    throw new JWKSetUnavailableException("Timeout while waiting for cache refresh (" + this.cacheRefreshTimeout + "ms exceeded)");
                }
                try {
                    final CachedObject<JWKSet> cachedJWKSet = this.getCachedJWKSet();
                    if (cachedJWKSet == null || refreshEvaluator.requiresRefresh(cachedJWKSet.get())) {
                        if (this.eventListener != null) {
                            this.eventListener.notify((Event<CachingJWKSetSource<C>, C>)new RefreshInitiatedEvent(this, this.lock.getQueueLength(), (SecurityContext)context));
                        }
                        cache = this.loadJWKSetNotThreadSafe(refreshEvaluator, currentTime, context);
                        if (this.eventListener != null) {
                            this.eventListener.notify((Event<CachingJWKSetSource<C>, C>)new RefreshCompletedEvent(this, (JWKSet)cache.get(), this.lock.getQueueLength(), (SecurityContext)context));
                        }
                    }
                    else {
                        cache = cachedJWKSet;
                    }
                }
                finally {
                    this.lock.unlock();
                }
            }
            if (cache != null && cache.isValid(currentTime)) {
                return cache.get();
            }
            if (this.eventListener != null) {
                this.eventListener.notify((Event<CachingJWKSetSource<C>, C>)new UnableToRefreshEvent(this, (SecurityContext)context));
            }
            throw new JWKSetUnavailableException("Unable to refresh cache");
        }
        catch (final InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new JWKSetUnavailableException("Interrupted while waiting for cache refresh", e);
        }
    }
    
    CachedObject<JWKSet> loadJWKSetNotThreadSafe(final JWKSetCacheRefreshEvaluator refreshEvaluator, final long currentTime, final C context) throws KeySourceException {
        final JWKSet jwkSet = this.getSource().getJWKSet(refreshEvaluator, currentTime, context);
        return this.cacheJWKSet(jwkSet, currentTime);
    }
    
    ReentrantLock getLock() {
        return this.lock;
    }
    
    static class AbstractCachingJWKSetSourceEvent<C extends SecurityContext> extends AbstractJWKSetSourceEvent<CachingJWKSetSource<C>, C>
    {
        private final int threadQueueLength;
        
        public AbstractCachingJWKSetSourceEvent(final CachingJWKSetSource<C> source, final int threadQueueLength, final C context) {
            super(source, context);
            this.threadQueueLength = threadQueueLength;
        }
        
        public int getThreadQueueLength() {
            return this.threadQueueLength;
        }
    }
    
    public static class RefreshInitiatedEvent<C extends SecurityContext> extends AbstractCachingJWKSetSourceEvent<C>
    {
        private RefreshInitiatedEvent(final CachingJWKSetSource<C> source, final int queueLength, final C context) {
            super(source, queueLength, context);
        }
    }
    
    public static class RefreshCompletedEvent<C extends SecurityContext> extends AbstractCachingJWKSetSourceEvent<C>
    {
        private final JWKSet jwkSet;
        
        private RefreshCompletedEvent(final CachingJWKSetSource<C> source, final JWKSet jwkSet, final int queueLength, final C context) {
            super(source, queueLength, context);
            Objects.requireNonNull(jwkSet);
            this.jwkSet = jwkSet;
        }
        
        public JWKSet getJWKSet() {
            return this.jwkSet;
        }
    }
    
    public static class WaitingForRefreshEvent<C extends SecurityContext> extends AbstractCachingJWKSetSourceEvent<C>
    {
        private WaitingForRefreshEvent(final CachingJWKSetSource<C> source, final int queueLength, final C context) {
            super(source, queueLength, context);
        }
    }
    
    public static class UnableToRefreshEvent<C extends SecurityContext> extends AbstractJWKSetSourceEvent<CachingJWKSetSource<C>, C>
    {
        private UnableToRefreshEvent(final CachingJWKSetSource<C> source, final C context) {
            super(source, context);
        }
    }
    
    public static class RefreshTimedOutEvent<C extends SecurityContext> extends AbstractCachingJWKSetSourceEvent<C>
    {
        private RefreshTimedOutEvent(final CachingJWKSetSource<C> source, final int queueLength, final C context) {
            super(source, queueLength, context);
        }
    }
}
