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

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