001package com.identityworksllc.iiq.common; 002 003import sailpoint.server.Environment; 004 005import java.util.function.Supplier; 006 007/** 008 * A thread-local container that is associated with the plugin cache version. If 009 * a new plugin is installed, the version increments, and the entire ThreadLocal 010 * will be replaced. This means that running processes relying on plugin-provided 011 * classes can retrieve new versions of those objects. 012 * 013 * Your {@link Supplier} code should always assume that the plugin cache has 014 * been refreshed since the last invocation and never cache plugin objects. 015 * 016 * The object type T, of course, cannot be the actual implementation 017 * in the plugin classloader, as that class will have become invalid when the 018 * plugin cache was reset. The type T should be either a JDK class or a custom 019 * interface implemented at the webapp layer. 020 * 021 * @param <T> the type of the value held in this ThreadLocal 022 */ 023public class VersionedThreadLocal<T> implements Supplier<T> { 024 025 /** 026 * Creates a new VersionedThreadLocal with the given initial value. 027 * @param supplier the initial value supplier 028 * @return a new VersionedThreadLocal 029 * @param <U> the type of the value held in this ThreadLocal 030 * 031 * @see ThreadLocal#withInitial(Supplier) 032 */ 033 public static <U> VersionedThreadLocal<U> withInitial(Supplier<? extends U> supplier) { 034 return new VersionedThreadLocal<>(supplier); 035 } 036 037 /** 038 * The supplier that provides the initial value for this ThreadLocal. 039 */ 040 private final Supplier<? extends T> supplier; 041 042 /** 043 * The ThreadLocal instance that holds the value. This will be replaced on the 044 * next call to {@link #get()} or {@link #set} when the value changes. 045 */ 046 private ThreadLocal<T> threadLocal; 047 048 /** 049 * The version of the plugin cache, used to determine if the threadlocal needs 050 * to be replaced. 051 */ 052 private int version; 053 054 /** 055 * Creates a new VersionedThreadLocal with the default initial value of null. 056 */ 057 public VersionedThreadLocal() { 058 this(() -> null); 059 } 060 061 /** 062 * Creates a new VersionedThreadLocal with the given initial value. 063 * 064 * @param supplier the initial value supplier 065 */ 066 private VersionedThreadLocal(Supplier<? extends T> supplier) { 067 this.supplier = supplier; 068 this.threadLocal = ThreadLocal.withInitial(supplier); 069 this.version = Environment.getEnvironment().getPluginsCache().getVersion(); 070 } 071 072 /** 073 * Computes the value for this ThreadLocal using the given supplier. If the 074 * value is already set, it will be returned. Otherwise, the supplier will be 075 * called to compute the value and set it in this ThreadLocal. 076 * 077 * @param supplier the supplier to compute the value 078 * @return the current or computed value 079 */ 080 public T compute(Supplier<? extends T> supplier) { 081 T value = get(); 082 if (value == null) { 083 value = supplier.get(); 084 set(value); 085 } 086 return value; 087 } 088 089 /** 090 * Returns the current value in this ThreadLocal. If the version has changed, 091 * a new ThreadLocal will be created and the value returned from that. 092 * 093 * @return the current value in this ThreadLocal 094 * @see ThreadLocal#get() 095 */ 096 @Override 097 public T get() { 098 int currentVersion = Environment.getEnvironment().getPluginsCache().getVersion(); 099 if (currentVersion != version) { 100 synchronized (this) { 101 version = currentVersion; 102 threadLocal = ThreadLocal.withInitial(supplier); 103 } 104 } 105 106 return threadLocal.get(); 107 } 108 109 /** 110 * Sets the value in this ThreadLocal. If the version has changed, a new 111 * ThreadLocal will be created and the value set in that. 112 * 113 * @param value the new value to set 114 * @see ThreadLocal#set(Object) 115 */ 116 public void set(T value) { 117 int currentVersion = Environment.getEnvironment().getPluginsCache().getVersion(); 118 if (currentVersion != version) { 119 synchronized(this) { 120 version = currentVersion; 121 threadLocal = ThreadLocal.withInitial(supplier); 122 } 123 } 124 125 threadLocal.set(value); 126 } 127 128}