/*
* Copyright (c) 2014, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you 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.wso2.carbon.identity.entitlement.proxy;
import javax.cache.Cache;
import javax.cache.CacheManager;
import javax.cache.Caching;
import java.util.Calendar;
import java.util.LinkedHashMap;
import java.util.Map.Entry;
class PEPProxyCache {
private SimpleCache<String, EntitlementDecision> simpleCache;
private boolean isCarbonCache = false;
private int invalidationInterval = 0;
PEPProxyCache(String enableCaching, int invalidationInterval, int maxEntries) {
if ("simple".equalsIgnoreCase(enableCaching)) {
simpleCache = new SimpleCache<String, EntitlementDecision>(maxEntries);
this.invalidationInterval = invalidationInterval;
} else if ("carbon".equalsIgnoreCase(enableCaching)) {
isCarbonCache = true;
}
}
/**
* Return an instance of a named cache that is common to all tenants.
*
* @param name the name of the cache.
* @return the named cache instance.
*/
private Cache<IdentityCacheKey, IdentityCacheEntry> getCommonCache() {
// TODO Should verify the cache creation done per tenant or as below
// We create a single cache for all tenants. It is not a good choice to create per-tenant
// caches in this case. We qualify tenants by adding the tenant identifier in the cache key.
CacheManager manager = Caching.getCacheManagerFactory().getCacheManager(ProxyConstants.DECISION_CACHE);
return manager.getCache(ProxyConstants.DECISION_CACHE);
}
void put(String key, String entry) {
if (simpleCache != null) {
EntitlementDecision entitlementDecision = new EntitlementDecision(entry, Calendar.getInstance().getTimeInMillis());
simpleCache.put(key, entitlementDecision);
} else if (isCarbonCache) {
Cache<IdentityCacheKey, IdentityCacheEntry> carbonCache = getCommonCache();
if (carbonCache != null) {
IdentityCacheKey identityKey = new IdentityCacheKey(key);
IdentityCacheEntry identityEntry = new IdentityCacheEntry(entry);
carbonCache.put(identityKey, identityEntry);
}
}
}
String get(String key) {
if (simpleCache != null) {
EntitlementDecision entitlementDecision = (EntitlementDecision) simpleCache.get(key);
if (entitlementDecision != null &&
(entitlementDecision.getCachedTime() + (long) invalidationInterval >
Calendar.getInstance().getTimeInMillis())) {
return entitlementDecision.getResponse();
}
} else if (isCarbonCache) {
Cache<IdentityCacheKey, IdentityCacheEntry> carbonCache = getCommonCache();
if (carbonCache != null) {
IdentityCacheKey identityKey = new IdentityCacheKey(key);
IdentityCacheEntry identityCacheEntry = (IdentityCacheEntry) carbonCache.get(identityKey);
if (identityCacheEntry != null) {
return identityCacheEntry.getCacheEntry();
}
}
}
return null;
}
void clear() {
if (simpleCache != null) {
simpleCache = new SimpleCache<>(simpleCache.maxEntries);
} else if (isCarbonCache) {
Cache<IdentityCacheKey, IdentityCacheEntry> carbonCache = getCommonCache();
if (carbonCache != null) {
carbonCache.removeAll();
}
}
}
private class SimpleCache<K, V> extends LinkedHashMap<K, V> {
private static final long serialVersionUID = -6958380913702000534L;
private int maxEntries;
public SimpleCache(int maxEntries) {
// removeEldestEntry() is called after a put(). To allow maxEntries in
// cache, capacity should be maxEntries + 1 (for the entry which will be
// removed). Load factor is taken as 1 because size is fixed. This is
// less space efficient when very less entries are present, but there
// will be no effect on time complexity for get(). The third parameter
// in the base class constructor says that this map is
// insertion-order oriented.
super(maxEntries + 1, 1, false);
this.maxEntries = maxEntries;
}
@Override
protected boolean removeEldestEntry(Entry<K, V> eldest) {
// After size exceeds max entries, this statement returns true and the
// oldest value will be removed. Behaves like a queue, the first
// inserted value will go away.
return size() > maxEntries;
}
}
/**
* Encapsulate the XACML Decision with XACML response and time stamp
*/
private class EntitlementDecision {
/**
* XACML response
*/
private String response;
/**
* time stamp
*/
private long cachedTime;
EntitlementDecision(String response, long cachedTime) {
this.response = response;
this.cachedTime = cachedTime;
}
public String getResponse() {
return response;
}
public long getCachedTime() {
return cachedTime;
}
}
}