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 0000000..e699203
--- /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 <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public class PathCache {
+
+ /**
+ * The load factor.
+ */
+ private static final float DEFAULT_LOAD_FACTOR = 0.75f;
+
+ private final Map<String, CacheEntry> 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<String, CacheEntry>(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;
+ }
+ }
+}