Add sample demonstrating the use of Spring Session with Gfsh started Apache Geode servers configured with DataSerialization.

Resolve gh-11.
This commit is contained in:
John Blum
2018-11-13 13:58:51 -08:00
parent ff0ae7cd5f
commit 680c6d4a61
12 changed files with 757 additions and 16 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

View File

@@ -0,0 +1,39 @@
import org.apache.tools.ant.filters.ReplaceTokens
apply plugin: 'io.spring.convention.spring-sample-boot'
dependencies {
compile project(':spring-session-data-geode')
compile("org.springframework.boot:spring-boot-starter-thymeleaf") {
exclude group: "org.springframework.boot", module: "spring-boot-starter-logging"
}
compile("org.springframework.boot:spring-boot-starter-web") {
exclude group: "org.springframework.boot", module: "spring-boot-starter-logging"
}
compile "org.springframework.data:spring-data-geode-test"
compile "org.webjars:bootstrap"
compile "org.webjars:webjars-locator"
runtime "org.springframework.shell:spring-shell"
testCompile("org.springframework.boot:spring-boot-starter-test") {
exclude group: "org.springframework.boot", module: "spring-boot-starter-logging"
}
}
bootJar {
mainClassName = 'sample.client.Application'
}
processResources {
filter ReplaceTokens, tokens: [
'spring.version' : project.property("springVersion"),
'spring-data.version' : project.property("springDataGeodeVersion"),
'spring-session.version' : project.property("springSessionVersion"),
'spring-session-data-geode.version' : project.property("version")
]
}

View File

