# CacheManager.java

    /*
     *  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);
                    }
                }

            }
        }
    }