You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

CurrentInstance.java 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  1. /*
  2. * Copyright 2000-2018 Vaadin Ltd.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. package com.vaadin.util;
  17. import java.io.Serializable;
  18. import java.lang.ref.WeakReference;
  19. import java.util.Collections;
  20. import java.util.HashMap;
  21. import java.util.Iterator;
  22. import java.util.Map;
  23. import java.util.Map.Entry;
  24. import java.util.concurrent.ConcurrentHashMap;
  25. import java.util.logging.Level;
  26. import java.util.logging.Logger;
  27. import com.vaadin.server.VaadinRequest;
  28. import com.vaadin.server.VaadinResponse;
  29. import com.vaadin.server.VaadinService;
  30. import com.vaadin.server.VaadinSession;
  31. import com.vaadin.ui.UI;
  32. /**
  33. * Keeps track of various current instances for the current thread. All the
  34. * instances are automatically cleared after handling a request from the client
  35. * to avoid leaking memory.
  36. * <p>
  37. * Please note that the instances are stored using {@link WeakReference}. This
  38. * means that the a current instance value may suddenly disappear if there a no
  39. * other references to the object.
  40. * <p>
  41. * Currently the framework uses the following instances:
  42. * </p>
  43. * <p>
  44. * {@link UI}, {@link VaadinService}, {@link VaadinSession},
  45. * {@link VaadinRequest}, {@link VaadinResponse}.
  46. * </p>
  47. *
  48. * @author Vaadin Ltd
  49. * @since 7.0.0
  50. */
  51. public class CurrentInstance implements Serializable {
  52. private static final Object NULL_OBJECT = new Object();
  53. private static final CurrentInstance CURRENT_INSTANCE_NULL = new CurrentInstance(
  54. NULL_OBJECT);
  55. private static final ConcurrentHashMap<Class<?>, CurrentInstanceFallbackResolver<?>> fallbackResolvers = new ConcurrentHashMap<Class<?>, CurrentInstanceFallbackResolver<?>>();
  56. private final WeakReference<Object> instance;
  57. private static final ThreadLocal<Map<Class<?>, CurrentInstance>> INSTANCES = new ThreadLocal<>();
  58. private CurrentInstance(Object instance) {
  59. this.instance = new WeakReference<>(instance);
  60. }
  61. /**
  62. * Gets the current instance of a specific type if available.
  63. * <p>
  64. * When a current instance of the specific type is not found, the
  65. * {@link CurrentInstanceFallbackResolver} registered via
  66. * {@link #defineFallbackResolver(Class, CurrentInstanceFallbackResolver)}
  67. * (if any) is invoked.
  68. *
  69. * @param type
  70. * the class to get an instance of
  71. * @return the current instance or the provided type, or <code>null</code>
  72. * if there is no current instance.
  73. */
  74. public static <T> T get(Class<T> type) {
  75. T result = doGet(type);
  76. if (result != null) {
  77. return result;
  78. }
  79. CurrentInstanceFallbackResolver<?> fallbackResolver = fallbackResolvers
  80. .get(type);
  81. if (fallbackResolver != null) {
  82. return (T) fallbackResolver.resolve();
  83. }
  84. return null;
  85. }
  86. private static <T> T doGet(Class<T> type) {
  87. Map<Class<?>, CurrentInstance> map = INSTANCES.get();
  88. if (map == null) {
  89. return null;
  90. }
  91. CurrentInstance currentInstance = map.get(type);
  92. if (currentInstance != null) {
  93. Object value = currentInstance.instance.get();
  94. if (value == null) {
  95. /*
  96. * This is believed to never actually happen since the
  97. * ThreadLocal should only outlive the referenced object on
  98. * threads that are not doing anything related to Vaadin, which
  99. * should thus never invoke CurrentInstance.get().
  100. *
  101. * At this point, there might also be other values that have
  102. * been collected, so we'll scan the entire map and remove stale
  103. * CurrentInstance objects. Using a ReferenceQueue could make
  104. * this assumingly rare case slightly more efficient, but would
  105. * significantly increase the complexity of the code for
  106. * maintaining a separate ReferenceQueue for each Thread.
  107. */
  108. removeStaleInstances(map);
  109. if (map.isEmpty()) {
  110. INSTANCES.remove();
  111. }
  112. return null;
  113. }
  114. return type.cast(value);
  115. } else {
  116. return null;
  117. }
  118. }
  119. /**
  120. * Adds a CurrentInstanceFallbackResolver, that is triggered when
  121. * {@link #get(Class)} can't find a suitable instance for the given type
  122. * parameter.
  123. *
  124. * @param type
  125. * the class used on {@link #get(Class)} invocations to retrieve
  126. * the current instance
  127. * @param fallbackResolver
  128. * the resolver, not <code>null</code>
  129. *
  130. * @throws IllegalArgumentException
  131. * if there's already a defined fallback resolver for the given
  132. * type
  133. * @since 8.5.2
  134. */
  135. public static <T> void defineFallbackResolver(Class<T> type,
  136. CurrentInstanceFallbackResolver<T> fallbackResolver) {
  137. if (fallbackResolver == null) {
  138. throw new IllegalArgumentException(
  139. "The fallback resolver can not be null.");
  140. }
  141. if (fallbackResolvers.putIfAbsent(type, fallbackResolver) != null) {
  142. throw new IllegalArgumentException(
  143. "A fallback resolver for the type " + type
  144. + " is already defined.");
  145. }
  146. }
  147. private static void removeStaleInstances(
  148. Map<Class<?>, CurrentInstance> map) {
  149. for (Iterator<Entry<Class<?>, CurrentInstance>> iterator = map
  150. .entrySet().iterator(); iterator.hasNext();) {
  151. Entry<Class<?>, CurrentInstance> entry = iterator.next();
  152. Object instance = entry.getValue().instance.get();
  153. if (instance == null) {
  154. iterator.remove();
  155. getLogger().log(Level.FINE,
  156. "CurrentInstance for {0} has been garbage collected.",
  157. entry.getKey());
  158. }
  159. }
  160. }
  161. /**
  162. * Sets the current instance of the given type.
  163. *
  164. * @see ThreadLocal
  165. *
  166. * @param type
  167. * the class that should be used when getting the current
  168. * instance back
  169. * @param instance
  170. * the actual instance
  171. */
  172. public static <T> CurrentInstance set(Class<T> type, T instance) {
  173. Map<Class<?>, CurrentInstance> map = INSTANCES.get();
  174. CurrentInstance previousInstance = null;
  175. if (instance == null) {
  176. // remove the instance
  177. if (map != null) {
  178. previousInstance = map.remove(type);
  179. if (map.isEmpty()) {
  180. INSTANCES.remove();
  181. map = null;
  182. }
  183. }
  184. } else {
  185. assert type.isInstance(instance) : "Invald instance type";
  186. if (map == null) {
  187. map = new HashMap<>();
  188. INSTANCES.set(map);
  189. }
  190. previousInstance = map.put(type, new CurrentInstance(instance));
  191. }
  192. if (previousInstance == null) {
  193. previousInstance = CURRENT_INSTANCE_NULL;
  194. }
  195. return previousInstance;
  196. }
  197. /**
  198. * Clears all current instances.
  199. */
  200. public static void clearAll() {
  201. INSTANCES.remove();
  202. }
  203. /**
  204. * Restores the given instances to the given values. Note that this should
  205. * only be used internally to restore Vaadin classes.
  206. *
  207. * @since 7.1
  208. *
  209. * @param old
  210. * A Class -> CurrentInstance map to set as current instances
  211. */
  212. public static void restoreInstances(Map<Class<?>, CurrentInstance> old) {
  213. boolean removeStale = false;
  214. for (Class c : old.keySet()) {
  215. CurrentInstance ci = old.get(c);
  216. Object v = ci.instance.get();
  217. if (v == null) {
  218. removeStale = true;
  219. } else if (v == NULL_OBJECT) {
  220. /*
  221. * NULL_OBJECT is used to identify objects that are null when
  222. * #setCurrent(UI) or #setCurrent(VaadinSession) are called on a
  223. * CurrentInstance. Without this a reference to an already
  224. * collected instance may be left in the CurrentInstance when it
  225. * really should be restored to null.
  226. *
  227. * One example case that this fixes:
  228. * VaadinService.runPendingAccessTasks() clears all current
  229. * instances and then sets everything but the UI. This makes
  230. * UI.accessSynchronously() save these values before calling
  231. * setCurrent(UI), which stores UI=null in the map it returns.
  232. * This map will be restored after UI.accessSync(), which,
  233. * unless it respects null values, will just leave the wrong UI
  234. * instance registered.
  235. */
  236. v = null;
  237. }
  238. set(c, v);
  239. }
  240. if (removeStale) {
  241. removeStaleInstances(old);
  242. }
  243. }
  244. /**
  245. * Gets the currently set instances so that they can later be restored using
  246. * {@link #restoreInstances(Map)}.
  247. *
  248. * @since 8.0
  249. *
  250. * @return a map containing the current instances
  251. */
  252. public static Map<Class<?>, CurrentInstance> getInstances() {
  253. Map<Class<?>, CurrentInstance> map = INSTANCES.get();
  254. if (map == null) {
  255. return Collections.emptyMap();
  256. } else {
  257. Map<Class<?>, CurrentInstance> copy = new HashMap<>();
  258. boolean removeStale = false;
  259. for (Class<?> c : map.keySet()) {
  260. CurrentInstance ci = map.get(c);
  261. if (ci.instance.get() == null) {
  262. removeStale = true;
  263. } else {
  264. copy.put(c, ci);
  265. }
  266. }
  267. if (removeStale) {
  268. removeStaleInstances(map);
  269. if (map.isEmpty()) {
  270. INSTANCES.remove();
  271. }
  272. }
  273. return copy;
  274. }
  275. }
  276. /**
  277. * Sets current instances for the UI and all related classes. The previously
  278. * defined values can be restored by passing the returned map to
  279. * {@link #restoreInstances(Map)}.
  280. *
  281. * @since 7.1
  282. *
  283. * @param ui
  284. * The UI
  285. * @return A map containing the old values of the instances that this method
  286. * updated.
  287. */
  288. public static Map<Class<?>, CurrentInstance> setCurrent(UI ui) {
  289. Map<Class<?>, CurrentInstance> old = setCurrent(ui.getSession());
  290. old.put(UI.class, set(UI.class, ui));
  291. return old;
  292. }
  293. /**
  294. * Sets current instances for the {@link VaadinSession} and all related
  295. * classes. The previously defined values can be restored by passing the
  296. * returned map to {@link #restoreInstances(Map)}.
  297. *
  298. * @since 7.1
  299. *
  300. * @param session
  301. * The VaadinSession
  302. * @return A map containing the old values of the instances this method
  303. * updated.
  304. */
  305. public static Map<Class<?>, CurrentInstance> setCurrent(
  306. VaadinSession session) {
  307. Map<Class<?>, CurrentInstance> old = new HashMap<>();
  308. old.put(VaadinSession.class, set(VaadinSession.class, session));
  309. VaadinService service = null;
  310. if (session != null) {
  311. service = session.getService();
  312. }
  313. old.put(VaadinService.class, set(VaadinService.class, service));
  314. return old;
  315. }
  316. private static Logger getLogger() {
  317. return Logger.getLogger(CurrentInstance.class.getName());
  318. }
  319. }