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}