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 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. /*
  2. * Copyright 2000-2016 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.logging.Level;
  25. import java.util.logging.Logger;
  26. import com.vaadin.server.VaadinRequest;
  27. import com.vaadin.server.VaadinResponse;
  28. import com.vaadin.server.VaadinService;
  29. import com.vaadin.server.VaadinSession;
  30. import com.vaadin.ui.UI;
  31. /**
  32. * Keeps track of various current instances for the current thread. All the
  33. * instances are automatically cleared after handling a request from the client
  34. * to avoid leaking memory.
  35. * <p>
  36. * Please note that the instances are stored using {@link WeakReference}. This
  37. * means that the a current instance value may suddenly disappear if there a no
  38. * other references to the object.
  39. * <p>
  40. * Currently the framework uses the following instances:
  41. * </p>
  42. * <p>
  43. * {@link UI}, {@link VaadinService}, {@link VaadinSession},
  44. * {@link VaadinRequest}, {@link VaadinResponse}.
  45. * </p>
  46. *
  47. * @author Vaadin Ltd
  48. * @since 7.0.0
  49. */
  50. public class CurrentInstance implements Serializable {
  51. private static final Object NULL_OBJECT = new Object();
  52. private static final CurrentInstance CURRENT_INSTANCE_NULL = new CurrentInstance(
  53. NULL_OBJECT);
  54. private final WeakReference<Object> instance;
  55. private static final ThreadLocal<Map<Class<?>, CurrentInstance>> INSTANCES = new ThreadLocal<>();
  56. private CurrentInstance(Object instance) {
  57. this.instance = new WeakReference<>(instance);
  58. }
  59. /**
  60. * Gets the current instance of a specific type if available.
  61. *
  62. * @param type
  63. * the class to get an instance of
  64. * @return the current instance or the provided type, or <code>null</code>
  65. * if there is no current instance.
  66. */
  67. public static <T> T get(Class<T> type) {
  68. Map<Class<?>, CurrentInstance> map = INSTANCES.get();
  69. if (map == null) {
  70. return null;
  71. }
  72. CurrentInstance currentInstance = map.get(type);
  73. if (currentInstance != null) {
  74. Object value = currentInstance.instance.get();
  75. if (value == null) {
  76. /*
  77. * This is believed to never actually happen since the
  78. * ThreadLocal should only outlive the referenced object on
  79. * threads that are not doing anything related to Vaadin, which
  80. * should thus never invoke CurrentInstance.get().
  81. *
  82. * At this point, there might also be other values that have
  83. * been collected, so we'll scan the entire map and remove stale
  84. * CurrentInstance objects. Using a ReferenceQueue could make
  85. * this assumingly rare case slightly more efficient, but would
  86. * significantly increase the complexity of the code for
  87. * maintaining a separate ReferenceQueue for each Thread.
  88. */
  89. removeStaleInstances(map);
  90. if (map.isEmpty()) {
  91. INSTANCES.remove();
  92. }
  93. return null;
  94. }
  95. return type.cast(value);
  96. } else {
  97. return null;
  98. }
  99. }
  100. private static void removeStaleInstances(
  101. Map<Class<?>, CurrentInstance> map) {
  102. for (Iterator<Entry<Class<?>, CurrentInstance>> iterator = map
  103. .entrySet().iterator(); iterator.hasNext();) {
  104. Entry<Class<?>, CurrentInstance> entry = iterator.next();
  105. Object instance = entry.getValue().instance.get();
  106. if (instance == null) {
  107. iterator.remove();
  108. getLogger().log(Level.FINE,
  109. "CurrentInstance for {0} has been garbage collected.",
  110. entry.getKey());
  111. }
  112. }
  113. }
  114. /**
  115. * Sets the current instance of the given type.
  116. *
  117. * @see ThreadLocal
  118. *
  119. * @param type
  120. * the class that should be used when getting the current
  121. * instance back
  122. * @param instance
  123. * the actual instance
  124. */
  125. public static <T> CurrentInstance set(Class<T> type, T instance) {
  126. Map<Class<?>, CurrentInstance> map = INSTANCES.get();
  127. CurrentInstance previousInstance = null;
  128. if (instance == null) {
  129. // remove the instance
  130. if (map != null) {
  131. previousInstance = map.remove(type);
  132. if (map.isEmpty()) {
  133. INSTANCES.remove();
  134. map = null;
  135. }
  136. }
  137. } else {
  138. assert type.isInstance(instance) : "Invald instance type";
  139. if (map == null) {
  140. map = new HashMap<>();
  141. INSTANCES.set(map);
  142. }
  143. previousInstance = map.put(type, new CurrentInstance(instance));
  144. }
  145. if (previousInstance == null) {
  146. previousInstance = CURRENT_INSTANCE_NULL;
  147. }
  148. return previousInstance;
  149. }
  150. /**
  151. * Clears all current instances.
  152. */
  153. public static void clearAll() {
  154. INSTANCES.remove();
  155. }
  156. /**
  157. * Restores the given instances to the given values. Note that this should
  158. * only be used internally to restore Vaadin classes.
  159. *
  160. * @since 7.1
  161. *
  162. * @param old
  163. * A Class -> CurrentInstance map to set as current instances
  164. */
  165. public static void restoreInstances(Map<Class<?>, CurrentInstance> old) {
  166. boolean removeStale = false;
  167. for (Class c : old.keySet()) {
  168. CurrentInstance ci = old.get(c);
  169. Object v = ci.instance.get();
  170. if (v == null) {
  171. removeStale = true;
  172. } else if (v == NULL_OBJECT) {
  173. /*
  174. * NULL_OBJECT is used to identify objects that are null when
  175. * #setCurrent(UI) or #setCurrent(VaadinSession) are called on a
  176. * CurrentInstance. Without this a reference to an already
  177. * collected instance may be left in the CurrentInstance when it
  178. * really should be restored to null.
  179. *
  180. * One example case that this fixes:
  181. * VaadinService.runPendingAccessTasks() clears all current
  182. * instances and then sets everything but the UI. This makes
  183. * UI.accessSynchronously() save these values before calling
  184. * setCurrent(UI), which stores UI=null in the map it returns.
  185. * This map will be restored after UI.accessSync(), which,
  186. * unless it respects null values, will just leave the wrong UI
  187. * instance registered.
  188. */
  189. v = null;
  190. }
  191. set(c, v);
  192. }
  193. if (removeStale) {
  194. removeStaleInstances(old);
  195. }
  196. }
  197. /**
  198. * Gets the currently set instances so that they can later be restored using
  199. * {@link #restoreInstances(Map)}.
  200. *
  201. * @since 8.0
  202. *
  203. * @return a map containing the current instances
  204. */
  205. public static Map<Class<?>, CurrentInstance> getInstances() {
  206. Map<Class<?>, CurrentInstance> map = INSTANCES.get();
  207. if (map == null) {
  208. return Collections.emptyMap();
  209. } else {
  210. Map<Class<?>, CurrentInstance> copy = new HashMap<>();
  211. boolean removeStale = false;
  212. for (Class<?> c : map.keySet()) {
  213. CurrentInstance ci = map.get(c);
  214. if (ci.instance.get() == null) {
  215. removeStale = true;
  216. } else {
  217. copy.put(c, ci);
  218. }
  219. }
  220. if (removeStale) {
  221. removeStaleInstances(map);
  222. if (map.isEmpty()) {
  223. INSTANCES.remove();
  224. }
  225. }
  226. return copy;
  227. }
  228. }
  229. /**
  230. * Sets current instances for the UI and all related classes. The previously
  231. * defined values can be restored by passing the returned map to
  232. * {@link #restoreInstances(Map)}.
  233. *
  234. * @since 7.1
  235. *
  236. * @param ui
  237. * The UI
  238. * @return A map containing the old values of the instances that this method
  239. * updated.
  240. */
  241. public static Map<Class<?>, CurrentInstance> setCurrent(UI ui) {
  242. Map<Class<?>, CurrentInstance> old = setCurrent(ui.getSession());
  243. old.put(UI.class, set(UI.class, ui));
  244. return old;
  245. }
  246. /**
  247. * Sets current instances for the {@link VaadinSession} and all related
  248. * classes. The previously defined values can be restored by passing the
  249. * returned map to {@link #restoreInstances(Map)}.
  250. *
  251. * @since 7.1
  252. *
  253. * @param session
  254. * The VaadinSession
  255. * @return A map containing the old values of the instances this method
  256. * updated.
  257. */
  258. public static Map<Class<?>, CurrentInstance> setCurrent(
  259. VaadinSession session) {
  260. Map<Class<?>, CurrentInstance> old = new HashMap<>();
  261. old.put(VaadinSession.class, set(VaadinSession.class, session));
  262. VaadinService service = null;
  263. if (session != null) {
  264. service = session.getService();
  265. }
  266. old.put(VaadinService.class, set(VaadinService.class, service));
  267. return old;
  268. }
  269. private static Logger getLogger() {
  270. return Logger.getLogger(CurrentInstance.class.getName());
  271. }
  272. }