From f6786e29c6d5033b809f00da9106bc1a4ce64689 Mon Sep 17 00:00:00 2001 From: Pedro Igor Date: Fri, 17 Mar 2017 09:34:16 -0300 Subject: [PATCH] [KEYCLOAK-4602] - A runtime cache for path configurations --- .../adapters/authorization/PathCache.java | 170 ++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/PathCache.java diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/PathCache.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/PathCache.java new file mode 100644 index 0000000000..e6992039c5 --- /dev/null +++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/PathCache.java @@ -0,0 +1,170 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.adapters.authorization; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.LockSupport; + +import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig; + +/** + * @author Pedro Igor + */ +public class PathCache { + + /** + * The load factor. + */ + private static final float DEFAULT_LOAD_FACTOR = 0.75f; + + private final Map cache; + + private final AtomicBoolean writing = new AtomicBoolean(false); + + private final long maxAge; + + /** + * Creates a new instance. + * + * @param maxEntries the maximum number of entries to keep in the cache + */ + public PathCache(int maxEntries) { + this(maxEntries, -1); + } + + /** + * Creates a new instance. + * + * @param maxEntries the maximum number of entries to keep in the cache + * @param maxAge the time in milliseconds that an entry can stay in the cache. If {@code -1}, entries never expire + */ + public PathCache(final int maxEntries, long maxAge) { + cache = new LinkedHashMap(16, DEFAULT_LOAD_FACTOR, true) { + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + return cache.size() > maxEntries; + } + }; + this.maxAge = maxAge; + } + + public void put(String uri, PathConfig newValue) { + try { + if (parkForWriteAndCheckInterrupt()) { + return; + } + + CacheEntry cacheEntry = cache.get(uri); + + if (cacheEntry == null) { + cache.put(uri, new CacheEntry(uri, newValue, maxAge)); + } + } finally { + writing.lazySet(false); + } + } + + public PathConfig get(String uri) { + if (parkForReadAndCheckInterrupt()) { + return null; + } + + CacheEntry cached = cache.get(uri); + + if (cached != null) { + return removeIfExpired(cached); + } + + return null; + } + + public void remove(String key) { + try { + if (parkForWriteAndCheckInterrupt()) { + return; + } + + cache.remove(key); + } finally { + writing.lazySet(false); + } + } + + private PathConfig removeIfExpired(CacheEntry cached) { + if (cached == null) { + return null; + } + + if (cached.isExpired()) { + remove(cached.key()); + return null; + } + + return cached.value(); + } + + private boolean parkForWriteAndCheckInterrupt() { + while (!writing.compareAndSet(false, true)) { + LockSupport.parkNanos(1L); + if (Thread.interrupted()) { + return true; + } + } + return false; + } + + private boolean parkForReadAndCheckInterrupt() { + while (writing.get()) { + LockSupport.parkNanos(1L); + if (Thread.interrupted()) { + return true; + } + } + return false; + } + + private static final class CacheEntry { + + final String key; + final PathConfig value; + final long expiration; + + CacheEntry(String key, PathConfig value, long maxAge) { + this.key = key; + this.value = value; + if(maxAge == -1) { + expiration = -1; + } else { + expiration = System.currentTimeMillis() + maxAge; + } + } + + String key() { + return key; + } + + PathConfig value() { + return value; + } + + boolean isExpired() { + return expiration != -1 ? System.currentTimeMillis() > expiration : false; + } + } +}