@@ -0,0 +1,160 @@
/*
* Copyright 2018 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 java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.stream.StreamSupport;
import javax.servlet.http.HttpSession;
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.data.gemfire.config.annotation.ClientCacheApplication;
import org.springframework.data.gemfire.util.CollectionUtils;
import org.springframework.session.data.gemfire.config.annotation.web.http.EnableGemFireHttpSession;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@SuppressWarnings("unused")
// tag::class[]
@SpringBootApplication // <1>
@ClientCacheApplication(subscriptionEnabled = true) // <2>
@EnableGemFireHttpSession(regionName = "Sessions", poolName = "DEFAULT") // <3>
@Controller // <4>
public class Application {
private static final String INDEX_TEMPLATE_VIEW_NAME = "index";
private static final String PING_RESPONSE = "PONG";
private static final String REQUEST_COUNT_ATTRIBUTE_NAME = "requestCount";
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Configuration
static class SpringWebMvcConfiguration { // <5>
@Bean
public WebMvcConfigurer webMvcConfig() {
return new WebMvcConfigurer() {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName(INDEX_TEMPLATE_VIEW_NAME);
}
};
}
}
@ExceptionHandler // <6>
public String errorHandler(Throwable error) {
StringWriter writer = new StringWriter();
error.printStackTrace(new PrintWriter(writer));
return writer.toString();
}
@GetMapping("/ping")
@ResponseBody
public String ping() {
return PING_RESPONSE;
}
@PostMapping("/session")
public String session(HttpSession session, ModelMap modelMap,
@RequestParam(name = "attributeName", required = false) String name,
@RequestParam(name = "attributeValue", required = false) String value) { // <7>
modelMap.addAttribute("sessionAttributes",
attributes(setAttribute(updateRequestCount(session), name, value)));
return INDEX_TEMPLATE_VIEW_NAME;
}
// end::class[]
HttpSession updateRequestCount(HttpSession session) {
synchronized (session) {
Integer currentRequestCount = (Integer) session.getAttribute(REQUEST_COUNT_ATTRIBUTE_NAME);
session.setAttribute(REQUEST_COUNT_ATTRIBUTE_NAME, nullSafeIncrement(currentRequestCount));
return session;
}
}
Integer nullSafeIncrement(Integer value) {
return nullSafeIntValue(value) + 1;
}
int nullSafeIntValue(Number value) {
return Optional.ofNullable(value).map(Number::intValue).orElse(0);
}
HttpSession setAttribute(HttpSession session, String attributeName, String attributeValue) {
if (isSet(attributeName, attributeValue)) {
session.setAttribute(attributeName, attributeValue);
}
return session;
}
boolean isSet(String... values) {
boolean set = true;
for (String value : values) {
set &= StringUtils.hasText(value);
}
return set;
}
Map<String, String> attributes(HttpSession session) {
Map<String, String> sessionAttributes = new HashMap<>();
StreamSupport.stream(toIterable(session.getAttributeNames()).spliterator(), false)
.forEach(attributeName -> sessionAttributes.put(attributeName,
String.valueOf(session.getAttribute(attributeName))));
return sessionAttributes;
}
<T> Iterable<T> toIterable(Enumeration<T> enumeration) {
return () -> Optional.ofNullable(enumeration)
.map(CollectionUtils::toIterator)
.orElseGet(Collections::emptyIterator);
}
}

View File

@@ -0,0 +1,40 @@
#tag::shell-script[]
#!$GEODE_HOME/bin/gfsh
set variable --name=USER_HOME --value=${SYS_USER_HOME}
set variable --name=REPO_HOME --value=${USER_HOME}/.m2/repository
set variable --name=SPRING_VERSION --value=@spring.version@
set variable --name=SPRING_DATA_VERSION --value=@spring-data.version@
set variable --name=SPRING_SESSION_VERSION --value=@spring-session.version@
set variable --name=SPRING_SESSION_DATA_GEODE_VERSION --value=@spring-session-data-geode.version@
set variable --name=MEMBER_TIMEOUT --value=5000
set variable --name=CACHE_XML_FILE --value=${USER_HOME}/spring-session-data-geode/samples/boot/gemfire-with-gfsh-servers/src/main/resources/initializer-cache.xml
#set variable --name=SERVER_CLASSPATH --value=${REPO_HOME}/org/springframework/spring-core/${SPRING_VERSION}/spring-core-${SPRING_VERSION}.jar\
#:${REPO_HOME}/org/springframework/spring-aop/${SPRING_VERSION}/spring-aop-${SPRING_VERSION}.jar\
#:${REPO_HOME}/org/springframework/spring-beans/${SPRING_VERSION}/spring-beans-${SPRING_VERSION}.jar\
#:${REPO_HOME}/org/springframework/spring-context/${SPRING_VERSION}/spring-context-${SPRING_VERSION}.jar\
#:${REPO_HOME}/org/springframework/spring-context-support/${SPRING_VERSION}/spring-context-support-${SPRING_VERSION}.jar\
#:${REPO_HOME}/org/springframework/spring-expression/${SPRING_VERSION}/spring-expression-${SPRING_VERSION}.jar\
#:${REPO_HOME}/org/springframework/spring-jcl/${SPRING_VERSION}/spring-jcl-${SPRING_VERSION}.jar\
#:${REPO_HOME}/org/springframework/spring-tx/${SPRING_VERSION}/spring-tx-${SPRING_VERSION}.jar\
#:${REPO_HOME}/org/springframework/data/spring-data-commons/${SPRING_DATA_VERSION}/spring-data-commons-${SPRING_DATA_VERSION}.jar\
#:${REPO_HOME}/org/springframework/data/spring-data-geode/${SPRING_DATA_VERSION}/spring-data-geode-${SPRING_DATA_VERSION}.jar\
#:${REPO_HOME}/org/springframework/session/spring-session-core/${SPRING_SESSION_VERSION}/spring-session-core-${SPRING_SESSION_VERSION}.jar\
#:${REPO_HOME}/org/springframework/session/spring-session-data-geode/${SPRING_SESSION_DATA_GEODE_VERSION}/spring-session-data-geode-${SPRING_SESSION_DATA_GEODE_VERSION}.jar\
#:${REPO_HOME}/org/slf4j/slf4j-api/1.7.25/slf4j-api-1.7.25.jar
set variable --name=SERVER_CLASSPATH --value=${REPO_HOME}/org/springframework/spring-core/${SPRING_VERSION}/spring-core-${SPRING_VERSION}.jar:${REPO_HOME}/org/springframework/spring-aop/${SPRING_VERSION}/spring-aop-${SPRING_VERSION}.jar:${REPO_HOME}/org/springframework/spring-beans/${SPRING_VERSION}/spring-beans-${SPRING_VERSION}.jar:${REPO_HOME}/org/springframework/spring-context/${SPRING_VERSION}/spring-context-${SPRING_VERSION}.jar:${REPO_HOME}/org/springframework/spring-context-support/${SPRING_VERSION}/spring-context-support-${SPRING_VERSION}.jar:${REPO_HOME}/org/springframework/spring-expression/${SPRING_VERSION}/spring-expression-${SPRING_VERSION}.jar:${REPO_HOME}/org/springframework/spring-jcl/${SPRING_VERSION}/spring-jcl-${SPRING_VERSION}.jar:${REPO_HOME}/org/springframework/spring-tx/${SPRING_VERSION}/spring-tx-${SPRING_VERSION}.jar:${REPO_HOME}/org/springframework/data/spring-data-commons/${SPRING_DATA_VERSION}/spring-data-commons-${SPRING_DATA_VERSION}.jar:${REPO_HOME}/org/springframework/data/spring-data-geode/${SPRING_DATA_VERSION}/spring-data-geode-${SPRING_DATA_VERSION}.jar:${REPO_HOME}/org/springframework/session/spring-session-core/${SPRING_SESSION_VERSION}/spring-session-core-${SPRING_SESSION_VERSION}.jar:${REPO_HOME}/org/springframework/session/spring-session-data-geode/${SPRING_SESSION_DATA_GEODE_VERSION}/spring-session-data-geode-${SPRING_SESSION_DATA_GEODE_VERSION}.jar:${REPO_HOME}/org/slf4j/slf4j-api/1.7.25/slf4j-api-1.7.25.jar
start locator --name=Locator1 --log-level=config
#start locator --name=Locator1 --log-level=config --J=-Dgemfire.member-timeout=${MEMBER_TIMEOUT}
start server --name=Server1 --log-level=config --cache-xml-file=${CACHE_XML_FILE} --classpath=${SERVER_CLASSPATH}
#start server --name=Server1 --log-level=config --cache-xml-file=${CACHE_XML_FILE} --classpath=${SERVER_CLASSPATH} --J=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 --J=-Dgemfire.member-timeout=${MEMBER_TIMEOUT}
start server --name=Server2 --server-port=0 --log-level=config --cache-xml-file=${CACHE_XML_FILE} --classpath=${SERVER_CLASSPATH}
#start server --name=Server2 --server-port=0 --log-level=config --cache-xml-file=${CACHE_XML_FILE} --classpath=${SERVER_CLASSPATH} --J=-Dgemfire.member-timeout=${MEMBER_TIMEOUT}
create region --name=Sessions --type=PARTITION --skip-if-exists --enable-statistics=true --entry-idle-time-expiration=15 --entry-idle-time-expiration-action=INVALIDATE
#end::shell-script[]

View File

@@ -0,0 +1,3 @@
#!/bin/bash
gfsh -e "run --file=${SYS_USER_HOME}/spring-session-data-geode/samples/boot/gemfire-with-gfsh-servers/src/main/resources/geode/bin/start-cluster.gfsh"

View File

@@ -0,0 +1,5 @@
#!$GEODE_HOME/bin/gfsh
stop server --name=Server2
stop server --name=Server1
stop locator --name=Locator1

View File

@@ -0,0 +1,3 @@
#!/bin/bash
gfsh -e "run --file=${SYS_USER_HOME}/spring-session-data-geode/samples/boot/gemfire-with-gfsh-servers/src/main/resources/geode/bin/stop-cluster.gfsh"

View File

@@ -0,0 +1,5 @@
# tag::queries[]
SELECT session.id FROM /Sessions session
SELECT session.getClass().getName() FROM /Sessions session
SELECT attributes.key, attributes.value FROM /Sessions session, session.attributes attributes
# end::queries[]

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- tag::cache-xml[] -->
<cache xmlns="http://geode.apache.org/schema/cache"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://geode.apache.org/schema/cache http://geode.apache.org/schema/cache/cache-1.0.xsd"
version="1.0">
<initializer>
<class-name>
org.springframework.session.data.gemfire.serialization.data.support.DataSerializableSessionSerializerInitializer
</class-name>
</initializer>
</cache>
<!-- end::cache-xml[] -->

View File

@@ -0,0 +1,49 @@
<!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>Session Attributes</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 to use a GemFire instance to back your session. Notice that there
is no JSESSIONID cookie. We are also able to customize the way of identifying what the requested
session id is.
</p>
<h1>Try it</h1>
<form class="form-inline" role="form" action="./session" method="post">
<label for="attributeName">Attribute Name</label>
<input id="attributeName" type="text" name="attributeName"/>
<label for="attributeValue">Attribute Value</label>
<input id="attributeValue" type="text" name="attributeValue"/>
<input type="submit" value="Set Attribute"/>
</form>
<hr/>
<table class="table table-striped">
<thead>
<tr>
<th>Attribute Name</th>
<th>Attribute Value</th>
</tr>
</thead>
<tbody>
<tr th:each="attribute: ${sessionAttributes}">
<td th:text="${attribute.key}">name</td>
<td th:text="${attribute.value}">value</td>
</tr>
</tbody>
</table>
</div>
</body>
</html>