Add a Spring Boot with Spring Session Data Geode sample showing an application using 'session' and 'request' scoped proxy bean.

Resolves Issue #3.
This commit is contained in:
John Blum
2017-11-03 19:22:33 -07:00
parent 83e4e96400
commit f33f3f6a4b
6 changed files with 449 additions and 0 deletions

View File

@@ -0,0 +1,93 @@
apply plugin: 'io.spring.convention.spring-sample-boot'
apply plugin: "application"
apply from: IDE_GRADLE
repositories {
mavenCentral()
}
dependencies {
compile project(':spring-session-data-geode')
compile "org.springframework.boot:spring-boot-starter-thymeleaf"
compile "org.springframework.boot:spring-boot-starter-web"
compile "org.webjars:bootstrap"
compile "org.webjars:webjars-locator"
runtime "org.springframework.shell:spring-shell"
testCompile "org.springframework.boot:spring-boot-starter-test"
testCompile seleniumDependencies
// integrationTestCompile seleniumDependencies
// integrationTestRuntime "org.springframework.shell:spring-shell"
}
run {
doFirst {
mainClassName = 'sample.server.GemFireServer'
}
}
bootJar {
mainClassName = 'sample.client.Application'
}
task runGemFireServer() {
doLast {
ext.port = reservePort()
println "Starting Apache Geode Server on port [$port] ..."
def out = new StringBuilder()
def err = new StringBuilder()
String classpath = sourceSets.main.runtimeClasspath.collect { it }.join(File.pathSeparator)
String[] commandLine = [
'java', '-server', '-ea', '-classpath', classpath,
//"-Dgemfire.log-file=gemfire-server.log",
//"-Dgemfire.log-level=config",
"-Dspring.session.data.geode.cache.server.port=$port",
'sample.server.GemFireServer'
]
//println commandLine
ext.process = commandLine.execute()
//ext.process = new ProcessBuilder().command(commandLine).redirectErrorStream(true).start();
ext.process.consumeProcessOutput(out, err)
//println 'OUT: ' + out
//println 'ERR: ' + err
}
}
integrationTest {
dependsOn runGemFireServer
doFirst {
def port = reservePort()
systemProperties['management.port'] = 0
systemProperties['server.port'] = port
//systemProperties['gemfire.log-file'] = "gemfire-client.log"
//systemProperties['gemfire.log-level'] = "config"
systemProperties['spring.session.data.geode.cache.server.port'] = runGemFireServer.port
}
doLast {
println 'Stopping Apache Geode Server...'
runGemFireServer.process?.destroyForcibly()
}
}
def reservePort() {
def socket = new ServerSocket(0)
def result = socket.localPort
socket.close()
result
}

View File

