/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF 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.surfnet.oaaas.model;
import org.springframework.util.Assert;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* Very simple - but highly effective - TokenResponseCache implementation. Please carefully monitor the performance / cache hit-ratio
* in production as the maxSize in combination with the expireTimeSeconds is important. If the maxSize is too small and the expireTimeSeconds to low
* it will result in a cache that with each addition will try to make space (e.g. effectively only removing the oldest entry each time).
*/
public class TokenResponseCacheImpl implements TokenResponseCache {
private Map<String, CacheEntry> cache = new ConcurrentHashMap<String, CacheEntry>();
private int maxSize;
private long expireTime;
public TokenResponseCacheImpl(int maxSize, long expireTimeSeconds) {
this.maxSize = maxSize;
this.expireTime = expireTimeSeconds * 1000;
invariant();
}
private void invariant() {
Assert.isTrue(maxSize > 0, "Maxsize must be greater then 0");
Assert.isTrue(expireTime < ((1000 * 60 * 60 * 24) + 1), "Maximal expireTime is one day");
Assert.isTrue(expireTime > 0, "ExpireTimeMilliseconds must be greater then 0");
}
@Override
public VerifyTokenResponse getVerifyToken(String accessToken) {
VerifyTokenResponse response = null;
if (accessToken != null) {
CacheEntry cacheEntry = cache.get(accessToken);
if (cacheEntry != null) {
if (isExpired(cacheEntry)) {
cache.remove(accessToken);
} else {
response = cacheEntry.value;
}
}
}
return response;
}
private boolean isExpired(CacheEntry cacheEntry) {
return cacheEntry.expireBy < System.currentTimeMillis();
}
@Override
public void storeVerifyToken(String accessToken, VerifyTokenResponse tokenResponse) {
if (accessToken != null && tokenResponse != null) {
if (cache.size() == maxSize) {
cleanUpCache();
}
cache.put(accessToken, new CacheEntry(tokenResponse, System.currentTimeMillis() + expireTime));
}
}
private void cleanUpCache() {
Set<Map.Entry<String, CacheEntry>> entries = cache.entrySet();
long ago = Long.MAX_VALUE;
String oldestKey = null;
for (Map.Entry<String, CacheEntry> entry : entries) {
if (isExpired(entry.getValue())) {
cache.remove(entry.getKey());
} else if (entry.getValue().expireBy < ago) {
oldestKey = entry.getKey();
ago = entry.getValue().expireBy;
}
}
if (oldestKey != null) {
cache.remove(oldestKey);
}
}
private class CacheEntry {
private VerifyTokenResponse value;
private long expireBy;
CacheEntry(VerifyTokenResponse verifyTokenResponse, long expireBy) {
this.value = verifyTokenResponse;
this.expireBy = expireBy;
}
}
}