/*
* 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.apache.synapse.transport.certificatevalidation.cache;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.synapse.transport.certificatevalidation.Constants;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
/**
* Cache Manager takes care and maintains an LRU cache which implements ManageableCache Interface.
* CAUTION!! If CacheManager is too much involved with the cache, other threads will be affected.
*/
public class CacheManager {
private final boolean DO_NOT_INTERRUPT_IF_RUNNING = false;
private ScheduledExecutorService scheduler;
private ScheduledFuture scheduledFuture = null;
private ManageableCache cache;
private int cacheMaxSize;
private int delay;
private CacheManagingTask cacheManagingTask;
private static final Log log = LogFactory.getLog(CacheManager.class);
/**
* A new cacheManager will be started on the given ManageableCache object.
*
* @param cache a Manageable Cache which could be managed by this cache manager.
* @param cacheMaxSize Maximum size of the cache. If the cache exceeds this size, LRU values will be
* removed
*/
public CacheManager(ManageableCache cache, int cacheMaxSize, int delay) {
int NUM_THREADS = 1;
scheduler = Executors.newScheduledThreadPool(NUM_THREADS);
this.cache = cache;
this.cacheMaxSize = cacheMaxSize;
this.cacheManagingTask = new CacheManagingTask();
this.delay = delay;
start();
}
/**
* To Start the CacheManager. Should be called only once per CacheManager so called in constructor.
* CacheManager will run its TimerTask every "delay" number of seconds.
*/
private boolean start() {
if(scheduledFuture == null || (scheduledFuture.isCancelled())) {
scheduledFuture = scheduler.scheduleWithFixedDelay(cacheManagingTask,
delay, delay, TimeUnit.MINUTES);
log.info(cache.getClass().getSimpleName()+" Cache Manager Started");
return true;
}
return false;
}
/**
* To wake cacheManager up at will. If this method is called while its task is running, it will run its task again
* soon after its done. CacheManagerTask will be rescheduled as before.
* @return true if successfully waken up. false otherwise.
*/
public boolean wakeUpNow(){
if(scheduledFuture !=null) {
if(!scheduledFuture.isCancelled()) {
scheduledFuture.cancel(DO_NOT_INTERRUPT_IF_RUNNING);
}
scheduledFuture = scheduler.scheduleWithFixedDelay(cacheManagingTask,
0, delay,TimeUnit.MINUTES);
log.info(cache.getClass().getSimpleName()+" Cache Manager Wakened Up.....");
return true;
}
return false;
}
public boolean changeDelay(int delay) throws IllegalArgumentException {
int min = Constants.CACHE_MIN_DELAY_MINS;
int max = Constants.CACHE_MAX_DELAY_MINS;
if(delay < min || delay > max) {
throw new IllegalArgumentException("Delay time should should be between " + min + " and " +
max + " minutes");
}
this.delay = delay;
return wakeUpNow();
}
public int getDelay(){
return delay;
}
/**
* Gracefully stop cacheManager.
*/
public boolean stop(){
if(scheduledFuture !=null && !scheduledFuture.isCancelled()){
scheduledFuture.cancel(DO_NOT_INTERRUPT_IF_RUNNING);
log.info(cache.getClass().getSimpleName()+" Cache Manager Stopped.....");
return true;
}
return false;
}
public boolean isRunning() {
return !scheduledFuture.isCancelled();
}
/**
* This is the Scheduled Task the CacheManager uses in order to remove invalid cache values and
* to remove LRU values if the cache reaches cacheMaxSize.
*/
private class CacheManagingTask implements Runnable {
public void run() {
long start = System.currentTimeMillis();
log.info(cache.getClass().getSimpleName()+" Cache Manager Task Started.");
ManageableCacheValue nextCacheValue;
//cache.getCacheSize() can vary when new entries are added. So get cache size at this point
int cacheSize = cache.getCacheSize();
int numberToRemove = (cacheSize>cacheMaxSize)? cacheSize - cacheMaxSize: 0;
List<ManageableCacheValue> entriesToRemove = new ArrayList<ManageableCacheValue>();
LRUEntryCollector lruEntryCollector = new LRUEntryCollector(entriesToRemove, numberToRemove);
//Start looking at cache entries from the beginning.
cache.resetIterator();
//Iteration through the cache entries.
while ((cacheSize--)>0) {
nextCacheValue = cache.getNextCacheValue();
if (nextCacheValue == null) {
log.info("Cache manager iteration through Cache values done");
break;
}
//Updating invalid cache values
if (!nextCacheValue.isValid()) {
log.info("Updating Invalid Cache Value by Manager");
nextCacheValue.updateCacheWithNewValue();
}
//There are LRU entries tobe removed since cacheSize > maxCacheSize. So collect them.
if(numberToRemove>0) {
lruEntryCollector.collectEntriesToRemove(nextCacheValue);
}
}
//LRU entries removing
for(ManageableCacheValue oldCacheValue: entriesToRemove) {
log.info("Removing LRU value from cache");
oldCacheValue.removeThisCacheValue();
}
log.info(cache.getClass().getSimpleName()+" Cache Manager Task Done. Took " +
(System.currentTimeMillis() - start) + " ms.");
}
private class LRUEntryCollector {
private List<ManageableCacheValue> entriesToRemove;
private int listMaxSize;
LRUEntryCollector(List<ManageableCacheValue> entriesToRemove, int numberToRemove){
this.entriesToRemove = entriesToRemove;
this.listMaxSize = numberToRemove;
}
/**
* This method collects the listMaxSize number of LRU values from the Cache. These values
* will be removed from the cache. This uses a part of the Logic in Insertion Sort.
* @param value to be collected.
*/
private void collectEntriesToRemove(ManageableCacheValue value) {
entriesToRemove.add(value);
int i = entriesToRemove.size()-1;
int j = i;
for(; j>0 && (value.getTimeStamp() < entriesToRemove.get(j-1).getTimeStamp()); j--) {
entriesToRemove.remove(j);
entriesToRemove.add(j,(entriesToRemove.get(j-1)));
}
entriesToRemove.remove(j);
entriesToRemove.add(j,value);
/**
* First entry in the list will be the oldest. Last is the earliest in the list.
* So remove the earliest since we need to collect the old (LRU) values to remove
* from cache later
*/
if(entriesToRemove.size() > listMaxSize) {
entriesToRemove.remove(entriesToRemove.size() -1);
}
}
}
}
}