@@ -0,0 +1,141 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.client;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Collections;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.data.gemfire.config.annotation.ClientCacheApplication;
import org.springframework.data.gemfire.config.annotation.ClientCacheConfigurer;
import org.springframework.data.gemfire.support.ConnectionEndpoint;
import org.springframework.session.data.gemfire.config.annotation.web.http.EnableGemFireHttpSession;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import sample.client.model.RequestScopedProxyBean;
import sample.client.model.SessionScopedProxyBean;
/**
* A Spring Boot, GemFire cache client, web application that reveals the current state of the HTTP Session.
*
* @author John Blum
* @see javax.servlet.http.HttpSession
* @see org.springframework.boot.SpringApplication
* @see org.springframework.boot.autoconfigure.SpringBootApplication
* @see org.springframework.context.annotation.Bean
* @see org.springframework.session.data.gemfire.config.annotation.web.http.EnableGemFireHttpSession
* @see org.springframework.stereotype.Controller
* @see org.apache.geode.cache.client.ClientCache
* @see org.apache.geode.cache.client.Pool
* @since 1.2.1
*/
@SuppressWarnings("unused")
// tag::class[]
@SpringBootApplication // <1>
@Controller // <2>
public class Application {
static final String INDEX_TEMPLATE_VIEW_NAME = "index";
static final String PING_RESPONSE = "PONG";
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@ClientCacheApplication(name = "SpringSessionDataGeodeClientWithScopedProxiesBootSample", logLevel = "config",
pingInterval = 5000L, readTimeout = 15000, retryAttempts = 1, subscriptionEnabled = true) // <3>
@EnableGemFireHttpSession(poolName = "DEFAULT") // <4>
static class ClientCacheConfiguration {
@Bean
static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
@Bean
ClientCacheConfigurer clientCacheServerPortConfigurer(
@Value("${spring.session.data.geode.cache.server.port:40404}") int port) { // <5>
return (beanName, clientCacheFactoryBean) ->
clientCacheFactoryBean.setServers(Collections.singletonList(
new ConnectionEndpoint("localhost", port)));
}
}
@Configuration
static class SpringWebMvcConfiguration { // <6>
@Bean
public WebMvcConfigurer webMvcConfig() {
return new WebMvcConfigurer() {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName(INDEX_TEMPLATE_VIEW_NAME);
}
};
}
}
@Autowired
private RequestScopedProxyBean requestBean;
@Autowired
private SessionScopedProxyBean sessionBean;
@ExceptionHandler
@ResponseBody
public String errorHandler(Throwable error) {
StringWriter writer = new StringWriter();
error.printStackTrace(new PrintWriter(writer));
return writer.toString();
}
@RequestMapping(method = RequestMethod.GET, path = "/ping")
@ResponseBody
public String ping() {
return PING_RESPONSE;
}
@RequestMapping(method = RequestMethod.GET, path = "/counts")
public String requestAndSessionInstanceCount(HttpServletRequest request, HttpSession session, Model model) {
model.addAttribute("sessionId", session.getId());
model.addAttribute("requestCount", this.requestBean.getCount());
model.addAttribute("sessionCount", this.sessionBean.getCount());
return INDEX_TEMPLATE_VIEW_NAME;
}
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.client.model;
import java.util.concurrent.atomic.AtomicInteger;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.stereotype.Component;
import org.springframework.web.context.annotation.RequestScope;
/**
* The RequestScopedProxyBean class...
*
* @author John Blum
* @since 1.0.0
*/
@Component
@RequestScope(proxyMode = ScopedProxyMode.TARGET_CLASS)
@SuppressWarnings("unused")
public class RequestScopedProxyBean {
private static final AtomicInteger INSTANCE_COUNTER = new AtomicInteger(0);
private final int count;
public RequestScopedProxyBean() {
this.count = INSTANCE_COUNTER.incrementAndGet();
}
public int getCount() {
return count;
}
@Override
public String toString() {
return String.format("{ @type = '%s', count = %d }", getClass().getName(), getCount());
}
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.client.model;
import java.io.Serializable;
import java.util.concurrent.atomic.AtomicInteger;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.stereotype.Component;
import org.springframework.web.context.annotation.SessionScope;
/**
* The SessionScopedProxyBean class...
*
* @author John Blum
* @since 1.0.0
*/
@Component
@SessionScope(proxyMode = ScopedProxyMode.TARGET_CLASS)
@SuppressWarnings("unused")
public class SessionScopedProxyBean implements Serializable {
private static final AtomicInteger INSTANCE_COUNTER = new AtomicInteger(0);
private final int count;
public SessionScopedProxyBean() {
this.count = INSTANCE_COUNTER.incrementAndGet();
}
public int getCount() {
return count;
}
@Override
public String toString() {
return String.format("{ @type = '%s', count = %d }", getClass().getName(), getCount());
}
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.server;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.data.gemfire.config.annotation.CacheServerApplication;
import org.springframework.data.gemfire.config.annotation.CacheServerConfigurer;
import org.springframework.data.gemfire.config.annotation.EnableManager;
import org.springframework.session.data.gemfire.config.annotation.web.http.EnableGemFireHttpSession;
/**
* A Spring Boot application bootstrapping a GemFire Cache Server JVM process.
*
* @author John Blum
* @see org.springframework.boot.SpringApplication
* @see org.springframework.boot.autoconfigure.SpringBootApplication
* @see org.springframework.context.annotation.Bean
* @see org.springframework.session.data.gemfire.config.annotation.web.http.EnableGemFireHttpSession
* @see org.apache.geode.cache.Cache
* @since 1.2.1
*/
@SuppressWarnings("unused")
// tag::class[]
@SpringBootApplication
@CacheServerApplication(name = "SpringSessionDataGeodeServerWithScopedProxiesBootSample", logLevel = "config")
@EnableGemFireHttpSession(maxInactiveIntervalInSeconds = 10)
@EnableManager(start = true)
public class GemFireServer {
public static void main(String[] args) {
new SpringApplicationBuilder(GemFireServer.class)
.web(WebApplicationType.NONE)
.build()
.run(args);
}
@Bean
static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
@Bean
CacheServerConfigurer cacheServerPortConfigurer(
@Value("${spring.session.data.geode.cache.server.port:40404}") int port) {
return (beanName, cacheServerFactoryBean) ->
cacheServerFactoryBean.setPort(port);
}
}
// end::class[]

View File

@@ -0,0 +1,40 @@
<!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-spring4-3.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<title>Request and Session Counts</title>
<link rel="stylesheet" th:href="@{/webjars/bootstrap/css/bootstrap.min.css}" href="/webjars/bootstrap/css/bootstrap.min.css"/>
<style type="text/css">
body {
padding: 1em;
}
</style>
</head>
<body>
<div class="container">
<h1>Description</h1>
<p>
This application demonstrates how both Spring 'request' and 'session' scoped (proxy) beans are utilized
in the context of Spring Session when GemFire is used to back the HTTP Session.
</p>
<table class="table table-striped">
<thead>
<tr>
<th> * </th>
<th>Session ID</th>
<th>Session Count</th>
<th>Request Count</th>
</tr>
</thead>
<tbody>
<tr>
<td><b>count</b></td>
<td th:text="${sessionId}">?</td>
<td th:text="${sessionCount}">0</td>
<td th:text="${requestCount}">0</td>
</tr>
</tbody>
</table>
</div>
</body>
</html>