123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356 |
- /*
- * Copyright 2000-2014 Vaadin Ltd.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
- package com.vaadin.util;
-
- import java.io.Serializable;
- import java.lang.ref.WeakReference;
- import java.util.Collections;
- import java.util.HashMap;
- import java.util.Iterator;
- import java.util.Map;
- import java.util.Map.Entry;
- import java.util.logging.Level;
- import java.util.logging.Logger;
-
- import com.vaadin.server.VaadinRequest;
- import com.vaadin.server.VaadinResponse;
- import com.vaadin.server.VaadinService;
- import com.vaadin.server.VaadinSession;
- import com.vaadin.ui.UI;
-
- /**
- * Keeps track of various current instances for the current thread. All the
- * instances are automatically cleared after handling a request from the client
- * to avoid leaking memory. The inheritable values are also maintained when
- * execution is moved to another thread, both when a new thread is created and
- * when {@link VaadinSession#access(Runnable)} or {@link UI#access(Runnable)} is
- * used.
- * <p>
- * Please note that the instances are stored using {@link WeakReference}. This
- * means that the a current instance value may suddenly disappear if there a no
- * other references to the object.
- * <p>
- * Currently the framework uses the following instances:
- * </p>
- * <p>
- * Inheritable: {@link UI}, {@link VaadinService}, {@link VaadinSession}.
- * </p>
- * <p>
- * Non-inheritable: {@link VaadinRequest}, {@link VaadinResponse}.
- * </p>
- *
- * @author Vaadin Ltd
- * @since 7.0.0
- */
- public class CurrentInstance implements Serializable {
- private static final Object NULL_OBJECT = new Object();
- private static final CurrentInstance CURRENT_INSTANCE_NULL = new CurrentInstance(
- NULL_OBJECT, true);
-
- private final WeakReference<Object> instance;
- private final boolean inheritable;
-
- private static InheritableThreadLocal<Map<Class<?>, CurrentInstance>> instances = new InheritableThreadLocal<Map<Class<?>, CurrentInstance>>() {
- @Override
- protected Map<Class<?>, CurrentInstance> childValue(
- Map<Class<?>, CurrentInstance> parentValue) {
- if (parentValue == null) {
- return null;
- }
-
- Map<Class<?>, CurrentInstance> value = new HashMap<Class<?>, CurrentInstance>();
-
- // Copy all inheritable values to child map
- for (Entry<Class<?>, CurrentInstance> e : parentValue.entrySet()) {
- if (e.getValue().inheritable) {
- value.put(e.getKey(), e.getValue());
- }
- }
-
- return value;
- }
- };
-
- private CurrentInstance(Object instance, boolean inheritable) {
- this.instance = new WeakReference<Object>(instance);
- this.inheritable = inheritable;
- }
-
- /**
- * Gets the current instance of a specific type if available.
- *
- * @param type
- * the class to get an instance of
- * @return the current instance or the provided type, or <code>null</code>
- * if there is no current instance.
- */
- public static <T> T get(Class<T> type) {
- Map<Class<?>, CurrentInstance> map = instances.get();
- if (map == null) {
- return null;
- }
- CurrentInstance currentInstance = map.get(type);
- if (currentInstance != null) {
- Object value = currentInstance.instance.get();
- if (value == null) {
- /*
- * This is believed to never actually happen since the
- * ThreadLocal should only outlive the referenced object on
- * threads that are not doing anything related to Vaadin, which
- * should thus never invoke CurrentInstance.get().
- *
- * At this point, there might also be other values that have
- * been collected, so we'll scan the entire map and remove stale
- * CurrentInstance objects. Using a ReferenceQueue could make
- * this assumingly rare case slightly more efficient, but would
- * significantly increase the complexity of the code for
- * maintaining a separate ReferenceQueue for each Thread.
- */
- removeStaleInstances(map);
-
- if (map.isEmpty()) {
- instances.remove();
- }
-
- return null;
- }
- return type.cast(value);
- } else {
- return null;
- }
- }
-
- private static void removeStaleInstances(Map<Class<?>, CurrentInstance> map) {
- for (Iterator<Entry<Class<?>, CurrentInstance>> iterator = map
- .entrySet().iterator(); iterator.hasNext();) {
- Entry<Class<?>, CurrentInstance> entry = iterator.next();
- Object instance = entry.getValue().instance.get();
- if (instance == null) {
- iterator.remove();
- getLogger().log(Level.FINE,
- "CurrentInstance for {0} has been garbage collected.",
- entry.getKey());
- }
- }
- }
-
- /**
- * Sets the current instance of the given type.
- *
- * @see #setInheritable(Class, Object)
- * @see ThreadLocal
- *
- * @param type
- * the class that should be used when getting the current
- * instance back
- * @param instance
- * the actual instance
- */
- public static <T> void set(Class<T> type, T instance) {
- set(type, instance, false);
- }
-
- /**
- * Sets the current inheritable instance of the given type. A current
- * instance that is inheritable will be available for child threads and in
- * code run by {@link VaadinSession#access(Runnable)} and
- * {@link UI#access(Runnable)}.
- *
- * @see #set(Class, Object)
- * @see InheritableThreadLocal
- *
- * @param type
- * the class that should be used when getting the current
- * instance back
- * @param instance
- * the actual instance
- */
- public static <T> void setInheritable(Class<T> type, T instance) {
- set(type, instance, true);
- }
-
- private static <T> CurrentInstance set(Class<T> type, T instance,
- boolean inheritable) {
- Map<Class<?>, CurrentInstance> map = instances.get();
- CurrentInstance previousInstance = null;
- if (instance == null) {
- // remove the instance
- if (map != null) {
- previousInstance = map.remove(type);
- if (map.isEmpty()) {
- instances.remove();
- map = null;
- }
- }
- } else {
- assert type.isInstance(instance) : "Invald instance type";
- if (map == null) {
- map = new HashMap<Class<?>, CurrentInstance>();
- instances.set(map);
- }
-
- previousInstance = map.put(type, new CurrentInstance(instance,
- inheritable));
- if (previousInstance != null) {
- assert previousInstance.inheritable == inheritable : "Inheritable status mismatch for "
- + type
- + " (previous was "
- + previousInstance.inheritable
- + ", new is "
- + inheritable + ")";
- }
- }
- if (previousInstance == null) {
- previousInstance = CURRENT_INSTANCE_NULL;
- }
- return previousInstance;
- }
-
- /**
- * Clears all current instances.
- */
- public static void clearAll() {
- instances.remove();
- }
-
- /**
- * Restores the given instances to the given values. Note that this should
- * only be used internally to restore Vaadin classes.
- *
- * @since 7.1
- *
- * @param old
- * A Class -> CurrentInstance map to set as current instances
- */
- public static void restoreInstances(Map<Class<?>, CurrentInstance> old) {
- boolean removeStale = false;
- for (Class c : old.keySet()) {
- CurrentInstance ci = old.get(c);
- Object v = ci.instance.get();
- if (v == null) {
- removeStale = true;
- } else if (v == NULL_OBJECT) {
- /*
- * NULL_OBJECT is used to identify objects that are null when
- * #setCurrent(UI) or #setCurrent(VaadinSession) are called on a
- * CurrentInstance. Without this a reference to an already
- * collected instance may be left in the CurrentInstance when it
- * really should be restored to null.
- *
- * One example case that this fixes:
- * VaadinService.runPendingAccessTasks() clears all current
- * instances and then sets everything but the UI. This makes
- * UI.accessSynchronously() save these values before calling
- * setCurrent(UI), which stores UI=null in the map it returns.
- * This map will be restored after UI.accessSync(), which,
- * unless it respects null values, will just leave the wrong UI
- * instance registered.
- */
- set(c, null, ci.inheritable);
- } else {
- set(c, v, ci.inheritable);
- }
- }
-
- if (removeStale) {
- removeStaleInstances(old);
- }
- }
-
- /**
- * Gets the currently set instances so that they can later be restored using
- * {@link #restoreInstances(Map)}.
- *
- * @since 7.1
- *
- * @param onlyInheritable
- * <code>true</code> if only the inheritable instances should be
- * included; <code>false</code> to get all instances.
- * @return a map containing the current instances
- */
- public static Map<Class<?>, CurrentInstance> getInstances(
- boolean onlyInheritable) {
- Map<Class<?>, CurrentInstance> map = instances.get();
- if (map == null) {
- return Collections.emptyMap();
- } else {
- Map<Class<?>, CurrentInstance> copy = new HashMap<Class<?>, CurrentInstance>();
- boolean removeStale = false;
- for (Class<?> c : map.keySet()) {
- CurrentInstance ci = map.get(c);
- if (ci.instance.get() == null) {
- removeStale = true;
- } else if (ci.inheritable || !onlyInheritable) {
- copy.put(c, ci);
- }
- }
- if (removeStale) {
- removeStaleInstances(map);
- if (map.isEmpty()) {
- instances.remove();
- }
- }
- return copy;
- }
- }
-
- /**
- * Sets current instances for the UI and all related classes. The previously
- * defined values can be restored by passing the returned map to
- * {@link #restoreInstances(Map)}.
- *
- * @since 7.1
- *
- * @param ui
- * The UI
- * @return A map containing the old values of the instances that this method
- * updated.
- */
- public static Map<Class<?>, CurrentInstance> setCurrent(UI ui) {
- Map<Class<?>, CurrentInstance> old = setCurrent(ui.getSession());
- old.put(UI.class, set(UI.class, ui, true));
- return old;
- }
-
- /**
- * Sets current instances for the {@link VaadinSession} and all related
- * classes. The previously defined values can be restored by passing the
- * returned map to {@link #restoreInstances(Map)}.
- *
- * @since 7.1
- *
- * @param session
- * The VaadinSession
- * @return A map containing the old values of the instances this method
- * updated.
- */
- public static Map<Class<?>, CurrentInstance> setCurrent(
- VaadinSession session) {
- Map<Class<?>, CurrentInstance> old = new HashMap<Class<?>, CurrentInstance>();
- old.put(VaadinSession.class, set(VaadinSession.class, session, true));
- VaadinService service = null;
- if (session != null) {
- service = session.getService();
- }
- old.put(VaadinService.class, set(VaadinService.class, service, true));
- return old;
- }
-
- private static Logger getLogger() {
- return Logger.getLogger(CurrentInstance.class.getName());
- }
- }
|