/* * 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. *

* 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. *

* Currently the framework uses the following instances: *

*

* Inheritable: {@link UI}, {@link VaadinService}, {@link VaadinSession}. *

*

* Non-inheritable: {@link VaadinRequest}, {@link VaadinResponse}. *

* * @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 instance; private final boolean inheritable; private static InheritableThreadLocal, CurrentInstance>> instances = new InheritableThreadLocal, CurrentInstance>>() { @Override protected Map, CurrentInstance> childValue( Map, CurrentInstance> parentValue) { if (parentValue == null) { return null; } Map, CurrentInstance> value = new HashMap, CurrentInstance>(); // Copy all inheritable values to child map for (Entry, 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(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 null * if there is no current instance. */ public static T get(Class type) { Map, 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, CurrentInstance> map) { for (Iterator, CurrentInstance>> iterator = map .entrySet().iterator(); iterator.hasNext();) { Entry, 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 void set(Class 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 void setInheritable(Class type, T instance) { set(type, instance, true); } private static CurrentInstance set(Class type, T instance, boolean inheritable) { Map, 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, 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, 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 * true if only the inheritable instances should be * included; false to get all instances. * @return a map containing the current instances */ public static Map, CurrentInstance> getInstances( boolean onlyInheritable) { Map, CurrentInstance> map = instances.get(); if (map == null) { return Collections.emptyMap(); } else { Map, CurrentInstance> copy = new HashMap, 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, CurrentInstance> setCurrent(UI ui) { Map, 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, CurrentInstance> setCurrent( VaadinSession session) { Map, CurrentInstance> old = new HashMap, 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()); } }