]> source.dussan.org Git - vaadin-framework.git/commitdiff
Add fallback resolvers for CurrentInstance (#10974)
authorGilberto Torrezan <gilberto-torrezan@users.noreply.github.com>
Wed, 13 Jun 2018 07:08:04 +0000 (10:08 +0300)
committerGilberto Torrezan <gilberto-torrezan@users.noreply.github.com>
Mon, 3 Sep 2018 12:21:22 +0000 (15:21 +0300)
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.

server/src/main/java/com/vaadin/util/CurrentInstance.java
server/src/main/java/com/vaadin/util/CurrentInstanceFallbackResolver.java [new file with mode: 0644]
server/src/test/java/com/vaadin/util/CurrentInstanceTest.java

index efffd491087c4517ed2a082b23876d77688fdfe7..b8567f0f0c9959760f1a17b30dd3ccea357cb8d7 100644 (file)
@@ -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 (file)
index 0000000..2c8cefe
--- /dev/null
@@ -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();
+
+}
index 87d6245dcb6a474aec016eef3a66f877ce6d5093..1beae2d3beaf9ce80258071e042f943d6f3400be 100644 (file)
@@ -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);
+        }
+
+    }
+
 }