diff options
author | Gilberto Torrezan <gilberto-torrezan@users.noreply.github.com> | 2018-06-13 10:08:04 +0300 |
---|---|---|
committer | Gilberto Torrezan <gilberto-torrezan@users.noreply.github.com> | 2018-09-03 15:21:22 +0300 |
commit | 8d61288b6fd19a7f5284c05b1917d33eeb816d2a (patch) | |
tree | fe3072366d6bd768e9f3025e057a6b499ebae290 /server | |
parent | 328b057f4087f546a0a26302e23014aae09920d7 (diff) | |
download | vaadin-framework-8d61288b6fd19a7f5284c05b1917d33eeb816d2a.tar.gz vaadin-framework-8d61288b6fd19a7f5284c05b1917d33eeb816d2a.zip |
Add fallback resolvers for CurrentInstance (#10974)
This allow applications to inject custom default instances when the
current instances cannot be found by regular means.
For example, when VaadinServlet.getCurrent() would return null, a
fallback resolver could be invoked to properly create the servlet and
return it.
Diffstat (limited to 'server')
3 files changed, 218 insertions, 0 deletions
diff --git a/server/src/main/java/com/vaadin/util/CurrentInstance.java b/server/src/main/java/com/vaadin/util/CurrentInstance.java index efffd49108..b8567f0f0c 100644 --- a/server/src/main/java/com/vaadin/util/CurrentInstance.java +++ b/server/src/main/java/com/vaadin/util/CurrentInstance.java @@ -23,6 +23,7 @@ import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; import java.util.logging.Logger; @@ -55,6 +56,7 @@ public class CurrentInstance implements Serializable { private static final Object NULL_OBJECT = new Object(); private static final CurrentInstance CURRENT_INSTANCE_NULL = new CurrentInstance( NULL_OBJECT); + private static final ConcurrentHashMap<Class<?>, CurrentInstanceFallbackResolver<?>> fallbackResolvers = new ConcurrentHashMap<Class<?>, CurrentInstanceFallbackResolver<?>>(); private final WeakReference<Object> instance; @@ -66,6 +68,11 @@ public class CurrentInstance implements Serializable { /** * Gets the current instance of a specific type if available. + * <p> + * When a current instance of the specific type is not found, the + * {@link CurrentInstanceFallbackResolver} registered via + * {@link #defineFallbackResolver(Class, CurrentInstanceFallbackResolver)} + * (if any) is invoked. * * @param type * the class to get an instance of @@ -73,6 +80,19 @@ public class CurrentInstance implements Serializable { * if there is no current instance. */ public static <T> T get(Class<T> type) { + T result = doGet(type); + if (result != null) { + return result; + } + CurrentInstanceFallbackResolver<?> fallbackResolver = fallbackResolvers + .get(type); + if (fallbackResolver != null) { + return (T) fallbackResolver.resolve(); + } + return null; + } + + private static <T> T doGet(Class<T> type) { Map<Class<?>, CurrentInstance> map = INSTANCES.get(); if (map == null) { return null; @@ -108,6 +128,35 @@ public class CurrentInstance implements Serializable { } } + /** + * Adds a CurrentInstanceFallbackResolver, that is triggered when + * {@link #get(Class)} can't find a suitable instance for the given type + * parameter. + * + * @param type + * the class used on {@link #get(Class)} invocations to retrieve + * the current instance + * @param fallbackResolver + * the resolver, not <code>null</code> + * + * @throws IllegalArgumentException + * if there's already a defined fallback resolver for the given + * type + * @since + */ + public static <T> void defineFallbackResolver(Class<T> type, + CurrentInstanceFallbackResolver<T> fallbackResolver) { + if (fallbackResolver == null) { + throw new IllegalArgumentException( + "The fallback resolver can not be null."); + } + if (fallbackResolvers.putIfAbsent(type, fallbackResolver) != null) { + throw new IllegalArgumentException( + "A fallback resolver for the type " + type + + " is already defined."); + } + } + private static void removeStaleInstances( Map<Class<?>, CurrentInstance> map) { for (Iterator<Entry<Class<?>, CurrentInstance>> iterator = map diff --git a/server/src/main/java/com/vaadin/util/CurrentInstanceFallbackResolver.java b/server/src/main/java/com/vaadin/util/CurrentInstanceFallbackResolver.java new file mode 100644 index 0000000000..2c8cefe953 --- /dev/null +++ b/server/src/main/java/com/vaadin/util/CurrentInstanceFallbackResolver.java @@ -0,0 +1,49 @@ +/* + * Copyright 2000-2018 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; + +/** + * Fallback that is used to revolve current instances when they are not + * available by regular means. + * <p> + * This interface is used internally by the framework and it's not meant for + * public usage. + * + * @author Vaadin Ltd. + * + * @param <T> + * the type of the instances returned by this resolver + * + * @see CurrentInstance#get(Class) + * @see CurrentInstance#defineFallbackResolver(Class, + * CurrentInstanceFallbackResolver) + * + * @since + * + */ +public interface CurrentInstanceFallbackResolver<T> extends Serializable { + + /** + * Resolves a current instance for the type {@code T}. + * + * @return the current instance, or <code>null</code> if none can be found + */ + T resolve(); + +} diff --git a/server/src/test/java/com/vaadin/util/CurrentInstanceTest.java b/server/src/test/java/com/vaadin/util/CurrentInstanceTest.java index 87d6245dcb..1beae2d3be 100644 --- a/server/src/test/java/com/vaadin/util/CurrentInstanceTest.java +++ b/server/src/test/java/com/vaadin/util/CurrentInstanceTest.java @@ -3,11 +3,13 @@ package com.vaadin.util; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; import java.lang.ref.WeakReference; import java.lang.reflect.Field; import java.util.Map; +import java.util.Properties; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; @@ -15,11 +17,17 @@ import java.util.concurrent.Executors; import java.util.concurrent.Future; import org.easymock.EasyMock; +import org.hamcrest.CoreMatchers; +import org.junit.After; import org.junit.Before; import org.junit.Test; +import com.vaadin.server.DefaultDeploymentConfiguration; +import com.vaadin.server.ServiceException; import com.vaadin.server.VaadinRequest; import com.vaadin.server.VaadinService; +import com.vaadin.server.VaadinServlet; +import com.vaadin.server.VaadinServletService; import com.vaadin.server.VaadinSession; import com.vaadin.ui.UI; @@ -31,6 +39,17 @@ public class CurrentInstanceTest { CurrentInstance.clearAll(); } + @Before + @After + public void clearExistingFallbackResolvers() throws Exception { + // Removes all static fallback resolvers + Field field = CurrentInstance.class + .getDeclaredField("fallbackResolvers"); + field.setAccessible(true); + Map<?, ?> map = (Map<?, ?>) field.get(null); + map.clear(); + } + @Test public void testInitiallyCleared() throws Exception { assertCleared(); @@ -144,6 +163,62 @@ public class CurrentInstanceTest { assertNull(VaadinSession.getCurrent()); } + @Test + public void testFallbackResolvers() throws Exception { + TestFallbackResolver<UI> uiResolver = new TestFallbackResolver<UI>( + new FakeUI()); + CurrentInstance.defineFallbackResolver(UI.class, uiResolver); + + TestFallbackResolver<VaadinSession> sessionResolver = new TestFallbackResolver<VaadinSession>( + new FakeSession()); + CurrentInstance.defineFallbackResolver(VaadinSession.class, + sessionResolver); + + TestFallbackResolver<VaadinService> serviceResolver = new TestFallbackResolver<VaadinService>( + new FakeService(new FakeServlet())); + CurrentInstance.defineFallbackResolver(VaadinService.class, + serviceResolver); + + assertThat(UI.getCurrent(), CoreMatchers.instanceOf(FakeUI.class)); + assertThat(VaadinSession.getCurrent(), + CoreMatchers.instanceOf(FakeSession.class)); + assertThat(VaadinService.getCurrent(), + CoreMatchers.instanceOf(FakeService.class)); + + assertEquals( + "The UI fallback resolver should have been called exactly once", + 1, uiResolver.getCalled()); + + assertEquals( + "The VaadinSession fallback resolver should have been called exactly once", + 1, sessionResolver.getCalled()); + + assertEquals( + "The VaadinService fallback resolver should have been called exactly once", + 1, serviceResolver.getCalled()); + + // the VaadinServlet.getCurrent() resolution uses the VaadinService type + assertThat(VaadinServlet.getCurrent(), + CoreMatchers.instanceOf(FakeServlet.class)); + assertEquals( + "The VaadinService fallback resolver should have been called exactly twice", + 2, serviceResolver.getCalled()); + + } + + @Test(expected = IllegalArgumentException.class) + public void testFallbackResolversWithAlreadyDefinedResolver() { + TestFallbackResolver<UI> uiResolver = new TestFallbackResolver<UI>( + new FakeUI()); + CurrentInstance.defineFallbackResolver(UI.class, uiResolver); + CurrentInstance.defineFallbackResolver(UI.class, uiResolver); + } + + @Test(expected = IllegalArgumentException.class) + public void testFallbackResolversWithNullResolver() { + CurrentInstance.defineFallbackResolver(UI.class, null); + } + public static void waitUntilGarbageCollected(WeakReference<?> ref) throws InterruptedException { for (int i = 0; i < 50; i++) { @@ -172,4 +247,49 @@ public class CurrentInstanceTest { Future<Void> future = service.submit(runnable); future.get(); } + + private static class TestFallbackResolver<T> + implements CurrentInstanceFallbackResolver<T> { + + private int called; + private final T instance; + + public TestFallbackResolver(T instance) { + this.instance = instance; + } + + @Override + public T resolve() { + called++; + return instance; + } + + public int getCalled() { + return called; + } + } + + private static class FakeUI extends UI { + @Override + protected void init(VaadinRequest request) { + } + } + + private static class FakeServlet extends VaadinServlet { + } + + private static class FakeService extends VaadinServletService { + public FakeService(VaadinServlet servlet) throws ServiceException { + super(servlet, new DefaultDeploymentConfiguration(FakeService.class, + new Properties())); + } + } + + private static class FakeSession extends VaadinSession { + public FakeSession() { + super(null); + } + + } + } |