@SuppressWarnings("serial")
public class VaadinSession implements HttpSessionBindingListener, Serializable {
+ private class FutureAccess extends FutureTask<Void> {
+ /**
+ * Snapshot of all non-inheritable current instances at the time this
+ * object was created.
+ */
+ private final Map<Class<?>, CurrentInstance> instances = CurrentInstance
+ .getInstances(true);
+
+ public FutureAccess(Runnable arg0) {
+ super(arg0, null);
+ }
+
+ @Override
+ public Void get() throws InterruptedException, ExecutionException {
+ /*
+ * Help the developer avoid programming patterns that cause
+ * deadlocks unless implemented very carefully. get(long, TimeUnit)
+ * does not have the same detection since a sensible timeout should
+ * avoid completely locking up the application.
+ *
+ * Even though no deadlock could occur after the runnable has been
+ * run, the check is always done as the deterministic behavior makes
+ * it easier to detect potential problems.
+ */
+ VaadinService.verifyNoOtherSessionLocked(VaadinSession.this);
+ return super.get();
+ }
+ }
+
/**
* The name of the parameter that is by default used in e.g. web.xml to
* define the name of the default {@link UI} class.
* session is serialized as long as it doesn't happen while some other
* thread has the lock.
*/
- private transient final ConcurrentLinkedQueue<FutureTask<Void>> pendingAccessQueue = new ConcurrentLinkedQueue<FutureTask<Void>>();
+ private transient final ConcurrentLinkedQueue<FutureAccess> pendingAccessQueue = new ConcurrentLinkedQueue<FutureAccess>();
/**
* Create a new service session tied to a Vaadin service
* <p>
* Please note that the runnable might be invoked on a different thread or
* later on the current thread, which means that custom thread locals might
- * not have the expected values when the runnable is executed. The session
- * and other thread locals provided by Vaadin are set properly before
- * executing the runnable.
+ * not have the expected values when the runnable is executed. Inheritable
+ * values in {@link CurrentInstance} will have the same values as when this
+ * method was invoked. {@link VaadinSession#getCurrent()} and
+ * {@link VaadinService#getCurrent()} are set according to this session
+ * before executing the runnable. Non-inheritable CurrentInstance values
+ * including {@link VaadinService#getCurrentRequest()} and
+ * {@link VaadinService#getCurrentResponse()} will not be defined.
* </p>
* <p>
* The returned future can be used to check for task completion and to
* cancel the task
*/
public Future<Void> access(Runnable runnable) {
- FutureTask<Void> future = new FutureTask<Void>(runnable, null) {
- @Override
- public Void get() throws InterruptedException, ExecutionException {
- /*
- * Help the developer avoid programming patterns that cause
- * deadlocks unless implemented very carefully. get(long,
- * TimeUnit) does not have the same detection since a sensible
- * timeout should avoid completely locking up the application.
- *
- * Even though no deadlock could occur after the runnable has
- * been run, the check is always done as the deterministic
- * behavior makes it easier to detect potential problems.
- */
- VaadinService.verifyNoOtherSessionLocked(VaadinSession.this);
- return super.get();
- }
- };
+ FutureAccess future = new FutureAccess(runnable);
pendingAccessQueue.add(future);
/*
public void runPendingAccessTasks() {
assert hasLock();
- FutureTask<Void> pendingAccess;
- while ((pendingAccess = pendingAccessQueue.poll()) != null) {
- if (!pendingAccess.isCancelled()) {
- accessSynchronously(pendingAccess);
+ if (pendingAccessQueue.isEmpty()) {
+ return;
+ }
+
+ Map<Class<?>, CurrentInstance> oldInstances = CurrentInstance
+ .getInstances(false);
+
+ FutureAccess pendingAccess;
+ try {
+ while ((pendingAccess = pendingAccessQueue.poll()) != null) {
+ if (!pendingAccess.isCancelled()) {
+ CurrentInstance.clearAll();
+ CurrentInstance
+ .restoreThreadLocals(pendingAccess.instances);
+ accessSynchronously(pendingAccess);
+ }
}
+ } finally {
+ CurrentInstance.clearAll();
+ CurrentInstance.restoreThreadLocals(oldInstances);
}
}
* <p>
* Please note that the runnable might be invoked on a different thread or
* later on the current thread, which means that custom thread locals might
- * not have the expected values when the runnable is executed. The UI and
- * other thread locals provided by Vaadin are set properly before executing
- * the runnable.
+ * not have the expected values when the runnable is executed. Inheritable
+ * values in {@link CurrentInstance} will have the same values as when this
+ * method was invoked. {@link UI#getCurrent()},
+ * {@link VaadinSession#getCurrent()} and {@link VaadinService#getCurrent()}
+ * are set according to this UI before executing the runnable.
+ * Non-inheritable CurrentInstance values including
+ * {@link VaadinService#getCurrentRequest()} and
+ * {@link VaadinService#getCurrentResponse()} will not be defined.
* </p>
* <p>
* The returned future can be used to check for task completion and to
package com.vaadin.util;
import java.io.Serializable;
+import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
-import com.vaadin.server.VaadinPortlet;
import com.vaadin.server.VaadinRequest;
import com.vaadin.server.VaadinResponse;
import com.vaadin.server.VaadinService;
-import com.vaadin.server.VaadinServlet;
import com.vaadin.server.VaadinSession;
import com.vaadin.ui.UI;
/**
- * Keeps track of various thread local instances used by the framework.
+ * 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>
* Currently the framework uses the following instances:
* </p>
* <p>
- * Inheritable: {@link UI}, {@link VaadinPortlet}, {@link VaadinService},
- * {@link VaadinServlet}, {@link VaadinSession}.
+ * Inheritable: {@link UI}, {@link VaadinService}, {@link VaadinSession}.
* </p>
* <p>
* Non-inheritable: {@link VaadinRequest}, {@link VaadinResponse}.
* </p>
*
* @author Vaadin Ltd
- * @version @VERSION@
* @since 7.0.0
*/
public class CurrentInstance implements Serializable {
/**
* Sets the current inheritable instance of the given type. A current
- * instance that is inheritable will be available for child threads.
+ * 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
}
}
+ /**
+ * Gets the currently set instances so that they can later be restored using
+ * {@link #restoreThreadLocals(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>();
+ for (Class<?> c : map.keySet()) {
+ CurrentInstance ci = map.get(c);
+ if (ci.inheritable || !onlyInheritable) {
+ copy.put(c, ci);
+ }
+ }
+ return copy;
+ }
+ }
+
/**
* Sets thread locals for the UI and all related classes
*
<td>vaadin=runcomvaadintestscomponentsuiUiAccess::PID_SLog_row_0</td>
<td>2. I was interrupted</td>
</tr>
-
+<tr>
+ <td>click</td>
+ <td>vaadin=runcomvaadintestscomponentsuiUiAccess::/VVerticalLayout[0]/Slot[2]/VVerticalLayout[0]/Slot[5]/VButton[0]/domChild[0]/domChild[0]</td>
+ <td></td>
+</tr>
+<tr>
+ <td>assertText</td>
+ <td>vaadin=runcomvaadintestscomponentsuiUiAccess::PID_SLog_row_3</td>
+ <td>0. accessSynchronously has request? true</td>
+</tr>
+<tr>
+ <td>assertText</td>
+ <td>vaadin=runcomvaadintestscomponentsuiUiAccess::PID_SLog_row_2</td>
+ <td>1. Test value in accessSynchronously: Set before accessSynchronosly</td>
+</tr>
+<tr>
+ <td>assertText</td>
+ <td>vaadin=runcomvaadintestscomponentsuiUiAccess::PID_SLog_row_1</td>
+ <td>2. has request after accessSynchronously? true</td>
+</tr>
+<tr>
+ <td>assertText</td>
+ <td>vaadin=runcomvaadintestscomponentsuiUiAccess::PID_SLog_row_0</td>
+ <td>3. Test value after accessSynchornously: Set in accessSynchronosly</td>
+</tr>
+<tr>
+ <td>click</td>
+ <td>vaadin=runcomvaadintestscomponentsuiUiAccess::/VVerticalLayout[0]/Slot[2]/VVerticalLayout[0]/Slot[6]/VButton[0]/domChild[0]/domChild[0]</td>
+ <td></td>
+</tr>
+<tr>
+ <td>assertText</td>
+ <td>vaadin=runcomvaadintestscomponentsuiUiAccess::PID_SLog_row_3</td>
+ <td>0. access has request? false</td>
+</tr>
+<tr>
+ <td>assertText</td>
+ <td>vaadin=runcomvaadintestscomponentsuiUiAccess::PID_SLog_row_2</td>
+ <td>1. Test value in access: Set before access</td>
+</tr>
+<tr>
+ <td>assertText</td>
+ <td>vaadin=runcomvaadintestscomponentsuiUiAccess::PID_SLog_row_1</td>
+ <td>2. has request after access? true</td>
+</tr>
+<tr>
+ <td>assertText</td>
+ <td>vaadin=runcomvaadintestscomponentsuiUiAccess::PID_SLog_row_0</td>
+ <td>3. Test value after access: Set before run pending</td>
+</tr>
</tbody></table>
</body>
</html>
import com.vaadin.tests.components.AbstractTestUIWithLog;
import com.vaadin.ui.Button;
import com.vaadin.ui.Button.ClickEvent;
+import com.vaadin.util.CurrentInstance;
public class UiAccess extends AbstractTestUIWithLog {
private Future<Void> checkFromBeforeClientResponse;
+ private class CurrentInstanceTestType {
+ private String value;
+
+ public CurrentInstanceTestType(String value) {
+ this.value = value;
+ }
+
+ @Override
+ public String toString() {
+ return value;
+ }
+ }
+
@Override
protected void setup(VaadinRequest request) {
addComponent(new Button("Access from UI thread",
}.start();
}
}));
+ addComponent(new Button("CurrentInstance accessSynchronously values",
+ new Button.ClickListener() {
+ @Override
+ public void buttonClick(ClickEvent event) {
+ log.clear();
+ // accessSynchronously should maintain values
+ CurrentInstance.set(CurrentInstanceTestType.class,
+ new CurrentInstanceTestType(
+ "Set before accessSynchronosly"));
+ accessSynchronously(new Runnable() {
+ @Override
+ public void run() {
+ log.log("accessSynchronously has request? "
+ + (VaadinService.getCurrentRequest() != null));
+ log.log("Test value in accessSynchronously: "
+ + CurrentInstance
+ .get(CurrentInstanceTestType.class));
+ CurrentInstance.set(
+ CurrentInstanceTestType.class,
+ new CurrentInstanceTestType(
+ "Set in accessSynchronosly"));
+ }
+ });
+ log.log("has request after accessSynchronously? "
+ + (VaadinService.getCurrentRequest() != null));
+ log("Test value after accessSynchornously: "
+ + CurrentInstance
+ .get(CurrentInstanceTestType.class));
+ }
+ }));
+ addComponent(new Button("CurrentInstance access values",
+ new Button.ClickListener() {
+ @Override
+ public void buttonClick(ClickEvent event) {
+ log.clear();
+ // accessSynchronously should maintain values
+ CurrentInstance
+ .setInheritable(CurrentInstanceTestType.class,
+ new CurrentInstanceTestType(
+ "Set before access"));
+ access(new Runnable() {
+ @Override
+ public void run() {
+ log.log("access has request? "
+ + (VaadinService.getCurrentRequest() != null));
+ log.log("Test value in access: "
+ + CurrentInstance
+ .get(CurrentInstanceTestType.class));
+ CurrentInstance.setInheritable(
+ CurrentInstanceTestType.class,
+ new CurrentInstanceTestType(
+ "Set in access"));
+ }
+ });
+ CurrentInstance.setInheritable(
+ CurrentInstanceTestType.class,
+ new CurrentInstanceTestType(
+ "Set before run pending"));
+
+ getSession().runPendingAccessTasks();
+
+ log.log("has request after access? "
+ + (VaadinService.getCurrentRequest() != null));
+ log("Test value after access: "
+ + CurrentInstance
+ .get(CurrentInstanceTestType.class));
+ }
+ }));
}
@Override