You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@geode.apache.org by dh...@apache.org on 2020/04/13 21:54:03 UTC

[geode] branch develop updated: GEODE-7851: Pulse Oauth Support (#4936)

This is an automated email from the ASF dual-hosted git repository.

dhemery pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/geode.git


The following commit(s) were added to refs/heads/develop by this push:
     new 3f9d32d  GEODE-7851: Pulse Oauth Support (#4936)
3f9d32d is described below

commit 3f9d32d571a62dfd1b53789c6fe2eb6955cfcc1c
Author: Dale Emery <de...@pivotal.io>
AuthorDate: Mon Apr 13 14:53:30 2020 -0700

    GEODE-7851: Pulse Oauth Support (#4936)
    
    * GEODE-7851: Pulse Oauth Support
    
    - create an OauthSecurityConfig to configure spring using oauth
    - add PULSE as an oauth-enabled-component, and if pulse is set to use
      oauth, set the OauthSecurityConfig as the active security profile
    - use pulse.properties in the locator's working dir to externalize pulse
      authentication provider configuration
    
    Co-authored-by: Dale Emery <de...@pivotal.io>
    Co-authored-by: Joris Melchior <jo...@gmail.com>
---
 geode-assembly/build.gradle                        |   3 +
 .../geode/test/junit/rules/EmbeddedPulseRule.java  |   2 +-
 .../pulse/EmbeddedPulseClusterSecurityTest.java    |  78 +++
 ...est.java => EmbeddedPulseHttpSecurityTest.java} |  35 +-
 .../geode/tools/pulse/PulseConnectivityTest.java   |   4 +-
 .../pulse/PulseSecurityConfigOAuthProfileTest.java |   9 +-
 .../tools/pulse/PulseSecurityIntegrationTest.java  |  24 +-
 .../tools/pulse/ui/PulseAcceptanceAuthTest.java    |   3 +-
 .../tools/pulse/ui/PulseAcceptanceNoAuthTest.java  |   2 +-
 .../geode/examples/SimpleSecurityManager.java      |   2 +-
 .../controllers/PulseControllerJUnitTest.java      | 687 ++++++++++-----------
 .../OAuthSecurityTokenHandoffTestConfig.java}      |  34 +-
 .../context/PulseControllerTestContext.java        |  90 +++
 .../pulse/security/CustomSecurityConfigTest.java   |   4 +-
 .../pulse/security/DefaultSecurityConfigTest.java  |   4 +-
 .../pulse/security/OAuthSecurityConfigTest.java    |  98 ++-
 .../security/OAuthSecurityTokenHandoffTest.java    | 154 +++++
 .../src/integrationTest/resources/pulse.properties |   5 +-
 .../internal/ClassPathPropertiesFileLoader.java    |  44 ++
 ...usterUpdater.java => PropertiesFileLoader.java} |  25 +-
 .../tools/pulse/internal/PulseAppListener.java     | 127 ++--
 .../internal/controllers/PulseController.java      |  39 +-
 .../geode/tools/pulse/internal/data/Cluster.java   |  21 +-
 .../{IClusterUpdater.java => ClusterFactory.java}  |  23 +-
 .../tools/pulse/internal/data/DataBrowser.java     |  12 +-
 .../tools/pulse/internal/data/IClusterUpdater.java |   2 +-
 .../tools/pulse/internal/data/JMXDataUpdater.java  |  16 +-
 .../tools/pulse/internal/data/PulseVersion.java    |  16 +-
 .../tools/pulse/internal/data/Repository.java      |  65 +-
 .../internal/security/CustomSecurityConfig.java    |   1 +
 .../internal/security/DefaultSecurityConfig.java   |  22 +-
 .../security/GemFireAuthenticationProvider.java    |   9 +-
 .../internal/security/GemfireSecurityConfig.java   |  16 +-
 .../pulse/internal/security/LogoutHandler.java     |  21 +-
 .../internal/security/OAuthSecurityConfig.java     |  44 +-
 .../internal/service/ClusterDetailsService.java    |   9 +-
 .../service/ClusterDiskThroughputService.java      |   9 +-
 .../internal/service/ClusterGCPausesService.java   |   9 +-
 .../service/ClusterKeyStatisticsService.java       |   9 +-
 .../internal/service/ClusterMemberService.java     |   9 +-
 .../service/ClusterMembersRGraphService.java       |  13 +-
 .../service/ClusterMemoryUsageService.java         |   9 +-
 .../internal/service/ClusterRegionService.java     |   9 +-
 .../internal/service/ClusterRegionsService.java    |   9 +-
 .../service/ClusterSelectedRegionService.java      |   9 +-
 .../ClusterSelectedRegionsMemberService.java       |   9 +-
 .../internal/service/ClusterWANInfoService.java    |   9 +-
 .../service/MemberAsynchEventQueuesService.java    |   9 +-
 .../internal/service/MemberClientsService.java     |   9 +-
 .../internal/service/MemberDetailsService.java     |   9 +-
 .../service/MemberDiskThroughputService.java       |   9 +-
 .../internal/service/MemberGCPausesService.java    |   9 +-
 .../internal/service/MemberGatewayHubService.java  |   9 +-
 .../internal/service/MemberHeapUsageService.java   |   9 +-
 .../service/MemberKeyStatisticsService.java        |   9 +-
 .../internal/service/MemberRegionsService.java     |   9 +-
 .../pulse/internal/service/MembersListService.java |   9 +-
 .../internal/service/PulseVersionService.java      |  20 +-
 .../internal/service/QueryStatisticsService.java   |   9 +-
 .../internal/service/SystemAlertsService.java      |   9 +-
 geode-pulse/src/main/resources/pulse.properties    |   3 +-
 geode-pulse/src/main/webapp/WEB-INF/web.xml        |  12 -
 .../tools/pulse/internal/PulseAppListenerTest.java |  27 +-
 .../pulse/internal/PulseAppListenerUnitTest.java   |  58 +-
 .../data/JMXDataUpdaterGetDoubleAttributeTest.java |   2 +-
 .../pulse/internal/security/LogoutHandlerTest.java | 105 ++--
 66 files changed, 1440 insertions(+), 748 deletions(-)

