001package com.identityworksllc.iiq.common;
002
003import com.identityworksllc.iiq.common.cache.CacheMap;
004
005import java.util.Map;
006import java.util.concurrent.TimeUnit;
007
008/**
009 * An advanced Global Storage class that expands on IIQ's CustomGlobal
010 * object. It allows simple get/set operations into a static global
011 * concurrent map, but also provides a mechanism for ThreadLocal and
012 * expiring values.
013 */
014@SuppressWarnings("unused")
015public final class TempStorage implements DelegatedMap<String, Object>, TypeFriendlyMap<String, Object> {
016    /**
017     * The background daemon thread to clean up the temporary map every X minutes.
018     * The {@link CacheMap} class only invalidates keys on certain operations, so
019     * this thread will make sure invalidation happens routinely to avoid memory
020     * leaks.
021     *
022     * This thread will be run with low priority and daemon status, meaning it will
023     * not interrupt a JVM shutdown.
024     */
025    private static class CleanupThread extends Thread {
026        /**
027         * The map to clean up
028         */
029        private final TempStorage globalStorage;
030
031        /**
032         * Constructor that initiates the storage
033         */
034        private CleanupThread(TempStorage storage) {
035            this.globalStorage = storage;
036        }
037
038        /**
039         * Invokes the {@link TempStorage#internalCleanup()} method every 30 seconds
040         */
041        @Override
042        public void run() {
043            boolean done = false;
044            while(!this.isInterrupted() && !done) {
045                try {
046                    Thread.sleep(30000L);
047                } catch(InterruptedException e) {
048                    done = true;
049                }
050                if (!this.isInterrupted() && !done) {
051                    globalStorage.internalCleanup();
052                }
053            }
054        }
055    }
056
057    /**
058     * Singleton lock
059     */
060    private static final Object _LOCK = new Object();
061
062    /**
063     * The singleton object
064     */
065    private static TempStorage _SINGLETON;
066
067    /**
068     * Gets the singleton object, creating a new one if it does not exist.
069     * @return The singleton TempStorage object
070     */
071    public static TempStorage get() {
072        if (_SINGLETON == null) {
073            synchronized (_LOCK) {
074                if (_SINGLETON == null) {
075                    _SINGLETON = new TempStorage();
076                }
077            }
078        }
079        return _SINGLETON;
080    }
081
082    /**
083     * The cleanup daemon thread
084     */
085    private final Thread cleanupDaemon;
086
087    /**
088     * The internal timed cache with a default timeout of 5 minutes
089     */
090    private final CacheMap<String, Object> timedStorage;
091
092    /**
093     * Private constructor which also starts up the background thread
094     */
095    private TempStorage() {
096        this.timedStorage = new CacheMap<>(5, TimeUnit.MINUTES);
097
098        this.cleanupDaemon = new CleanupThread(this);
099        this.cleanupDaemon.setDaemon(true);
100        this.cleanupDaemon.setName("IDW IIQCommon Global TempStorage Cleanup Thread");
101        this.cleanupDaemon.setPriority(Thread.MIN_PRIORITY);
102        this.cleanupDaemon.start();
103    }
104
105    /**
106     * Gets the internal CacheMap, required by DelegatedMap.
107     * @return The internal cache map
108     */
109    @Override
110    public Map<String, Object> getDelegate() {
111        return timedStorage;
112    }
113
114    /**
115     * Invokes a cleanup on the internal map
116     */
117    private void internalCleanup() {
118        timedStorage.invalidateRecords();
119    }
120
121    /**
122     * Returns true if the background cleanup thread is running. Note that like
123     * {@link Thread#isAlive()}, this method may not return true for a couple of
124     * seconds after the thread is initially started.
125     *
126     * @return True if the background thread is running
127     */
128    public boolean isCleanupDaemonRunning() {
129        return this.cleanupDaemon.isAlive();
130    }
131
132    /**
133     * Stops the background thread by interrupting it
134     */
135    public void stop() {
136        this.cleanupDaemon.interrupt();
137    }
138}