From aca4876199e0d3858793bf8b3df02a678cb72099 Mon Sep 17 00:00:00 2001 From: Marco Collovati Date: Mon, 4 Nov 2024 11:19:33 +0100 Subject: [PATCH] fix: support serialization of Vaadin scoped beans Makes sure that VaadinSession and UI thread locals are available during both serialization and deserialization, to allow other libraries to perform inspection and injection of Vaadin scoped beans. Also refactors VaadinRouteScope to be independent from VaadinService when fetching RouteScopeOwner annotation for the bean, and replaces VaadinSession.unlock() calls with direct access to the lock instance to prevent unwanted push during bean lookup. Fixes #19967 Part of vaadin/kubernetes-kit#140 --- .../com/vaadin/flow/component/Component.java | 40 +++++ .../com/vaadin/flow/server/VaadinSession.java | 53 ++++-- .../tests/server/SerializationTest.java | 167 +++++++++++++++++- .../vaadin/flow/spring/scopes/BeanStore.java | 4 +- .../flow/spring/scopes/VaadinRouteScope.java | 95 ++++++---- .../spring/scopes/VaadinSessionScope.java | 5 +- .../flow/spring/scopes/VaadinUIScope.java | 4 +- .../spring/SpringClassesSerializableTest.java | 1 + .../flow/spring/scopes/AbstractScopeTest.java | 2 + .../spring/scopes/VaadinRouteScopeTest.java | 15 +- 10 files changed, 317 insertions(+), 69 deletions(-) diff --git a/flow-server/src/main/java/com/vaadin/flow/component/Component.java b/flow-server/src/main/java/com/vaadin/flow/component/Component.java index a68d6895e8d..854a7ea97eb 100644 --- a/flow-server/src/main/java/com/vaadin/flow/component/Component.java +++ b/flow-server/src/main/java/com/vaadin/flow/component/Component.java @@ -15,10 +15,15 @@ */ package com.vaadin.flow.component; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serial; import java.io.Serializable; import java.util.Collection; import java.util.Collections; import java.util.Locale; +import java.util.Map; import java.util.Optional; import java.util.stream.Stream; import java.util.stream.Stream.Builder; @@ -32,6 +37,7 @@ import com.vaadin.flow.dom.ShadowRoot; import com.vaadin.flow.i18n.I18NProvider; import com.vaadin.flow.internal.AnnotationReader; +import com.vaadin.flow.internal.CurrentInstance; import com.vaadin.flow.internal.LocaleUtil; import com.vaadin.flow.internal.nodefeature.ElementData; import com.vaadin.flow.server.Attributes; @@ -820,4 +826,38 @@ public void removeFromParent() { getElement().removeFromParent(); } + @Serial + private void writeObject(ObjectOutputStream out) throws IOException { + if (this instanceof UI ui) { + Map, CurrentInstance> instances = CurrentInstance + .setCurrent(ui); + try { + out.defaultWriteObject(); + } finally { + CurrentInstance.restoreInstances(instances); + } + } else { + out.defaultWriteObject(); + } + } + + @Serial + private void readObject(ObjectInputStream in) + throws IOException, ClassNotFoundException { + if (this instanceof UI ui) { + Map, CurrentInstance> instances = CurrentInstance + .getInstances(); + // Cannot use CurrentInstance.setCurrent(this) because it will try + // to get VaadinSession from UI.internals that is not yet available + CurrentInstance.set(UI.class, ui); + try { + in.defaultReadObject(); + } finally { + CurrentInstance.restoreInstances(instances); + } + } else { + in.defaultReadObject(); + } + } + } diff --git a/flow-server/src/main/java/com/vaadin/flow/server/VaadinSession.java b/flow-server/src/main/java/com/vaadin/flow/server/VaadinSession.java index b1170eb84ff..09dacdb03d8 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/VaadinSession.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/VaadinSession.java @@ -19,6 +19,7 @@ import jakarta.servlet.http.HttpSession; import jakarta.servlet.http.HttpSessionBindingEvent; import jakarta.servlet.http.HttpSessionBindingListener; + import java.io.IOException; import java.io.ObjectInputStream; import java.io.Serializable; @@ -1097,6 +1098,14 @@ private void readObject(ObjectInputStream stream) Map, CurrentInstance> old = CurrentInstance.setCurrent(this); try { stream.defaultReadObject(); + // Add-ons may have Listener classes that nullify themselves during + // serialization (e.g. Collaboration Kit) and restore instances in + // some custom way later on. + // Removing null elements prevents application to fail if restore + // actions are not applied eagerly + requestHandlers.remove(null); + destroyListeners.remove(null); + uIs = (Map) stream.readObject(); resourceRegistry = (StreamResourceRegistry) stream.readObject(); pendingAccessQueue = new ConcurrentLinkedQueue<>(); @@ -1107,27 +1116,33 @@ private void readObject(ObjectInputStream stream) private void writeObject(java.io.ObjectOutputStream stream) throws IOException { - boolean serializeUIs = true; - - // If service is null it has just been deserialized and should be - // serialized in - // the same way again - if (getService() != null) { - ApplicationConfiguration appConfiguration = ApplicationConfiguration - .get(getService().getContext()); - if (!appConfiguration.isProductionMode() && !appConfiguration - .isDevModeSessionSerializationEnabled()) { - serializeUIs = false; + Map, CurrentInstance> instanceMap = CurrentInstance + .setCurrent(this); + try { + boolean serializeUIs = true; + + // If service is null it has just been deserialized and should be + // serialized in + // the same way again + if (getService() != null) { + ApplicationConfiguration appConfiguration = ApplicationConfiguration + .get(getService().getContext()); + if (!appConfiguration.isProductionMode() && !appConfiguration + .isDevModeSessionSerializationEnabled()) { + serializeUIs = false; + } } - } - stream.defaultWriteObject(); - if (serializeUIs) { - stream.writeObject(uIs); - stream.writeObject(resourceRegistry); - } else { - stream.writeObject(new HashMap<>()); - stream.writeObject(new StreamResourceRegistry(this)); + stream.defaultWriteObject(); + if (serializeUIs) { + stream.writeObject(uIs); + stream.writeObject(resourceRegistry); + } else { + stream.writeObject(new HashMap<>()); + stream.writeObject(new StreamResourceRegistry(this)); + } + } finally { + CurrentInstance.restoreInstances(instanceMap); } } diff --git a/flow-server/src/test/java/com/vaadin/tests/server/SerializationTest.java b/flow-server/src/test/java/com/vaadin/tests/server/SerializationTest.java index 57be740c9e1..e646a5d3b3f 100644 --- a/flow-server/src/test/java/com/vaadin/tests/server/SerializationTest.java +++ b/flow-server/src/test/java/com/vaadin/tests/server/SerializationTest.java @@ -5,19 +5,29 @@ import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; +import java.io.Serial; import java.io.Serializable; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Consumer; +import org.junit.After; import org.junit.Assert; +import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; +import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.Tag; +import com.vaadin.flow.component.UI; import com.vaadin.flow.function.DeploymentConfiguration; +import com.vaadin.flow.server.RequestHandler; import com.vaadin.flow.server.StreamRegistration; import com.vaadin.flow.server.StreamResource; import com.vaadin.flow.server.VaadinContext; import com.vaadin.flow.server.VaadinRequest; +import com.vaadin.flow.server.VaadinResponse; import com.vaadin.flow.server.VaadinService; import com.vaadin.flow.server.VaadinServletService; import com.vaadin.flow.server.VaadinSession; @@ -29,6 +39,30 @@ public class SerializationTest { + Runnable cleaner; + + @Before + public void enabledSerializationDebugInfo() { + String extendedDebugInfo = System + .getProperty("sun.io.serialization.extendedDebugInfo"); + System.setProperty("sun.io.serialization.extendedDebugInfo", "true"); + cleaner = () -> { + if (extendedDebugInfo != null) { + System.setProperty("sun.io.serialization.extendedDebugInfo", + extendedDebugInfo); + } else { + System.clearProperty("sun.io.serialization.extendedDebugInfo"); + } + }; + } + + @After + public void restore() { + if (cleaner != null) { + cleaner.run(); + } + } + @Test public void testSerializeVaadinSession_accessQueueIsRecreated() throws Exception { @@ -123,8 +157,119 @@ public void testSerializeVaadinSession_notProductionMode_canSerializeWithoutTran Assert.assertNull(againSerializedAndDeserializedSession.getService()); } + @Test + // Covers serialization of UI scoped beans, e.g. in Kubernetes Kit + // https://github.com/vaadin/flow/issues/19967 + // https://github.com/vaadin/kubernetes-kit/issues/140 + public void serializeUI_currentUI_availableDuringSerialization() + throws Exception { + VaadinSession deserializeSession = serializeAndDeserializeWithUI(true, + true, ui -> ui.add(new MyComponent())); + MyComponent deserializedComponent = deserializeSession.getUIs() + .iterator().next().getChildren() + .filter(MyComponent.class::isInstance) + .map(MyComponent.class::cast).findFirst() + .orElseThrow(() -> new AssertionError( + "Custom component has not been deserialized")); + + deserializedComponent.checker.assertInstancesAvailable(); + } + + @Test + // Covers serialization of UI scoped beans, e.g. in Kubernetes Kit + // https://github.com/vaadin/flow/issues/19967 + // https://github.com/vaadin/kubernetes-kit/issues/140 + public void serializeUI_currentVaadinSession_availableDuringSerialization() + throws Exception { + VaadinSession deserializeSession = serializeAndDeserializeWithUI(true, + true, + ui -> ui.getSession().addRequestHandler(new MyListener())); + + MyListener deserializedListener = deserializeSession + .getRequestHandlers().stream() + .filter(MyListener.class::isInstance) + .map(MyListener.class::cast).findFirst() + .orElseThrow(() -> new AssertionError( + "Session request listener has not been deserialized")); + + deserializedListener.checker.assertSessionAvailable(); + } + + private static class SerializationInstancesChecker implements Serializable { + private boolean uiAvailableOnRead = false; + private boolean sessionAvailableOnRead = false; + private boolean uiAvailableOnWrite = false; + private boolean sessionAvailableOnWrite = false; + + @Serial + private void writeObject(ObjectOutputStream out) throws IOException { + uiAvailableOnWrite = UI.getCurrent() != null; + sessionAvailableOnWrite = VaadinSession.getCurrent() != null; + out.defaultWriteObject(); + } + + @Serial + private void readObject(ObjectInputStream in) + throws IOException, ClassNotFoundException { + in.defaultReadObject(); + uiAvailableOnRead = UI.getCurrent() != null; + sessionAvailableOnRead = VaadinSession.getCurrent() != null; + } + + void assertInstancesAvailable() { + assertUIAvailable(); + assertSessionAvailable(); + } + + void assertUIAvailable() { + Assert.assertTrue( + "Expecting serialization hook to be called with UI thread local set", + uiAvailableOnWrite); + Assert.assertTrue( + "Expecting deserialization hook to be called with UI thread local set", + uiAvailableOnRead); + } + + void assertSessionAvailable() { + Assert.assertTrue( + "Expecting serialization hook to be called with VaadinSession thread local set", + sessionAvailableOnWrite); + Assert.assertTrue( + "Expecting deserialization hook to be called with VaadinSession thread local set", + sessionAvailableOnRead); + } + + } + + @Tag("my-component") + private static class MyComponent extends Component { + + private final SerializationInstancesChecker checker = new SerializationInstancesChecker(); + + } + + private static class MyListener implements RequestHandler { + + private final SerializationInstancesChecker checker = new SerializationInstancesChecker(); + + @Override + public boolean handleRequest(VaadinSession session, + VaadinRequest request, VaadinResponse response) + throws IOException { + return false; + } + } + + private static VaadinSession serializeAndDeserializeWithUI( + boolean serializeUI) throws Exception { + return serializeAndDeserializeWithUI(serializeUI, false, ui -> { + }); + } + private static VaadinSession serializeAndDeserializeWithUI( - boolean serializeUI) throws IOException, ClassNotFoundException { + boolean serializeUI, boolean background, Consumer uiConsumer) + throws Exception { + VaadinService vaadinService = new MockVaadinService(false, serializeUI); VaadinSession session = new VaadinSession(vaadinService); // This is done only for test purpose to init the session lock, @@ -136,13 +281,25 @@ private static VaadinSession serializeAndDeserializeWithUI( MockUI ui = new MockUI(session); ui.doInit(null, 42); session.addUI(ui); - - session = serializeAndDeserialize(session); + uiConsumer.accept(ui); + + VaadinSession deserializedSession; + if (background) { + deserializedSession = CompletableFuture.supplyAsync(() -> { + try { + return serializeAndDeserialize(session); + } catch (Exception e) { + throw new RuntimeException(e); + } + }).get(); + } else { + deserializedSession = serializeAndDeserialize(session); + } // This is done only for test purpose to refresh the session lock, // should be called by Flow internally as soon as the session has // been retrieved from http session. - session.refreshTransients(null, vaadinService); - return session; + deserializedSession.refreshTransients(null, vaadinService); + return deserializedSession; } private static S serializeAndDeserialize(S s) diff --git a/vaadin-spring/src/main/java/com/vaadin/flow/spring/scopes/BeanStore.java b/vaadin-spring/src/main/java/com/vaadin/flow/spring/scopes/BeanStore.java index 74e1c1d3c7a..fe077909642 100644 --- a/vaadin-spring/src/main/java/com/vaadin/flow/spring/scopes/BeanStore.java +++ b/vaadin-spring/src/main/java/com/vaadin/flow/spring/scopes/BeanStore.java @@ -149,11 +149,11 @@ private T execute(Supplier supplier) { if (session.hasLock()) { return supplier.get(); } else { - session.lock(); + session.getLockInstance().lock(); try { return supplier.get(); } finally { - session.unlock(); + session.getLockInstance().unlock(); } } } diff --git a/vaadin-spring/src/main/java/com/vaadin/flow/spring/scopes/VaadinRouteScope.java b/vaadin-spring/src/main/java/com/vaadin/flow/spring/scopes/VaadinRouteScope.java index c179f339df8..ae57e1bcb58 100644 --- a/vaadin-spring/src/main/java/com/vaadin/flow/spring/scopes/VaadinRouteScope.java +++ b/vaadin-spring/src/main/java/com/vaadin/flow/spring/scopes/VaadinRouteScope.java @@ -15,8 +15,6 @@ */ package com.vaadin.flow.spring.scopes; -import jakarta.servlet.ServletContext; - import java.io.Serializable; import java.util.Collections; import java.util.HashMap; @@ -26,11 +24,10 @@ import java.util.Set; import org.slf4j.LoggerFactory; +import org.springframework.beans.BeansException; import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.context.ApplicationContext; import org.springframework.lang.NonNull; -import org.springframework.web.context.support.WebApplicationContextUtils; import com.vaadin.flow.component.ComponentEventListener; import com.vaadin.flow.component.ComponentUtil; @@ -45,9 +42,6 @@ import com.vaadin.flow.router.RouterLayout; import com.vaadin.flow.server.UIInitEvent; import com.vaadin.flow.server.UIInitListener; -import com.vaadin.flow.server.VaadinContext; -import com.vaadin.flow.server.VaadinService; -import com.vaadin.flow.server.VaadinServletContext; import com.vaadin.flow.server.VaadinSession; import com.vaadin.flow.shared.Registration; import com.vaadin.flow.spring.annotation.RouteScopeOwner; @@ -63,7 +57,7 @@ * @since * */ -public class VaadinRouteScope extends AbstractScope implements UIInitListener { +public class VaadinRouteScope extends AbstractScope { public static final String VAADIN_ROUTE_SCOPE_NAME = "vaadin-route"; @@ -141,7 +135,7 @@ private void destroy() { } - private class NavigationListener + private static class NavigationListener implements BeforeEnterListener, AfterNavigationListener, ComponentEventListener, Serializable { @@ -315,8 +309,8 @@ public void onComponentEvent(DetachEvent event) { @Override protected Object doGet(String name, ObjectFactory objectFactory) { - RouteScopeOwner owner = getContext().findAnnotationOnBean(name, - RouteScopeOwner.class); + RouteScopeObjectFactory cast = (RouteScopeObjectFactory) objectFactory; + RouteScopeOwner owner = cast.getOwner(); if (!getNavigationListener().hasNavigationOwner(owner)) { assert owner != null; throw new IllegalStateException(String.format( @@ -324,15 +318,18 @@ protected Object doGet(String name, ObjectFactory objectFactory) { + "active navigation components chain: the scope defined by the bean '%s' doesn't exist.", owner.value(), name)); } - return super.doGet(name, objectFactory); + Object object = super.doGet(name, objectFactory); + if (object instanceof ObjectWithOwner wrapper) { + return wrapper.object; + } + return object; } @Override protected void storeBean(String name, Object bean) { - super.storeBean(name, bean); - RouteScopeOwner owner = getContext().findAnnotationOnBean(name, - RouteScopeOwner.class); - getNavigationListener().storeOwner(name, owner); + ObjectWithOwner wrapper = (ObjectWithOwner) bean; + super.storeBean(name, wrapper.object); + getNavigationListener().storeOwner(name, wrapper.owner); } BeanNamesWrapper getBeanNamesWrapper() { @@ -348,17 +345,6 @@ private boolean resetUI() { return true; } - @NonNull - private ApplicationContext getContext() { - VaadinService service = currentUI.getSession().getService(); - VaadinContext context = service.getContext(); - ServletContext servletContext = ((VaadinServletContext) context) - .getContext(); - assert servletContext != null; - return WebApplicationContextUtils - .getRequiredWebApplicationContext(servletContext); - } - @NonNull private NavigationListener getNavigationListener() { NavigationListener navigationListener = ComponentUtil @@ -369,10 +355,52 @@ private NavigationListener getNavigationListener() { } + private record ObjectWithOwner(Object object, RouteScopeOwner owner) { + } + + private static class RouteScopeObjectFactory + implements ObjectFactory { + + private final ObjectFactory objectFactory; + private final RouteScopeOwner owner; + + public RouteScopeObjectFactory(ObjectFactory objectFactory, + RouteScopeOwner owner) { + this.objectFactory = objectFactory; + this.owner = owner; + } + + @Override + public ObjectWithOwner getObject() throws BeansException { + return new ObjectWithOwner(objectFactory.getObject(), owner); + } + + public RouteScopeOwner getOwner() { + return owner; + } + } + + static class NavigationListenerRegistrar implements UIInitListener { + + @Override + public void uiInit(UIInitEvent event) { + NavigationListener listener = new NavigationListener(event.getUI()); + ComponentUtil.setData(event.getUI(), NavigationListener.class, + listener); + } + + } + + private ConfigurableListableBeanFactory beanFactory; + @Override public void postProcessBeanFactory( ConfigurableListableBeanFactory beanFactory) { beanFactory.registerScope(VAADIN_ROUTE_SCOPE_NAME, this); + beanFactory.registerSingleton( + NavigationListenerRegistrar.class.getName(), + new NavigationListenerRegistrar()); + this.beanFactory = beanFactory; } @Override @@ -381,16 +409,15 @@ public String getConversationId() { } @Override - public void uiInit(UIInitEvent event) { - NavigationListener listener = new NavigationListener(event.getUI()); - ComponentUtil.setData(event.getUI(), NavigationListener.class, - listener); + public Object get(String name, ObjectFactory objectFactory) { + return super.get(name, new RouteScopeObjectFactory(objectFactory, + beanFactory.findAnnotationOnBean(name, RouteScopeOwner.class))); } @Override protected BeanStore getBeanStore() { final VaadinSession session = getVaadinSession(); - session.lock(); + session.getLockInstance().lock(); try { BeanStore store = getBeanStoreIfExists(session); if (store == null) { @@ -400,11 +427,11 @@ protected BeanStore getBeanStore() { } return store; } finally { - session.unlock(); + session.getLockInstance().unlock(); } } - private RouteBeanStore getBeanStoreIfExists(VaadinSession session) { + private static RouteBeanStore getBeanStoreIfExists(VaadinSession session) { assert session.hasLock(); RouteStoreWrapper wrapper = session .getAttribute(RouteStoreWrapper.class); diff --git a/vaadin-spring/src/main/java/com/vaadin/flow/spring/scopes/VaadinSessionScope.java b/vaadin-spring/src/main/java/com/vaadin/flow/spring/scopes/VaadinSessionScope.java index a4893aaa204..5d5aa11e602 100644 --- a/vaadin-spring/src/main/java/com/vaadin/flow/spring/scopes/VaadinSessionScope.java +++ b/vaadin-spring/src/main/java/com/vaadin/flow/spring/scopes/VaadinSessionScope.java @@ -19,7 +19,6 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import com.vaadin.flow.server.VaadinSession; -import com.vaadin.flow.shared.Registration; /** * Implementation of Spring's @@ -67,7 +66,7 @@ public String getConversationId() { @Override protected BeanStore getBeanStore() { final VaadinSession session = getVaadinSession(); - session.lock(); + session.getLockInstance().lock(); try { BeanStore beanStore = session.getAttribute(BeanStore.class); if (beanStore == null) { @@ -76,7 +75,7 @@ protected BeanStore getBeanStore() { } return beanStore; } finally { - session.unlock(); + session.getLockInstance().unlock(); } } diff --git a/vaadin-spring/src/main/java/com/vaadin/flow/spring/scopes/VaadinUIScope.java b/vaadin-spring/src/main/java/com/vaadin/flow/spring/scopes/VaadinUIScope.java index bfc11b79203..9e86020f460 100644 --- a/vaadin-spring/src/main/java/com/vaadin/flow/spring/scopes/VaadinUIScope.java +++ b/vaadin-spring/src/main/java/com/vaadin/flow/spring/scopes/VaadinUIScope.java @@ -108,7 +108,7 @@ public String getConversationId() { @Override protected BeanStore getBeanStore() { final VaadinSession session = getVaadinSession(); - session.lock(); + session.getLockInstance().lock(); try { UIStoreWrapper wrapper = session.getAttribute(UIStoreWrapper.class); if (wrapper == null) { @@ -117,7 +117,7 @@ protected BeanStore getBeanStore() { } return wrapper.getBeanStore(getUI()); } finally { - session.unlock(); + session.getLockInstance().unlock(); } } diff --git a/vaadin-spring/src/test/java/com/vaadin/flow/spring/SpringClassesSerializableTest.java b/vaadin-spring/src/test/java/com/vaadin/flow/spring/SpringClassesSerializableTest.java index a0eb5d846d9..886073b9e75 100644 --- a/vaadin-spring/src/test/java/com/vaadin/flow/spring/SpringClassesSerializableTest.java +++ b/vaadin-spring/src/test/java/com/vaadin/flow/spring/SpringClassesSerializableTest.java @@ -97,6 +97,7 @@ protected Stream getExcludedPatterns() { "com\\.vaadin\\.flow\\.spring\\.springnative\\.VaadinBeanFactoryInitializationAotProcessor", "com\\.vaadin\\.flow\\.spring\\.springnative\\.VaadinBeanFactoryInitializationAotProcessor\\$Marker", "com\\.vaadin\\.flow\\.spring\\.springnative\\.VaadinHintsRegistrar", + "com\\.vaadin\\.flow\\.spring\\.scopes\\.VaadinRouteScope(\\$.*)?", "com\\.vaadin\\.flow\\.spring\\.scopes\\.VaadinSessionScope", "com\\.vaadin\\.flow\\.spring\\.scopes\\.AbstractScope", "com\\.vaadin\\.flow\\.spring\\.scopes\\.VaadinUIScope", diff --git a/vaadin-spring/src/test/java/com/vaadin/flow/spring/scopes/AbstractScopeTest.java b/vaadin-spring/src/test/java/com/vaadin/flow/spring/scopes/AbstractScopeTest.java index 75d91b6cfaf..7622f9000d7 100644 --- a/vaadin-spring/src/test/java/com/vaadin/flow/spring/scopes/AbstractScopeTest.java +++ b/vaadin-spring/src/test/java/com/vaadin/flow/spring/scopes/AbstractScopeTest.java @@ -180,6 +180,8 @@ protected VaadinSession mockSession() { when(session.getConfiguration()).thenReturn(config); VaadinSession.setCurrent(session); + ReentrantLock lock = new ReentrantLock(); + when(session.getLockInstance()).thenReturn(lock); when(session.hasLock()).thenReturn(true); // keep a reference to the session so that it cannot be GCed. diff --git a/vaadin-spring/src/test/java/com/vaadin/flow/spring/scopes/VaadinRouteScopeTest.java b/vaadin-spring/src/test/java/com/vaadin/flow/spring/scopes/VaadinRouteScopeTest.java index 3ed0babf774..d124ea822ac 100644 --- a/vaadin-spring/src/test/java/com/vaadin/flow/spring/scopes/VaadinRouteScopeTest.java +++ b/vaadin-spring/src/test/java/com/vaadin/flow/spring/scopes/VaadinRouteScopeTest.java @@ -25,6 +25,7 @@ import org.junit.Test; import org.mockito.Mockito; import org.springframework.beans.factory.ObjectFactory; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.web.context.WebApplicationContext; import com.vaadin.flow.component.Component; @@ -57,7 +58,10 @@ public static class AnotherNavigationTarget extends Component { @Override protected VaadinRouteScope getScope() { - return new VaadinRouteScope(); + VaadinRouteScope scope = new VaadinRouteScope(); + scope.postProcessBeanFactory( + Mockito.mock(ConfigurableListableBeanFactory.class)); + return scope; } @Test @@ -148,7 +152,8 @@ public void refresh_uiWithTheSameWindowName_beanInScopeIsDestroyedAfterRefresh() AtomicInteger count = new AtomicInteger(); scope.registerDestructionCallback("foo", () -> count.getAndIncrement()); - scope.uiInit(new UIInitEvent(ui, ui.getSession().getService())); + new VaadinRouteScope.NavigationListenerRegistrar() + .uiInit(new UIInitEvent(ui, ui.getSession().getService())); navigateTo(ui, new NavigationTarget()); @@ -195,7 +200,8 @@ public void detachUI_uiWithDifferentWindowName_beanInScopeIsDestroyedwhenUIIsDet AtomicInteger count = new AtomicInteger(); scope.registerDestructionCallback("foo", () -> count.getAndIncrement()); - scope.uiInit(new UIInitEvent(ui, ui.getSession().getService())); + new VaadinRouteScope.NavigationListenerRegistrar() + .uiInit(new UIInitEvent(ui, ui.getSession().getService())); navigateTo(ui, new NavigationTarget()); @@ -243,7 +249,8 @@ private VaadinRouteScope initScope(UI ui) { VaadinRouteScope scope = getScope(); scope.getBeanStore(); - scope.uiInit(new UIInitEvent(ui, ui.getSession().getService())); + new VaadinRouteScope.NavigationListenerRegistrar() + .uiInit(new UIInitEvent(ui, ui.getSession().getService())); return scope; }