Cache Application#getSingletons not be called twice

Signed-off-by: jansupol <jan.supol@oracle.com>
diff --git a/core-server/src/main/java/org/glassfish/jersey/server/ResourceConfig.java b/core-server/src/main/java/org/glassfish/jersey/server/ResourceConfig.java
index 361acc4..1b13d05 100644
--- a/core-server/src/main/java/org/glassfish/jersey/server/ResourceConfig.java
+++ b/core-server/src/main/java/org/glassfish/jersey/server/ResourceConfig.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2019 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2021 Oracle and/or its affiliates. All rights reserved.
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v. 2.0, which is available at
@@ -42,6 +42,7 @@
 import org.glassfish.jersey.internal.inject.Binder;
 import org.glassfish.jersey.internal.inject.InjectionManager;
 import org.glassfish.jersey.internal.spi.AutoDiscoverable;
+import org.glassfish.jersey.internal.util.Producer;
 import org.glassfish.jersey.internal.util.PropertiesHelper;
 import org.glassfish.jersey.internal.util.ReflectionHelper;
 import org.glassfish.jersey.internal.util.Tokenizer;
@@ -1194,16 +1195,18 @@
             this.application = original;
 
             final Application customRootApp = ResourceConfig.unwrapCustomRootApplication(original);
-            if (customRootApp != null) {
-                registerComponentsOf(customRootApp);
-            }
+
+            final Set<Object> rootSingletons = customRootApp != null ? registerComponentsOf(customRootApp) : null;
 
             originalRegistrations = Collections.newSetFromMap(new IdentityHashMap<>());
             originalRegistrations.addAll(super.getRegisteredClasses());
 
+            // Do not call the same Application#getSingletons twice
+            final Set<Object> origSingletons = customRootApp != null ? rootSingletons : original.getSingletons();
+
             // Register externally provided instances.
             final Set<Object> externalInstances =
-                    original.getSingletons().stream()
+                    origSingletons.stream()
                             .filter(external -> !originalRegistrations.contains(external.getClass()))
                             .collect(Collectors.toSet());
 
@@ -1216,10 +1219,10 @@
             registerClasses(externalClasses);
         }
 
-        private void registerComponentsOf(final Application application) {
-            Errors.processWithException(new Runnable() {
+        private Set<Object> registerComponentsOf(final Application application) {
+            return Errors.process(new Producer<Set<Object>>() {
                 @Override
-                public void run() {
+                public Set<Object> call() {
                     // First register instances that should take precedence over classes
                     // in case of duplicate registrations
                     final Set<Object> singletons = application.getSingletons();
@@ -1248,8 +1251,10 @@
                                                })
                                                .collect(Collectors.toSet()));
                     }
+                    return singletons;
                 }
             });
+
         }
 
         private RuntimeConfig(final Application application) {
diff --git a/tests/e2e/src/test/java/org/glassfish/jersey/tests/api/ApplicationCachingTest.java b/tests/e2e/src/test/java/org/glassfish/jersey/tests/api/ApplicationCachingTest.java
new file mode 100644
index 0000000..6286924
--- /dev/null
+++ b/tests/e2e/src/test/java/org/glassfish/jersey/tests/api/ApplicationCachingTest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.tests.api;
+
+import org.glassfish.jersey.server.ServerProperties;
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.Assert;
+import org.junit.Test;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.container.ContainerRequestFilter;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.Response;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class ApplicationCachingTest extends JerseyTest {
+
+    private static AtomicInteger singletonCounter = new AtomicInteger(0);
+
+    public static class ApplicationCachingTestFilter implements ContainerRequestFilter {
+        @Override
+        public void filter(ContainerRequestContext requestContext) throws IOException {
+
+        }
+    }
+
+    @Path("/")
+    public static class ApplicationCachingTestResource {
+        @GET
+        public String get() {
+            return "GET";
+        }
+    }
+
+    public static class OneTimeCalledApplication extends Application {
+        @Override
+        public Map<String, Object> getProperties() {
+            Map<String, Object> map = new HashMap<>();
+            map.put(ServerProperties.WADL_FEATURE_DISABLE, true);
+            return map;
+        }
+
+        @Override
+        public Set<Object> getSingletons() {
+            singletonCounter.incrementAndGet();
+            return Collections.singleton(new ApplicationCachingTestFilter());
+        }
+
+        @Override
+        public Set<Class<?>> getClasses() {
+            return Collections.singleton(ApplicationCachingTestResource.class);
+        }
+    }
+
+    @Override
+    protected Application configure() {
+        return new OneTimeCalledApplication();
+    }
+
+    @Test
+    public void testOneTimeCalled() {
+        try (Response r = target().request().get()) {
+            Assert.assertEquals(200, r.getStatus());
+        }
+        Assert.assertEquals(1, singletonCounter.get());
+    }
+}