Class EmulatorContext
VaadinSession — from a Vaadin UI thread onto a background
thread, so emulator date code (SimpleDateFormat and the
Calendar emulator) formats in the user's zone off the UI thread too.
Why this is needed. On the desktop one JVM serves one user, so
TimeZone.getDefault() is the user's zone on every thread — EDT and
background alike. On the server the user's zone is per-session and lives on
the UI thread; a background thread doesn't know whose user it serves, so
without help it falls back to the server zone, which is usually
wrong. This class is the propagation vehicle: get() on the UI thread
snapshots the zone, run(Runnable) / call(Callable) make it
visible on the worker thread.
This never calls VaadinSession.setCurrent(VaadinSession) or
UI.setCurrent. Inside a run(Runnable)/call(Callable) body,
VaadinSession.getCurrent() stays null — only the captured
browser time zone is exposed, and only to emulator date code via
currentTimeZone(). The captured VaadinSession is a handle,
never a current session: it is what lock-aware delivery (SwingWorker.done()
through EHelper.onSessionLiveUI(VaadinSession, Runnable)) and the off-UI-thread peer write
(Component.withPeer(Consumer) through EHelper.runInUIThread(boolean, Runnable)) take
the lock with. It is never used for off-lock session-attribute access.
SB-Emulators's own background primitives (SwingWorker) capture and
apply the context automatically. A migrator's own executor gets it with one wrap:
this.pool = EmulatorContext.wrap(Executors.newFixedThreadPool(4)); // once, at startup
pool.submit(() -> { ...; label.setText("done"); }); // ordinary submits
A thread the app creates by hand carries it the long way, since there is no
construction to wrap:
EmulatorContext ctx = EmulatorContext.get(); // on the UI thread
new Thread(() -> ctx.run(() -> { ... })).start(); // on the worker thread
-
Method Summary
Modifier and TypeMethodDescription<T> TAsrun(Runnable)for a body that returns a value or throws.static TimeZoneThe time zone emulator date code (SimpleDateFormat, the Calendar emulator) should use on the current thread: UI thread → the browser zone (BrowserTimeZone.get(), which throws SD_browser_timezone iffetch()was never called — a loud, one-line-fix wiring bug); background thread with a propagated context → that context's zone; background thread with none (or a zone-less context) → the server default zone, best-effort, with a once-per-thread WARN pointing atrun(Runnable)/call(Callable). Never returnsnull; the only throw is the UI-thread SD_browser_timezone case.static EmulatorContextget()The emulator context for the current thread.static EmulatorContextAsget()but returnsnullinstead of throwing when there is no context.voidRunsbodyon the calling thread with this context active — its zone visible tocurrentTimeZone()— restoring any previous context in afinally(so it nests, and pooled threads don't leak a stale context).com.vaadin.flow.server.VaadinSessionsession()The captured session — retained only as a handle for lock-aware result delivery (see class javadoc); it is never made current and grants no UI access.static ExecutorAswrap(ExecutorService), for a bareExecutor.static ExecutorServicewrap(ExecutorService delegate) Wrapsdelegateso every task submitted through it runs with the submitting thread's context active — therun(Runnable)recipe applied once, at the executor, instead of at every call site:zone()The captured browser zone; may benullif unfetched at capture time.
-
Method Details
-
get
The emulator context for the current thread. On a UI thread (a currentVaadinSession) builds a fresh snapshot of the session + browser zone; on a background thread returns whateverrun(Runnable)/call(Callable)installed. Lenient on the zone — the snapshot'szone()may benullifBrowserTimeZone.fetch()hasn't run yet; the loud SD_browser_timezone check is enforced only bycurrentTimeZone(), when a zone is actually needed to format.- Throws:
IllegalStateException- on a background thread with no context — callget()on the UI thread and propagate it viarun(Runnable)/call(Callable).
-
getOrNull
Asget()but returnsnullinstead of throwing when there is no context. Never throws — including on a session-associated but unlocked thread (Vaadin's license checker on a Jetty request-pool thread is the canonical case): a session snapshot captures the browser zone viaBrowserTimeZone.getOrNull(), which is lock-safe (it briefly locks to read when needed). With no current session, falls back to whatever contextrun(Runnable)/call(Callable)propagated onto this thread. -
run
Runsbodyon the calling thread with this context active — its zone visible tocurrentTimeZone()— restoring any previous context in afinally(so it nests, and pooled threads don't leak a stale context). Intended for background threads; on a UI thread the live session takes precedence incurrentTimeZone(), so this is a no-op for zone resolution there. Does not install a current session (see class javadoc). -
call
Asrun(Runnable)for a body that returns a value or throws.- Throws:
Exception
-
wrap
Wrapsdelegateso every task submitted through it runs with the submitting thread's context active — therun(Runnable)recipe applied once, at the executor, instead of at every call site:// once, wherever the app builds its pool — no session needed here this.pool = EmulatorContext.wrap(Executors.newFixedThreadPool(4)); // thereafter, ordinary submits from a UI thread; the worker sees the context pool.submit(() -> { ...; label.setText("done"); });The context is captured per submit, on the submitting thread — never when the executor is wrapped. That is the whole reason this exists: a pool is typically built once at app startup, where there is no session to capture, so a wrap-time snapshot would capture nothing for the pool's entire life. Capturing per submit also chains — a submit from a worker that already carries a context propagates that one.
A submit from a thread with no context of its own is passed through unchanged, so the task runs contextless rather than borrowing an unrelated user's session.
- Parameters:
delegate- the app's own executor; it is not shut down or otherwise adopted- Returns:
- a view over
delegate— both remain usable, and only work submitted through the returned instance carries the context
-
wrap
Aswrap(ExecutorService), for a bareExecutor. -
session
public com.vaadin.flow.server.VaadinSession session()The captured session — retained only as a handle for lock-aware result delivery (see class javadoc); it is never made current and grants no UI access. -
zone
The captured browser zone; may benullif unfetched at capture time. -
currentTimeZone
The time zone emulator date code (SimpleDateFormat, the Calendar emulator) should use on the current thread:- UI thread → the browser zone (
BrowserTimeZone.get(), which throws SD_browser_timezone iffetch()was never called — a loud, one-line-fix wiring bug); - background thread with a propagated context → that context's zone;
- background thread with none (or a zone-less context) → the server
default zone, best-effort, with a once-per-thread WARN pointing at
run(Runnable)/call(Callable).
null; the only throw is the UI-thread SD_browser_timezone case. - UI thread → the browser zone (
-