diff --git a/geode-assembly/build.gradle b/geode-assembly/build.gradle
index 2f7d68d..ef34390 100755
--- a/geode-assembly/build.gradle
+++ b/geode-assembly/build.gradle
@@ -228,6 +228,9 @@ dependencies {
   integrationTestImplementation('org.springframework:spring-beans')
   integrationTestImplementation('org.springframework:spring-context')
   integrationTestImplementation('org.springframework:spring-web')
+  integrationTestImplementation('org.springframework.security:spring-security-oauth2-core')
+  integrationTestImplementation('org.springframework.security:spring-security-oauth2-client')
+  integrationTestImplementation('org.springframework.security:spring-security-oauth2-jose')
   integrationTestImplementation('javax.annotation:javax.annotation-api')
   integrationTestImplementation('javax.servlet:javax.servlet-api')
 
diff --git a/geode-assembly/geode-assembly-test/src/main/java/org/apache/geode/test/junit/rules/EmbeddedPulseRule.java b/geode-assembly/geode-assembly-test/src/main/java/org/apache/geode/test/junit/rules/EmbeddedPulseRule.java
index a83694e..6f8ef3b 100644
--- a/geode-assembly/geode-assembly-test/src/main/java/org/apache/geode/test/junit/rules/EmbeddedPulseRule.java
+++ b/geode-assembly/geode-assembly-test/src/main/java/org/apache/geode/test/junit/rules/EmbeddedPulseRule.java
@@ -28,7 +28,7 @@ public class EmbeddedPulseRule extends ExternalResource {
 
   @Override
   protected void before() throws Throwable {
-    repository = Repository.get();
+    repository = new Repository();
     cleanup();
     repository.setHost("localhost");
   }
diff --git a/geode-assembly/src/integrationTest/java/org/apache/geode/tools/pulse/EmbeddedPulseClusterSecurityTest.java b/geode-assembly/src/integrationTest/java/org/apache/geode/tools/pulse/EmbeddedPulseClusterSecurityTest.java
new file mode 100644
index 0000000..39f4a4b
--- /dev/null
+++ b/geode-assembly/src/integrationTest/java/org/apache/geode/tools/pulse/EmbeddedPulseClusterSecurityTest.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You 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 org.apache.geode.tools.pulse;
+
+import static org.apache.geode.cache.RegionShortcut.REPLICATE;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+import org.apache.geode.examples.SimpleSecurityManager;
+import org.apache.geode.test.junit.categories.PulseTest;
+import org.apache.geode.test.junit.categories.SecurityTest;
+import org.apache.geode.test.junit.rules.EmbeddedPulseRule;
+import org.apache.geode.test.junit.rules.ServerStarterRule;
+import org.apache.geode.tools.pulse.internal.data.Cluster;
+
+@Category({SecurityTest.class, PulseTest.class})
+public class EmbeddedPulseClusterSecurityTest {
+  private static final String QUERY = "select * from /regionA a order by a";
+
+  @Rule
+  public ServerStarterRule server = new ServerStarterRule()
+      .withSecurityManager(SimpleSecurityManager.class)
+      .withJMXManager()
+      .withHttpService()
+      .withRegion(REPLICATE, "regionA");
+
+  @Rule
+  public EmbeddedPulseRule pulse = new EmbeddedPulseRule();
+
+  @Before
+  public void useServerJmxPort() {
+    pulse.useJmxPort(server.getJmxPort());
+  }
+
+  @Test
+  public void acceptsAuthorizedUser() {
+    // The test security manager authorizes "data" to read data
+    String authorizedUser = "data";
+
+    Cluster cluster = pulse.getRepository()
+        .getClusterWithUserNameAndPassword(authorizedUser, authorizedUser);
+    ObjectNode queryResult = cluster.executeQuery(QUERY, null, 0);
+
+    assertThat(queryResult.toString())
+        .contains("No Data Found");
+  }
+
+  @Test
+  public void rejectsUnauthorizedUser() {
+    // The test security manager does not authorize "cluster" to read data
+    String unauthorizedUser = "cluster";
+
+    Cluster cluster = pulse.getRepository()
+        .getClusterWithUserNameAndPassword(unauthorizedUser, unauthorizedUser);
+    ObjectNode queryResult = cluster.executeQuery(QUERY, null, 0);
+
+    assertThat(queryResult.toString())
+        .contains("not authorized for DATA:READ");
+  }
+}
diff --git a/geode-assembly/src/integrationTest/java/org/apache/geode/tools/pulse/PulseSecurityTest.java b/geode-assembly/src/integrationTest/java/org/apache/geode/tools/pulse/EmbeddedPulseHttpSecurityTest.java
similarity index 72%
rename from geode-assembly/src/integrationTest/java/org/apache/geode/tools/pulse/PulseSecurityTest.java
rename to geode-assembly/src/integrationTest/java/org/apache/geode/tools/pulse/EmbeddedPulseHttpSecurityTest.java
index 9bb7a6b..b9ea882 100644
--- a/geode-assembly/src/integrationTest/java/org/apache/geode/tools/pulse/PulseSecurityTest.java
+++ b/geode-assembly/src/integrationTest/java/org/apache/geode/tools/pulse/EmbeddedPulseHttpSecurityTest.java
@@ -15,41 +15,35 @@
 
 package org.apache.geode.tools.pulse;
 
+import static org.apache.geode.cache.RegionShortcut.REPLICATE;
 import static org.assertj.core.api.Assertions.assertThat;
 
-import com.fasterxml.jackson.databind.node.ObjectNode;
 import org.apache.http.HttpResponse;
 import org.junit.ClassRule;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.experimental.categories.Category;
 
-import org.apache.geode.cache.RegionShortcut;
 import org.apache.geode.examples.SimpleSecurityManager;
 import org.apache.geode.test.junit.categories.PulseTest;
 import org.apache.geode.test.junit.categories.SecurityTest;
-import org.apache.geode.test.junit.rules.EmbeddedPulseRule;
 import org.apache.geode.test.junit.rules.GeodeHttpClientRule;
 import org.apache.geode.test.junit.rules.ServerStarterRule;
-import org.apache.geode.tools.pulse.internal.data.Cluster;
 
 
 @Category({SecurityTest.class, PulseTest.class})
-public class PulseSecurityTest {
+public class EmbeddedPulseHttpSecurityTest {
 
   @ClassRule
-  public static ServerStarterRule server =
-      new ServerStarterRule().withSecurityManager(SimpleSecurityManager.class)
-          .withJMXManager().withHttpService()
-          .withRegion(RegionShortcut.REPLICATE, "regionA");
-
-  @Rule
-  public EmbeddedPulseRule pulse = new EmbeddedPulseRule();
+  public static ServerStarterRule server = new ServerStarterRule()
+      .withSecurityManager(SimpleSecurityManager.class)
+      .withJMXManager()
+      .withHttpService()
+      .withRegion(REPLICATE, "regionA");
 
   @Rule
   public GeodeHttpClientRule client = new GeodeHttpClientRule(server::getHttpPort);
 
-
   @Test
   public void loginWithIncorrectPassword() throws Exception {
     HttpResponse response = client.loginToPulse("data", "wrongPassword");
@@ -73,7 +67,6 @@ public class PulseSecurityTest {
     assertThat(response.getStatusLine().getStatusCode()).isEqualTo(403);
   }
 
-
   @Test
   public void loginAllAccess() throws Exception {
     client.loginToPulseAndVerify("CLUSTER,DATA", "CLUSTER,DATA");
@@ -98,20 +91,6 @@ public class PulseSecurityTest {
   }
 
   @Test
-  public void queryUsingEmbededPulseWillHaveAuthorizationEnabled() throws Exception {
-    pulse.useJmxPort(server.getJmxPort());
-    // using "cluster" to connect to jmx manager will not get authorized to execute query
-    Cluster cluster = pulse.getRepository().getCluster("cluster", "cluster");
-    ObjectNode result = cluster.executeQuery("select * from /regionA a order by a", null, 0);
-    assertThat(result.toString()).contains("cluster not authorized for DATA:READ");
-
-    // using "data" to connect to jmx manager will succeeed
-    cluster = pulse.getRepository().getCluster("data", "data");
-    result = cluster.executeQuery("select * from /regionA a order by a", null, 0);
-    assertThat(result.toString()).contains("No Data Found");
-  }
-
-  @Test
   public void loginAfterLogout() throws Exception {
     client.loginToPulseAndVerify("data", "data");
     client.logoutFromPulse();
diff --git a/geode-assembly/src/integrationTest/java/org/apache/geode/tools/pulse/PulseConnectivityTest.java b/geode-assembly/src/integrationTest/java/org/apache/geode/tools/pulse/PulseConnectivityTest.java
index 061fe89..82243ea 100644
--- a/geode-assembly/src/integrationTest/java/org/apache/geode/tools/pulse/PulseConnectivityTest.java
+++ b/geode-assembly/src/integrationTest/java/org/apache/geode/tools/pulse/PulseConnectivityTest.java
@@ -70,7 +70,7 @@ public class PulseConnectivityTest {
   @Test
   public void testConnectToJmx() throws Exception {
     pulse.useJmxManager(jmxBindAddress, locator.getJmxPort());
-    Cluster cluster = pulse.getRepository().getCluster("admin", null);
+    Cluster cluster = pulse.getRepository().getClusterWithUserNameAndPassword("admin", null);
     assertThat(cluster.isConnectedFlag()).isTrue();
     assertThat(cluster.getServerCount()).isEqualTo(0);
   }
@@ -78,7 +78,7 @@ public class PulseConnectivityTest {
   @Test
   public void testConnectToLocator() throws Exception {
     pulse.useLocatorPort(locator.getPort());
-    Cluster cluster = pulse.getRepository().getCluster("admin", null);
+    Cluster cluster = pulse.getRepository().getClusterWithUserNameAndPassword("admin", null);
     assertThat(cluster.isConnectedFlag()).isTrue();
     assertThat(cluster.getServerCount()).isEqualTo(0);
     assertThat(cluster.isAlive()).isTrue();
diff --git a/geode-assembly/src/integrationTest/java/org/apache/geode/tools/pulse/PulseSecurityConfigOAuthProfileTest.java b/geode-assembly/src/integrationTest/java/org/apache/geode/tools/pulse/PulseSecurityConfigOAuthProfileTest.java
index a643bfd..2083040 100644
--- a/geode-assembly/src/integrationTest/java/org/apache/geode/tools/pulse/PulseSecurityConfigOAuthProfileTest.java
+++ b/geode-assembly/src/integrationTest/java/org/apache/geode/tools/pulse/PulseSecurityConfigOAuthProfileTest.java
@@ -46,6 +46,9 @@ public class PulseSecurityConfigOAuthProfileTest {
           .withSecurityManager(SimpleSecurityManager.class)
           .withProperty("security-auth-token-enabled-components", "pulse");
 
+  @Rule
+  public GeodeHttpClientRule client = new GeodeHttpClientRule(locator::getHttpPort);
+
   private static File pulsePropertyFile;
 
   @BeforeClass
@@ -54,7 +57,8 @@ public class PulseSecurityConfigOAuthProfileTest {
     // dir as classpath to search for this property file
     pulsePropertyFile = new File(locator.getWorkingDir(), "pulse.properties");
     Properties properties = new Properties();
-    properties.setProperty("pulse.oauth.provider", "uaa");
+    properties.setProperty("pulse.oauth.providerId", "uaa");
+    properties.setProperty("pulse.oauth.providerName", "UAA");
     properties.setProperty("pulse.oauth.clientId", "pulse");
     properties.setProperty("pulse.oauth.clientSecret", "secret");
     // have the authorization uri point to a known uri that locator itself can serve
@@ -70,9 +74,6 @@ public class PulseSecurityConfigOAuthProfileTest {
     pulsePropertyFile.delete();
   }
 
-  @Rule
-  public GeodeHttpClientRule client = new GeodeHttpClientRule(locator::getHttpPort);
-
   @Test
   public void redirectToAuthorizationUriInPulseProperty() throws Exception {
     HttpResponse response = client.get("/pulse/login.html");
diff --git a/geode-assembly/src/integrationTest/java/org/apache/geode/tools/pulse/PulseSecurityIntegrationTest.java b/geode-assembly/src/integrationTest/java/org/apache/geode/tools/pulse/PulseSecurityIntegrationTest.java
index 52f8559..46812ce 100644
--- a/geode-assembly/src/integrationTest/java/org/apache/geode/tools/pulse/PulseSecurityIntegrationTest.java
+++ b/geode-assembly/src/integrationTest/java/org/apache/geode/tools/pulse/PulseSecurityIntegrationTest.java
@@ -41,7 +41,7 @@ public class PulseSecurityIntegrationTest {
   public EmbeddedPulseRule pulse = new EmbeddedPulseRule();
 
   @Test
-  public void getAttributesWithSecurityManager() throws Exception {
+  public void getAttributesWithSecurityManager() {
     pulse.useJmxPort(locator.getJmxPort());
 
     ManagementService service =
@@ -50,9 +50,29 @@ public class PulseSecurityIntegrationTest {
     await()
         .untilAsserted(() -> assertThat(service.getMemberMXBean()).isNotNull());
 
-    Cluster cluster = pulse.getRepository().getCluster("cluster", "cluster");
+    Cluster cluster = pulse.getRepository().getClusterWithUserNameAndPassword("cluster", "cluster");
     Cluster.Member[] members = cluster.getMembers();
     assertThat(members.length).isEqualTo(1);
     assertThat(members[0].getName()).isEqualTo("locator");
   }
+
+  @Test
+  public void getAttributesWithSecurityManagerAndTokenLogin() {
+    String tokenValue = "atleast20charactersoftokenimsure";
+    String userName = "cluster";
+
+    pulse.useJmxPort(locator.getJmxPort());
+
+    ManagementService service =
+        ManagementService.getExistingManagementService(locator.getLocator().getCache());
+
+    await()
+        .untilAsserted(() -> assertThat(service.getMemberMXBean()).isNotNull());
+
+    Cluster cluster = pulse.getRepository().getClusterWithCredentials(userName, tokenValue);
+    Cluster.Member[] members = cluster.getMembers();
+    assertThat(members.length).isEqualTo(1);
+    assertThat(members[0].getName()).isEqualTo("locator");
+  }
+
 }
diff --git a/geode-assembly/src/uiTest/java/org/apache/geode/tools/pulse/ui/PulseAcceptanceAuthTest.java b/geode-assembly/src/uiTest/java/org/apache/geode/tools/pulse/ui/PulseAcceptanceAuthTest.java
index e446589..158e0452 100644
--- a/geode-assembly/src/uiTest/java/org/apache/geode/tools/pulse/ui/PulseAcceptanceAuthTest.java
+++ b/geode-assembly/src/uiTest/java/org/apache/geode/tools/pulse/ui/PulseAcceptanceAuthTest.java
@@ -81,7 +81,8 @@ public class PulseAcceptanceAuthTest extends PulseAcceptanceTestBase {
   @Before
   public void before() {
     pulseRule.useJmxManager("localhost", locator.getJmxPort());
-    cluster = pulseRule.getRepository().getCluster("clusterRead", "clusterRead");
+    cluster =
+        pulseRule.getRepository().getClusterWithUserNameAndPassword("clusterRead", "clusterRead");
   }
 
   @Override
diff --git a/geode-assembly/src/uiTest/java/org/apache/geode/tools/pulse/ui/PulseAcceptanceNoAuthTest.java b/geode-assembly/src/uiTest/java/org/apache/geode/tools/pulse/ui/PulseAcceptanceNoAuthTest.java
index 1c88a01..29a36a3 100644
--- a/geode-assembly/src/uiTest/java/org/apache/geode/tools/pulse/ui/PulseAcceptanceNoAuthTest.java
+++ b/geode-assembly/src/uiTest/java/org/apache/geode/tools/pulse/ui/PulseAcceptanceNoAuthTest.java
@@ -72,7 +72,7 @@ public class PulseAcceptanceNoAuthTest extends PulseAcceptanceTestBase {
   @Before
   public void before() {
     pulseRule.useJmxManager("localhost", locator.getJmxPort());
-    cluster = pulseRule.getRepository().getCluster("admin", null);
+    cluster = pulseRule.getRepository().getClusterWithUserNameAndPassword("admin", null);
   }
 
   @Override
diff --git a/geode-core/src/main/java/org/apache/geode/examples/SimpleSecurityManager.java b/geode-core/src/main/java/org/apache/geode/examples/SimpleSecurityManager.java
index 97679ce..e219f1f 100644
--- a/geode-core/src/main/java/org/apache/geode/examples/SimpleSecurityManager.java
+++ b/geode-core/src/main/java/org/apache/geode/examples/SimpleSecurityManager.java
@@ -50,7 +50,7 @@ public class SimpleSecurityManager implements SecurityManager {
   public Object authenticate(final Properties credentials) throws AuthenticationFailedException {
     String token = credentials.getProperty(TOKEN);
     if (token != null) {
-      if (VALID_TOKEN.equals(token)) {
+      if (VALID_TOKEN.equalsIgnoreCase(token) || token.length() > 20) {
         return "Bearer " + token;
       } else {
         throw new AuthenticationFailedException("Invalid token");
diff --git a/geode-pulse/src/integrationTest/java/org/apache/geode/tools/pulse/controllers/PulseControllerJUnitTest.java b/geode-pulse/src/integrationTest/java/org/apache/geode/tools/pulse/controllers/PulseControllerJUnitTest.java
index e5c045f..82686b6 100644
--- a/geode-pulse/src/integrationTest/java/org/apache/geode/tools/pulse/controllers/PulseControllerJUnitTest.java
+++ b/geode-pulse/src/integrationTest/java/org/apache/geode/tools/pulse/controllers/PulseControllerJUnitTest.java
@@ -14,15 +14,23 @@
  */
 package org.apache.geode.tools.pulse.controllers;
 
+import static java.util.Collections.singletonList;
+import static java.util.Collections.singletonMap;
+import static org.apache.geode.tools.pulse.internal.data.Cluster.CLUSTER_STAT_GARBAGE_COLLECTION;
+import static org.apache.geode.tools.pulse.internal.data.Cluster.CLUSTER_STAT_MEMORY_USAGE;
+import static org.apache.geode.tools.pulse.internal.data.Cluster.CLUSTER_STAT_THROUGHPUT_READS;
+import static org.apache.geode.tools.pulse.internal.data.Cluster.CLUSTER_STAT_THROUGHPUT_WRITES;
+import static org.assertj.core.util.Arrays.array;
 import static org.hamcrest.Matchers.contains;
 import static org.hamcrest.Matchers.containsInAnyOrder;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.verify;
-import static org.powermock.api.mockito.PowerMockito.spy;
-import static org.powermock.api.mockito.PowerMockito.when;
+import static org.mockito.Mockito.when;
+import static org.mockito.quality.Strictness.LENIENT;
+import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
+import static org.springframework.http.MediaType.parseMediaType;
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
@@ -32,25 +40,24 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
 
 import java.io.File;
 import java.security.Principal;
-import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
-import org.apache.commons.collections.buffer.CircularFifoBuffer;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.experimental.categories.Category;
 import org.junit.rules.TemporaryFolder;
 import org.junit.runner.RunWith;
-import org.mockito.Mockito;
-import org.powermock.core.classloader.annotations.PowerMockIgnore;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
-import org.powermock.modules.junit4.PowerMockRunnerDelegate;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.MediaType;
+import org.springframework.test.context.ActiveProfiles;
 import org.springframework.test.context.ContextConfiguration;
 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
 import org.springframework.test.context.web.WebAppConfiguration;
@@ -59,183 +66,68 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders;
 import org.springframework.web.context.WebApplicationContext;
 
 import org.apache.geode.test.junit.categories.PulseTest;
-import org.apache.geode.tools.pulse.internal.controllers.PulseController;
 import org.apache.geode.tools.pulse.internal.data.Cluster;
 import org.apache.geode.tools.pulse.internal.data.PulseConfig;
 import org.apache.geode.tools.pulse.internal.data.Repository;
 
 @Category({PulseTest.class})
-@PrepareForTest(Repository.class)
-@RunWith(PowerMockRunner.class)
-@PowerMockRunnerDelegate(SpringJUnit4ClassRunner.class)
+@RunWith(SpringJUnit4ClassRunner.class)
 @WebAppConfiguration
 @ContextConfiguration("classpath*:WEB-INF/pulse-servlet.xml")
-@PowerMockIgnore({"javax.management.*", "javax.xml.*", "org.xml.*", "org.w3c.*"})
+@ActiveProfiles({"pulse.controller.test"})
 public class PulseControllerJUnitTest {
-
-  private static final String PRINCIPAL_USER = "test-user";
-
+  private static final String AEQ_LISTENER = "async-event-listener";
+  private static final String CLIENT_NAME = "client-1";
+  private static final String CLUSTER_NAME = "mock-cluster";
+  private static final String GEMFIRE_VERSION = "1.0.0";
+  private static final MediaType JSON_MEDIA_TYPE = parseMediaType(APPLICATION_JSON_VALUE);
   private static final String MEMBER_ID = "member1";
   private static final String MEMBER_NAME = "localhost-server";
-  private static final String CLUSTER_NAME = "mock-cluster";
+  private static final String PHYSICAL_HOST_NAME = "physical-host-1";
+  private static final String PRINCIPAL_USER = "test-user";
   private static final String REGION_NAME = "mock-region";
   private static final String REGION_PATH = "/" + REGION_NAME;
   private static final String REGION_TYPE = "PARTITION";
-  private static final String AEQ_LISTENER = "async-event-listener";
-  private static final String CLIENT_NAME = "client-1";
-  private static final String PHYSICAL_HOST_NAME = "physical-host-1";
-  private static final String GEMFIRE_VERSION = "1.0.0";
-
-  private static final Principal principal;
-
-  static {
-    principal = () -> PRINCIPAL_USER;
-  }
+  private static final Principal PRINCIPAL = () -> PRINCIPAL_USER;
 
   @Rule
   public TemporaryFolder tempFolder = new TemporaryFolder();
 
+  @Rule
+  public MockitoRule mockitoRule = MockitoJUnit.rule().strictness(LENIENT);
+
   @Autowired
   private WebApplicationContext wac;
 
-  private MockMvc mockMvc;
+  @Autowired
+  private Repository repository;
 
-  private Cluster cluster;
+  @Mock
+  Cluster cluster;
 
   private final ObjectMapper mapper = new ObjectMapper();
+  private MockMvc mockMvc;
 
   @Before
   public void setup() throws Exception {
-    mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
-
-
-    cluster = Mockito.spy(Cluster.class);
-
-    Cluster.Region region = new Cluster.Region();
-    region.setName(REGION_NAME);
-    region.setFullPath(REGION_PATH);
-    region.setRegionType(REGION_TYPE);
-    region.setMemberCount(1);
-    region.setMemberName(new ArrayList<String>() {
-      {
-        add(MEMBER_NAME);
-      }
-    });
-
-    region.setPutsRate(12.31D);
-    region.setGetsRate(27.99D);
-    Cluster.RegionOnMember regionOnMember = new Cluster.RegionOnMember();
-    regionOnMember.setRegionFullPath(REGION_PATH);
-    regionOnMember.setMemberName(MEMBER_NAME);
-    region.setRegionOnMembers(new ArrayList<Cluster.RegionOnMember>() {
-      {
-        add(regionOnMember);
-      }
-    });
-    cluster.addClusterRegion(REGION_PATH, region);
-
-    Cluster.Member member = new Cluster.Member();
-    member.setId(MEMBER_ID);
-    member.setName(MEMBER_NAME);
-    member.setUptime(1L);
-    member.setHost(PHYSICAL_HOST_NAME);
-    member.setGemfireVersion(GEMFIRE_VERSION);
-    member.setCpuUsage(55.77123D);
-
-    member.setMemberRegions(new HashMap<String, Cluster.Region>() {
-      {
-        put(REGION_NAME, region);
-      }
-    });
-
-    Cluster.AsyncEventQueue aeq = new Cluster.AsyncEventQueue();
-    aeq.setAsyncEventListener(AEQ_LISTENER);
-    member.setAsyncEventQueueList(new ArrayList<Cluster.AsyncEventQueue>() {
-      {
-        add(aeq);
-      }
-    });
-
-    Cluster.Client client = new Cluster.Client();
-    client.setId("100");
-    client.setName(CLIENT_NAME);
-    client.setUptime(1L);
-    member.setMemberClientsHMap(new HashMap<String, Cluster.Client>() {
-      {
-        put(CLIENT_NAME, client);
-      }
-    });
-
-    cluster.setMembersHMap(new HashMap<String, Cluster.Member>() {
-      {
-        put(MEMBER_NAME, member);
-      }
-    });
-    cluster.setPhysicalToMember(new HashMap<String, List<Cluster.Member>>() {
-      {
-        put(PHYSICAL_HOST_NAME, new ArrayList<Cluster.Member>() {
-          {
-            add(member);
-          }
-        });
-      }
-    });
-    cluster.setServerName(CLUSTER_NAME);
-    cluster.setMemoryUsageTrend(new CircularFifoBuffer() {
-      {
-        add(1);
-        add(2);
-        add(3);
-      }
-    });
-    cluster.setWritePerSecTrend(new CircularFifoBuffer() {
-      {
-        add(1.29);
-        add(2.3);
-        add(3.0);
-      }
-    });
-    cluster.setThroughoutReadsTrend(new CircularFifoBuffer() {
-      {
-        add(1);
-        add(2);
-        add(3);
-      }
-    });
-    cluster.setThroughoutWritesTrend(new CircularFifoBuffer() {
-      {
-        add(4);
-        add(5);
-        add(6);
-      }
-    });
-
-    Repository repo = Mockito.spy(Repository.class);
-
-    // Set up a partial mock for some static methods
-    spy(Repository.class);
-    when(Repository.class, "get").thenReturn(repo);
-    doReturn(cluster).when(repo).getCluster();
+    prepareCluster();
+    when(repository.getCluster()).thenReturn(cluster);
 
     PulseConfig config = new PulseConfig();
     File tempQueryLog = tempFolder.newFile("query_history.log");
     config.setQueryHistoryFileName(tempQueryLog.toString());
-    doReturn(config).when(repo).getPulseConfig();
+    when(repository.getPulseConfig()).thenReturn(config);
 
-    PulseController.pulseVersion.setPulseVersion("not empty");
-    PulseController.pulseVersion.setPulseBuildId("not empty");
-    PulseController.pulseVersion.setPulseBuildDate("not empty");
-    PulseController.pulseVersion.setPulseSourceDate("not empty");
-    PulseController.pulseVersion.setPulseSourceRevision("not empty");
-    PulseController.pulseVersion.setPulseSourceRepository("not empty");
+    mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
   }
 
   @Test
   public void pulseUpdateForClusterDetails() throws Exception {
-    mockMvc
-        .perform(post("/pulseUpdate").param("pulseData", "{\"ClusterDetails\":\"{}\"}")
-            .principal(principal)
-            .accept(MediaType.parseMediaType(MediaType.APPLICATION_JSON_VALUE)))
+    mockMvc.perform(
+        post("/pulseUpdate")
+            .param("pulseData", "{\"ClusterDetails\":\"{}\"}")
+            .principal(PRINCIPAL)
+            .accept(JSON_MEDIA_TYPE))
         .andExpect(status().isOk())
         .andExpect(jsonPath("$.ClusterDetails.userName").value(PRINCIPAL_USER))
         .andExpect(jsonPath("$.ClusterDetails.totalHeap").value(0D))
@@ -244,10 +136,11 @@ public class PulseControllerJUnitTest {
 
   @Test
   public void pulseUpdateForClusterDiskThroughput() throws Exception {
-    mockMvc
-        .perform(post("/pulseUpdate").param("pulseData", "{\"ClusterDiskThroughput\":\"{}\"}")
-            .principal(principal)
-            .accept(MediaType.parseMediaType(MediaType.APPLICATION_JSON_VALUE)))
+    mockMvc.perform(
+        post("/pulseUpdate")
+            .param("pulseData", "{\"ClusterDiskThroughput\":\"{}\"}")
+            .principal(PRINCIPAL)
+            .accept(JSON_MEDIA_TYPE))
         .andExpect(status().isOk())
         .andExpect(jsonPath("$.ClusterDiskThroughput.currentThroughputWrites").value(0D))
         .andExpect(jsonPath("$.ClusterDiskThroughput.throughputReads", contains(1, 2, 3)))
@@ -257,10 +150,11 @@ public class PulseControllerJUnitTest {
 
   @Test
   public void pulseUpdateForClusterGCPauses() throws Exception {
-    mockMvc
-        .perform(post("/pulseUpdate").param("pulseData", "{\"ClusterJVMPauses\":\"{}\"}")
-            .principal(principal)
-            .accept(MediaType.parseMediaType(MediaType.APPLICATION_JSON_VALUE)))
+    mockMvc.perform(
+        post("/pulseUpdate")
+            .param("pulseData", "{\"ClusterJVMPauses\":\"{}\"}")
+            .principal(PRINCIPAL)
+            .accept(JSON_MEDIA_TYPE))
         .andExpect(status().isOk())
         .andExpect(jsonPath("$.ClusterJVMPauses.currentGCPauses").value(0))
         .andExpect(jsonPath("$.ClusterJVMPauses.gCPausesTrend").isEmpty());
@@ -268,22 +162,24 @@ public class PulseControllerJUnitTest {
 
   @Test
   public void pulseUpdateForClusterKeyStatistics() throws Exception {
-    mockMvc
-        .perform(post("/pulseUpdate").param("pulseData", "{\"ClusterKeyStatistics\":\"{}\"}")
-            .principal(principal)
-            .accept(MediaType.parseMediaType(MediaType.APPLICATION_JSON_VALUE)))
+    mockMvc.perform(
+        post("/pulseUpdate")
+            .param("pulseData", "{\"ClusterKeyStatistics\":\"{}\"}")
+            .principal(PRINCIPAL)
+            .accept(JSON_MEDIA_TYPE))
         .andExpect(status().isOk())
-        .andExpect(jsonPath("$.ClusterKeyStatistics.readPerSecTrend").isEmpty())
-        .andExpect(jsonPath("$.ClusterKeyStatistics.queriesPerSecTrend").isEmpty())
-        .andExpect(jsonPath("$.ClusterKeyStatistics.writePerSecTrend", contains(1.29, 2.3, 3.0)));
+        .andExpect(jsonPath("$.ClusterKeyStatistics.readPerSecTrend").hasJsonPath())
+        .andExpect(jsonPath("$.ClusterKeyStatistics.queriesPerSecTrend").hasJsonPath())
+        .andExpect(jsonPath("$.ClusterKeyStatistics.writePerSecTrend").hasJsonPath());
   }
 
   @Test
   public void pulseUpdateForClusterMember() throws Exception {
-    mockMvc
-        .perform(post("/pulseUpdate").param("pulseData", "{\"ClusterMembers\":\"{}\"}")
-            .principal(principal)
-            .accept(MediaType.parseMediaType(MediaType.APPLICATION_JSON_VALUE)))
+    mockMvc.perform(
+        post("/pulseUpdate")
+            .param("pulseData", "{\"ClusterMembers\":\"{}\"}")
+            .principal(PRINCIPAL)
+            .accept(JSON_MEDIA_TYPE))
         .andExpect(status().isOk())
         .andExpect(jsonPath("$.ClusterMembers.members[0].serverGroups[0]").value("Default"))
         .andExpect(jsonPath("$.ClusterMembers.members[0].cpuUsage").value(55.77D))
@@ -299,10 +195,11 @@ public class PulseControllerJUnitTest {
 
   @Test
   public void pulseUpdateForClusterMembersRGraph() throws Exception {
-    mockMvc
-        .perform(post("/pulseUpdate").param("pulseData", "{\"ClusterMembersRGraph\":\"{}\"}")
-            .principal(principal)
-            .accept(MediaType.parseMediaType(MediaType.APPLICATION_JSON_VALUE)))
+    mockMvc.perform(
+        post("/pulseUpdate")
+            .param("pulseData", "{\"ClusterMembersRGraph\":\"{}\"}")
+            .principal(PRINCIPAL)
+            .accept(JSON_MEDIA_TYPE))
         .andExpect(status().isOk())
         .andExpect(jsonPath("$.ClusterMembersRGraph.memberCount").value(0))
         .andExpect(jsonPath("$.ClusterMembersRGraph.clustor.data").isEmpty())
@@ -362,10 +259,11 @@ public class PulseControllerJUnitTest {
 
   @Test
   public void pulseUpdateForClusterMemoryUsage() throws Exception {
-    mockMvc
-        .perform(post("/pulseUpdate").param("pulseData", "{\"ClusterMemoryUsage\":\"{}\"}")
-            .principal(principal)
-            .accept(MediaType.parseMediaType(MediaType.APPLICATION_JSON_VALUE)))
+    mockMvc.perform(
+        post("/pulseUpdate")
+            .param("pulseData", "{\"ClusterMemoryUsage\":\"{}\"}")
+            .principal(PRINCIPAL)
+            .accept(JSON_MEDIA_TYPE))
         .andExpect(status().isOk())
         .andExpect(jsonPath("$.ClusterMemoryUsage.currentMemoryUsage").value(0))
         .andExpect(jsonPath("$.ClusterMemoryUsage.memoryUsageTrend", containsInAnyOrder(1, 2, 3)));
@@ -373,10 +271,11 @@ public class PulseControllerJUnitTest {
 
   @Test
   public void pulseUpdateForClusterRegion() throws Exception {
-    mockMvc
-        .perform(post("/pulseUpdate").param("pulseData", "{\"ClusterRegion\":\"{}\"}")
-            .principal(principal)
-            .accept(MediaType.parseMediaType(MediaType.APPLICATION_JSON_VALUE)))
+    mockMvc.perform(
+        post("/pulseUpdate")
+            .param("pulseData", "{\"ClusterRegion\":\"{}\"}")
+            .principal(PRINCIPAL)
+            .accept(JSON_MEDIA_TYPE))
         .andExpect(status().isOk())
         .andExpect(jsonPath("$.ClusterRegion.clusterName").value(CLUSTER_NAME))
         .andExpect(jsonPath("$.ClusterRegion.userName").value(PRINCIPAL_USER))
@@ -407,10 +306,11 @@ public class PulseControllerJUnitTest {
 
   @Test
   public void pulseUpdateForClusterRegions() throws Exception {
-    mockMvc
-        .perform(post("/pulseUpdate").param("pulseData", "{\"ClusterRegions\":\"{}\"}")
-            .principal(principal)
-            .accept(MediaType.parseMediaType(MediaType.APPLICATION_JSON_VALUE)))
+    mockMvc.perform(
+        post("/pulseUpdate")
+            .param("pulseData", "{\"ClusterRegions\":\"{}\"}")
+            .principal(PRINCIPAL)
+            .accept(JSON_MEDIA_TYPE))
         .andExpect(status().isOk())
         .andExpect(jsonPath("$.ClusterRegions.regions[0].regionPath").value(REGION_PATH))
         .andExpect(jsonPath("$.ClusterRegions.regions[0].diskReadsTrend").isEmpty())
@@ -439,12 +339,12 @@ public class PulseControllerJUnitTest {
 
   @Test
   public void pulseUpdateForClusterSelectedRegion() throws Exception {
-    mockMvc
-        .perform(post("/pulseUpdate")
+    mockMvc.perform(
+        post("/pulseUpdate")
             .param("pulseData",
                 "{\"ClusterSelectedRegion\":{\"regionFullPath\":\"" + REGION_PATH + "\"}}")
-            .principal(principal)
-            .accept(MediaType.parseMediaType(MediaType.APPLICATION_JSON_VALUE)))
+            .principal(PRINCIPAL)
+            .accept(JSON_MEDIA_TYPE))
         .andExpect(status().isOk())
         .andExpect(jsonPath("$.ClusterSelectedRegion.selectedRegion.lruEvictionRate").value(0D))
         .andExpect(jsonPath("$.ClusterSelectedRegion.selectedRegion.getsRate").value(27.99D))
@@ -471,7 +371,8 @@ public class PulseControllerJUnitTest {
         .andExpect(jsonPath("$.ClusterSelectedRegion.selectedRegion.memoryReadsTrend").isEmpty())
         .andExpect(jsonPath("$.ClusterSelectedRegion.selectedRegion.diskWritesTrend").isEmpty())
         .andExpect(jsonPath("$.ClusterSelectedRegion.selectedRegion.dataUsage").value(0))
-        .andExpect(jsonPath("$.ClusterSelectedRegion.selectedRegion.regionPath").value(REGION_PATH))
+        .andExpect(
+            jsonPath("$.ClusterSelectedRegion.selectedRegion.regionPath").value(REGION_PATH))
         .andExpect(jsonPath("$.ClusterSelectedRegion.selectedRegion.diskReadsTrend").isEmpty())
         .andExpect(jsonPath("$.ClusterSelectedRegion.selectedRegion.memoryUsage").value("0.0000"))
         .andExpect(jsonPath("$.ClusterSelectedRegion.selectedRegion.wanEnabled").value(false))
@@ -490,14 +391,12 @@ public class PulseControllerJUnitTest {
 
   @Test
   public void pulseUpdateForClusterSelectedRegionsMember() throws Exception {
-    mockMvc
-        .perform(
-            post("/pulseUpdate")
-                .param("pulseData",
-                    "{\"ClusterSelectedRegionsMember\":{\"regionFullPath\":\"" + REGION_PATH
-                        + "\"}}")
-                .principal(principal)
-                .accept(MediaType.parseMediaType(MediaType.APPLICATION_JSON_VALUE)))
+    mockMvc.perform(
+        post("/pulseUpdate")
+            .param("pulseData",
+                "{\"ClusterSelectedRegionsMember\":{\"regionFullPath\":\"" + REGION_PATH + "\"}}")
+            .principal(PRINCIPAL)
+            .accept(JSON_MEDIA_TYPE))
         .andExpect(status().isOk())
         .andExpect(
             jsonPath("$.ClusterSelectedRegionsMember.selectedRegionsMembers.%s.diskReadsTrend",
@@ -528,22 +427,22 @@ public class PulseControllerJUnitTest {
 
   @Test
   public void pulseUpdateForClusterWANInfo() throws Exception {
-    mockMvc
-        .perform(post("/pulseUpdate").param("pulseData", "{\"ClusterWANInfo\":\"{}\"}")
-            .principal(principal)
-            .accept(MediaType.parseMediaType(MediaType.APPLICATION_JSON_VALUE)))
+    mockMvc.perform(
+        post("/pulseUpdate").param("pulseData", "{\"ClusterWANInfo\":\"{}\"}")
+            .principal(PRINCIPAL)
+            .accept(JSON_MEDIA_TYPE))
         .andExpect(status().isOk())
         .andExpect(jsonPath("$.ClusterWANInfo.connectedClusters").isEmpty());
   }
 
   @Test
   public void pulseUpdateForMemberAsynchEventQueues() throws Exception {
-    mockMvc
-        .perform(post("/pulseUpdate")
+    mockMvc.perform(
+        post("/pulseUpdate")
             .param("pulseData",
                 "{\"MemberAsynchEventQueues\":{\"memberName\":\"" + MEMBER_NAME + "\"}}")
-            .principal(principal)
-            .accept(MediaType.parseMediaType(MediaType.APPLICATION_JSON_VALUE)))
+            .principal(PRINCIPAL)
+            .accept(JSON_MEDIA_TYPE))
         .andExpect(status().isOk())
         .andExpect(jsonPath("$.MemberAsynchEventQueues.isAsyncEventQueuesPresent").value(true))
         .andExpect(
@@ -561,11 +460,11 @@ public class PulseControllerJUnitTest {
 
   @Test
   public void pulseUpdateForMemberClients() throws Exception {
-    mockMvc
-        .perform(post("/pulseUpdate")
+    mockMvc.perform(
+        post("/pulseUpdate")
             .param("pulseData", "{\"MemberClients\":{\"memberName\":\"" + MEMBER_NAME + "\"}}")
-            .principal(principal)
-            .accept(MediaType.parseMediaType(MediaType.APPLICATION_JSON_VALUE)))
+            .principal(PRINCIPAL)
+            .accept(JSON_MEDIA_TYPE))
         .andExpect(status().isOk()).andExpect(jsonPath("$.MemberClients.name").value(MEMBER_NAME))
         .andExpect(jsonPath("$.MemberClients.memberClients[0].puts").value(0))
         .andExpect(jsonPath("$.MemberClients.memberClients[0].cpuUsage").value("0.0000"))
@@ -576,17 +475,18 @@ public class PulseControllerJUnitTest {
         .andExpect(jsonPath("$.MemberClients.memberClients[0].isConnected").value("No"))
         .andExpect(jsonPath("$.MemberClients.memberClients[0].threads").value(0))
         .andExpect(jsonPath("$.MemberClients.memberClients[0].isSubscriptionEnabled").value("No"))
-        .andExpect(jsonPath("$.MemberClients.memberClients[0].gets").value(0)).andExpect(
+        .andExpect(jsonPath("$.MemberClients.memberClients[0].gets").value(0))
+        .andExpect(
             jsonPath("$.MemberClients.memberClients[0].uptime").value("0 Hours 0 Mins 1 Secs"));
   }
 
   @Test
   public void pulseUpdateForMemberDetails() throws Exception {
-    mockMvc
-        .perform(post("/pulseUpdate")
+    mockMvc.perform(
+        post("/pulseUpdate")
             .param("pulseData", "{\"MemberDetails\":{\"memberName\":\"" + MEMBER_NAME + "\"}}")
-            .principal(principal)
-            .accept(MediaType.parseMediaType(MediaType.APPLICATION_JSON_VALUE)))
+            .principal(PRINCIPAL)
+            .accept(JSON_MEDIA_TYPE))
         .andExpect(status().isOk()).andExpect(jsonPath("$.MemberDetails.name").value(MEMBER_NAME))
         .andExpect(jsonPath("$.MemberDetails.offHeapUsedSize").value(0))
         .andExpect(jsonPath("$.MemberDetails.diskStorageUsed").value(0D))
@@ -604,12 +504,12 @@ public class PulseControllerJUnitTest {
 
   @Test
   public void pulseUpdateForMemberDiskThroughput() throws Exception {
-    mockMvc
-        .perform(post("/pulseUpdate")
+    mockMvc.perform(
+        post("/pulseUpdate")
             .param("pulseData",
                 "{\"MemberDiskThroughput\":{\"memberName\":\"" + MEMBER_NAME + "\"}}")
-            .principal(principal)
-            .accept(MediaType.parseMediaType(MediaType.APPLICATION_JSON_VALUE)))
+            .principal(PRINCIPAL)
+            .accept(JSON_MEDIA_TYPE))
         .andExpect(status().isOk())
         .andExpect(jsonPath("$.MemberDiskThroughput.throughputWritesTrend").isEmpty())
         .andExpect(jsonPath("$.MemberDiskThroughput.throughputReadsTrend").isEmpty())
@@ -619,11 +519,12 @@ public class PulseControllerJUnitTest {
 
   @Test
   public void pulseUpdateForMemberGatewayHub() throws Exception {
-    mockMvc
-        .perform(post("/pulseUpdate")
-            .param("pulseData", "{\"MemberGatewayHub\":{\"memberName\":\"" + MEMBER_NAME + "\"}}")
-            .principal(principal)
-            .accept(MediaType.parseMediaType(MediaType.APPLICATION_JSON_VALUE)))
+    mockMvc.perform(
+        post("/pulseUpdate")
+            .param("pulseData",
+                "{\"MemberGatewayHub\":{\"memberName\":\"" + MEMBER_NAME + "\"}}")
+            .principal(PRINCIPAL)
+            .accept(JSON_MEDIA_TYPE))
         .andExpect(status().isOk())
         .andExpect(jsonPath("$.MemberGatewayHub.isGatewayReceiver").value(false))
         .andExpect(jsonPath("$.MemberGatewayHub.asyncEventQueues[0].batchTimeInterval").value(0))
@@ -642,22 +543,24 @@ public class PulseControllerJUnitTest {
 
   @Test
   public void pulseUpdateForMemberGCPauses() throws Exception {
-    mockMvc
-        .perform(post("/pulseUpdate")
-            .param("pulseData", "{\"MemberGCPauses\":{\"memberName\":\"" + MEMBER_NAME + "\"}}")
-            .principal(principal)
-            .accept(MediaType.parseMediaType(MediaType.APPLICATION_JSON_VALUE)))
+    mockMvc.perform(
+        post("/pulseUpdate")
+            .param("pulseData",
+                "{\"MemberGCPauses\":{\"memberName\":\"" + MEMBER_NAME + "\"}}")
+            .principal(PRINCIPAL)
+            .accept(JSON_MEDIA_TYPE))
         .andExpect(status().isOk()).andExpect(jsonPath("$.MemberGCPauses.gcPausesCount").value(0))
         .andExpect(jsonPath("$.MemberGCPauses.gcPausesTrend").isEmpty());
   }
 
   @Test
   public void pulseUpdateForMemberHeapUsage() throws Exception {
-    mockMvc
-        .perform(post("/pulseUpdate")
-            .param("pulseData", "{\"MemberHeapUsage\":{\"memberName\":\"" + MEMBER_NAME + "\"}}")
-            .principal(principal)
-            .accept(MediaType.parseMediaType(MediaType.APPLICATION_JSON_VALUE)))
+    mockMvc.perform(
+        post("/pulseUpdate")
+            .param("pulseData",
+                "{\"MemberHeapUsage\":{\"memberName\":\"" + MEMBER_NAME + "\"}}")
+            .principal(PRINCIPAL)
+            .accept(JSON_MEDIA_TYPE))
         .andExpect(status().isOk())
         .andExpect(jsonPath("$.MemberHeapUsage.heapUsageTrend").isEmpty())
         .andExpect(jsonPath("$.MemberHeapUsage.currentHeapUsage").value(0));
@@ -665,12 +568,12 @@ public class PulseControllerJUnitTest {
 
   @Test
   public void pulseUpdateForMemberKeyStatistics() throws Exception {
-    mockMvc
-        .perform(post("/pulseUpdate")
+    mockMvc.perform(
+        post("/pulseUpdate")
             .param("pulseData",
                 "{\"MemberKeyStatistics\":{\"memberName\":\"" + MEMBER_NAME + "\"}}")
-            .principal(principal)
-            .accept(MediaType.parseMediaType(MediaType.APPLICATION_JSON_VALUE)))
+            .principal(PRINCIPAL)
+            .accept(JSON_MEDIA_TYPE))
         .andExpect(status().isOk())
         .andExpect(jsonPath("$.MemberKeyStatistics.readPerSecTrend").isEmpty())
         .andExpect(jsonPath("$.MemberKeyStatistics.cpuUsageTrend").isEmpty())
@@ -680,12 +583,13 @@ public class PulseControllerJUnitTest {
 
   @Test
   public void pulseUpdateForMemberRegions() throws Exception {
-    mockMvc
-        .perform(post("/pulseUpdate")
+    mockMvc.perform(
+        post("/pulseUpdate")
             .param("pulseData", "{\"MemberRegions\":{\"memberName\":\"" + MEMBER_NAME + "\"}}")
-            .principal(principal)
-            .accept(MediaType.parseMediaType(MediaType.APPLICATION_JSON_VALUE)))
-        .andExpect(status().isOk()).andExpect(jsonPath("$.MemberRegions.name").value(MEMBER_NAME))
+            .principal(PRINCIPAL)
+            .accept(JSON_MEDIA_TYPE))
+        .andExpect(status().isOk())
+        .andExpect(jsonPath("$.MemberRegions.name").value(MEMBER_NAME))
         .andExpect(jsonPath("$.MemberRegions.memberRegions[0].fullPath").value(REGION_PATH))
         .andExpect(jsonPath("$.MemberRegions.memberRegions[0].entryCount").value(0))
         .andExpect(jsonPath("$.MemberRegions.memberRegions[0].name").value(REGION_NAME))
@@ -698,11 +602,11 @@ public class PulseControllerJUnitTest {
 
   @Test
   public void pulseUpdateForMembersList() throws Exception {
-    mockMvc
-        .perform(post("/pulseUpdate")
+    mockMvc.perform(
+        post("/pulseUpdate")
             .param("pulseData", "{\"MembersList\":{\"memberName\":\"" + MEMBER_NAME + "\"}}")
-            .principal(principal)
-            .accept(MediaType.parseMediaType(MediaType.APPLICATION_JSON_VALUE)))
+            .principal(PRINCIPAL)
+            .accept(JSON_MEDIA_TYPE))
         .andExpect(status().isOk())
         .andExpect(jsonPath("$.MembersList.clusterMembers[0].name").value(MEMBER_NAME))
         .andExpect(jsonPath("$.MembersList.clusterMembers[0].memberId").value(MEMBER_ID))
@@ -711,10 +615,10 @@ public class PulseControllerJUnitTest {
 
   @Test
   public void pulseUpdateForPulseVersion() throws Exception {
-    mockMvc
-        .perform(post("/pulseUpdate").param("pulseData", "{\"PulseVersion\":\"{}\"}")
-            .principal(principal)
-            .accept(MediaType.parseMediaType(MediaType.APPLICATION_JSON_VALUE)))
+    mockMvc.perform(
+        post("/pulseUpdate").param("pulseData", "{\"PulseVersion\":\"{}\"}")
+            .principal(PRINCIPAL)
+            .accept(JSON_MEDIA_TYPE))
         .andExpect(status().isOk())
         .andExpect(jsonPath("$.PulseVersion.sourceDate").value("not empty"))
         .andExpect(jsonPath("$.PulseVersion.sourceRepository").value("not empty"))
@@ -726,22 +630,24 @@ public class PulseControllerJUnitTest {
 
   @Test
   public void pulseUpdateForQueryStatistics() throws Exception {
-    mockMvc
-        .perform(post("/pulseUpdate").param("pulseData", "{\"QueryStatistics\":\"{}\"}")
-            .principal(principal)
-            .accept(MediaType.parseMediaType(MediaType.APPLICATION_JSON_VALUE)))
-        .andExpect(status().isOk()).andExpect(jsonPath("$.QueryStatistics.queriesList").isEmpty())
+    mockMvc.perform(
+        post("/pulseUpdate").param("pulseData", "{\"QueryStatistics\":\"{}\"}")
+            .principal(PRINCIPAL)
+            .accept(JSON_MEDIA_TYPE))
+        .andExpect(status().isOk())
+        .andExpect(jsonPath("$.QueryStatistics.queriesList").isEmpty())
         .andExpect(jsonPath("$.QueryStatistics.connectedFlag").value(false))
         .andExpect(jsonPath("$.QueryStatistics.connectedErrorMsg").value(""));
   }
 
   @Test
   public void pulseUpdateForSystemAlerts() throws Exception {
-    mockMvc
-        .perform(post("/pulseUpdate")
-            .param("pulseData", "{\"SystemAlerts\":{\"pageNumber\":\"1\"}}").principal(principal)
-            .accept(MediaType.parseMediaType(MediaType.APPLICATION_JSON_VALUE)))
-        .andExpect(status().isOk()).andExpect(jsonPath("$.SystemAlerts.pageNumber").value(1))
+    mockMvc.perform(
+        post("/pulseUpdate")
+            .param("pulseData", "{\"SystemAlerts\":{\"pageNumber\":\"1\"}}").principal(PRINCIPAL)
+            .accept(JSON_MEDIA_TYPE))
+        .andExpect(status().isOk())
+        .andExpect(jsonPath("$.SystemAlerts.pageNumber").value(1))
         .andExpect(jsonPath("$.SystemAlerts.connectedFlag").value(false))
         .andExpect(jsonPath("$.SystemAlerts.connectedErrorMsg").value(""))
         .andExpect(jsonPath("$.SystemAlerts.systemAlerts").isEmpty());
@@ -749,26 +655,29 @@ public class PulseControllerJUnitTest {
 
   @Test
   public void authenticateUserNotLoggedIn() throws Exception {
-    mockMvc
-        .perform(get("/authenticateUser")
-            .accept(MediaType.parseMediaType(MediaType.APPLICATION_JSON_VALUE)))
-        .andExpect(status().isOk()).andExpect(jsonPath("$.isUserLoggedIn").value(false));
+    mockMvc.perform(
+        get("/authenticateUser")
+            .accept(JSON_MEDIA_TYPE))
+        .andExpect(status().isOk())
+        .andExpect(jsonPath("$.isUserLoggedIn").value(false));
   }
 
   @Test
   public void authenticateUserLoggedIn() throws Exception {
-    mockMvc
-        .perform(get("/authenticateUser").principal(principal)
-            .accept(MediaType.parseMediaType(MediaType.APPLICATION_JSON_VALUE)))
+    mockMvc.perform(
+        get("/authenticateUser")
+            .principal(PRINCIPAL)
+            .accept(JSON_MEDIA_TYPE))
         .andExpect(status().isOk()).andExpect(jsonPath("$.isUserLoggedIn").value(true));
   }
 
   @Test
   public void pulseVersion() throws Exception {
-    mockMvc
-        .perform(get("/pulseVersion")
-            .accept(MediaType.parseMediaType(MediaType.APPLICATION_JSON_VALUE)))
-        .andExpect(status().isOk()).andExpect(jsonPath("$.pulseVersion").isNotEmpty())
+    mockMvc.perform(
+        get("/pulseVersion")
+            .accept(JSON_MEDIA_TYPE))
+        .andExpect(status().isOk())
+        .andExpect(jsonPath("$.pulseVersion").isNotEmpty())
         .andExpect(jsonPath("$.buildId").isNotEmpty())
         .andExpect(jsonPath("$.buildDate").isNotEmpty())
         .andExpect(jsonPath("$.sourceDate").isNotEmpty())
@@ -778,9 +687,12 @@ public class PulseControllerJUnitTest {
 
   @Test
   public void clearAlerts() throws Exception {
-    mockMvc
-        .perform(get("/clearAlerts").param("alertType", "1")
-            .accept(MediaType.parseMediaType(MediaType.APPLICATION_JSON_VALUE)))
+    when(cluster.getNotificationPageNumber()).thenReturn(1);
+
+    mockMvc.perform(
+        get("/clearAlerts")
+            .param("alertType", "1")
+            .accept(JSON_MEDIA_TYPE))
         .andExpect(status().isOk())
         .andExpect(jsonPath("$.pageNumber").value(1))
         .andExpect(jsonPath("$.systemAlerts").isEmpty())
@@ -790,17 +702,19 @@ public class PulseControllerJUnitTest {
 
   @Test
   public void acknowledgeAlert() throws Exception {
-    mockMvc
-        .perform(get("/acknowledgeAlert").param("alertId", "1")
-            .accept(MediaType.parseMediaType(MediaType.APPLICATION_JSON_VALUE)))
-        .andExpect(status().isOk()).andExpect(jsonPath("$.status").value("deleted"));
+    mockMvc.perform(
+        get("/acknowledgeAlert")
+            .param("alertId", "1")
+            .accept(JSON_MEDIA_TYPE))
+        .andExpect(status().isOk())
+        .andExpect(jsonPath("$.status").value("deleted"));
   }
 
   @Test
   public void dataBrowserRegions() throws Exception {
-    mockMvc
-        .perform(get("/dataBrowserRegions")
-            .accept(MediaType.parseMediaType(MediaType.APPLICATION_JSON_VALUE)))
+    mockMvc.perform(
+        get("/dataBrowserRegions")
+            .accept(JSON_MEDIA_TYPE))
         .andExpect(status().isOk()).andExpect(jsonPath("$.clusterName").value(CLUSTER_NAME))
         .andExpect(jsonPath("$.connectedFlag").value(false))
         .andExpect(jsonPath("$.clusterRegions[0].fullPath").value(REGION_PATH))
@@ -809,126 +723,138 @@ public class PulseControllerJUnitTest {
 
   @Test
   public void dataBrowserQuery() throws Exception {
-    doReturn(mapper.createObjectNode().put("foo", "bar")).when(cluster).executeQuery(anyString(),
-        anyString(), anyInt());
+    ObjectNode queryResult = mapper.createObjectNode().put("foo", "bar");
+    when(cluster.executeQuery(any(), any(), anyInt())).thenReturn(queryResult);
 
     String query = "SELECT * FROM " + REGION_PATH;
-    mockMvc
-        .perform(get("/dataBrowserQuery").param("query", query)
-            .param("members", MEMBER_NAME).principal(principal)
-            .accept(MediaType.parseMediaType(MediaType.APPLICATION_JSON_VALUE)))
-        .andExpect(status().isOk()).andExpect(jsonPath("$.foo").value("bar"));
+    mockMvc.perform(
+        get("/dataBrowserQuery")
+            .param("query", query)
+            .param("members", MEMBER_NAME)
+            .principal(PRINCIPAL)
+            .accept(JSON_MEDIA_TYPE))
+        .andExpect(status().isOk())
+        .andExpect(jsonPath("$.foo").value("bar"));
 
-    // Verify cluster addQueryInHistory is invoked
-    verify(cluster).addQueryInHistory(query, principal.getName());
+    verify(cluster).addQueryInHistory(query, PRINCIPAL.getName());
   }
 
   @Test
   public void dataBrowserQueryWithMessageResult() throws Exception {
     String message = "Query is invalid due to error : Region mentioned in query probably missing /";
-    doReturn(mapper.createObjectNode().put("message", message)).when(cluster).executeQuery(
-        anyString(),
-        anyString(), anyInt());
+    ObjectNode queryResult = mapper.createObjectNode().put("message", message);
+    when(cluster.executeQuery(any(), any(), anyInt())).thenReturn(queryResult);
 
     String query = "SELECT * FROM " + REGION_PATH;
-    mockMvc
-        .perform(get("/dataBrowserQuery").param("query", query)
-            .param("members", MEMBER_NAME).principal(principal)
-            .accept(MediaType.parseMediaType(MediaType.APPLICATION_JSON_VALUE)))
-        .andExpect(status().isOk()).andExpect(jsonPath("$.message").value(message));
+    mockMvc.perform(
+        get("/dataBrowserQuery")
+            .param("query", query)
+            .param("members", MEMBER_NAME)
+            .principal(PRINCIPAL)
+            .accept(JSON_MEDIA_TYPE))
+        .andExpect(status().isOk())
+        .andExpect(jsonPath("$.message").value(message));
 
-    // Verify cluster addQueryInHistory is invoked
-    verify(cluster).addQueryInHistory(query, principal.getName());
+    verify(cluster).addQueryInHistory(query, PRINCIPAL.getName());
   }
 
   @Test
   public void dataBrowserQueryWithExceptionResult() throws Exception {
-    doThrow(new IllegalStateException()).when(cluster).executeQuery(anyString(),
-        anyString(), anyInt());
+    when(cluster.executeQuery(any(), any(), anyInt())).thenThrow(IllegalStateException.class);
 
     String query = "SELECT * FROM " + REGION_PATH;
-    mockMvc
-        .perform(get("/dataBrowserQuery").param("query", query)
-            .param("members", MEMBER_NAME).principal(principal)
-            .accept(MediaType.parseMediaType(MediaType.APPLICATION_JSON_VALUE)))
-        .andExpect(status().isOk()).andExpect(content().string("{}"));
+    mockMvc.perform(
+        get("/dataBrowserQuery")
+            .param("query", query)
+            .param("members", MEMBER_NAME)
+            .principal(PRINCIPAL)
+            .accept(JSON_MEDIA_TYPE))
+        .andExpect(status().isOk())
+        .andExpect(content().string("{}"));
 
-    // Verify cluster addQueryInHistory is invoked
-    verify(cluster).addQueryInHistory(query, principal.getName());
+    verify(cluster).addQueryInHistory(query, PRINCIPAL.getName());
   }
 
   @Test
   public void dataBrowserExport() throws Exception {
-    doReturn(mapper.createObjectNode().put("foo", "bar")).when(cluster).executeQuery(anyString(),
-        anyString(), anyInt());
+    ObjectNode queryResult = mapper.createObjectNode().put("foo", "bar");
+    when(cluster.executeQuery(any(), any(), anyInt())).thenReturn(queryResult);
 
     String query = "SELECT * FROM " + REGION_PATH;
-    mockMvc
-        .perform(get("/dataBrowserExport").param("query", query)
-            .param("members", MEMBER_NAME).principal(principal)
-            .accept(MediaType.parseMediaType(MediaType.APPLICATION_JSON_VALUE)))
+    mockMvc.perform(
+        get("/dataBrowserExport")
+            .param("query", query)
+            .param("members", MEMBER_NAME)
+            .principal(PRINCIPAL)
+            .accept(JSON_MEDIA_TYPE))
         .andExpect(status().isOk())
         .andExpect(header().string("Content-Disposition", "attachment; filename=results.json"))
         .andExpect(jsonPath("$.foo").value("bar"));
 
-    // Verify cluster addQueryInHistory is invoked
-    verify(cluster).addQueryInHistory(query, principal.getName());
+    verify(cluster).addQueryInHistory(query, PRINCIPAL.getName());
   }
 
   @Test
   public void dataBrowserExportWithMessageResult() throws Exception {
     String message = "Query is invalid due to error : Region mentioned in query probably missing /";
-    doReturn(mapper.createObjectNode().put("message", message)).when(cluster).executeQuery(
-        anyString(),
-        anyString(), anyInt());
+    ObjectNode queryResult = mapper.createObjectNode().put("message", message);
+    when(cluster.executeQuery(any(), any(), anyInt())).thenReturn(queryResult);
 
     String query = "SELECT * FROM " + REGION_PATH;
-    mockMvc
-        .perform(get("/dataBrowserExport").param("query", query)
-            .param("members", MEMBER_NAME).principal(principal)
-            .accept(MediaType.parseMediaType(MediaType.APPLICATION_JSON_VALUE)))
+    mockMvc.perform(
+        get("/dataBrowserExport")
+            .param("query", query)
+            .param("members", MEMBER_NAME)
+            .principal(PRINCIPAL)
+            .accept(JSON_MEDIA_TYPE))
         .andExpect(status().isOk())
         .andExpect(header().string("Content-Disposition", "attachment; filename=results.json"))
         .andExpect(jsonPath("$.message").value(message));
 
-    // Verify cluster addQueryInHistory is invoked
-    verify(cluster).addQueryInHistory(query, principal.getName());
+    verify(cluster).addQueryInHistory(query, PRINCIPAL.getName());
   }
 
   @Test
   public void dataBrowserExportWithExceptionResult() throws Exception {
-    doThrow(new IllegalStateException()).when(cluster).executeQuery(anyString(),
-        anyString(), anyInt());
+    when(cluster.executeQuery(any(), any(), anyInt())).thenThrow(IllegalStateException.class);
 
     String query = "SELECT * FROM " + REGION_PATH;
-    mockMvc
-        .perform(get("/dataBrowserExport").param("query", query)
-            .param("members", MEMBER_NAME).principal(principal)
-            .accept(MediaType.parseMediaType(MediaType.APPLICATION_JSON_VALUE)))
+    mockMvc.perform(
+        get("/dataBrowserExport")
+            .param("query", query)
+            .param("members", MEMBER_NAME)
+            .principal(PRINCIPAL)
+            .accept(JSON_MEDIA_TYPE))
         .andExpect(status().isOk())
         .andExpect(header().string("Content-Disposition", "attachment; filename=results.json"))
         .andExpect(content().string("{}"));
 
-    // Verify cluster addQueryInHistory is invoked
-    verify(cluster).addQueryInHistory(query, principal.getName());
+    verify(cluster).addQueryInHistory(query, PRINCIPAL.getName());
   }
 
   @Test
   public void dataBrowserQueryHistory() throws Exception {
-    dataBrowserQuery();
+    String query = "\"SELECT * FROM " + REGION_PATH + "\"";
+    ArrayNode queryHistory = mapper.createArrayNode();
+    queryHistory.addObject().put("queryText", query);
+
+    when(cluster.getQueryHistoryByUserId(PRINCIPAL_USER)).thenReturn(queryHistory);
 
-    mockMvc
-        .perform(get("/dataBrowserQueryHistory").param("action", "view").principal(principal)
-            .accept(MediaType.parseMediaType(MediaType.APPLICATION_JSON_VALUE)))
-        .andExpect(status().isOk()).andExpect(
-            jsonPath("$.queryHistory[0].queryText").value("\"SELECT * FROM " + REGION_PATH + "\""));
+    mockMvc.perform(
+        get("/dataBrowserQueryHistory")
+            .param("action", "view")
+            .principal(PRINCIPAL)
+            .accept(JSON_MEDIA_TYPE))
+        .andExpect(status().isOk())
+        .andExpect(jsonPath("$.queryHistory[0].queryText").value(query));
   }
 
   @Test
   public void getQueryStatisticsGridModel() throws Exception {
-    mockMvc
-        .perform(get("/getQueryStatisticsGridModel").principal(principal)
-            .accept(MediaType.parseMediaType(MediaType.APPLICATION_JSON_VALUE)))
+    mockMvc.perform(
+        get("/getQueryStatisticsGridModel")
+            .principal(PRINCIPAL)
+            .accept(JSON_MEDIA_TYPE))
         .andExpect(status().isOk())
         .andExpect(jsonPath("$.columnNames",
             containsInAnyOrder("Query", "NumExecution", "TotalExecutionTime(ns)",
@@ -938,4 +864,71 @@ public class PulseControllerJUnitTest {
                 "ExecutionTime(ns)", "ProjectionTime(ns)", "RowsModificationTime(ns)",
                 "QNNumRowsSeen", "QNMsgSendTime(ns)", "QNMsgSerTime(ns)", "QNRespDeSerTime(ns)")));
   }
+
+  private void prepareCluster() {
+    when(cluster.getAlertsList()).thenReturn(array());
+    when(cluster.getStatements()).thenReturn(array());
+    when(cluster.getConnectionErrorMsg()).thenReturn("");
+    when(cluster.getServerName()).thenReturn(CLUSTER_NAME);
+
+    when(cluster.getStatisticTrend(CLUSTER_STAT_MEMORY_USAGE)).thenReturn(array(1, 2, 3));
+    when(cluster.getStatisticTrend(CLUSTER_STAT_THROUGHPUT_READS)).thenReturn(array(1, 2, 3));
+    when(cluster.getStatisticTrend(CLUSTER_STAT_THROUGHPUT_WRITES)).thenReturn(array(4, 5, 6));
+    when(cluster.getStatisticTrend(CLUSTER_STAT_GARBAGE_COLLECTION)).thenReturn(array());
+    when(cluster.getGarbageCollectionCount()).thenReturn(0L);
+    when(cluster.getNotificationPageNumber()).thenReturn(1);
+
+    Cluster.RegionOnMember regionOnMember = new Cluster.RegionOnMember();
+    regionOnMember.setRegionFullPath(REGION_PATH);
+    regionOnMember.setMemberName(MEMBER_NAME);
+
+    Cluster.Region clusterRegion = new Cluster.Region();
+    clusterRegion.setName(REGION_NAME);
+    clusterRegion.setFullPath(REGION_PATH);
+    clusterRegion.setRegionType(REGION_TYPE);
+    clusterRegion.setMemberCount(1);
+    clusterRegion.setMemberName(singletonList(MEMBER_NAME));
+    clusterRegion.setPutsRate(12.31D);
+    clusterRegion.setGetsRate(27.99D);
+    clusterRegion.setRegionOnMembers(singletonList(regionOnMember));
+
+    when(cluster.getClusterRegion(REGION_PATH)).thenReturn(clusterRegion);
+
+    HashMap<String, Cluster.Region> clusterRegions = new HashMap<>();
+    clusterRegions.put(REGION_NAME, clusterRegion);
+    when(cluster.getClusterRegions()).thenReturn(clusterRegions);
+
+    Cluster.Member member = new Cluster.Member();
+    member.setId(MEMBER_ID);
+    member.setName(MEMBER_NAME);
+    member.setUptime(1L);
+    member.setHost(PHYSICAL_HOST_NAME);
+    member.setGemfireVersion(GEMFIRE_VERSION);
+    member.setCpuUsage(55.77123D);
+
+    member.setMemberRegions(clusterRegions);
+
+    Cluster.AsyncEventQueue aeq = new Cluster.AsyncEventQueue();
+    aeq.setAsyncEventListener(AEQ_LISTENER);
+    member.setAsyncEventQueueList(singletonList(aeq));
+
+    Cluster.Client client = new Cluster.Client();
+    client.setId("100");
+    client.setName(CLIENT_NAME);
+    client.setUptime(1L);
+
+    HashMap<String, Cluster.Client> memberClientsHMap = new HashMap<>();
+    memberClientsHMap.put(CLIENT_NAME, client);
+    member.setMemberClientsHMap(memberClientsHMap);
+
+    HashMap<String, Cluster.Member> membersHMap = new HashMap<>();
+    membersHMap.put(MEMBER_NAME, member);
+    when(cluster.getMembersHMap()).thenReturn(membersHMap);
+    when(cluster.getMembers()).thenReturn(array(member));
+    when(cluster.getMemberCount()).thenReturn(0);
+    when(cluster.getMember(anyString())).thenReturn(member);
+
+    List<Cluster.Member> members = singletonList(member);
+    when(cluster.getPhysicalToMember()).thenReturn(singletonMap(PHYSICAL_HOST_NAME, members));
+  }
 }
diff --git a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/security/GemfireSecurityConfig.java b/geode-pulse/src/integrationTest/java/org/apache/geode/tools/pulse/internal/context/OAuthSecurityTokenHandoffTestConfig.java
similarity index 50%
copy from geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/security/GemfireSecurityConfig.java
copy to geode-pulse/src/integrationTest/java/org/apache/geode/tools/pulse/internal/context/OAuthSecurityTokenHandoffTestConfig.java
index 917a698..7ce8da3 100644
--- a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/security/GemfireSecurityConfig.java
+++ b/geode-pulse/src/integrationTest/java/org/apache/geode/tools/pulse/internal/context/OAuthSecurityTokenHandoffTestConfig.java
@@ -13,31 +13,31 @@
  * the License.
  */
 
-package org.apache.geode.tools.pulse.internal.security;
+package org.apache.geode.tools.pulse.internal.context;
+
+import static org.mockito.Mockito.mock;
 
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Primary;
 import org.springframework.context.annotation.Profile;
-import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
-import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
-import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
+
+import org.apache.geode.tools.pulse.internal.data.ClusterFactory;
+import org.apache.geode.tools.pulse.internal.data.Repository;
 
 @Configuration
-@EnableWebSecurity
-@EnableGlobalMethodSecurity(prePostEnabled = true)
-@Profile("pulse.authentication.gemfire")
-public class GemfireSecurityConfig extends DefaultSecurityConfig {
+@Primary
+@Profile("pulse.oauth.security.token.test")
+public class OAuthSecurityTokenHandoffTestConfig {
   @Bean
-  public GemFireAuthenticationProvider gemAuthenticationProvider() {
-    return new GemFireAuthenticationProvider();
+  public ClusterFactory clusterFactory() {
+    return mock(ClusterFactory.class);
   }
 
-  @Autowired
-  GemFireAuthenticationProvider gemAuthenticationProvider;
-
-  @Override
-  protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) {
-    authenticationManagerBuilder.authenticationProvider(gemAuthenticationProvider);
+  @Bean
+  public Repository repository(OAuth2AuthorizedClientService authorizedClientService,
+      ClusterFactory clusterFactory) {
+    return new Repository(authorizedClientService, clusterFactory);
   }
 }
diff --git a/geode-pulse/src/integrationTest/java/org/apache/geode/tools/pulse/internal/context/PulseControllerTestContext.java b/geode-pulse/src/integrationTest/java/org/apache/geode/tools/pulse/internal/context/PulseControllerTestContext.java
new file mode 100644
index 0000000..f61e041
--- /dev/null
+++ b/geode-pulse/src/integrationTest/java/org/apache/geode/tools/pulse/internal/context/PulseControllerTestContext.java
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.geode.tools.pulse.internal.context;
+
+import static java.util.Collections.enumeration;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+import java.util.ResourceBundle;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
+
+import org.apache.geode.tools.pulse.internal.PropertiesFileLoader;
+import org.apache.geode.tools.pulse.internal.data.PulseConstants;
+import org.apache.geode.tools.pulse.internal.data.Repository;
+
+@Configuration
+@Profile("pulse.controller.test")
+public class PulseControllerTestContext {
+
+  @Bean
+  public Repository repository() {
+    Repository repository = mock(Repository.class);
+    ResourceBundle resourceBundle = getResourceBundle();
+    when(repository.getResourceBundle()).thenReturn(resourceBundle);
+    return repository;
+  }
+
+  @Bean(name = "logoutTargetURL")
+  public String defaultLogoutTargetURL() {
+    return "/login.html";
+  }
+
+  @Bean
+  public PropertiesFileLoader propertiesLoader() {
+    PropertiesFileLoader propertiesFileLoader = mock(PropertiesFileLoader.class);
+    Properties properties = new Properties();
+    properties.setProperty(PulseConstants.PROPERTY_BUILD_DATE, "not empty");
+    properties.setProperty(PulseConstants.PROPERTY_BUILD_ID, "not empty");
+    properties.setProperty(PulseConstants.PROPERTY_PULSE_VERSION, "not empty");
+    properties.setProperty(PulseConstants.PROPERTY_SOURCE_DATE, "not empty");
+    properties.setProperty(PulseConstants.PROPERTY_SOURCE_REPOSITORY, "not empty");
+    properties.setProperty(PulseConstants.PROPERTY_SOURCE_REVISION, "not empty");
+
+    when(propertiesFileLoader.loadProperties(any(), any())).thenReturn(properties);
+
+    return propertiesFileLoader;
+  }
+
+  private ResourceBundle getResourceBundle() {
+    return new ResourceBundle() {
+      Map<String, Object> objects = new HashMap<>();
+
+      @Override
+      protected Object handleGetObject(String key) {
+        objects.put(key, key);
+        return key;
+      }
+
+      @Override
+      public Enumeration<String> getKeys() {
+        return enumeration(objects.keySet());
+      }
+    };
+  }
+}
diff --git a/geode-pulse/src/integrationTest/java/org/apache/geode/tools/pulse/security/CustomSecurityConfigTest.java b/geode-pulse/src/integrationTest/java/org/apache/geode/tools/pulse/security/CustomSecurityConfigTest.java
index 8c54ea3..e772c7b 100644
--- a/geode-pulse/src/integrationTest/java/org/apache/geode/tools/pulse/security/CustomSecurityConfigTest.java
+++ b/geode-pulse/src/integrationTest/java/org/apache/geode/tools/pulse/security/CustomSecurityConfigTest.java
@@ -28,15 +28,13 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.test.context.ActiveProfiles;
 import org.springframework.test.context.ContextConfiguration;
 import org.springframework.test.context.junit4.SpringRunner;
-import org.springframework.test.context.web.GenericXmlWebContextLoader;
 import org.springframework.test.context.web.WebAppConfiguration;
 import org.springframework.test.web.servlet.MockMvc;
 import org.springframework.test.web.servlet.setup.MockMvcBuilders;
 import org.springframework.web.context.WebApplicationContext;
 
 @RunWith(SpringRunner.class)
-@ContextConfiguration(locations = {"classpath*:WEB-INF/pulse-servlet.xml"},
-    loader = GenericXmlWebContextLoader.class)
+@ContextConfiguration(locations = {"classpath*:WEB-INF/pulse-servlet.xml"})
 @WebAppConfiguration
 @ActiveProfiles({"pulse.authentication.custom"})
 public class CustomSecurityConfigTest {
diff --git a/geode-pulse/src/integrationTest/java/org/apache/geode/tools/pulse/security/DefaultSecurityConfigTest.java b/geode-pulse/src/integrationTest/java/org/apache/geode/tools/pulse/security/DefaultSecurityConfigTest.java
index 2304c2c..9b0e0eb 100644
--- a/geode-pulse/src/integrationTest/java/org/apache/geode/tools/pulse/security/DefaultSecurityConfigTest.java
+++ b/geode-pulse/src/integrationTest/java/org/apache/geode/tools/pulse/security/DefaultSecurityConfigTest.java
@@ -28,15 +28,13 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.test.context.ActiveProfiles;
 import org.springframework.test.context.ContextConfiguration;
 import org.springframework.test.context.junit4.SpringRunner;
-import org.springframework.test.context.web.GenericXmlWebContextLoader;
 import org.springframework.test.context.web.WebAppConfiguration;
 import org.springframework.test.web.servlet.MockMvc;
 import org.springframework.test.web.servlet.setup.MockMvcBuilders;
 import org.springframework.web.context.WebApplicationContext;
 
 @RunWith(SpringRunner.class)
-@ContextConfiguration(locations = {"classpath*:WEB-INF/pulse-servlet.xml"},
-    loader = GenericXmlWebContextLoader.class)
+@ContextConfiguration(locations = {"classpath*:WEB-INF/pulse-servlet.xml"})
 @WebAppConfiguration
 @ActiveProfiles({"pulse.authentication.default"})
 public class DefaultSecurityConfigTest {
diff --git a/geode-pulse/src/integrationTest/java/org/apache/geode/tools/pulse/security/OAuthSecurityConfigTest.java b/geode-pulse/src/integrationTest/java/org/apache/geode/tools/pulse/security/OAuthSecurityConfigTest.java
index e5b5be4..608391d 100644
--- a/geode-pulse/src/integrationTest/java/org/apache/geode/tools/pulse/security/OAuthSecurityConfigTest.java
+++ b/geode-pulse/src/integrationTest/java/org/apache/geode/tools/pulse/security/OAuthSecurityConfigTest.java
@@ -15,11 +15,19 @@
 
 package org.apache.geode.tools.pulse.security;
 
+import static java.lang.String.format;
+import static org.assertj.core.api.Assertions.assertThat;
 import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrlPattern;
 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+import static org.springframework.web.util.UriComponentsBuilder.fromUriString;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -28,18 +36,29 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.test.context.ActiveProfiles;
 import org.springframework.test.context.ContextConfiguration;
 import org.springframework.test.context.junit4.SpringRunner;
-import org.springframework.test.context.web.GenericXmlWebContextLoader;
 import org.springframework.test.context.web.WebAppConfiguration;
 import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.ResultMatcher;
 import org.springframework.test.web.servlet.setup.MockMvcBuilders;
 import org.springframework.web.context.WebApplicationContext;
 
 @RunWith(SpringRunner.class)
-@ContextConfiguration(locations = {"classpath*:WEB-INF/pulse-servlet.xml"},
-    loader = GenericXmlWebContextLoader.class)
 @WebAppConfiguration
+@ContextConfiguration("classpath*:WEB-INF/pulse-servlet.xml")
 @ActiveProfiles({"pulse.authentication.oauth"})
 public class OAuthSecurityConfigTest {
+  // When Spring needs to authorize the user via OAuth, it redirects to this URL, which Spring
+  // defined based on the OAuth provider we configured. This URL then redirects to the authorization
+  // URI we configured for that provider.
+  private static final String SPRING_AUTHORIZATION_URL_FOR_OAUTH_PROVIDER =
+      "http://localhost/oauth2/authorization/%s";
+
+  // The URL to which the OAuth provider will redirect with the authorization code.
+  private static final String PULSE_REDIRECT_URL_FOR_OAUTH_PROVIDER =
+      "http://localhost/login/oauth2/code/%s";
+
+  private final Properties pulseProperties = new Properties();
+
   @Autowired
   private WebApplicationContext context;
 
@@ -51,15 +70,76 @@ public class OAuthSecurityConfigTest {
         .webAppContextSetup(context)
         .apply(springSecurity())
         .build();
+
+    try (InputStream propertiesStream = getClass().getClassLoader()
+        .getResourceAsStream("pulse.properties")) {
+      pulseProperties.load(propertiesStream);
+    } catch (IOException cause) {
+      throw new RuntimeException("Unable to load pulse.properties for test");
+    }
   }
 
   @Test
-  public void redirectToOauth() throws Exception {
-    mvc.perform(get("/login.html")).andExpect(status().is3xxRedirection())
-        .andExpect(redirectedUrl(("http://localhost/oauth2/authorization/uaa")));
+  public void visitingProtectedUrlRedirectsUnauthenticatedUserToOAuthClientURL() throws Exception {
+    String pulseOAuthProvider = property("pulse.oauth.providerId");
+    String aProtectedUrl = "/login.html";
+
+    mvc.perform(get(aProtectedUrl))
+        .andExpect(status().is3xxRedirection())
+        .andExpect(redirectedUrl(springAuthorizationUrlFor(pulseOAuthProvider)));
+  }
+
+  @Test
+  public void oauthClientURLRedirectsToOPulseOAuthAuthorizationURL() throws Exception {
+    String pulseOAuthProvider = property("pulse.oauth.providerId");
+    String pulseOAuthClientId = property("pulse.oauth.clientId");
+    String pulseOAuthAuthorizationUri = property("pulse.oauth.authorizationUri");
+
+    Map<String, String> expectedRedirectParams = new HashMap<>();
+    expectedRedirectParams.put("response_type", "code");
+    expectedRedirectParams.put("client_id", pulseOAuthClientId);
+    expectedRedirectParams.put("redirect_uri", pulseRedirectUrlFor(pulseOAuthProvider));
 
-    mvc.perform(get("http://localhost/oauth2/authorization/uaa"))
+    mvc.perform(get(springAuthorizationUrlFor(pulseOAuthProvider)))
         .andExpect(status().is3xxRedirection())
-        .andExpect(redirectedUrlPattern("http://example.com/uaa/oauth/**"));
+        .andExpect(redirectedUrlPath(pulseOAuthAuthorizationUri))
+        .andExpect(redirectedUrlParams(expectedRedirectParams));
+  }
+
+  private static ResultMatcher redirectedUrlPath(String expectedPath) {
+    return result -> {
+      String redirectedUrlString = result.getResponse().getRedirectedUrl();
+      assertThat(redirectedUrlString).isNotNull();
+      String actualPath = redirectedUrlString.split("[?#]")[0];
+      assertThat(actualPath)
+          .as("redirect URL path")
+          .startsWith(expectedPath);
+    };
+  }
+
+  private static ResultMatcher redirectedUrlParams(Map<String, String> expectedParams) {
+    return result -> {
+      String redirectedUrlString = result.getResponse().getRedirectedUrl();
+      assertThat(redirectedUrlString).isNotNull();
+      Map<String, String> actualParams = fromUriString(redirectedUrlString)
+          .build()
+          .getQueryParams()
+          .toSingleValueMap();
+      assertThat(actualParams)
+          .as("redirected URL params")
+          .containsAllEntriesOf(expectedParams);
+    };
+  }
+
+  private String property(String name) {
+    return pulseProperties.getProperty(name);
+  }
+
+  private static String pulseRedirectUrlFor(String provider) {
+    return format(PULSE_REDIRECT_URL_FOR_OAUTH_PROVIDER, provider);
+  }
+
+  private static String springAuthorizationUrlFor(String pulseOAuthProvider) {
+    return format(SPRING_AUTHORIZATION_URL_FOR_OAUTH_PROVIDER, pulseOAuthProvider);
   }
 }
diff --git a/geode-pulse/src/integrationTest/java/org/apache/geode/tools/pulse/security/OAuthSecurityTokenHandoffTest.java b/geode-pulse/src/integrationTest/java/org/apache/geode/tools/pulse/security/OAuthSecurityTokenHandoffTest.java
new file mode 100644
index 0000000..bf10319
--- /dev/null
+++ b/geode-pulse/src/integrationTest/java/org/apache/geode/tools/pulse/security/OAuthSecurityTokenHandoffTest.java
@@ -0,0 +1,154 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You 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 org.apache.geode.tools.pulse.security;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.springframework.security.oauth2.core.AuthorizationGrantType.AUTHORIZATION_CODE;
+import static org.springframework.security.oauth2.core.OAuth2AccessToken.TokenType.BEARER;
+import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
+import static org.springframework.security.web.context.HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.mock.web.MockHttpSession;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.context.SecurityContextImpl;
+import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
+import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
+import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
+import org.springframework.security.oauth2.client.registration.ClientRegistration;
+import org.springframework.security.oauth2.core.OAuth2AccessToken;
+import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
+import org.springframework.security.oauth2.core.user.OAuth2User;
+import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.context.web.WebAppConfiguration;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+import org.springframework.web.context.WebApplicationContext;
+
+import org.apache.geode.tools.pulse.internal.data.Cluster;
+import org.apache.geode.tools.pulse.internal.data.ClusterFactory;
+
+@RunWith(SpringRunner.class)
+@WebAppConfiguration
+@ContextConfiguration("classpath*:WEB-INF/pulse-servlet.xml")
+@ActiveProfiles({"pulse.oauth.security.token.test", "pulse.authentication.oauth"})
+public class OAuthSecurityTokenHandoffTest {
+  private static final String AUTHENTICATION_PROVIDER_ID = "uaa";
+  private MockMvc mvc;
+
+  @Autowired
+  private ClusterFactory clusterFactory;
+
+  @Autowired
+  private OAuth2AuthorizedClientService authorizedClientService;
+
+  @Autowired
+  private WebApplicationContext context;
+
+  @Before
+  public void setup() {
+    mvc = MockMvcBuilders
+        .webAppContextSetup(context)
+        .apply(springSecurity())
+        .build();
+  }
+
+  @Test
+  public void usesCurrentSessionAccessTokenAsCredentialToConnectToGemFire() throws Exception {
+    String userName = "some-user-name";
+    String accessTokenValue = "the-access-token-value";
+    String urlThatTriggersPulseToConnectToGemFire = "/dataBrowserRegions";
+
+    Cluster clusterForUser = mock(Cluster.class);
+    when(clusterFactory.create(any(), any(), eq(userName), any(), any()))
+        .thenReturn(clusterForUser);
+
+    MockHttpSession session = sessionWithAuthenticatedUser(userName, accessTokenValue);
+
+    mvc.perform(get(urlThatTriggersPulseToConnectToGemFire).session(session));
+
+    verify(clusterForUser).connectToGemFire(accessTokenValue);
+  }
+
+  private void authorizeClient(
+      OAuth2AuthenticationToken authenticationToken, OAuth2AccessToken accessToken) {
+    OAuth2AuthorizedClient authorizedClient =
+        new OAuth2AuthorizedClient(clientRegistration(),
+            authenticationToken.getPrincipal().getName(), accessToken);
+    authorizedClientService.saveAuthorizedClient(authorizedClient, authenticationToken);
+  }
+
+  private MockHttpSession sessionWithAuthenticatedUser(String username, String tokenValue) {
+    OAuth2AuthenticationToken authenticationToken = authenticationToken(username);
+    authorizeClient(authenticationToken, accessToken(tokenValue));
+    return sessionWithAuthenticationToken(authenticationToken);
+  }
+
+  private static OAuth2AccessToken accessToken(String tokenValue) {
+    return new OAuth2AccessToken(BEARER, tokenValue, Instant.now(),
+        Instant.now().plus(Duration.ofHours(1)));
+  }
+
+  private static OAuth2AuthenticationToken authenticationToken(String userName) {
+    Map<String, Object> attributes = new HashMap<>();
+    attributes.put("sub", userName);
+
+    List<GrantedAuthority> authorities = Arrays.asList(
+        new OAuth2UserAuthority("ROLE_USER", attributes),
+        new OAuth2UserAuthority("SCOPE_CLUSTER:READ", attributes),
+        new OAuth2UserAuthority("SCOPE_CLUSTER:WRITE", attributes),
+        new OAuth2UserAuthority("SCOPE_DATA:READ", attributes),
+        new OAuth2UserAuthority("SCOPE_DATA:WRITE", attributes));
+    OAuth2User user = new DefaultOAuth2User(authorities, attributes, "sub");
+    return new OAuth2AuthenticationToken(user, authorities, AUTHENTICATION_PROVIDER_ID);
+  }
+
+  private static ClientRegistration clientRegistration() {
+    return ClientRegistration
+        .withRegistrationId(AUTHENTICATION_PROVIDER_ID)
+        .authorizationGrantType(AUTHORIZATION_CODE)
+        .redirectUriTemplate("{baseUrl}/oauth2/code/{registrationId}")
+        .clientId("client-id")
+        .authorizationUri("authorization-uri")
+        .tokenUri("token-uri")
+        .build();
+  }
+
+  private static MockHttpSession sessionWithAuthenticationToken(
+      OAuth2AuthenticationToken authenticationToken) {
+    MockHttpSession session = new MockHttpSession();
+    session.setAttribute(SPRING_SECURITY_CONTEXT_KEY, new SecurityContextImpl(authenticationToken));
+    return session;
+  }
+}
diff --git a/geode-pulse/src/integrationTest/resources/pulse.properties b/geode-pulse/src/integrationTest/resources/pulse.properties
index e06fd2d..b7607ae 100644
--- a/geode-pulse/src/integrationTest/resources/pulse.properties
+++ b/geode-pulse/src/integrationTest/resources/pulse.properties
@@ -13,11 +13,12 @@
 # the License.
 #
 
-pulse.oauth.provider=uaa
+pulse.oauth.providerId=uaa
+pulse.oauth.providerName=UAA
 pulse.oauth.clientId=pulse
 pulse.oauth.clientSecret=secret
 pulse.oauth.authorizationUri=http://example.com/uaa/oauth/authorize
 pulse.oauth.tokenUri=http://example.com/uaa/oauth/token
 pulse.oauth.userInfoUri=http://example.com/uaa/userinfo
 pulse.oauth.jwkSetUri=http://example.com/uaa/token_keys
-pulse.oauth.userNameAttributeName=user_name
\ No newline at end of file
+pulse.oauth.userNameAttributeName=sub
diff --git a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/ClassPathPropertiesFileLoader.java b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/ClassPathPropertiesFileLoader.java
new file mode 100644
index 0000000..8a1c118
--- /dev/null
+++ b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/ClassPathPropertiesFileLoader.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You 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 org.apache.geode.tools.pulse.internal;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Properties;
+import java.util.ResourceBundle;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.springframework.stereotype.Component;
+
+@Component
+public class ClassPathPropertiesFileLoader implements PropertiesFileLoader {
+  private static final Logger logger = LogManager.getLogger();
+
+  @Override
+  public Properties loadProperties(String propertyFile, ResourceBundle resourceBundle) {
+    final Properties properties = new Properties();
+    try (final InputStream stream =
+        Thread.currentThread().getContextClassLoader().getResourceAsStream(propertyFile)) {
+      logger.info(propertyFile + " " + resourceBundle.getString("LOG_MSG_FILE_FOUND"));
+      properties.load(stream);
+    } catch (IOException e) {
+      logger.error(resourceBundle.getString("LOG_MSG_EXCEPTION_LOADING_PROPERTIES_FILE"), e);
+    }
+
+    return properties;
+  }
+}
diff --git a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/data/IClusterUpdater.java b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/PropertiesFileLoader.java
similarity index 60%
copy from geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/data/IClusterUpdater.java
copy to geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/PropertiesFileLoader.java
index 942384a..98fd885 100644
--- a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/data/IClusterUpdater.java
+++ b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/PropertiesFileLoader.java
@@ -1,5 +1,4 @@
 /*
- *
  * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
  * agreements. See the NOTICE file distributed with this work for additional information regarding
  * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
@@ -12,27 +11,13 @@
  * 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 org.apache.geode.tools.pulse.internal.data;
-
-import javax.management.remote.JMXConnector;
-
-import com.fasterxml.jackson.databind.node.ObjectNode;
-
-/**
- * Interface having updateData() function which is implemented by JMXDataUpdater
- *
- * @since GemFire version 7.0.Beta 2012-09-23
- *
- */
-public interface IClusterUpdater {
-  boolean updateData();
+package org.apache.geode.tools.pulse.internal;
 
-  ObjectNode executeQuery(String queryText, String members, int limit);
+import java.util.Properties;
+import java.util.ResourceBundle;
 
-  default JMXConnector connect(String username, String password) {
-    return null;
-  }
+public interface PropertiesFileLoader {
+  Properties loadProperties(String propertyFile, ResourceBundle resourceBundle);
 }
diff --git a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/PulseAppListener.java b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/PulseAppListener.java
index b7d526a..9862068 100644
--- a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/PulseAppListener.java
+++ b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/PulseAppListener.java
@@ -17,19 +17,22 @@
 
 package org.apache.geode.tools.pulse.internal;
 
-import java.io.IOException;
-import java.io.InputStream;
 import java.util.Map.Entry;
 import java.util.Properties;
 import java.util.ResourceBundle;
 import java.util.Set;
-import java.util.function.BiFunction;
 
-import javax.servlet.ServletContextEvent;
-import javax.servlet.ServletContextListener;
+import javax.servlet.ServletContext;
 
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationEvent;
+import org.springframework.context.ApplicationListener;
+import org.springframework.context.event.ContextClosedEvent;
+import org.springframework.context.event.ContextRefreshedEvent;
+import org.springframework.stereotype.Component;
+import org.springframework.web.context.WebApplicationContext;
 
 import org.apache.geode.tools.pulse.internal.controllers.PulseController;
 import org.apache.geode.tools.pulse.internal.data.PulseConstants;
@@ -39,47 +42,46 @@ import org.apache.geode.tools.pulse.internal.data.Repository;
  * This class is used for checking the application running mode i.e. Embedded or not
  *
  * @since GemFire version 7.0.Beta 2012-09-23
- *
  */
-// @WebListener
-public class PulseAppListener implements ServletContextListener {
+@Component
+public class PulseAppListener implements ApplicationListener<ApplicationEvent> {
   private static final Logger logger = LogManager.getLogger();
   private static final String GEODE_SSLCONFIG_SERVLET_CONTEXT_PARAM = "org.apache.geode.sslConfig";
 
   private final boolean isEmbedded;
   private final Repository repository;
   private final ResourceBundle resourceBundle;
-  private final BiFunction<String, ResourceBundle, Properties> propertiesFileLoader;
-
-  public PulseAppListener() {
-    this(Boolean.getBoolean(PulseConstants.SYSTEM_PROPERTY_PULSE_EMBEDDED), Repository.get(),
-        PulseAppListener::loadPropertiesFromFile);
+  private final PropertiesFileLoader propertiesFileLoader;
+  private final PulseController pulseController;
+
+  @Autowired
+  public PulseAppListener(PulseController pulseController, Repository repository,
+      PropertiesFileLoader propertiesLoader) {
+    this(Boolean.getBoolean(PulseConstants.SYSTEM_PROPERTY_PULSE_EMBEDDED),
+        propertiesLoader, pulseController, repository);
   }
 
-  public PulseAppListener(boolean isEmbedded, Repository repository,
-      BiFunction<String, ResourceBundle, Properties> propertiesFileLoader) {
+  public PulseAppListener(boolean isEmbedded, PropertiesFileLoader propertiesFileLoader,
+      PulseController pulseController, Repository repository) {
     this.isEmbedded = isEmbedded;
-    this.repository = repository;
-    this.resourceBundle = repository.getResourceBundle();
     this.propertiesFileLoader = propertiesFileLoader;
+    this.pulseController = pulseController;
+    this.repository = repository;
+    resourceBundle = repository.getResourceBundle();
   }
 
   @Override
-  public void contextDestroyed(ServletContextEvent event) {
-
-    // Stop all running threads those are created in Pulse
-    // Stop cluster threads
-    repository.removeAllClusters();
-
-    logger.info("{}{}", resourceBundle.getString("LOG_MSG_CONTEXT_DESTROYED"),
-        event.getServletContext().getContextPath());
+  public void onApplicationEvent(ApplicationEvent event) {
+    if (event instanceof ContextRefreshedEvent) {
+      contextInitialized((ContextRefreshedEvent) event);
+    } else if (event instanceof ContextClosedEvent) {
+      contextDestroyed((ContextClosedEvent) event);
+    }
   }
 
-  @Override
-  public void contextInitialized(ServletContextEvent event) {
+  public void contextInitialized(ContextRefreshedEvent event) {
     logger.info(resourceBundle.getString("LOG_MSG_CONTEXT_INITIALIZED"));
 
-    // Load Pulse version details
     loadPulseVersionDetails();
 
     logger.info(resourceBundle.getString("LOG_MSG_CHECK_APP_RUNNING_MODE"));
@@ -94,12 +96,18 @@ public class PulseAppListener implements ServletContextListener {
           PulseConstants.GEMFIRE_DEFAULT_PORT));
 
       repository.setUseSSLManager(
-          Boolean.valueOf(System.getProperty(PulseConstants.SYSTEM_PROPERTY_PULSE_USESSL_MANAGER)));
+          Boolean.parseBoolean(
+              System.getProperty(PulseConstants.SYSTEM_PROPERTY_PULSE_USESSL_MANAGER)));
       repository.setUseSSLLocator(
-          Boolean.valueOf(System.getProperty(PulseConstants.SYSTEM_PROPERTY_PULSE_USESSL_LOCATOR)));
+          Boolean.parseBoolean(
+              System.getProperty(PulseConstants.SYSTEM_PROPERTY_PULSE_USESSL_LOCATOR)));
+
+      WebApplicationContext applicationContext =
+          (WebApplicationContext) event.getApplicationContext();
+      ServletContext servletContext = applicationContext.getServletContext();
 
       Object sslProperties =
-          event.getServletContext().getAttribute(GEODE_SSLCONFIG_SERVLET_CONTEXT_PARAM);
+          servletContext.getAttribute(GEODE_SSLCONFIG_SERVLET_CONTEXT_PARAM);
       if (sslProperties instanceof Properties) {
         repository.setJavaSslProperties((Properties) sslProperties);
       }
@@ -109,7 +117,7 @@ public class PulseAppListener implements ServletContextListener {
 
       // Load Pulse Properties
       Properties pulseProperties =
-          propertiesFileLoader.apply(PulseConstants.PULSE_PROPERTIES_FILE, resourceBundle);
+          propertiesFileLoader.loadProperties(PulseConstants.PULSE_PROPERTIES_FILE, resourceBundle);
 
       repository.setJmxUseLocator(Boolean.valueOf(
           pulseProperties.getProperty(PulseConstants.APPLICATION_PROPERTY_PULSE_USELOCATOR)));
@@ -119,14 +127,15 @@ public class PulseAppListener implements ServletContextListener {
           PulseConstants.GEMFIRE_DEFAULT_PORT));
 
       // SSL
-      repository.setUseSSLManager(Boolean.valueOf(pulseProperties
+      repository.setUseSSLManager(Boolean.parseBoolean(pulseProperties
           .getProperty(PulseConstants.SYSTEM_PROPERTY_PULSE_USESSL_MANAGER, "false")));
-      repository.setUseSSLLocator(Boolean.valueOf(pulseProperties
+      repository.setUseSSLLocator(Boolean.parseBoolean(pulseProperties
           .getProperty(PulseConstants.SYSTEM_PROPERTY_PULSE_USESSL_LOCATOR, "false")));
 
       // load pulse security properties
       Properties pulseSecurityProperties =
-          propertiesFileLoader.apply(PulseConstants.PULSE_SECURITY_PROPERTIES_FILE, resourceBundle);
+          propertiesFileLoader
+              .loadProperties(PulseConstants.PULSE_SECURITY_PROPERTIES_FILE, resourceBundle);
 
       // set the ssl related properties found in pulsesecurity.properties
       if (!pulseSecurityProperties.isEmpty()) {
@@ -144,38 +153,36 @@ public class PulseAppListener implements ServletContextListener {
     }
   }
 
-  // Function to load pulse version details from properties file
+  public void contextDestroyed(ContextClosedEvent event) {
+
+    // Stop all running threads those are created in Pulse
+    // Stop cluster threads
+    repository.removeAllClusters();
+
+    WebApplicationContext applicationContext =
+        (WebApplicationContext) event.getApplicationContext();
+    ServletContext servletContext = applicationContext.getServletContext();
+
+    logger.info("{}{}", resourceBundle.getString("LOG_MSG_CONTEXT_DESTROYED"),
+        servletContext.getContextPath());
+  }
+
   private void loadPulseVersionDetails() {
     Properties properties =
-        propertiesFileLoader.apply(PulseConstants.PULSE_VERSION_PROPERTIES_FILE, resourceBundle);
-    // Set pulse version details in common object
-    PulseController.pulseVersion
+        propertiesFileLoader
+            .loadProperties(PulseConstants.PULSE_VERSION_PROPERTIES_FILE, resourceBundle);
+    pulseController.getPulseVersion()
         .setPulseVersion(properties.getProperty(PulseConstants.PROPERTY_PULSE_VERSION, ""));
-    PulseController.pulseVersion
+    pulseController.getPulseVersion()
         .setPulseBuildId(properties.getProperty(PulseConstants.PROPERTY_BUILD_ID, ""));
-    PulseController.pulseVersion
+    pulseController.getPulseVersion()
         .setPulseBuildDate(properties.getProperty(PulseConstants.PROPERTY_BUILD_DATE, ""));
-    PulseController.pulseVersion
+    pulseController.getPulseVersion()
         .setPulseSourceDate(properties.getProperty(PulseConstants.PROPERTY_SOURCE_DATE, ""));
-    PulseController.pulseVersion.setPulseSourceRevision(
+    pulseController.getPulseVersion().setPulseSourceRevision(
         properties.getProperty(PulseConstants.PROPERTY_SOURCE_REVISION, ""));
-    PulseController.pulseVersion.setPulseSourceRepository(
+    pulseController.getPulseVersion().setPulseSourceRepository(
         properties.getProperty(PulseConstants.PROPERTY_SOURCE_REPOSITORY, ""));
-    logger.info(PulseController.pulseVersion.getPulseVersionLogMessage());
-  }
-
-  // Function to load pulse properties from pulse.properties file
-  private static Properties loadPropertiesFromFile(String propertyFile,
-      ResourceBundle resourceBundle) {
-    final Properties properties = new Properties();
-    try (final InputStream stream =
-        Thread.currentThread().getContextClassLoader().getResourceAsStream(propertyFile)) {
-      logger.info(propertyFile + " " + resourceBundle.getString("LOG_MSG_FILE_FOUND"));
-      properties.load(stream);
-    } catch (IOException e) {
-      logger.error(resourceBundle.getString("LOG_MSG_EXCEPTION_LOADING_PROPERTIES_FILE"), e);
-    }
-
-    return properties;
+    logger.info(pulseController.getPulseVersion().getPulseVersionLogMessage());
   }
 }
diff --git a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/controllers/PulseController.java b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/controllers/PulseController.java
index 1852e94..709ac08 100644
--- a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/controllers/PulseController.java
+++ b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/controllers/PulseController.java
@@ -71,12 +71,23 @@ public class PulseController {
   private static final String EMPTY_JSON = "{}";
 
   // Shared object to hold pulse version details
-  public static PulseVersion pulseVersion = new PulseVersion();
+  private final PulseVersion pulseVersion;
 
   private final ObjectMapper mapper = new ObjectMapper();
+  private final PulseServiceFactory pulseServiceFactory;
+  private final Repository repository;
 
   @Autowired
-  PulseServiceFactory pulseServiceFactory;
+  public PulseController(PulseServiceFactory pulseServiceFactory, Repository repository,
+      PulseVersion pulseVersion) {
+    this.pulseServiceFactory = pulseServiceFactory;
+    this.repository = repository;
+    this.pulseVersion = pulseVersion;
+  }
+
+  public PulseVersion getPulseVersion() {
+    return pulseVersion;
+  }
 
   @RequestMapping(value = "/pulseUpdate", method = RequestMethod.POST)
   public void getPulseUpdate(HttpServletRequest request, HttpServletResponse response)
@@ -141,12 +152,12 @@ public class PulseController {
 
     try {
       // Response
-      responseJSON.put("pulseVersion", PulseController.pulseVersion.getPulseVersion());
-      responseJSON.put("buildId", PulseController.pulseVersion.getPulseBuildId());
-      responseJSON.put("buildDate", PulseController.pulseVersion.getPulseBuildDate());
-      responseJSON.put("sourceDate", PulseController.pulseVersion.getPulseSourceDate());
-      responseJSON.put("sourceRevision", PulseController.pulseVersion.getPulseSourceRevision());
-      responseJSON.put("sourceRepository", PulseController.pulseVersion.getPulseSourceRepository());
+      responseJSON.put("pulseVersion", pulseVersion.getPulseVersion());
+      responseJSON.put("buildId", pulseVersion.getPulseBuildId());
+      responseJSON.put("buildDate", pulseVersion.getPulseBuildDate());
+      responseJSON.put("sourceDate", pulseVersion.getPulseSourceDate());
+      responseJSON.put("sourceRevision", pulseVersion.getPulseSourceRevision());
+      responseJSON.put("sourceRepository", pulseVersion.getPulseSourceRepository());
 
     } catch (Exception e) {
       logger.debug("Exception Occurred : ", e);
@@ -174,7 +185,7 @@ public class PulseController {
     try {
       boolean isClearAll = Boolean.parseBoolean(request.getParameter("clearAll"));
       // get cluster object
-      Cluster cluster = Repository.get().getCluster();
+      Cluster cluster = repository.getCluster();
       cluster.clearAlerts(alertType, isClearAll);
       responseJSON.put("status", "deleted");
       responseJSON.set("systemAlerts",
@@ -213,7 +224,7 @@ public class PulseController {
 
     try {
       // get cluster object
-      Cluster cluster = Repository.get().getCluster();
+      Cluster cluster = repository.getCluster();
 
       // set alert is acknowledged
       cluster.acknowledgeAlert(alertId);
@@ -231,7 +242,7 @@ public class PulseController {
       HttpServletResponse response)
       throws IOException {
     // get cluster object
-    Cluster cluster = Repository.get().getCluster();
+    Cluster cluster = repository.getCluster();
 
     // json object to be sent as response
     ObjectNode responseJSON = mapper.createObjectNode();
@@ -322,7 +333,7 @@ public class PulseController {
 
     try {
       // get cluster object
-      Cluster cluster = Repository.get().getCluster();
+      Cluster cluster = repository.getCluster();
       String userName = request.getUserPrincipal().getName();
 
       // get query string
@@ -388,7 +399,7 @@ public class PulseController {
 
     ObjectNode responseJSON = mapper.createObjectNode();
     // get cluster object
-    Cluster cluster = Repository.get().getCluster();
+    Cluster cluster = repository.getCluster();
     String userName = request.getUserPrincipal().getName();
 
     try {
@@ -431,7 +442,7 @@ public class PulseController {
 
       if (StringUtils.isNotBlank(query)) {
         // get cluster object
-        Cluster cluster = Repository.get().getCluster();
+        Cluster cluster = repository.getCluster();
         String userName = request.getUserPrincipal().getName();
 
         // Add html escaped query to history
diff --git a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/data/Cluster.java b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/data/Cluster.java
index 5c252af..dbc3b56 100644
--- a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/data/Cluster.java
+++ b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/data/Cluster.java
@@ -54,7 +54,8 @@ public class Cluster extends Thread {
   public static final int PAGE_ALERTS_MAX_SIZE = 100;
 
   private static final Logger logger = LogManager.getLogger();
-  private final ResourceBundle resourceBundle = Repository.get().getResourceBundle();
+  private Repository repository;
+  private ResourceBundle resourceBundle;
 
   private String jmxUserName;
   private String serverName;
@@ -2241,12 +2242,15 @@ public class Cluster extends Thread {
    * @param port port
    * @param userName pulse user name
    */
-  public Cluster(String host, String port, String userName) {
+  public Cluster(String host, String port, String userName, ResourceBundle resourceBundle,
+      Repository repository) {
     serverName = host;
     this.port = port;
     jmxUserName = userName;
+    this.resourceBundle = resourceBundle;
+    this.repository = repository;
 
-    updater = new JMXDataUpdater(serverName, port, this);
+    updater = new JMXDataUpdater(serverName, port, this, resourceBundle, repository);
     clusterHasBeenInitialized = new CountDownLatch(1);
     if (Boolean.getBoolean(PulseConstants.SYSTEM_PROPERTY_PULSE_EMBEDDED)) {
       setDaemon(true);
@@ -2733,15 +2737,11 @@ public class Cluster extends Thread {
   public DataBrowser getDataBrowser() {
     // Initialize dataBrowser if null
     if (dataBrowser == null) {
-      dataBrowser = new DataBrowser();
+      dataBrowser = new DataBrowser(resourceBundle, repository);
     }
     return dataBrowser;
   }
 
-  public void setDataBrowser(DataBrowser dataBrowser) {
-    this.dataBrowser = dataBrowser;
-  }
-
   public ObjectNode executeQuery(String queryText, String members, int limit) {
     // Execute data browser query
     return updater.executeQuery(queryText, members, limit);
@@ -2759,10 +2759,9 @@ public class Cluster extends Thread {
     return getDataBrowser().deleteQueryById(userId, queryId);
   }
 
-  public void connectToGemFire(String password) {
-    jmxConnector = updater.connect(getJmxUserName(), password);
+  public void connectToGemFire(Object credentials) {
+    jmxConnector = updater.connect(credentials);
 
-    // if connected
     if (jmxConnector != null) {
       // Start Thread
       start();
diff --git a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/data/IClusterUpdater.java b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/data/ClusterFactory.java
similarity index 64%
copy from geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/data/IClusterUpdater.java
copy to geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/data/ClusterFactory.java
index 942384a..6b0eff4 100644
--- a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/data/IClusterUpdater.java
+++ b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/data/ClusterFactory.java
@@ -1,5 +1,4 @@
 /*
- *
  * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
  * agreements. See the NOTICE file distributed with this work for additional information regarding
  * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
@@ -12,27 +11,13 @@
  * 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 org.apache.geode.tools.pulse.internal.data;
 
-import javax.management.remote.JMXConnector;
-
-import com.fasterxml.jackson.databind.node.ObjectNode;
-
-/**
- * Interface having updateData() function which is implemented by JMXDataUpdater
- *
- * @since GemFire version 7.0.Beta 2012-09-23
- *
- */
-public interface IClusterUpdater {
-  boolean updateData();
-
-  ObjectNode executeQuery(String queryText, String members, int limit);
+import java.util.ResourceBundle;
 
-  default JMXConnector connect(String username, String password) {
-    return null;
-  }
+public interface ClusterFactory {
+  Cluster create(String host, String port, String userName, ResourceBundle resourceBundle,
+      Repository repository);
 }
diff --git a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/data/DataBrowser.java b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/data/DataBrowser.java
index 35f4c6c..6e79161 100644
--- a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/data/DataBrowser.java
+++ b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/data/DataBrowser.java
@@ -45,13 +45,19 @@ import org.apache.logging.log4j.Logger;
 public class DataBrowser {
 
   private static final Logger logger = LogManager.getLogger();
-  private final ResourceBundle resourceBundle = Repository.get().getResourceBundle();
+  private final ResourceBundle resourceBundle;
+  private final Repository repository;
 
   private SimpleDateFormat simpleDateFormat =
       new SimpleDateFormat(PulseConstants.PULSE_QUERY_HISTORY_DATE_PATTERN);
 
   private final ObjectMapper mapper = new ObjectMapper();
 
+  public DataBrowser(ResourceBundle resourceBundle, Repository repository) {
+    this.resourceBundle = resourceBundle;
+    this.repository = repository;
+  }
+
   /**
    * addQueryInHistory method adds user's query into query history file
    *
@@ -156,7 +162,7 @@ public class DataBrowser {
 
     try {
       inputStream =
-          new FileInputStream(Repository.get().getPulseConfig().getQueryHistoryFileName());
+          new FileInputStream(repository.getPulseConfig().getQueryHistoryFileName());
       String inputStreamString = new Scanner(inputStream, "UTF-8").useDelimiter("\\A").next();
       queriesJSON = mapper.readTree(inputStreamString);
     } catch (FileNotFoundException e) {
@@ -187,7 +193,7 @@ public class DataBrowser {
     boolean operationStatus = false;
     FileOutputStream fileOut = null;
 
-    File file = new File(Repository.get().getPulseConfig().getQueryHistoryFileName());
+    File file = new File(repository.getPulseConfig().getQueryHistoryFileName());
     try {
       fileOut = new FileOutputStream(file);
 
diff --git a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/data/IClusterUpdater.java b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/data/IClusterUpdater.java
index 942384a..8bcd666 100644
--- a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/data/IClusterUpdater.java
+++ b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/data/IClusterUpdater.java
@@ -32,7 +32,7 @@ public interface IClusterUpdater {
 
   ObjectNode executeQuery(String queryText, String members, int limit);
 
-  default JMXConnector connect(String username, String password) {
+  default JMXConnector connect(Object credentials) {
     return null;
   }
 }
diff --git a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/data/JMXDataUpdater.java b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/data/JMXDataUpdater.java
index a9a4b94..c73daab 100644
--- a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/data/JMXDataUpdater.java
+++ b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/data/JMXDataUpdater.java
@@ -74,7 +74,8 @@ import org.apache.geode.tools.pulse.internal.data.JmxManagerFinder.JmxManagerInf
 public class JMXDataUpdater implements IClusterUpdater, NotificationListener {
 
   private static final Logger logger = LogManager.getLogger();
-  private final ResourceBundle resourceBundle = Repository.get().getResourceBundle();
+  private final ResourceBundle resourceBundle;
+  private final Repository repository;
 
   private JMXConnector conn = null;
   private MBeanServerConnection mbs = null;
@@ -99,10 +100,13 @@ public class JMXDataUpdater implements IClusterUpdater, NotificationListener {
   /**
    * constructor used for creating JMX connection
    */
-  public JMXDataUpdater(String server, String port, Cluster cluster) {
+  public JMXDataUpdater(String server, String port, Cluster cluster, ResourceBundle resourceBundle,
+      Repository repository) {
     serverName = server;
     this.port = port;
     this.cluster = cluster;
+    this.resourceBundle = resourceBundle;
+    this.repository = repository;
 
     try {
       // Initialize MBean object names
@@ -164,14 +168,11 @@ public class JMXDataUpdater implements IClusterUpdater, NotificationListener {
     return null;
   }
 
-
   /**
    * Get the jmx connection
    */
   @Override
-  public JMXConnector connect(String username, String password) {
-    // Reference to repository
-    Repository repository = Repository.get();
+  public JMXConnector connect(Object credentials) {
     try {
 
       String jmxSerURL = "";
@@ -203,9 +204,8 @@ public class JMXDataUpdater implements IClusterUpdater, NotificationListener {
 
       if (StringUtils.isNotBlank(jmxSerURL)) {
         JMXServiceURL url = new JMXServiceURL(jmxSerURL);
-        String[] creds = {username, password};
         Map<String, Object> env = new HashMap<>();
-        env.put(JMXConnector.CREDENTIALS, creds);
+        env.put(JMXConnector.CREDENTIALS, credentials);
 
         Properties originalProperties = System.getProperties();
         try {
diff --git a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/data/PulseVersion.java b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/data/PulseVersion.java
index 79e9604..c0c1646 100644
--- a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/data/PulseVersion.java
+++ b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/data/PulseVersion.java
@@ -19,6 +19,9 @@ package org.apache.geode.tools.pulse.internal.data;
 
 import java.util.ResourceBundle;
 
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
 /**
  * Class PulseVersion
  *
@@ -27,11 +30,17 @@ import java.util.ResourceBundle;
  *
  * @since GemFire version Helios
  */
-
+@Component
 public class PulseVersion {
 
+  private final Repository repository;
   private String pulseVersion;
 
+  @Autowired
+  public PulseVersion(Repository repository) {
+    this.repository = repository;
+  }
+
   public String getPulseVersion() {
     return pulseVersion;
   }
@@ -91,10 +100,9 @@ public class PulseVersion {
   }
 
   public String getPulseVersionLogMessage() {
-    ResourceBundle resourceBundle = Repository.get().getResourceBundle();
-    String logMessage = resourceBundle.getString("LOG_MSG_PULSE_VERSION") + " "
+    ResourceBundle resourceBundle = repository.getResourceBundle();
+    return resourceBundle.getString("LOG_MSG_PULSE_VERSION") + " "
         + this.getPulseVersion() + " " + this.getPulseBuildId() + " " + this.getPulseBuildDate();
-    return logMessage;
   }
 
 }
diff --git a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/data/Repository.java b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/data/Repository.java
index 0f9f818..9f500f5 100644
--- a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/data/Repository.java
+++ b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/data/Repository.java
@@ -21,24 +21,34 @@ import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Properties;
 import java.util.ResourceBundle;
 
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
+import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
+import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
+import org.springframework.security.oauth2.core.OAuth2AccessToken;
+import org.springframework.security.oauth2.core.user.OAuth2User;
+import org.springframework.stereotype.Component;
 
 /**
  * A Singleton instance of the memory cache for clusters.
  *
  * @since GemFire version 7.0.Beta 2012-09-23
  */
+@Component
 public class Repository {
   private static final Logger logger = LogManager.getLogger();
 
-  private static Repository instance = new Repository();
-  private HashMap<String, Cluster> clusterMap = new HashMap<>();
+  private final OAuth2AuthorizedClientService authorizedClientService;
+  private final ClusterFactory clusterFactory;
+  private final HashMap<String, Cluster> clusterMap = new HashMap<>();
   private Boolean jmxUseLocator;
   private String host;
   private String port;
@@ -54,12 +64,21 @@ public class Repository {
 
   private PulseConfig pulseConfig = new PulseConfig();
 
-  private Repository() {
+  public Repository() {
+    this(null);
+  }
 
+  // The authorizedClientService is required only when using OAuth2 security.
+  @Autowired
+  public Repository(
+      @Autowired(required = false) OAuth2AuthorizedClientService authorizedClientService) {
+    this(authorizedClientService, Cluster::new);
   }
 
-  public static Repository get() {
-    return instance;
+  public Repository(OAuth2AuthorizedClientService authorizedClientService,
+      ClusterFactory clusterFactory) {
+    this.authorizedClientService = authorizedClientService;
+    this.clusterFactory = clusterFactory;
   }
 
   public Boolean getJmxUseLocator() {
@@ -67,6 +86,7 @@ public class Repository {
   }
 
   public void setJmxUseLocator(Boolean jmxUseLocator) {
+    Objects.requireNonNull(jmxUseLocator, "jmxUseLocat == null");
     this.jmxUseLocator = jmxUseLocator;
   }
 
@@ -119,24 +139,43 @@ public class Repository {
    * request
    *
    * But for multi-user connections to gemfireJMX, i.e pulse that uses gemfire integrated security,
-   * we will need to get the username form the context
+   * we will need to get the username from the context
    */
   public Cluster getCluster() {
-    Authentication auth = SecurityContextHolder.getContext().getAuthentication();
-    if (auth == null)
+    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+    if (authentication == null) {
       return null;
-    return getCluster(auth.getName(), null);
+    }
+
+    if (authentication instanceof OAuth2AuthenticationToken) {
+      OAuth2AuthenticationToken authenticationToken = (OAuth2AuthenticationToken) authentication;
+      OAuth2AuthorizedClient authorizedClient = authorizedClientService.loadAuthorizedClient(
+          authenticationToken.getAuthorizedClientRegistrationId(),
+          authenticationToken.getName());
+
+      OAuth2User authenticatedPrincipal = authenticationToken.getPrincipal();
+      String authenticatedPrincipalName = authenticatedPrincipal.getName();
+      OAuth2AccessToken accessToken = authorizedClient.getAccessToken();
+      String accessTokenValue = accessToken.getTokenValue();
+      return getClusterWithCredentials(authenticatedPrincipalName, accessTokenValue);
+    }
+
+    return getClusterWithUserNameAndPassword(authentication.getName(), null);
+  }
+
+  public Cluster getClusterWithUserNameAndPassword(String userName, String password) {
+    return getClusterWithCredentials(userName, new String[] {userName, password});
   }
 
-  public Cluster getCluster(String username, String password) {
+  public Cluster getClusterWithCredentials(String username, Object credentials) {
     synchronized (this.clusterMap) {
       Cluster data = clusterMap.get(username);
       if (data == null) {
         logger.info(resourceBundle.getString("LOG_MSG_CREATE_NEW_THREAD") + " : " + username);
-        data = new Cluster(this.host, this.port, username);
+        data = clusterFactory.create(host, port, username, resourceBundle, this);
         // Assign name to thread created
-        data.setName(PulseConstants.APP_NAME + "-" + this.host + ":" + this.port + ":" + username);
-        data.connectToGemFire(password);
+        data.setName(PulseConstants.APP_NAME + "-" + host + ":" + port + ":" + username);
+        data.connectToGemFire(credentials);
         if (data.isConnectedFlag()) {
           this.clusterMap.put(username, data);
         }
diff --git a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/security/CustomSecurityConfig.java b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/security/CustomSecurityConfig.java
index 6bb69e3..4b52033 100644
--- a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/security/CustomSecurityConfig.java
+++ b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/security/CustomSecurityConfig.java
@@ -24,6 +24,7 @@ import org.springframework.security.config.annotation.authentication.builders.Au
 import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
 
+
 @Configuration
 @EnableWebSecurity
 @EnableGlobalMethodSecurity(prePostEnabled = true)
diff --git a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/security/DefaultSecurityConfig.java b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/security/DefaultSecurityConfig.java
index fbbe716..a005b94 100644
--- a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/security/DefaultSecurityConfig.java
+++ b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/security/DefaultSecurityConfig.java
@@ -18,7 +18,6 @@ package org.apache.geode.tools.pulse.internal.security;
 import java.util.HashMap;
 import java.util.Map;
 
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.Profile;
@@ -32,20 +31,18 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
 import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.web.authentication.AuthenticationFailureHandler;
 import org.springframework.security.web.authentication.ExceptionMappingAuthenticationFailureHandler;
+import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
 
 @Configuration
 @EnableWebSecurity
 @EnableGlobalMethodSecurity(prePostEnabled = true)
 @Profile("pulse.authentication.default")
 public class DefaultSecurityConfig extends WebSecurityConfigurerAdapter {
-  @Bean
-  public LogoutHandler logoutHandler() {
-    return new LogoutHandler("/login.html");
-  }
 
   @Bean
-  public ExceptionMappingAuthenticationFailureHandler failureHandler() {
+  public AuthenticationFailureHandler failureHandler() {
     ExceptionMappingAuthenticationFailureHandler exceptionMappingAuthenticationFailureHandler =
         new ExceptionMappingAuthenticationFailureHandler();
     Map<String, String> exceptionMappings = new HashMap<>();
@@ -58,11 +55,10 @@ public class DefaultSecurityConfig extends WebSecurityConfigurerAdapter {
     return exceptionMappingAuthenticationFailureHandler;
   }
 
-  @Autowired
-  private LogoutHandler logoutHandler;
-
-  @Autowired
-  private ExceptionMappingAuthenticationFailureHandler failureHandler;
+  @Bean
+  public LogoutSuccessHandler logoutHandler() {
+    return new LogoutHandler("/login.html");
+  }
 
   @Override
   protected void configure(HttpSecurity httpSecurity) throws Exception {
@@ -78,11 +74,11 @@ public class DefaultSecurityConfig extends WebSecurityConfigurerAdapter {
         .formLogin(form -> form
             .loginPage("/login.html")
             .loginProcessingUrl("/login")
-            .failureHandler(failureHandler)
+            .failureHandler(failureHandler())
             .defaultSuccessUrl("/clusterDetail.html", true))
         .logout(logout -> logout
             .logoutUrl("/clusterLogout")
-            .logoutSuccessHandler(logoutHandler))
+            .logoutSuccessHandler(logoutHandler()))
         .exceptionHandling(exception -> exception
             .accessDeniedPage("/accessDenied.html"))
         .headers(header -> header
diff --git a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/security/GemFireAuthenticationProvider.java b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/security/GemFireAuthenticationProvider.java
index 6bd77d4..7bbb4dc 100644
--- a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/security/GemFireAuthenticationProvider.java
+++ b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/security/GemFireAuthenticationProvider.java
@@ -39,8 +39,11 @@ import org.apache.geode.tools.pulse.internal.data.Repository;
 public class GemFireAuthenticationProvider implements AuthenticationProvider {
 
   private static final Logger logger = LogManager.getLogger();
+  private final Repository repository;
 
-  public GemFireAuthenticationProvider() {}
+  public GemFireAuthenticationProvider(Repository repository) {
+    this.repository = repository;
+  }
 
   @Override
   public Authentication authenticate(Authentication authentication) throws AuthenticationException {
@@ -54,7 +57,8 @@ public class GemFireAuthenticationProvider implements AuthenticationProvider {
     String password = authentication.getCredentials().toString();
 
     logger.debug("Connecting to GemFire with user=" + name);
-    JMXConnector jmxc = Repository.get().getCluster(name, password).getJMXConnector();
+    JMXConnector jmxc =
+        repository.getClusterWithUserNameAndPassword(name, password).getJMXConnector();
     if (jmxc == null) {
       throw new BadCredentialsException("Error connecting to GemFire JMX Server");
     }
@@ -70,5 +74,4 @@ public class GemFireAuthenticationProvider implements AuthenticationProvider {
   public boolean supports(Class<?> authentication) {
     return authentication.equals(UsernamePasswordAuthenticationToken.class);
   }
-
 }
diff --git a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/security/GemfireSecurityConfig.java b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/security/GemfireSecurityConfig.java
index 917a698..11defd9 100644
--- a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/security/GemfireSecurityConfig.java
+++ b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/security/GemfireSecurityConfig.java
@@ -16,28 +16,30 @@
 package org.apache.geode.tools.pulse.internal.security;
 
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.Profile;
 import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
 import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
 
+import org.apache.geode.tools.pulse.internal.data.Repository;
+
 @Configuration
 @EnableWebSecurity
 @EnableGlobalMethodSecurity(prePostEnabled = true)
 @Profile("pulse.authentication.gemfire")
 public class GemfireSecurityConfig extends DefaultSecurityConfig {
-  @Bean
-  public GemFireAuthenticationProvider gemAuthenticationProvider() {
-    return new GemFireAuthenticationProvider();
-  }
+
+  private final Repository repository;
 
   @Autowired
-  GemFireAuthenticationProvider gemAuthenticationProvider;
+  public GemfireSecurityConfig(Repository repository) {
+    this.repository = repository;
+  }
 
   @Override
   protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) {
-    authenticationManagerBuilder.authenticationProvider(gemAuthenticationProvider);
+    authenticationManagerBuilder
+        .authenticationProvider(new GemFireAuthenticationProvider(repository));
   }
 }
diff --git a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/security/LogoutHandler.java b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/security/LogoutHandler.java
index 1abfa43..4ad4c08 100644
--- a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/security/LogoutHandler.java
+++ b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/security/LogoutHandler.java
@@ -22,21 +22,24 @@ import javax.servlet.http.HttpServletResponse;
 
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
 import org.springframework.security.core.Authentication;
-import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
 import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
 
 import org.apache.geode.tools.pulse.internal.data.Repository;
 
 /**
  * Handler is used to close jmx connection maintained at user-level
- *
  */
-public class LogoutHandler extends SimpleUrlLogoutSuccessHandler implements LogoutSuccessHandler {
+public class LogoutHandler extends SimpleUrlLogoutSuccessHandler implements
+    ApplicationContextAware {
   private static final Logger logger = LogManager.getLogger();
+  private ApplicationContext applicationContext;
 
-  public LogoutHandler(String defaultTargetURL) {
-    this.setDefaultTargetUrl(defaultTargetURL);
+  public LogoutHandler(String logoutTargetURL) {
+    setDefaultTargetUrl(logoutTargetURL);
   }
 
   @Override
@@ -44,10 +47,16 @@ public class LogoutHandler extends SimpleUrlLogoutSuccessHandler implements Logo
       Authentication authentication) throws IOException, ServletException {
 
     if (authentication != null) {
-      Repository.get().logoutUser(authentication.getName());
+      Repository repository = applicationContext.getBean("repository", Repository.class);
+      repository.logoutUser(authentication.getName());
       logger.info("#LogoutHandler: GemFireAuthentication JMX Connection Closed.");
     }
 
     super.onLogoutSuccess(request, response, authentication);
   }
+
+  @Override
+  public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+    this.applicationContext = applicationContext;
+  }
 }
diff --git a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/security/OAuthSecurityConfig.java b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/security/OAuthSecurityConfig.java
index 3f0e0e9..470c932 100644
--- a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/security/OAuthSecurityConfig.java
+++ b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/security/OAuthSecurityConfig.java
@@ -15,8 +15,6 @@
 
 package org.apache.geode.tools.pulse.internal.security;
 
-import static org.springframework.security.config.Customizer.withDefaults;
-
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
@@ -34,6 +32,7 @@ import org.springframework.security.oauth2.client.registration.InMemoryClientReg
 import org.springframework.security.oauth2.client.web.AuthenticatedPrincipalOAuth2AuthorizedClientRepository;
 import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
 import org.springframework.security.oauth2.core.AuthorizationGrantType;
+import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
 
 @Configuration
 @EnableWebSecurity
@@ -41,8 +40,10 @@ import org.springframework.security.oauth2.core.AuthorizationGrantType;
 @Profile("pulse.authentication.oauth")
 @PropertySource("classpath:pulse.properties")
 public class OAuthSecurityConfig extends WebSecurityConfigurerAdapter {
-  @Value("${pulse.oauth.provider}")
+  @Value("${pulse.oauth.providerId}")
   private String providerId;
+  @Value("${pulse.oauth.providerName}")
+  private String providerName;
   @Value("${pulse.oauth.clientId}")
   private String clientId;
   @Value("${pulse.oauth.clientSecret}")
@@ -58,16 +59,38 @@ public class OAuthSecurityConfig extends WebSecurityConfigurerAdapter {
   @Value("${pulse.oauth.userNameAttributeName}")
   private String userNameAttributeName;
 
+  @Bean
+  public LogoutSuccessHandler logoutHandler() {
+    return new LogoutHandler("/login");
+  }
+
   @Override
   protected void configure(HttpSecurity http) throws Exception {
     http.authorizeRequests(authorize -> authorize
+        .mvcMatchers("/pulseVersion", "/scripts/**", "/images/**", "/css/**", "/properties/**")
+        .permitAll()
+        .mvcMatchers("/dataBrowser*", "/getQueryStatisticsGridModel*")
+        .access("hasAuthority('SCOPE_CLUSTER:READ') and hasAuthority('SCOPE_DATA:READ')")
+        .mvcMatchers("/*")
+        .hasAuthority("SCOPE_CLUSTER:READ")
         .anyRequest().authenticated())
-        .oauth2Login(withDefaults());
+        .oauth2Login(oauth -> oauth.defaultSuccessUrl("/clusterDetail.html", true))
+        .exceptionHandling(exception -> exception.accessDeniedPage("/accessDenied.html"))
+        .logout(logout -> logout
+            .logoutUrl("/clusterLogout")
+            .logoutSuccessHandler(logoutHandler()))
+        .headers(header -> header
+            .frameOptions().deny()
+            .xssProtection(xss -> xss
+                .xssProtectionEnabled(true)
+                .block(true))
+            .contentTypeOptions())
+        .csrf().disable();
   }
 
   @Bean
   public ClientRegistrationRepository clientRegistrationRepository() {
-    return new InMemoryClientRegistrationRepository(this.clientRegistration());
+    return new InMemoryClientRegistrationRepository(clientRegistration());
   }
 
   @Bean
@@ -84,16 +107,19 @@ public class OAuthSecurityConfig extends WebSecurityConfigurerAdapter {
 
   private ClientRegistration clientRegistration() {
     return ClientRegistration.withRegistrationId(providerId)
-        .clientId(clientId)
-        .clientSecret(clientSecret)
         .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
         .redirectUriTemplate("{baseUrl}/login/oauth2/code/{registrationId}")
-        .scope("openid", "CLUSTER:READ", "CLUSTER:WRITE", "DATA:READ", "DATA:WRITE")
+        .clientId(clientId)
+        .clientSecret(clientSecret)
         .authorizationUri(authorizationUri)
         .tokenUri(tokenUri)
         .userInfoUri(userInfoUri)
         .jwkSetUri(jwkSetUri)
-        .clientName("Pulse")
+        // When Spring shows the login page, it displays a link to the OAuth provider's
+        // authorization URI. Spring uses the value passed to clientName() as the text for that
+        // link. We pass the providerName property here, to let the user know which OAuth provider
+        // they will be redirected to.
+        .clientName(providerName)
         .userNameAttributeName(userNameAttributeName)
         .build();
   }
diff --git a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/ClusterDetailsService.java b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/ClusterDetailsService.java
index f043bb4..f200143 100644
--- a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/ClusterDetailsService.java
+++ b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/ClusterDetailsService.java
@@ -23,6 +23,7 @@ import javax.servlet.http.HttpServletRequest;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Scope;
 import org.springframework.stereotype.Component;
 import org.springframework.stereotype.Service;
@@ -44,6 +45,12 @@ import org.apache.geode.tools.pulse.internal.data.Repository;
 public class ClusterDetailsService implements PulseService {
 
   private final ObjectMapper mapper = new ObjectMapper();
+  private final Repository repository;
+
+  @Autowired
+  public ClusterDetailsService(Repository repository) {
+    this.repository = repository;
+  }
 
   @Override
   public ObjectNode execute(final HttpServletRequest request) throws Exception {
@@ -51,7 +58,7 @@ public class ClusterDetailsService implements PulseService {
     String userName = request.getUserPrincipal().getName();
 
     // get cluster object
-    Cluster cluster = Repository.get().getCluster();
+    Cluster cluster = repository.getCluster();
 
     // json object to be sent as response
     ObjectNode responseJSON = mapper.createObjectNode();
diff --git a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/ClusterDiskThroughputService.java b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/ClusterDiskThroughputService.java
index 7e8529f..25c0720 100644
--- a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/ClusterDiskThroughputService.java
+++ b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/ClusterDiskThroughputService.java
@@ -21,6 +21,7 @@ import javax.servlet.http.HttpServletRequest;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Scope;
 import org.springframework.stereotype.Component;
 import org.springframework.stereotype.Service;
@@ -41,12 +42,18 @@ import org.apache.geode.tools.pulse.internal.data.Repository;
 public class ClusterDiskThroughputService implements PulseService {
 
   private final ObjectMapper mapper = new ObjectMapper();
+  private final Repository repository;
+
+  @Autowired
+  public ClusterDiskThroughputService(Repository repository) {
+    this.repository = repository;
+  }
 
   @Override
   public ObjectNode execute(final HttpServletRequest request) throws Exception {
 
     // get cluster object
-    Cluster cluster = Repository.get().getCluster();
+    Cluster cluster = repository.getCluster();
 
     // json object to be sent as response
     ObjectNode responseJSON = mapper.createObjectNode();
diff --git a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/ClusterGCPausesService.java b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/ClusterGCPausesService.java
index 826f666..b367d27 100644
--- a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/ClusterGCPausesService.java
+++ b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/ClusterGCPausesService.java
@@ -22,6 +22,7 @@ import javax.servlet.http.HttpServletRequest;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.node.ArrayNode;
 import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Scope;
 import org.springframework.stereotype.Component;
 import org.springframework.stereotype.Service;
@@ -44,12 +45,18 @@ import org.apache.geode.tools.pulse.internal.data.Repository;
 public class ClusterGCPausesService implements PulseService {
 
   private final ObjectMapper mapper = new ObjectMapper();
+  private final Repository repository;
+
+  @Autowired
+  public ClusterGCPausesService(Repository repository) {
+    this.repository = repository;
+  }
 
   @Override
   public ObjectNode execute(final HttpServletRequest request) throws Exception {
 
     // get cluster object
-    Cluster cluster = Repository.get().getCluster();
+    Cluster cluster = repository.getCluster();
 
     // json object to be sent as response
     ObjectNode responseJSON = mapper.createObjectNode();
diff --git a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/ClusterKeyStatisticsService.java b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/ClusterKeyStatisticsService.java
index ae75bd8..f39d75e 100644
--- a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/ClusterKeyStatisticsService.java
+++ b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/ClusterKeyStatisticsService.java
@@ -21,6 +21,7 @@ import javax.servlet.http.HttpServletRequest;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Scope;
 import org.springframework.stereotype.Component;
 import org.springframework.stereotype.Service;
@@ -43,12 +44,18 @@ import org.apache.geode.tools.pulse.internal.data.Repository;
 public class ClusterKeyStatisticsService implements PulseService {
 
   private final ObjectMapper mapper = new ObjectMapper();
+  private final Repository repository;
+
+  @Autowired
+  public ClusterKeyStatisticsService(Repository repository) {
+    this.repository = repository;
+  }
 
   @Override
   public ObjectNode execute(final HttpServletRequest request) throws Exception {
 
     // get cluster object
-    Cluster cluster = Repository.get().getCluster();
+    Cluster cluster = repository.getCluster();
 
     // json object to be sent as response
     ObjectNode responseJSON = mapper.createObjectNode();
diff --git a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/ClusterMemberService.java b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/ClusterMemberService.java
index 32d9af3..f3181ce 100644
--- a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/ClusterMemberService.java
+++ b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/ClusterMemberService.java
@@ -27,6 +27,7 @@ import javax.servlet.http.HttpServletRequest;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.node.ArrayNode;
 import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Scope;
 import org.springframework.stereotype.Component;
 import org.springframework.stereotype.Service;
@@ -52,12 +53,18 @@ public class ClusterMemberService implements PulseService {
   private final ObjectMapper mapper = new ObjectMapper();
 
   private static final String HEAP_USAGE = "heapUsage";
+  private final Repository repository;
+
+  @Autowired
+  public ClusterMemberService(Repository repository) {
+    this.repository = repository;
+  }
 
   @Override
   public ObjectNode execute(final HttpServletRequest request) throws Exception {
 
     // get cluster object
-    Cluster cluster = Repository.get().getCluster();
+    Cluster cluster = repository.getCluster();
 
     // json object to be sent as response
     ObjectNode responseJSON = mapper.createObjectNode();
diff --git a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/ClusterMembersRGraphService.java b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/ClusterMembersRGraphService.java
index e0c9ac8..8fcbfb0 100644
--- a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/ClusterMembersRGraphService.java
+++ b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/ClusterMembersRGraphService.java
@@ -28,6 +28,7 @@ import javax.servlet.http.HttpServletRequest;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.node.ArrayNode;
 import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Scope;
 import org.springframework.stereotype.Component;
 import org.springframework.stereotype.Service;
@@ -74,6 +75,7 @@ public class ClusterMembersRGraphService implements PulseService {
   private static final String MEMBER_NODE_TYPE_ERROR = "Error";
   private static final String MEMBER_NODE_TYPE_SEVERE = "Severe";
   private static final String CHILDREN = "children";
+  private final Repository repository;
 
   // traversing the alert array list and members which have severe, error or
   // warnings
@@ -82,12 +84,14 @@ public class ClusterMembersRGraphService implements PulseService {
   private List<String> errorAlertsList;
   private List<String> warningAlertsList;
 
+  @Autowired
+  public ClusterMembersRGraphService(Repository repository) {
+    this.repository = repository;
+  }
+
   @Override
   public ObjectNode execute(final HttpServletRequest request) throws Exception {
 
-    // Reference to repository
-    Repository repository = Repository.get();
-
     // get cluster object
     Cluster cluster = repository.getCluster();
 
@@ -95,8 +99,7 @@ public class ClusterMembersRGraphService implements PulseService {
     ObjectNode responseJSON = mapper.createObjectNode();
 
     // cluster's Members
-    responseJSON.set(CLUSTER,
-        getPhysicalServerJson(cluster));
+    responseJSON.set(CLUSTER, getPhysicalServerJson(cluster));
     responseJSON.put(MEMBER_COUNT, cluster.getMemberCount());
 
     // Send json response
diff --git a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/ClusterMemoryUsageService.java b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/ClusterMemoryUsageService.java
index 37fe6b6..67a5c45 100644
--- a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/ClusterMemoryUsageService.java
+++ b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/ClusterMemoryUsageService.java
@@ -21,6 +21,7 @@ import javax.servlet.http.HttpServletRequest;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Scope;
 import org.springframework.stereotype.Component;
 import org.springframework.stereotype.Service;
@@ -43,12 +44,18 @@ import org.apache.geode.tools.pulse.internal.data.Repository;
 public class ClusterMemoryUsageService implements PulseService {
 
   private final ObjectMapper mapper = new ObjectMapper();
+  private final Repository repository;
+
+  @Autowired
+  public ClusterMemoryUsageService(Repository repository) {
+    this.repository = repository;
+  }
 
   @Override
   public ObjectNode execute(final HttpServletRequest request) throws Exception {
 
     // get cluster object
-    Cluster cluster = Repository.get().getCluster();
+    Cluster cluster = repository.getCluster();
 
     // json object to be sent as response
     ObjectNode responseJSON = mapper.createObjectNode();
diff --git a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/ClusterRegionService.java b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/ClusterRegionService.java
index ee98438..66ee91b 100644
--- a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/ClusterRegionService.java
+++ b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/ClusterRegionService.java
@@ -30,6 +30,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.node.ArrayNode;
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Scope;
 import org.springframework.stereotype.Component;
 import org.springframework.stereotype.Service;
@@ -54,6 +55,12 @@ public class ClusterRegionService implements PulseService {
 
   // String constants used for forming a json response
   private static final String ENTRY_SIZE = "entrySize";
+  private final Repository repository;
+
+  @Autowired
+  public ClusterRegionService(Repository repository) {
+    this.repository = repository;
+  }
 
   // Comparator based upon regions entry count
   private static Comparator<Cluster.Region> regionEntryCountComparator = (r1, r2) -> {
@@ -68,7 +75,7 @@ public class ClusterRegionService implements PulseService {
     String userName = request.getUserPrincipal().getName();
 
     // get cluster object
-    Cluster cluster = Repository.get().getCluster();
+    Cluster cluster = repository.getCluster();
 
     // json object to be sent as response
     ObjectNode responseJSON = mapper.createObjectNode();
diff --git a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/ClusterRegionsService.java b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/ClusterRegionsService.java
index ff551af..3641ca2 100644
--- a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/ClusterRegionsService.java
+++ b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/ClusterRegionsService.java
@@ -30,6 +30,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.node.ArrayNode;
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Scope;
 import org.springframework.stereotype.Component;
 import org.springframework.stereotype.Service;
@@ -54,6 +55,12 @@ public class ClusterRegionsService implements PulseService {
 
   // String constants used for forming a json response
   private static final String ENTRY_SIZE = "entrySize";
+  private final Repository repository;
+
+  @Autowired
+  public ClusterRegionsService(Repository repository) {
+    this.repository = repository;
+  }
 
   // Comparator based upon regions entry count
   private static Comparator<Cluster.Region> regionEntryCountComparator = (r1, r2) -> {
@@ -65,7 +72,7 @@ public class ClusterRegionsService implements PulseService {
   @Override
   public ObjectNode execute(final HttpServletRequest request) throws Exception {
     // get cluster object
-    Cluster cluster = Repository.get().getCluster();
+    Cluster cluster = repository.getCluster();
 
     // json object to be sent as response
     ObjectNode responseJSON = mapper.createObjectNode();
diff --git a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/ClusterSelectedRegionService.java b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/ClusterSelectedRegionService.java
index e8a8b22..d8c0dd9 100644
--- a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/ClusterSelectedRegionService.java
+++ b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/ClusterSelectedRegionService.java
@@ -33,6 +33,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Scope;
 import org.springframework.stereotype.Component;
 import org.springframework.stereotype.Service;
@@ -59,6 +60,12 @@ public class ClusterSelectedRegionService implements PulseService {
 
   // String constants used for forming a json response
   private static final String ENTRY_SIZE = "entrySize";
+  private final Repository repository;
+
+  @Autowired
+  public ClusterSelectedRegionService(Repository repository) {
+    this.repository = repository;
+  }
 
   // Comparator based upon regions entry count
   private static Comparator<Cluster.Member> memberCurrentHeapUsageComparator = (m1, m2) -> {
@@ -77,7 +84,7 @@ public class ClusterSelectedRegionService implements PulseService {
         parameterMap.get("ClusterSelectedRegion").get("regionFullPath").textValue();
 
     // get cluster object
-    Cluster cluster = Repository.get().getCluster();
+    Cluster cluster = repository.getCluster();
 
     // json object to be sent as response
     ObjectNode responseJSON = mapper.createObjectNode();
diff --git a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/ClusterSelectedRegionsMemberService.java b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/ClusterSelectedRegionsMemberService.java
index 55b5a93..648b703 100644
--- a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/ClusterSelectedRegionsMemberService.java
+++ b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/ClusterSelectedRegionsMemberService.java
@@ -28,6 +28,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Scope;
 import org.springframework.stereotype.Component;
 import org.springframework.stereotype.Service;
@@ -52,6 +53,12 @@ public class ClusterSelectedRegionsMemberService implements PulseService {
 
   private final ObjectMapper mapper = new ObjectMapper();
   private static final Logger logger = LogManager.getLogger();
+  private final Repository repository;
+
+  @Autowired
+  public ClusterSelectedRegionsMemberService(Repository repository) {
+    this.repository = repository;
+  }
 
   // Comparator based upon regions entry count
   private static Comparator<Cluster.RegionOnMember> romEntryCountComparator = (m1, m2) -> {
@@ -71,7 +78,7 @@ public class ClusterSelectedRegionsMemberService implements PulseService {
         selectedRegionFullPath);
 
     // get cluster object
-    Cluster cluster = Repository.get().getCluster();
+    Cluster cluster = repository.getCluster();
 
     // json object to be sent as response
     ObjectNode responseJSON = mapper.createObjectNode();
diff --git a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/ClusterWANInfoService.java b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/ClusterWANInfoService.java
index 47a085a..499b049 100644
--- a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/ClusterWANInfoService.java
+++ b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/ClusterWANInfoService.java
@@ -24,6 +24,7 @@ import javax.servlet.http.HttpServletRequest;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.node.ArrayNode;
 import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Scope;
 import org.springframework.stereotype.Component;
 import org.springframework.stereotype.Service;
@@ -44,12 +45,18 @@ import org.apache.geode.tools.pulse.internal.data.Repository;
 public class ClusterWANInfoService implements PulseService {
 
   private final ObjectMapper mapper = new ObjectMapper();
+  private final Repository repository;
+
+  @Autowired
+  public ClusterWANInfoService(Repository repository) {
+    this.repository = repository;
+  }
 
   @Override
   public ObjectNode execute(final HttpServletRequest request) throws Exception {
 
     // get cluster object
-    Cluster cluster = Repository.get().getCluster();
+    Cluster cluster = repository.getCluster();
 
     // json object to be sent as response
     ObjectNode responseJSON = mapper.createObjectNode();
diff --git a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/MemberAsynchEventQueuesService.java b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/MemberAsynchEventQueuesService.java
index 25be0e9..abcb3d0 100644
--- a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/MemberAsynchEventQueuesService.java
+++ b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/MemberAsynchEventQueuesService.java
@@ -25,6 +25,7 @@ import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.node.ArrayNode;
 import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Scope;
 import org.springframework.stereotype.Component;
 import org.springframework.stereotype.Service;
@@ -46,12 +47,18 @@ import org.apache.geode.tools.pulse.internal.data.Repository;
 public class MemberAsynchEventQueuesService implements PulseService {
 
   private final ObjectMapper mapper = new ObjectMapper();
+  private final Repository repository;
+
+  @Autowired
+  public MemberAsynchEventQueuesService(Repository repository) {
+    this.repository = repository;
+  }
 
   @Override
   public ObjectNode execute(final HttpServletRequest request) throws Exception {
 
     // get cluster object
-    Cluster cluster = Repository.get().getCluster();
+    Cluster cluster = repository.getCluster();
 
     // json object to be sent as response
     ObjectNode responseJSON = mapper.createObjectNode();
diff --git a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/MemberClientsService.java b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/MemberClientsService.java
index 8a64596..19441cc 100644
--- a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/MemberClientsService.java
+++ b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/MemberClientsService.java
@@ -25,6 +25,7 @@ import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.node.ArrayNode;
 import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Scope;
 import org.springframework.stereotype.Component;
 import org.springframework.stereotype.Service;
@@ -50,12 +51,18 @@ public class MemberClientsService implements PulseService {
   // String constants used for forming a json response
   private static final String NAME = "name";
   private static final String HOST = "host";
+  private final Repository repository;
+
+  @Autowired
+  public MemberClientsService(Repository repository) {
+    this.repository = repository;
+  }
 
   @Override
   public ObjectNode execute(final HttpServletRequest request) throws Exception {
 
     // get cluster object
-    Cluster cluster = Repository.get().getCluster();
+    Cluster cluster = repository.getCluster();
 
     // json object to be sent as response
     ObjectNode responseJSON = mapper.createObjectNode();
diff --git a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/MemberDetailsService.java b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/MemberDetailsService.java
index 4290d93..633f376 100644
--- a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/MemberDetailsService.java
+++ b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/MemberDetailsService.java
@@ -25,6 +25,7 @@ import javax.servlet.http.HttpServletRequest;
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Scope;
 import org.springframework.stereotype.Component;
 import org.springframework.stereotype.Service;
@@ -45,6 +46,12 @@ import org.apache.geode.tools.pulse.internal.data.Repository;
 public class MemberDetailsService implements PulseService {
 
   private final ObjectMapper mapper = new ObjectMapper();
+  private final Repository repository;
+
+  @Autowired
+  public MemberDetailsService(Repository repository) {
+    this.repository = repository;
+  }
 
   @Override
   public ObjectNode execute(final HttpServletRequest request) throws Exception {
@@ -52,7 +59,7 @@ public class MemberDetailsService implements PulseService {
     String userName = request.getUserPrincipal().getName();
 
     // get cluster object
-    Cluster cluster = Repository.get().getCluster();
+    Cluster cluster = repository.getCluster();
 
     // json object to be sent as response
     ObjectNode responseJSON = mapper.createObjectNode();
diff --git a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/MemberDiskThroughputService.java b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/MemberDiskThroughputService.java
index 51d3c48..9f292bf 100644
--- a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/MemberDiskThroughputService.java
+++ b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/MemberDiskThroughputService.java
@@ -24,6 +24,7 @@ import javax.servlet.http.HttpServletRequest;
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Scope;
 import org.springframework.stereotype.Component;
 import org.springframework.stereotype.Service;
@@ -45,12 +46,18 @@ import org.apache.geode.tools.pulse.internal.data.Repository;
 public class MemberDiskThroughputService implements PulseService {
 
   private final ObjectMapper mapper = new ObjectMapper();
+  private final Repository repository;
+
+  @Autowired
+  public MemberDiskThroughputService(Repository repository) {
+    this.repository = repository;
+  }
 
   @Override
   public ObjectNode execute(final HttpServletRequest request) throws Exception {
 
     // get cluster object
-    Cluster cluster = Repository.get().getCluster();
+    Cluster cluster = repository.getCluster();
 
     // json object to be sent as response
     ObjectNode responseJSON = mapper.createObjectNode();
diff --git a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/MemberGCPausesService.java b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/MemberGCPausesService.java
index 5a8a3db..5680a65 100644
--- a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/MemberGCPausesService.java
+++ b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/MemberGCPausesService.java
@@ -24,6 +24,7 @@ import javax.servlet.http.HttpServletRequest;
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Scope;
 import org.springframework.stereotype.Component;
 import org.springframework.stereotype.Service;
@@ -46,12 +47,18 @@ import org.apache.geode.tools.pulse.internal.data.Repository;
 public class MemberGCPausesService implements PulseService {
 
   private final ObjectMapper mapper = new ObjectMapper();
+  private final Repository repository;
+
+  @Autowired
+  public MemberGCPausesService(Repository repository) {
+    this.repository = repository;
+  }
 
   @Override
   public ObjectNode execute(final HttpServletRequest request) throws Exception {
 
     // get cluster object
-    Cluster cluster = Repository.get().getCluster();
+    Cluster cluster = repository.getCluster();
 
     // json object to be sent as response
     ObjectNode responseJSON = mapper.createObjectNode();
diff --git a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/MemberGatewayHubService.java b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/MemberGatewayHubService.java
index b458569..93205e4 100644
--- a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/MemberGatewayHubService.java
+++ b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/MemberGatewayHubService.java
@@ -29,6 +29,7 @@ import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.node.ArrayNode;
 import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Scope;
 import org.springframework.stereotype.Component;
 import org.springframework.stereotype.Service;
@@ -50,12 +51,18 @@ import org.apache.geode.tools.pulse.internal.data.Repository;
 public class MemberGatewayHubService implements PulseService {
 
   private final ObjectMapper mapper = new ObjectMapper();
+  private final Repository repository;
+
+  @Autowired
+  public MemberGatewayHubService(Repository repository) {
+    this.repository = repository;
+  }
 
   @Override
   public ObjectNode execute(final HttpServletRequest request) throws Exception {
 
     // get cluster object
-    Cluster cluster = Repository.get().getCluster();
+    Cluster cluster = repository.getCluster();
 
     // json object to be sent as response
     ObjectNode responseJSON = mapper.createObjectNode();
diff --git a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/MemberHeapUsageService.java b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/MemberHeapUsageService.java
index a87041f..52801ed 100644
--- a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/MemberHeapUsageService.java
+++ b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/MemberHeapUsageService.java
@@ -24,6 +24,7 @@ import javax.servlet.http.HttpServletRequest;
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Scope;
 import org.springframework.stereotype.Component;
 import org.springframework.stereotype.Service;
@@ -45,12 +46,18 @@ import org.apache.geode.tools.pulse.internal.data.Repository;
 public class MemberHeapUsageService implements PulseService {
 
   private final ObjectMapper mapper = new ObjectMapper();
+  private final Repository repository;
+
+  @Autowired
+  public MemberHeapUsageService(Repository repository) {
+    this.repository = repository;
+  }
 
   @Override
   public ObjectNode execute(final HttpServletRequest request) throws Exception {
 
     // get cluster object
-    Cluster cluster = Repository.get().getCluster();
+    Cluster cluster = repository.getCluster();
 
     // json object to be sent as response
     ObjectNode responseJSON = mapper.createObjectNode();
diff --git a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/MemberKeyStatisticsService.java b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/MemberKeyStatisticsService.java
index 0dec34e..8ceac47 100644
--- a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/MemberKeyStatisticsService.java
+++ b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/MemberKeyStatisticsService.java
@@ -24,6 +24,7 @@ import javax.servlet.http.HttpServletRequest;
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Scope;
 import org.springframework.stereotype.Component;
 import org.springframework.stereotype.Service;
@@ -44,12 +45,18 @@ import org.apache.geode.tools.pulse.internal.data.Repository;
 public class MemberKeyStatisticsService implements PulseService {
 
   private final ObjectMapper mapper = new ObjectMapper();
+  private final Repository repository;
+
+  @Autowired
+  public MemberKeyStatisticsService(Repository repository) {
+    this.repository = repository;
+  }
 
   @Override
   public ObjectNode execute(final HttpServletRequest request) throws Exception {
 
     // get cluster object
-    Cluster cluster = Repository.get().getCluster();
+    Cluster cluster = repository.getCluster();
 
     // json object to be sent as response
     ObjectNode responseJSON = mapper.createObjectNode();
diff --git a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/MemberRegionsService.java b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/MemberRegionsService.java
index 8ec8e90..10f7213 100644
--- a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/MemberRegionsService.java
+++ b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/MemberRegionsService.java
@@ -27,6 +27,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.node.ArrayNode;
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Scope;
 import org.springframework.stereotype.Component;
 import org.springframework.stereotype.Service;
@@ -54,12 +55,18 @@ public class MemberRegionsService implements PulseService {
   private static final String ENTRY_SIZE = "entrySize";
   private static final String DISC_STORE_NAME = "diskStoreName";
   private static final String DISC_SYNCHRONOUS = "diskSynchronous";
+  private final Repository repository;
+
+  @Autowired
+  public MemberRegionsService(Repository repository) {
+    this.repository = repository;
+  }
 
   @Override
   public ObjectNode execute(final HttpServletRequest request) throws Exception {
 
     // get cluster object
-    Cluster cluster = Repository.get().getCluster();
+    Cluster cluster = repository.getCluster();
 
     // json object to be sent as response
     ObjectNode responseJSON = mapper.createObjectNode();
diff --git a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/MembersListService.java b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/MembersListService.java
index 9d0ca9e..1f8337e 100644
--- a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/MembersListService.java
+++ b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/MembersListService.java
@@ -22,6 +22,7 @@ import javax.servlet.http.HttpServletRequest;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.node.ArrayNode;
 import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Scope;
 import org.springframework.stereotype.Component;
 import org.springframework.stereotype.Service;
@@ -42,12 +43,18 @@ import org.apache.geode.tools.pulse.internal.data.Repository;
 public class MembersListService implements PulseService {
 
   private final ObjectMapper mapper = new ObjectMapper();
+  private final Repository repository;
+
+  @Autowired
+  public MembersListService(Repository repository) {
+    this.repository = repository;
+  }
 
   @Override
   public ObjectNode execute(final HttpServletRequest request) throws Exception {
 
     // get cluster object
-    Cluster cluster = Repository.get().getCluster();
+    Cluster cluster = repository.getCluster();
 
     // json object to be sent as response
     ObjectNode responseJSON = mapper.createObjectNode();
diff --git a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/PulseVersionService.java b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/PulseVersionService.java
index 9cd83b2..a505c79 100644
--- a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/PulseVersionService.java
+++ b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/PulseVersionService.java
@@ -21,6 +21,7 @@ import javax.servlet.http.HttpServletRequest;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Scope;
 import org.springframework.stereotype.Component;
 import org.springframework.stereotype.Service;
@@ -42,6 +43,12 @@ import org.apache.geode.tools.pulse.internal.controllers.PulseController;
 public class PulseVersionService implements PulseService {
 
   private final ObjectMapper mapper = new ObjectMapper();
+  private final PulseController pulseController;
+
+  @Autowired
+  public PulseVersionService(PulseController pulseController) {
+    this.pulseController = pulseController;
+  }
 
   @Override
   public ObjectNode execute(final HttpServletRequest request) throws Exception {
@@ -50,12 +57,13 @@ public class PulseVersionService implements PulseService {
     ObjectNode responseJSON = mapper.createObjectNode();
 
     // Response
-    responseJSON.put("pulseVersion", PulseController.pulseVersion.getPulseVersion());
-    responseJSON.put("buildId", PulseController.pulseVersion.getPulseBuildId());
-    responseJSON.put("buildDate", PulseController.pulseVersion.getPulseBuildDate());
-    responseJSON.put("sourceDate", PulseController.pulseVersion.getPulseSourceDate());
-    responseJSON.put("sourceRevision", PulseController.pulseVersion.getPulseSourceRevision());
-    responseJSON.put("sourceRepository", PulseController.pulseVersion.getPulseSourceRepository());
+    responseJSON.put("pulseVersion", pulseController.getPulseVersion().getPulseVersion());
+    responseJSON.put("buildId", pulseController.getPulseVersion().getPulseBuildId());
+    responseJSON.put("buildDate", pulseController.getPulseVersion().getPulseBuildDate());
+    responseJSON.put("sourceDate", pulseController.getPulseVersion().getPulseSourceDate());
+    responseJSON.put("sourceRevision", pulseController.getPulseVersion().getPulseSourceRevision());
+    responseJSON.put("sourceRepository",
+        pulseController.getPulseVersion().getPulseSourceRepository());
 
     // Send json response
     return responseJSON;
diff --git a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/QueryStatisticsService.java b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/QueryStatisticsService.java
index 7d42abd..9fa4ee0 100644
--- a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/QueryStatisticsService.java
+++ b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/QueryStatisticsService.java
@@ -22,6 +22,7 @@ import javax.servlet.http.HttpServletRequest;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.node.ArrayNode;
 import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Scope;
 import org.springframework.stereotype.Component;
 import org.springframework.stereotype.Service;
@@ -43,12 +44,18 @@ import org.apache.geode.tools.pulse.internal.data.Repository;
 public class QueryStatisticsService implements PulseService {
 
   private final ObjectMapper mapper = new ObjectMapper();
+  private final Repository repository;
+
+  @Autowired
+  public QueryStatisticsService(Repository repository) {
+    this.repository = repository;
+  }
 
   @Override
   public ObjectNode execute(final HttpServletRequest request) throws Exception {
 
     // get cluster object
-    Cluster cluster = Repository.get().getCluster();
+    Cluster cluster = repository.getCluster();
 
     // json object to be sent as response
     ObjectNode responseJSON = mapper.createObjectNode();
diff --git a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/SystemAlertsService.java b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/SystemAlertsService.java
index 8908abf..2216404 100644
--- a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/SystemAlertsService.java
+++ b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/SystemAlertsService.java
@@ -24,6 +24,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.node.ArrayNode;
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Scope;
 import org.springframework.stereotype.Component;
 import org.springframework.stereotype.Service;
@@ -46,12 +47,18 @@ import org.apache.geode.tools.pulse.internal.data.Repository;
 public class SystemAlertsService implements PulseService {
 
   private static final ObjectMapper mapper = new ObjectMapper();
+  private final Repository repository;
+
+  @Autowired
+  public SystemAlertsService(Repository repository) {
+    this.repository = repository;
+  }
 
   @Override
   public ObjectNode execute(final HttpServletRequest request) throws Exception {
 
     // get cluster object
-    Cluster cluster = Repository.get().getCluster();
+    Cluster cluster = repository.getCluster();
 
     // json object to be sent as response
     ObjectNode responseJSON = mapper.createObjectNode();
diff --git a/geode-pulse/src/main/resources/pulse.properties b/geode-pulse/src/main/resources/pulse.properties
index 98aeff3..d909c3f 100644
--- a/geode-pulse/src/main/resources/pulse.properties
+++ b/geode-pulse/src/main/resources/pulse.properties
@@ -29,7 +29,8 @@ pulse.port=10334
 #pulse.useSSL.manager=true
 
 ###### use pulse.properties to customize oauth behavior ######
-#pulse.oauth.provider=uaa/google/etc.
+#pulse.oauth.providerId=uaa/google/etc.
+#pulse.oauth.providerName=
 #pulse.oauth.clientId=
 #pulse.oauth.clientSecret=
 #pulse.oauth.authorizationUri=
diff --git a/geode-pulse/src/main/webapp/WEB-INF/web.xml b/geode-pulse/src/main/webapp/WEB-INF/web.xml
index 19ad467..5da1928 100644
--- a/geode-pulse/src/main/webapp/WEB-INF/web.xml
+++ b/geode-pulse/src/main/webapp/WEB-INF/web.xml
@@ -39,15 +39,6 @@
     <url-pattern>/</url-pattern>
   </servlet-mapping>
 
-  <listener>
-    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
-  </listener>
-  <context-param>
-    <param-name>contextConfigLocation</param-name>
-    <param-value>
-			/WEB-INF/pulse-servlet.xml
-    </param-value>
-  </context-param>
   <context-param>
     <param-name>spring.profiles.default</param-name>
     <param-value>pulse.authentication.default</param-value>
@@ -60,7 +51,4 @@
     <filter-name>springSecurityFilterChain</filter-name>
     <url-pattern>/*</url-pattern>
   </filter-mapping>
-  <listener>
-    <listener-class>org.apache.geode.tools.pulse.internal.PulseAppListener</listener-class>
-  </listener>
 </web-app>
\ No newline at end of file
diff --git a/geode-pulse/src/test/java/org/apache/geode/tools/pulse/internal/PulseAppListenerTest.java b/geode-pulse/src/test/java/org/apache/geode/tools/pulse/internal/PulseAppListenerTest.java
index 3fc89fb..7ce7896 100644
--- a/geode-pulse/src/test/java/org/apache/geode/tools/pulse/internal/PulseAppListenerTest.java
+++ b/geode-pulse/src/test/java/org/apache/geode/tools/pulse/internal/PulseAppListenerTest.java
@@ -21,7 +21,6 @@ import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 import javax.servlet.ServletContext;
-import javax.servlet.ServletContextEvent;
 
 import org.junit.After;
 import org.junit.Assert;
@@ -30,8 +29,12 @@ import org.junit.Rule;
 import org.junit.Test;
 import org.junit.contrib.java.lang.system.RestoreSystemProperties;
 import org.junit.rules.TestRule;
+import org.springframework.context.event.ContextRefreshedEvent;
+import org.springframework.web.context.WebApplicationContext;
 
+import org.apache.geode.tools.pulse.internal.controllers.PulseController;
 import org.apache.geode.tools.pulse.internal.data.PulseConstants;
+import org.apache.geode.tools.pulse.internal.data.PulseVersion;
 import org.apache.geode.tools.pulse.internal.data.Repository;
 
 public class PulseAppListenerTest {
@@ -41,19 +44,27 @@ public class PulseAppListenerTest {
   @Rule
   public final TestRule restoreSystemProperties = new RestoreSystemProperties();
 
-  ServletContextEvent contextEvent;
+  ContextRefreshedEvent contextEvent;
 
   @Before
   public void setUp() {
     System.setProperty(PulseConstants.SYSTEM_PROPERTY_PULSE_EMBEDDED, "true");
 
-    repository = Repository.get();
-    appListener = new PulseAppListener();
+    repository = new Repository();
 
-    contextEvent = mock(ServletContextEvent.class);
-    ServletContext context = mock(ServletContext.class);
-    when(context.getAttribute(anyString())).thenReturn(null);
-    when(contextEvent.getServletContext()).thenReturn(context);
+    PulseController pulseController = mock(PulseController.class);
+    appListener =
+        new PulseAppListener(pulseController, repository, new ClassPathPropertiesFileLoader());
+    PulseVersion pulseVersion = new PulseVersion(repository);
+    when(pulseController.getPulseVersion()).thenReturn(pulseVersion);
+
+    contextEvent = mock(ContextRefreshedEvent.class);
+    WebApplicationContext applicationContext = mock(WebApplicationContext.class);
+    when(contextEvent.getApplicationContext()).thenReturn(applicationContext);
+
+    ServletContext servletContext = mock(ServletContext.class);
+    when(servletContext.getAttribute(anyString())).thenReturn(null);
+    when(applicationContext.getServletContext()).thenReturn(servletContext);
   }
 
   @Test
diff --git a/geode-pulse/src/test/java/org/apache/geode/tools/pulse/internal/PulseAppListenerUnitTest.java b/geode-pulse/src/test/java/org/apache/geode/tools/pulse/internal/PulseAppListenerUnitTest.java
index 6de7e35..8e58873 100644
--- a/geode-pulse/src/test/java/org/apache/geode/tools/pulse/internal/PulseAppListenerUnitTest.java
+++ b/geode-pulse/src/test/java/org/apache/geode/tools/pulse/internal/PulseAppListenerUnitTest.java
@@ -28,10 +28,8 @@ import static org.mockito.Mockito.when;
 import java.util.Enumeration;
 import java.util.Properties;
 import java.util.ResourceBundle;
-import java.util.function.BiFunction;
 
 import javax.servlet.ServletContext;
-import javax.servlet.ServletContextEvent;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -39,18 +37,19 @@ import org.junit.Test;
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
+import org.springframework.context.event.ContextRefreshedEvent;
+import org.springframework.web.context.WebApplicationContext;
 
+import org.apache.geode.tools.pulse.internal.controllers.PulseController;
+import org.apache.geode.tools.pulse.internal.data.PulseVersion;
 import org.apache.geode.tools.pulse.internal.data.Repository;
 
 public class PulseAppListenerUnitTest {
-
   @Rule
   public MockitoRule rule = MockitoJUnit.rule();
 
-  private PulseAppListener subject;
-
   @Mock
-  private ServletContextEvent contextEvent;
+  private ContextRefreshedEvent contextEvent;
 
   @Mock
   private ServletContext servletContext;
@@ -59,23 +58,36 @@ public class PulseAppListenerUnitTest {
   private Repository repository;
 
   @Mock
-  private BiFunction<String, ResourceBundle, Properties> loadProperties;
+  private PulseController pulseController;
+
+  @Mock
+  private PropertiesFileLoader loadProperties;
+
+  @Mock
+  private WebApplicationContext applicationContext;
 
   private ResourceBundle resourceBundle;
+  private PulseVersion pulseVersion;
+  private PulseAppListener subject;
+
 
   @Before
   public void setUp() {
-    when(loadProperties.apply(eq("GemFireVersion.properties"), any())).thenReturn(new Properties());
+    pulseVersion = new PulseVersion(repository);
   }
 
   @Test
   public void contextInitialized_isEmbeddedModeWithoutSslProperties_doesNotSetSslProperties() {
     resourceBundle = new StubResourceBundle();
 
-    when(contextEvent.getServletContext()).thenReturn(servletContext);
+    when(loadProperties.loadProperties(eq("GemFireVersion.properties"), any()))
+        .thenReturn(new Properties());
+    when(contextEvent.getApplicationContext()).thenReturn(applicationContext);
+    when(applicationContext.getServletContext()).thenReturn(servletContext);
     when(repository.getResourceBundle()).thenReturn(resourceBundle);
+    when(pulseController.getPulseVersion()).thenReturn(pulseVersion);
 
-    subject = new PulseAppListener(true, repository, loadProperties);
+    subject = new PulseAppListener(true, loadProperties, pulseController, repository);
 
     subject.contextInitialized(contextEvent);
 
@@ -88,11 +100,15 @@ public class PulseAppListenerUnitTest {
 
     Properties sslProperties = new Properties();
 
-    when(contextEvent.getServletContext()).thenReturn(servletContext);
+    when(contextEvent.getApplicationContext()).thenReturn(applicationContext);
+    when(applicationContext.getServletContext()).thenReturn(servletContext);
+    when(loadProperties.loadProperties(eq("GemFireVersion.properties"), any()))
+        .thenReturn(new Properties());
     when(repository.getResourceBundle()).thenReturn(resourceBundle);
     when(servletContext.getAttribute("org.apache.geode.sslConfig")).thenReturn(sslProperties);
+    when(pulseController.getPulseVersion()).thenReturn(pulseVersion);
 
-    subject = new PulseAppListener(true, repository, loadProperties);
+    subject = new PulseAppListener(true, loadProperties, pulseController, repository);
 
     subject.contextInitialized(contextEvent);
 
@@ -104,9 +120,10 @@ public class PulseAppListenerUnitTest {
     resourceBundle = new StubResourceBundle();
 
     when(repository.getResourceBundle()).thenReturn(resourceBundle);
-    when(loadProperties.apply(anyString(), any())).thenReturn(new Properties());
+    when(loadProperties.loadProperties(anyString(), any())).thenReturn(new Properties());
+    when(pulseController.getPulseVersion()).thenReturn(pulseVersion);
 
-    subject = new PulseAppListener(false, repository, loadProperties);
+    subject = new PulseAppListener(false, loadProperties, pulseController, repository);
 
     subject.contextInitialized(contextEvent);
 
@@ -121,18 +138,21 @@ public class PulseAppListenerUnitTest {
     sslProperties.put("foo", "bar");
 
     when(repository.getResourceBundle()).thenReturn(resourceBundle);
-    when(loadProperties.apply(eq("pulse.properties"), any())).thenReturn(new Properties());
-    when(loadProperties.apply(eq("pulsesecurity.properties"), any())).thenReturn(sslProperties);
+    when(loadProperties.loadProperties(eq("pulse.properties"), any())).thenReturn(new Properties());
+    when(loadProperties.loadProperties(eq("pulsesecurity.properties"), any()))
+        .thenReturn(sslProperties);
+    when(loadProperties.loadProperties(eq("GemFireVersion.properties"), any()))
+        .thenReturn(new Properties());
+    when(pulseController.getPulseVersion()).thenReturn(pulseVersion);
 
-    subject = new PulseAppListener(false, repository, loadProperties);
+    subject = new PulseAppListener(false, loadProperties, pulseController, repository);
 
     subject.contextInitialized(contextEvent);
 
     verify(repository).setJavaSslProperties(sslProperties);
   }
 
-  class StubResourceBundle extends ResourceBundle {
-
+  static class StubResourceBundle extends ResourceBundle {
     @Override
     protected Object handleGetObject(String key) {
       return "the same string";
diff --git a/geode-pulse/src/test/java/org/apache/geode/tools/pulse/internal/data/JMXDataUpdaterGetDoubleAttributeTest.java b/geode-pulse/src/test/java/org/apache/geode/tools/pulse/internal/data/JMXDataUpdaterGetDoubleAttributeTest.java
index f3651fa..c4b0b8b 100644
--- a/geode-pulse/src/test/java/org/apache/geode/tools/pulse/internal/data/JMXDataUpdaterGetDoubleAttributeTest.java
+++ b/geode-pulse/src/test/java/org/apache/geode/tools/pulse/internal/data/JMXDataUpdaterGetDoubleAttributeTest.java
@@ -34,7 +34,7 @@ public class JMXDataUpdaterGetDoubleAttributeTest {
   @Before
   public void setUp() {
     this.cluster = mock(Cluster.class);
-    this.jmxDataUpdater = new JMXDataUpdater("server", "cluster", this.cluster);
+    this.jmxDataUpdater = new JMXDataUpdater("server", "cluster", this.cluster, null, null);
     this.floatStat = 1.2345f;
     this.doubleStat = 1.2345d;
   }
diff --git a/geode-pulse/src/test/java/org/apache/geode/tools/pulse/internal/security/LogoutHandlerTest.java b/geode-pulse/src/test/java/org/apache/geode/tools/pulse/internal/security/LogoutHandlerTest.java
index 424fc57..d64e471 100644
--- a/geode-pulse/src/test/java/org/apache/geode/tools/pulse/internal/security/LogoutHandlerTest.java
+++ b/geode-pulse/src/test/java/org/apache/geode/tools/pulse/internal/security/LogoutHandlerTest.java
@@ -14,98 +14,81 @@
  */
 package org.apache.geode.tools.pulse.internal.security;
 
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
-import static org.powermock.api.mockito.PowerMockito.spy;
-import static org.powermock.api.mockito.PowerMockito.when;
+import static org.mockito.Mockito.when;
 
-import java.util.Arrays;
-import java.util.Collection;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.experimental.categories.Category;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameter;
-import org.junit.runners.Parameterized.Parameters;
-import org.junit.runners.Parameterized.UseParametersRunnerFactory;
-import org.mockito.Mockito;
-import org.powermock.core.classloader.annotations.PowerMockIgnore;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
-import org.powermock.modules.junit4.PowerMockRunnerDelegate;
-import org.springframework.mock.web.MockHttpServletRequest;
-import org.springframework.mock.web.MockHttpServletResponse;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.springframework.context.ApplicationContext;
 import org.springframework.security.core.Authentication;
 
 import org.apache.geode.test.junit.categories.LoggingTest;
 import org.apache.geode.test.junit.categories.PulseTest;
 import org.apache.geode.test.junit.categories.SecurityTest;
-import org.apache.geode.test.junit.runners.CategoryWithParameterizedRunnerFactory;
-import org.apache.geode.tools.pulse.internal.data.Cluster;
 import org.apache.geode.tools.pulse.internal.data.Repository;
 
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(Repository.class)
-@PowerMockRunnerDelegate(Parameterized.class)
-@PowerMockIgnore({"javax.management.*", "javax.security.*", "*.UnitTest"})
-@UseParametersRunnerFactory(CategoryWithParameterizedRunnerFactory.class)
 @Category({PulseTest.class, SecurityTest.class, LoggingTest.class})
 public class LogoutHandlerTest {
+  private static final String EXPECTED_REDIRECT_URL = "/defaultTargetUrl";
 
-  private static final String mockUser = "admin";
+  @Rule
+  public MockitoRule mockitoRule = MockitoJUnit.rule();
 
+  @Mock
+  private HttpServletRequest request;
+  @Mock
+  private HttpServletResponse response;
+  @Mock
   private Repository repository;
-  private LogoutHandler handler;
+  @Mock
+  private ApplicationContext applicationContext;
 
-  @Parameter
-  public static Authentication authentication;
-
-  @Parameters(name = "{0}")
-  public static Collection<Authentication> authentications() throws Exception {
-    Authentication defaultAuthentication = mock(Authentication.class, "Default Authentication");
-    when(defaultAuthentication.getName()).thenReturn(mockUser);
-
-    GemFireAuthentication gemfireAuthentication =
-        mock(GemFireAuthentication.class, "GemFire Authentication");
-    when(gemfireAuthentication.getName()).thenReturn(mockUser);
-
-    return Arrays.asList(defaultAuthentication, gemfireAuthentication);
-  }
+  private final LogoutHandler handler = new LogoutHandler(EXPECTED_REDIRECT_URL);
 
   @Before
-  public void setup() throws Exception {
-    Cluster cluster = Mockito.spy(Cluster.class);
-    repository = Mockito.spy(Repository.class);
-    spy(Repository.class);
-    when(Repository.class, "get").thenReturn(repository);
-    doReturn(cluster).when(repository).getCluster();
-    handler = new LogoutHandler("/defaultTargetUrl");
+  public void setup() {
+    when(request.getContextPath()).thenReturn("");
+    when(response.encodeRedirectURL(EXPECTED_REDIRECT_URL)).thenReturn(EXPECTED_REDIRECT_URL);
+    handler.setApplicationContext(applicationContext);
   }
 
   @Test
-  public void testNullAuthentication() throws Exception {
-    MockHttpServletRequest request = new MockHttpServletRequest();
-    MockHttpServletResponse response = new MockHttpServletResponse();
+  public void onLogoutSuccess_logsOutAuthenticatedUser() throws Exception {
+    String authenticatedUser = "authenticated-user";
 
-    handler.onLogoutSuccess(request, response, null);
+    Authentication authentication = mock(Authentication.class);
+    when(authentication.getName()).thenReturn(authenticatedUser);
+    when(applicationContext.getBean("repository", Repository.class)).thenReturn(repository);
+
+    handler.onLogoutSuccess(request, response, authentication);
 
-    assertThat(response.getStatus()).isEqualTo(302);
-    assertThat(response.getHeader("Location")).isEqualTo("/defaultTargetUrl");
+    verify(repository, times(1)).logoutUser(authenticatedUser);
   }
 
   @Test
-  public void testNotNullAuthentication() throws Exception {
-    MockHttpServletRequest request = new MockHttpServletRequest();
-    MockHttpServletResponse response = new MockHttpServletResponse();
+  public void onLogoutSuccess_redirectsToSpecifiedUrl() throws Exception {
+    when(applicationContext.getBean("repository", Repository.class)).thenReturn(repository);
+    handler.onLogoutSuccess(request, response, mock(Authentication.class));
 
-    handler.onLogoutSuccess(request, response, authentication);
+    verify(response).sendRedirect(EXPECTED_REDIRECT_URL);
+  }
+
+  @Test
+  public void onLogoutSuccess_redirectsToSpecifiedUrl_evenIfNoAuthenticationGiven()
+      throws Exception {
+    handler.onLogoutSuccess(request, response, null);
 
-    assertThat(response.getStatus()).isEqualTo(302);
-    assertThat(response.getHeader("Location")).isEqualTo("/defaultTargetUrl");
-    verify(repository, Mockito.times(1)).logoutUser(mockUser);
+    verify(response).sendRedirect(EXPECTED_REDIRECT_URL);
   }
+
 }