Class EmulatorContext

java.lang.Object
vaadinx.EmulatorContext

public final class EmulatorContext extends Object
Carries the per-user emulator context — today the browser time zone, plus the owning 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 Type
    Method
    Description
    <T> T
    call(Callable<T> body)
    As run(Runnable) for a body that returns a value or throws.
    static TimeZone
    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 if fetch() 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). Never returns null; the only throw is the UI-thread SD_browser_timezone case.
    get()
    The emulator context for the current thread.
    As get() but returns null instead of throwing when there is no context.
    void
    run(Runnable body)
    Runs body on the calling thread with this context active — its zone visible to currentTimeZone() — restoring any previous context in a finally (so it nests, and pooled threads don't leak a stale context).
    com.vaadin.flow.server.VaadinSession
    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 Executor
    wrap(Executor delegate)
    Wraps delegate so every task submitted through it runs with the submitting thread's context active — the run(Runnable) recipe applied once, at the executor, instead of at every call site:
    The captured browser zone; may be null if unfetched at capture time.

    Methods inherited from class Object

    clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
  • Method Details

    • get

      public static EmulatorContext get()
      The emulator context for the current thread. On a UI thread (a current VaadinSession) builds a fresh snapshot of the session + browser zone; on a background thread returns whatever run(Runnable)/call(Callable) installed. Lenient on the zone — the snapshot's zone() may be null if BrowserTimeZone.fetch() hasn't run yet; the loud SD_browser_timezone check is enforced only by currentTimeZone(), when a zone is actually needed to format.
      Throws:
      IllegalStateException - on a background thread with no context — call get() on the UI thread and propagate it via run(Runnable)/call(Callable).
    • getOrNull

      public static EmulatorContext getOrNull()
      As get() but returns null instead 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 via BrowserTimeZone.getOrNull(), which is lock-safe (it briefly locks to read when needed). With no current session, falls back to whatever context run(Runnable)/call(Callable) propagated onto this thread.
    • run

      public void run(Runnable body)
      Runs body on the calling thread with this context active — its zone visible to currentTimeZone() — restoring any previous context in a finally (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 in currentTimeZone(), so this is a no-op for zone resolution there. Does not install a current session (see class javadoc).
    • call

      public <T> T call(Callable<T> body) throws Exception
      As run(Runnable) for a body that returns a value or throws.
      Throws:
      Exception
    • wrap

      public static ExecutorService wrap(ExecutorService delegate)
      Wraps delegate so every task submitted through it runs with the submitting thread's context active — the run(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

      public static Executor wrap(Executor delegate)
    • 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

      public ZoneId zone()
      The captured browser zone; may be null if unfetched at capture time.
    • currentTimeZone

      public static TimeZone 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 if fetch() 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).
      Never returns null; the only throw is the UI-thread SD_browser_timezone case.