Compare commits
198 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c14fdb283d | ||
|
|
ee1ff3ed3b | ||
|
|
eb7bcc5eeb | ||
|
|
188e5ba4e0 | ||
|
|
1e46630467 | ||
|
|
b72c600884 | ||
|
|
274aec1691 | ||
|
|
52ea98b4ce | ||
|
|
5c294ae1d2 | ||
|
|
1752928d96 | ||
|
|
0cdee25405 | ||
|
|
4a9f1700d5 | ||
|
|
36ab358d24 | ||
|
|
8e3371aed9 | ||
|
|
2161f966de | ||
|
|
63b67a501d | ||
|
|
2b0431eae4 | ||
|
|
04ec086014 | ||
|
|
5697f49a71 | ||
|
|
dfce66383f | ||
|
|
a83e59bf52 | ||
|
|
8b233e84ef | ||
|
|
84e7fbace1 | ||
|
|
f455df3333 | ||
|
|
a7bb9d3b31 | ||
|
|
5f0e4c3b85 | ||
|
|
23c6c7cf31 | ||
|
|
c8c5fae678 | ||
|
|
f4a58622e4 | ||
|
|
5384764021 | ||
|
|
56033a9b68 | ||
|
|
99a2b079ac | ||
|
|
9120151692 | ||
|
|
5abbe66b1d | ||
|
|
f00c196430 | ||
|
|
be2604ca69 | ||
|
|
2aa71ffb6d | ||
|
|
8bdcba6e50 | ||
|
|
8dd1a10f1b | ||
|
|
1d247aa96f | ||
|
|
c00d6a7bf2 | ||
|
|
c0df3bf28b | ||
|
|
1b8c9838a4 | ||
|
|
8a1b454121 | ||
|
|
ef69c8169a | ||
|
|
40b3d07224 | ||
|
|
8c726f2215 | ||
|
|
c2a86a27ce | ||
|
|
6a08ef6f97 | ||
|
|
9c4e20f074 | ||
|
|
5845a9c46a | ||
|
|
7c6693a268 | ||
|
|
05a3f59813 | ||
|
|
47a7a35aa4 | ||
|
|
04b4fe3e3b | ||
|
|
36bb65e4b5 | ||
|
|
8ef36e4f3e | ||
|
|
ab3e280993 | ||
|
|
30562b5749 | ||
|
|
d42a7b65ea | ||
|
|
db9807d12b | ||
|
|
db09fa8168 | ||
|
|
031541bc05 | ||
|
|
084e3428fb | ||
|
|
b321ff02f0 | ||
|
|
c6c6beb40c | ||
|
|
0127ef9f9b | ||
|
|
cd8686ae9c | ||
|
|
233d179bfa | ||
|
|
4e8ae8d9d4 | ||
|
|
8c6810c6dd | ||
|
|
fca411996a | ||
|
|
79b8296e1c | ||
|
|
043cb42149 | ||
|
|
c28f047eb5 | ||
|
|
972cf66d7e | ||
|
|
f1319483ee | ||
|
|
6ad5006280 | ||
|
|
f7e07b7f6b | ||
|
|
4cf26d9c36 | ||
|
|
a848df1235 | ||
|
|
f8292ba512 | ||
|
|
21bcc6e8d7 | ||
|
|
905a77a3a8 | ||
|
|
295f9f78c3 | ||
|
|
04ecc82d09 | ||
|
|
2ddd9e58a3 | ||
|
|
3c52298c47 | ||
|
|
7b385c7d33 | ||
|
|
87d51c54c9 | ||
|
|
210e8eebc5 | ||
|
|
7d52c87173 | ||
|
|
e3c6fb67f2 | ||
|
|
79f187ddd6 | ||
|
|
22f4b0bc9d | ||
|
|
c5ea626d03 | ||
|
|
d067cd1e66 | ||
|
|
76a6be572a | ||
|
|
78db900303 | ||
|
|
7f9a9c4185 | ||
|
|
83c67d3e11 | ||
|
|
4f3324bac4 | ||
|
|
23a28f790a | ||
|
|
dd4983f33e | ||
|
|
e9e5d8eda6 | ||
|
|
a745d471ad | ||
|
|
df267774da | ||
|
|
2b2f385d5f | ||
|
|
86e892c806 | ||
|
|
448133494f | ||
|
|
e0fc9e92ba | ||
|
|
5b4d0c40d8 | ||
|
|
78ea101a43 | ||
|
|
63097e9d82 | ||
|
|
2ebbe762f0 | ||
|
|
63b836b212 | ||
|
|
02da23a2a0 | ||
|
|
b254c7c6b9 | ||
|
|
89adc13201 | ||
|
|
e6e752aea5 | ||
|
|
d590ca58e4 | ||
|
|
e23aaeca5f | ||
|
|
0312c31a42 | ||
|
|
815cbf4ee8 | ||
|
|
6327d36ce9 | ||
|
|
707b8bb062 | ||
|
|
adbff45a23 | ||
|
|
f30cb7a1e6 | ||
|
|
432eb84a94 | ||
|
|
bd31710117 | ||
|
|
e67afefcd8 | ||
|
|
327323da38 | ||
|
|
83e5d6f2a7 | ||
|
|
887f024551 | ||
|
|
8dd6aa38ed | ||
|
|
6f4025eacb | ||
|
|
3cc53fae2c | ||
|
|
25ded686ac | ||
|
|
9b30726805 | ||
|
|
aeb182712c | ||
|
|
18ccee051f | ||
|
|
3f239b4956 | ||
|
|
dc3b6ba6f1 | ||
|
|
ddf9ef66c1 | ||
|
|
b65423f296 | ||
|
|
b3706addbb | ||
|
|
5c6565bd9c | ||
|
|
3e24393e9a | ||
|
|
0e10b7763c | ||
|
|
012f121c48 | ||
|
|
c0cc15679c | ||
|
|
43d83f6398 | ||
|
|
862659b9b7 | ||
|
|
536156a4ec | ||
|
|
aa3536a71a | ||
|
|
dd23c96c1a | ||
|
|
41fbc90ec2 | ||
|
|
3cc3784313 | ||
|
|
489cf01812 | ||
|
|
94fc80a8f0 | ||
|
|
3ad0028785 | ||
|
|
801f88d793 | ||
|
|
0d6b62b7a9 | ||
|
|
00d5d76833 | ||
|
|
85a1b43242 | ||
|
|
800d52279f | ||
|
|
61a6344ffa | ||
|
|
c182e90a1a | ||
|
|
7dc3e12e07 | ||
|
|
ce5e44233e | ||
|
|
22c416e32b | ||
|
|
ff72bf1234 | ||
|
|
a45199059b | ||
|
|
37045e337c | ||
|
|
41e3f91b75 | ||
|
|
efeed5e2cf | ||
|
|
1952a4550f | ||
|
|
9efb5b59e5 | ||
|
|
4b196744f2 | ||
|
|
8e7c736a0a | ||
|
|
1a318b89d9 | ||
|
|
f98697416e | ||
|
|
d66fa56513 | ||
|
|
89c91c19d8 | ||
|
|
5e294b805f | ||
|
|
a7b5f86bcd | ||
|
|
9c236fa256 | ||
|
|
304e32eef5 | ||
|
|
288c622012 | ||
|
|
3827ae1e72 | ||
|
|
9067f8235d | ||
|
|
19928e6b7f | ||
|
|
1df1a76069 | ||
|
|
17e397212d | ||
|
|
39503a21a7 | ||
|
|
ebbc10b2b4 | ||
|
|
9a51cb9ca7 | ||
|
|
5e0ee5077a |
3
.gitignore
vendored
@@ -11,4 +11,5 @@ out
|
||||
.springBeans
|
||||
*.rdb
|
||||
!eclispe/.checkstyle
|
||||
.checkstyle
|
||||
.checkstyle
|
||||
!**/src/**/build
|
||||
|
||||
15
.travis.yml
@@ -1,19 +1,20 @@
|
||||
language: java
|
||||
|
||||
services:
|
||||
- redis-server
|
||||
sudo: required
|
||||
|
||||
jdk:
|
||||
- oraclejdk8
|
||||
services: docker
|
||||
|
||||
os:
|
||||
- linux
|
||||
jdk: oraclejdk8
|
||||
|
||||
before_cache:
|
||||
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
|
||||
- rm -fr $HOME/.gradle/caches/*/plugin-resolution/
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.gradle/caches/
|
||||
- $HOME/.gradle/wrapper/
|
||||
|
||||
script: ./gradlew build
|
||||
install: true
|
||||
|
||||
script: ./gradlew clean build --refresh-dependencies --no-daemon
|
||||
|
||||
88
Jenkinsfile
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
def projectProperties = [
|
||||
[$class: 'BuildDiscarderProperty',
|
||||
strategy: [$class: 'LogRotator', numToKeepStr: '5']],
|
||||
pipelineTriggers([cron('@daily')])
|
||||
]
|
||||
properties(projectProperties)
|
||||
|
||||
def SUCCESS = hudson.model.Result.SUCCESS.toString()
|
||||
currentBuild.result = SUCCESS
|
||||
|
||||
try {
|
||||
parallel check: {
|
||||
stage('Check') {
|
||||
node {
|
||||
checkout scm
|
||||
try {
|
||||
sh "./gradlew clean check --refresh-dependencies --no-daemon"
|
||||
} catch(Exception e) {
|
||||
currentBuild.result = 'FAILED: check'
|
||||
throw e
|
||||
} finally {
|
||||
junit '**/build/*-results/*.xml'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
springio: {
|
||||
stage('Spring IO') {
|
||||
node {
|
||||
checkout scm
|
||||
try {
|
||||
sh "./gradlew clean springIoCheck -PplatformVersion=Cairo-BUILD-SNAPSHOT -PexcludeProjects='**/samples/**' --refresh-dependencies --no-daemon --stacktrace"
|
||||
} catch(Exception e) {
|
||||
currentBuild.result = 'FAILED: springio'
|
||||
throw e
|
||||
} finally {
|
||||
junit '**/build/spring-io*-results/*.xml'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(currentBuild.result == 'SUCCESS') {
|
||||
parallel artifactory: {
|
||||
stage('Artifactory Deploy') {
|
||||
node {
|
||||
checkout scm
|
||||
withCredentials([usernamePassword(credentialsId: '02bd1690-b54f-4c9f-819d-a77cb7a9822c', usernameVariable: 'ARTIFACTORY_USERNAME', passwordVariable: 'ARTIFACTORY_PASSWORD')]) {
|
||||
sh "./gradlew artifactoryPublish -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --no-daemon --stacktrace"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
docs: {
|
||||
stage('Deploy Docs') {
|
||||
node {
|
||||
checkout scm
|
||||
withCredentials([file(credentialsId: 'docs.spring.io-jenkins_private_ssh_key', variable: 'DEPLOY_SSH_KEY')]) {
|
||||
sh "./gradlew deployDocs -PdeployDocsSshKeyPath=$DEPLOY_SSH_KEY -PdeployDocsSshUsername=$SPRING_DOCS_USERNAME --refresh-dependencies --no-daemon --stacktrace"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
def buildStatus = currentBuild.result
|
||||
def buildNotSuccess = !SUCCESS.equals(buildStatus)
|
||||
def lastBuildNotSuccess = !SUCCESS.equals(currentBuild.previousBuild?.result)
|
||||
|
||||
if(buildNotSuccess || lastBuildNotSuccess) {
|
||||
|
||||
stage('Notifiy') {
|
||||
node {
|
||||
final def RECIPIENTS = [[$class: 'DevelopersRecipientProvider'], [$class: 'RequesterRecipientProvider']]
|
||||
|
||||
def subject = "${buildStatus}: Build ${env.JOB_NAME} ${env.BUILD_NUMBER} status is now ${buildStatus}"
|
||||
def details = """The build status changed to ${buildStatus}. For details see ${env.BUILD_URL}"""
|
||||
|
||||
emailext (
|
||||
subject: subject,
|
||||
body: details,
|
||||
recipientProviders: RECIPIENTS,
|
||||
to: "$SPRING_SESSION_TEAM_EMAILS"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
68
build.gradle
@@ -1,74 +1,20 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
maven { url "https://repo.spring.io/plugins-release" }
|
||||
}
|
||||
dependencies {
|
||||
classpath 'io.spring.gradle:dependency-management-plugin:0.6.1.RELEASE'
|
||||
classpath("com.bmuschko:gradle-tomcat-plugin:2.2.5")
|
||||
classpath("org.springframework.build.gradle:propdeps-plugin:0.0.7")
|
||||
classpath("io.spring.gradle:spring-io-plugin:0.0.4.RELEASE")
|
||||
classpath('me.champeau.gradle:gradle-javadoc-hotfix-plugin:0.1')
|
||||
classpath 'org.asciidoctor:asciidoctor-gradle-plugin:1.5.2'
|
||||
classpath 'com.github.ben-manes:gradle-versions-plugin:0.12.0'
|
||||
classpath 'io.spring.gradle:spring-build-conventions:0.0.3.RELEASE'
|
||||
classpath "org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion"
|
||||
}
|
||||
repositories {
|
||||
maven { url 'https://repo.spring.io/plugins-release' }
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id "org.sonarqube" version "2.1"
|
||||
}
|
||||
apply plugin: 'io.spring.convention.root'
|
||||
|
||||
group = 'org.springframework.session'
|
||||
description = 'Spring Session'
|
||||
|
||||
ext.springBootVersion = '1.4.2.RELEASE'
|
||||
ext.IDE_GRADLE = "$rootDir/gradle/ide.gradle"
|
||||
ext.JAVA_GRADLE = "$rootDir/gradle/java.gradle"
|
||||
ext.SPRING3_GRADLE = "$rootDir/gradle/spring3.gradle"
|
||||
ext.MAVEN_GRADLE = "$rootDir/gradle/publish-maven.gradle"
|
||||
ext.BOM_GRADLE = "$rootDir/gradle/bom.gradle"
|
||||
ext.SAMPLE_GRADLE = "$rootDir/gradle/sample.gradle"
|
||||
ext.TOMCAT_GRADLE = "$rootDir/gradle/tomcat.gradle"
|
||||
ext.TOMCAT_6_GRADLE = "$rootDir/gradle/tomcat6.gradle"
|
||||
ext.TOMCAT_7_GRADLE = "$rootDir/gradle/tomcat7.gradle"
|
||||
|
||||
ext.releaseBuild = version.endsWith('RELEASE')
|
||||
ext.snapshotBuild = version.endsWith('SNAPSHOT')
|
||||
ext.milestoneBuild = !(releaseBuild || snapshotBuild)
|
||||
|
||||
apply plugin: 'base'
|
||||
|
||||
|
||||
sonarqube {
|
||||
properties {
|
||||
property "sonar.java.coveragePlugin", "jacoco"
|
||||
property "sonar.projectName", "Spring Session"
|
||||
property "sonar.jacoco.reportPath", "${buildDir.name}/jacoco.exec"
|
||||
property "sonar.links.homepage", 'https://github.com/spring-projects/spring-session'
|
||||
property "sonar.links.ci", 'https://build.spring.io/browse/SESSION'
|
||||
property "sonar.links.issue", 'https://github.com/spring-projects/spring-session/issues'
|
||||
property "sonar.links.scm", 'https://github.com/spring-projects/spring-session'
|
||||
property "sonar.links.scm_dev", 'https://github.com/spring-projects/spring-session.git'
|
||||
property "sonar.java.coveragePlugin", "jacoco"
|
||||
}
|
||||
}
|
||||
|
||||
task configDocsZip(dependsOn: [':docs:asciidoctor',':spring-session:javadoc']) << {
|
||||
project.tasks.docsZip.from(project(':docs').asciidoctor) {
|
||||
into('reference')
|
||||
}
|
||||
project.tasks.docsZip.from(project(':spring-session').javadoc) {
|
||||
into('api')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
task docsZip(type: Zip, dependsOn: 'configDocsZip') {
|
||||
group = "Distribution"
|
||||
baseName = "spring-session"
|
||||
classifier = "docs"
|
||||
description = "Builds -${classifier} archive containing api and reference " +
|
||||
"for deployment."
|
||||
}
|
||||
|
||||
artifacts {
|
||||
archives docsZip
|
||||
}
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
jcenter()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'org.kordamp.gradle:livereload-gradle-plugin:0.2.1'
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'org.kordamp.gradle.livereload'
|
||||
apply from: JAVA_GRADLE
|
||||
apply plugin: 'org.asciidoctor.convert'
|
||||
|
||||
liveReload {
|
||||
docRoot asciidoctor.sourceDir.canonicalPath
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven { url 'http://dist.gemstone.com/maven/release' }
|
||||
}
|
||||
|
||||
asciidoctorj {
|
||||
|
||||
}
|
||||
|
||||
tasks.findByPath("artifactoryPublish")?.enabled = false
|
||||
|
||||
dependencies {
|
||||
testCompile project(':spring-session'),
|
||||
project(':spring-session-data-mongo'),
|
||||
"org.springframework.data:spring-data-gemfire:$springDataGemFireVersion",
|
||||
"org.springframework.data:spring-data-redis:$springDataRedisVersion",
|
||||
"org.springframework.data:spring-data-gemfire:$springDataGemFireVersion",
|
||||
"org.springframework:spring-webmvc:${springVersion}",
|
||||
"org.springframework:spring-websocket:${springVersion}",
|
||||
"org.springframework:spring-messaging:${springVersion}",
|
||||
"org.springframework:spring-jdbc:${springVersion}",
|
||||
"org.springframework.security:spring-security-config:${springSecurityVersion}",
|
||||
"org.springframework.security:spring-security-web:${springSecurityVersion}",
|
||||
"org.springframework.security:spring-security-test:${springSecurityVersion}",
|
||||
"junit:junit:$junitVersion",
|
||||
"org.mockito:mockito-core:$mockitoVersion",
|
||||
"org.springframework:spring-test:$springVersion",
|
||||
"org.assertj:assertj-core:$assertjVersion",
|
||||
"com.hazelcast:hazelcast:$hazelcastVersion",
|
||||
"biz.paluch.redis:lettuce:$lettuceVersion",
|
||||
"javax.servlet:javax.servlet-api:$servletApiVersion"
|
||||
}
|
||||
|
||||
asciidoctor {
|
||||
def ghTag = snapshotBuild ? 'master' : project.version
|
||||
def ghUrl = "https://github.com/spring-projects/spring-session/tree/$ghTag/"
|
||||
attributes 'version-snapshot': snapshotBuild,
|
||||
'version-milestone': milestoneBuild,
|
||||
'version-release': releaseBuild,
|
||||
'gh-url': ghUrl,
|
||||
'gh-samples-url': "$ghUrl/samples/",
|
||||
'download-url' : "https://github.com/spring-projects/spring-session/archive/${ghTag}.zip",
|
||||
'spring-session-version' : version,
|
||||
'spring-version' : springVersion,
|
||||
'lettuce-version' : lettuceVersion,
|
||||
'hazelcast-version' : hazelcastVersion,
|
||||
'docs-itest-dir' : rootProject.projectDir.path + '/docs/src/integration-test/java/',
|
||||
'docs-test-dir' : rootProject.projectDir.path + '/docs/src/test/java/',
|
||||
'docs-test-resources-dir' : rootProject.projectDir.path + '/docs/src/test/resources/',
|
||||
'samples-dir' : rootProject.projectDir.path + '/samples/',
|
||||
'session-main-resources-dir' : rootProject.projectDir.path + '/spring-session/src/main/resources/',
|
||||
|
||||
'source-highlighter' : 'coderay',
|
||||
'imagesdir':'./images',
|
||||
'icons': 'font',
|
||||
'sectanchors':'',
|
||||
'idprefix':'',
|
||||
'idseparator':'-',
|
||||
'docinfo1':'true',
|
||||
'revnumber' : project.version
|
||||
}
|
||||
45
docs/spring-session-docs.gradle
Normal file
@@ -0,0 +1,45 @@
|
||||
apply plugin: 'io.spring.convention.docs'
|
||||
apply plugin: 'io.spring.convention.spring-test'
|
||||
|
||||
dependencies {
|
||||
testCompile project(':spring-session-core')
|
||||
testCompile project(':spring-session-data-redis')
|
||||
testCompile project(':spring-session-hazelcast')
|
||||
testCompile project(':spring-session-jdbc')
|
||||
testCompile "org.springframework:spring-jdbc"
|
||||
testCompile "org.springframework:spring-messaging"
|
||||
testCompile "org.springframework:spring-webmvc"
|
||||
testCompile "org.springframework:spring-websocket"
|
||||
testCompile "org.springframework.security:spring-security-config"
|
||||
testCompile "org.springframework.security:spring-security-web"
|
||||
testCompile "org.springframework.security:spring-security-test"
|
||||
testCompile "junit:junit"
|
||||
testCompile "org.mockito:mockito-core"
|
||||
testCompile "org.springframework:spring-test"
|
||||
testCompile "org.assertj:assertj-core"
|
||||
testCompile "com.hazelcast:hazelcast"
|
||||
testCompile "io.lettuce:lettuce-core"
|
||||
testCompile "javax.servlet:javax.servlet-api"
|
||||
}
|
||||
|
||||
def versions = dependencyManagement.managedVersions
|
||||
|
||||
asciidoctor {
|
||||
def ghTag = snapshotBuild ? 'master' : project.version
|
||||
def ghUrl = "https://github.com/spring-projects/spring-session/tree/$ghTag"
|
||||
attributes 'version-snapshot': snapshotBuild,
|
||||
'version-milestone': milestoneBuild,
|
||||
'version-release': releaseBuild,
|
||||
'gh-url': ghUrl,
|
||||
'gh-samples-url': "$ghUrl/samples/",
|
||||
'download-url' : "https://github.com/spring-projects/spring-session/archive/${ghTag}.zip",
|
||||
'spring-session-version' : version,
|
||||
'spring-version' : versions['org.springframework:spring-core'],
|
||||
'lettuce-version' : versions['io.lettuce:lettuce-core'],
|
||||
'hazelcast-version' : versions['com.hazelcast:hazelcast'],
|
||||
'docs-itest-dir' : rootProject.projectDir.path + '/docs/src/integration-test/java/',
|
||||
'docs-test-dir' : rootProject.projectDir.path + '/docs/src/test/java/',
|
||||
'docs-test-resources-dir' : rootProject.projectDir.path + '/docs/src/test/resources/',
|
||||
'samples-dir' : rootProject.projectDir.path + '/samples/',
|
||||
'session-jdbc-main-resources-dir' : project(':spring-session-jdbc').projectDir.path + '/src/main/resources/'
|
||||
}
|
||||
@@ -65,7 +65,7 @@ For example, our sample application includes the location and access type of the
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::{samples-dir}findbyusername/src/main/java/sample/session/SessionDetails.java[tags=class]
|
||||
include::{samples-dir}boot/findbyusername/src/main/java/sample/session/SessionDetails.java[tags=class]
|
||||
----
|
||||
|
||||
We then inject that information into the session on each HTTP request using a `SessionDetailsFilter`.
|
||||
@@ -73,7 +73,7 @@ For example:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::{samples-dir}findbyusername/src/main/java/sample/session/SessionDetailsFilter.java[tags=dofilterinternal]
|
||||
include::{samples-dir}boot/findbyusername/src/main/java/sample/session/SessionDetailsFilter.java[tags=dofilterinternal]
|
||||
----
|
||||
|
||||
We obtain the information we want and then set the `SessionDetails` as an attribute in the `Session`.
|
||||
@@ -93,7 +93,7 @@ We can now find all the sessions for a specific user.
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::{samples-dir}findbyusername/src/main/java/sample/mvc/IndexController.java[tags=findbyusername]
|
||||
include::{samples-dir}boot/findbyusername/src/main/java/sample/mvc/IndexController.java[tags=findbyusername]
|
||||
----
|
||||
|
||||
In our instance, we find all sessions for the currently logged in user.
|
||||
@@ -110,6 +110,7 @@ You can run the sample by obtaining the {download-url}[source code] and invoking
|
||||
====
|
||||
For the sample to work, you must http://redis.io/download[install Redis 2.8+] on localhost and run it with the default port (6379).
|
||||
Alternatively, you can update the `JedisConnectionFactory` to point to a Redis server.
|
||||
Another option is to use https://www.docker.com/[Docker] to run Redis on localhost. See https://hub.docker.com/_/redis/[Docker Redis repository] for detailed instructions.
|
||||
====
|
||||
|
||||
----
|
||||
@@ -21,65 +21,40 @@ If you are using Maven, ensure to add the following dependencies:
|
||||
<dependency>
|
||||
<groupId>org.springframework.session</groupId>
|
||||
<artifactId>spring-session-jdbc</artifactId>
|
||||
<version>{spring-session-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-jdbc</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
----
|
||||
|
||||
ifeval::["{version-snapshot}" == "true"]
|
||||
Since we are using a SNAPSHOT version, we need to ensure to add the Spring Snapshot Maven Repository.
|
||||
Ensure you have the following in your pom.xml:
|
||||
|
||||
.pom.xml
|
||||
[source,xml]
|
||||
----
|
||||
<repositories>
|
||||
|
||||
<!-- ... -->
|
||||
|
||||
<repository>
|
||||
<id>spring-snapshot</id>
|
||||
<url>https://repo.spring.io/libs-snapshot</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
----
|
||||
endif::[]
|
||||
|
||||
ifeval::["{version-milestone}" == "true"]
|
||||
Since We are using a Milestone version, we need to ensure to add the Spring Milestone Maven Repository.
|
||||
Ensure you have the following in your pom.xml:
|
||||
|
||||
.pom.xml
|
||||
[source,xml]
|
||||
----
|
||||
<repository>
|
||||
<id>spring-milestone</id>
|
||||
<url>https://repo.spring.io/libs-milestone</url>
|
||||
</repository>
|
||||
----
|
||||
endif::[]
|
||||
Spring Boot provides dependency management for Spring Session modules, so there's no need to explicitly declare dependency version.
|
||||
|
||||
// tag::config[]
|
||||
|
||||
[[httpsession-jdbc-boot-spring-configuration]]
|
||||
== Spring Configuration
|
||||
== Spring Boot Configuration
|
||||
|
||||
After adding the required dependencies, we can create our Spring configuration.
|
||||
The Spring configuration is responsible for creating a Servlet Filter that replaces the `HttpSession` implementation with an implementation backed by Spring Session.
|
||||
Add the following Spring Configuration:
|
||||
After adding the required dependencies, we can create our Spring Boot configuration.
|
||||
Thanks to first-class auto configuration support, setting up Spring Session backed by a relational database is as simple as adding a single configuration property to your `application.properties`:
|
||||
|
||||
[source,java]
|
||||
.src/main/resources/application.properties
|
||||
----
|
||||
include::{samples-dir}httpsession-jdbc-boot/src/main/java/sample/config/HttpSessionConfig.java[tags=class]
|
||||
spring.session.store-type=jdbc
|
||||
----
|
||||
|
||||
<1> The `@EnableJdbcHttpSession` annotation creates a Spring Bean with the name of `springSessionRepositoryFilter` that implements Filter.
|
||||
Under the hood, Spring Boot will apply configuration that is equivalent to manually adding `@EnableJdbcHttpSession` annotation.
|
||||
This creates a Spring Bean with the name of `springSessionRepositoryFilter` that implements Filter.
|
||||
The filter is what is in charge of replacing the `HttpSession` implementation to be backed by Spring Session.
|
||||
In this instance Spring Session is backed by a relational database.
|
||||
|
||||
Further customization is possible using `application.properties`:
|
||||
|
||||
.src/main/resources/application.properties
|
||||
----
|
||||
server.session.timeout= # Session timeout in seconds.
|
||||
spring.session.jdbc.initializer.enabled= # Create the required session tables on startup if necessary. Enabled automatically if the default table name is set or a custom schema is configured.
|
||||
spring.session.jdbc.schema=classpath:org/springframework/session/jdbc/schema-@@platform@@.sql # Path to the SQL file to use to initialize the database schema.
|
||||
spring.session.jdbc.table-name=SPRING_SESSION # Name of database table used to store sessions.
|
||||
----
|
||||
|
||||
For more information, refer to http://docs.spring.io/spring-boot/docs/{spring-boot-version}/reference/htmlsingle/#boot-features-session[Spring Session] portion of the Spring Boot documentation.
|
||||
|
||||
[[httpsession-jdbc-boot-configuration]]
|
||||
== Configuring the DataSource
|
||||
@@ -95,12 +70,12 @@ spring.datasource.username=myapp
|
||||
spring.datasource.password=secret
|
||||
----
|
||||
|
||||
For more information, refer to http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-configure-datasource[Configure a DataSource] portion of the Spring Boot documentation.
|
||||
For more information, refer to http://docs.spring.io/spring-boot/docs/{spring-boot-version}/reference/htmlsingle/#boot-features-configure-datasource[Configure a DataSource] portion of the Spring Boot documentation.
|
||||
|
||||
[[httpsession-jdbc-boot-servlet-configuration]]
|
||||
== Servlet Container Initialization
|
||||
|
||||
Our <<httpsession-jdbc-boot-spring-configuration,Spring Configuration>> created a Spring Bean named `springSessionRepositoryFilter` that implements `Filter`.
|
||||
Our <<httpsession-jdbc-boot-spring-configuration,Spring Boot Configuration>> created a Spring Bean named `springSessionRepositoryFilter` that implements `Filter`.
|
||||
The `springSessionRepositoryFilter` bean is responsible for replacing the `HttpSession` with a custom implementation that is backed by Spring Session.
|
||||
|
||||
In order for our `Filter` to do its magic, Spring needs to load our `Config` class.
|
||||
@@ -1,5 +1,5 @@
|
||||
= Spring Session - Spring Boot
|
||||
Rob Winch
|
||||
Rob Winch, Vedran Pavić
|
||||
:toc:
|
||||
|
||||
This guide describes how to use Spring Session to transparently leverage Redis to back a web application's `HttpSession` when using Spring Boot.
|
||||
@@ -20,64 +20,38 @@ If you are using Maven, ensure to add the following dependencies:
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.session</groupId>
|
||||
<artifactId>spring-session</artifactId>
|
||||
<version>{spring-session-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-redis</artifactId>
|
||||
<artifactId>spring-session-data-redis</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
----
|
||||
|
||||
ifeval::["{version-snapshot}" == "true"]
|
||||
Since We are using a SNAPSHOT version, we need to ensure to add the Spring Snapshot Maven Repository.
|
||||
Ensure you have the following in your pom.xml:
|
||||
|
||||
.pom.xml
|
||||
[source,xml]
|
||||
----
|
||||
<repositories>
|
||||
|
||||
<!-- ... -->
|
||||
|
||||
<repository>
|
||||
<id>spring-snapshot</id>
|
||||
<url>https://repo.spring.io/libs-snapshot</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
----
|
||||
endif::[]
|
||||
|
||||
ifeval::["{version-milestone}" == "true"]
|
||||
Since We are using a Milestone version, we need to ensure to add the Spring Milestone Maven Repository.
|
||||
Ensure you have the following in your pom.xml:
|
||||
|
||||
.pom.xml
|
||||
[source,xml]
|
||||
----
|
||||
<repository>
|
||||
<id>spring-milestone</id>
|
||||
<url>https://repo.spring.io/libs-milestone</url>
|
||||
</repository>
|
||||
----
|
||||
endif::[]
|
||||
Spring Boot provides dependency management for Spring Session modules, so there's no need to explicitly declare dependency version.
|
||||
|
||||
[[boot-spring-configuration]]
|
||||
== Spring Configuration
|
||||
== Spring Boot Configuration
|
||||
|
||||
After adding the required dependencies, we can create our Spring configuration.
|
||||
The Spring configuration is responsible for creating a Servlet Filter that replaces the `HttpSession` implementation with an implementation backed by Spring Session.
|
||||
Add the following Spring Configuration:
|
||||
After adding the required dependencies, we can create our Spring Boot configuration.
|
||||
Thanks to first-class auto configuration support, setting up Spring Session backed by Redis is as simple as adding a single configuration property to your `application.properties`:
|
||||
|
||||
[source,java]
|
||||
.src/main/resources/application.properties
|
||||
----
|
||||
include::{samples-dir}boot/src/main/java/sample/config/HttpSessionConfig.java[tags=class]
|
||||
spring.session.store-type=redis
|
||||
----
|
||||
|
||||
<1> The `@EnableRedisHttpSession` annotation creates a Spring Bean with the name of `springSessionRepositoryFilter` that implements Filter.
|
||||
Under the hood, Spring Boot will apply configuration that is equivalent to manually adding `@EnableRedisHttpSession` annotation.
|
||||
This creates a Spring Bean with the name of `springSessionRepositoryFilter` that implements Filter.
|
||||
The filter is what is in charge of replacing the `HttpSession` implementation to be backed by Spring Session.
|
||||
In this instance Spring Session is backed by Redis.
|
||||
|
||||
Further customization is possible using `application.properties`:
|
||||
|
||||
.src/main/resources/application.properties
|
||||
----
|
||||
server.session.timeout= # Session timeout in seconds.
|
||||
spring.session.redis.flush-mode= # Sessions flush mode.
|
||||
spring.session.redis.namespace= # Namespace for keys used to store sessions.
|
||||
----
|
||||
|
||||
For more information, refer to http://docs.spring.io/spring-boot/docs/{spring-boot-version}/reference/htmlsingle/#boot-features-session[Spring Session] portion of the Spring Boot documentation.
|
||||
|
||||
[[boot-redis-configuration]]
|
||||
== Configuring the Redis Connection
|
||||
@@ -93,12 +67,12 @@ spring.redis.password=secret
|
||||
spring.redis.port=6379
|
||||
----
|
||||
|
||||
For more information, refer to http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-connecting-to-redis[Connecting to Redis] portion of the Spring Boot documentation.
|
||||
For more information, refer to http://docs.spring.io/spring-boot/docs/{spring-boot-version}/reference/htmlsingle/#boot-features-connecting-to-redis[Connecting to Redis] portion of the Spring Boot documentation.
|
||||
|
||||
[[boot-servlet-configuration]]
|
||||
== Servlet Container Initialization
|
||||
|
||||
Our <<boot-spring-configuration,Spring Configuration>> created a Spring Bean named `springSessionRepositoryFilter` that implements `Filter`.
|
||||
Our <<boot-spring-configuration,Spring Boot Configuration>> created a Spring Bean named `springSessionRepositoryFilter` that implements `Filter`.
|
||||
The `springSessionRepositoryFilter` bean is responsible for replacing the `HttpSession` with a custom implementation that is backed by Spring Session.
|
||||
|
||||
In order for our `Filter` to do its magic, Spring needs to load our `Config` class.
|
||||
@@ -106,12 +80,12 @@ Last we need to ensure that our Servlet Container (i.e. Tomcat) uses our `spring
|
||||
Fortunately, Spring Boot takes care of both of these steps for us.
|
||||
|
||||
[[boot-sample]]
|
||||
== boot Sample Application
|
||||
== Boot Sample Application
|
||||
|
||||
The boot Sample Application demonstrates how to use Spring Session to transparently leverage Redis to back a web application's `HttpSession` when using Spring Boot.
|
||||
The Boot Sample Application demonstrates how to use Spring Session to transparently leverage Redis to back a web application's `HttpSession` when using Spring Boot.
|
||||
|
||||
[[boot-running]]
|
||||
=== Running the boot Sample Application
|
||||
=== Running the Boot Sample Application
|
||||
|
||||
You can run the sample by obtaining the {download-url}[source code] and invoking the following command:
|
||||
|
||||
@@ -119,6 +93,7 @@ You can run the sample by obtaining the {download-url}[source code] and invoking
|
||||
====
|
||||
For the sample to work, you must http://redis.io/download[install Redis 2.8+] on localhost and run it with the default port (6379).
|
||||
Alternatively, you can update the `JedisConnectionFactory` to point to a Redis server.
|
||||
Another option is to use https://www.docker.com/[Docker] to run Redis on localhost. See https://hub.docker.com/_/redis/[Docker Redis repository] for detailed instructions.
|
||||
====
|
||||
|
||||
----
|
||||
@@ -38,7 +38,7 @@ For example:
|
||||
.src/main/java/samples/config/WebSocketConfig.java
|
||||
[source,java]
|
||||
----
|
||||
include::{samples-dir}websocket/src/main/java/sample/config/WebSocketConfig.java[tags=class]
|
||||
include::{samples-dir}boot/websocket/src/main/java/sample/config/WebSocketConfig.java[tags=class]
|
||||
----
|
||||
|
||||
To hook in the Spring Session support we only need to change two things:
|
||||
@@ -73,12 +73,11 @@ You can run the sample by obtaining the {download-url}[source code] and invoking
|
||||
|
||||
[TIP]
|
||||
====
|
||||
For the purposes of testing session expiration, you may want to change the session expiration to be 1 minute (default is 30 minutes) by removing the comment from the following file before starting the application:
|
||||
For the purposes of testing session expiration, you may want to change the session expiration to be 1 minute (default is 30 minutes) by adding the following configuration property starting before the application:
|
||||
|
||||
.src/main/java/samples/config/WebSecurityConfig.java
|
||||
[source,java]
|
||||
.src/main/resources/application.properties
|
||||
----
|
||||
include::{samples-dir}websocket/src/main/java/sample/config/WebSecurityConfig.java[tags=enable-redis-httpsession]
|
||||
server.session.timeout=60
|
||||
----
|
||||
====
|
||||
|
||||
@@ -86,6 +85,7 @@ include::{samples-dir}websocket/src/main/java/sample/config/WebSecurityConfig.ja
|
||||
====
|
||||
For the sample to work, you must http://redis.io/download[install Redis 2.8+] on localhost and run it with the default port (6379).
|
||||
Alternatively, you can update the `LettuceConnectionFactory` to point to a Redis server.
|
||||
Another option is to use https://www.docker.com/[Docker] to run Redis on localhost. See https://hub.docker.com/_/redis/[Docker Redis repository] for detailed instructions.
|
||||
====
|
||||
|
||||
----
|
||||
@@ -86,6 +86,7 @@ You can run the sample by obtaining the {download-url}[source code] and invoking
|
||||
====
|
||||
For the sample to work, you must http://redis.io/download[install Redis 2.8+] on localhost and run it with the default port (6379).
|
||||
Alternatively, you can update the `JedisConnectionFactory` to point to a Redis server.
|
||||
Another option is to use https://www.docker.com/[Docker] to run Redis on localhost. See https://hub.docker.com/_/redis/[Docker Redis repository] for detailed instructions.
|
||||
====
|
||||
|
||||
----
|
||||
@@ -127,3 +128,6 @@ Alternatively, you can also delete the explicit key. Enter the following into yo
|
||||
$ redis-cli del spring:session:sessions:7e8383a4-082c-4ffe-a4bc-c40fd3363c5e
|
||||
|
||||
Now visit the application at http://localhost:8080/test/index and observe that we are no longer authenticated.
|
||||
|
||||
NOTE: Spring Session will not work with grails flash scope without additional work. +
|
||||
See this answer for an explanation: https://stackoverflow.com/a/43311427
|
||||
|
||||
@@ -1,267 +0,0 @@
|
||||
= Spring Session - HttpSession with GemFire Client/Server using Spring Boot
|
||||
John Blum
|
||||
:toc:
|
||||
|
||||
This guide describes how to build a _Spring Boot_ application configured with _Spring Session_ to transparently leverage
|
||||
Pivotal GemFire to back a web application's `HttpSession`.
|
||||
|
||||
In this sample, GemFire's client/server topology is employed using a pair of _Spring Boot_ applications, one to
|
||||
configure and run a GemFire Server and another to configure and run the client, Spring MVC-based web application
|
||||
making use of the `HttpSession`.
|
||||
|
||||
NOTE: The completed guide can be found in the <<httpsession-gemfire-boot-sample,HttpSession with GemFire using Spring Boot Sample Application>>.
|
||||
|
||||
== Updating Dependencies
|
||||
Before using _Spring Session_, you must ensure that the required dependencies are included.
|
||||
If you are using Maven, include the following `dependencies` in your _pom.xml_:
|
||||
|
||||
.pom.xml
|
||||
[source,xml]
|
||||
[subs="verbatim,attributes"]
|
||||
----
|
||||
<dependencies>
|
||||
<!-- ... -->
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.session</groupId>
|
||||
<artifactId>spring-session-data-gemfire</artifactId>
|
||||
<version>{spring-session-version}</version>
|
||||
<type>pom</type>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
----
|
||||
|
||||
ifeval::["{version-snapshot}" == "true"]
|
||||
Since we are using a SNAPSHOT version, we need to add the Spring Snapshot Maven Repository.
|
||||
If you are using Maven, include the following `repository` declaration in your _pom.xml_:
|
||||
|
||||
.pom.xml
|
||||
[source,xml]
|
||||
----
|
||||
<repositories>
|
||||
<!-- ... -->
|
||||
|
||||
<repository>
|
||||
<id>spring-snapshot</id>
|
||||
<url>https://repo.spring.io/libs-snapshot</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
----
|
||||
endif::[]
|
||||
|
||||
ifeval::["{version-milestone}" == "true"]
|
||||
Since we are using a Milestone version, we need to add the Spring Milestone Maven Repository.
|
||||
If you are using Maven, include the following `repository` declaration in your _pom.xml_:
|
||||
|
||||
.pom.xml
|
||||
[source,xml]
|
||||
----
|
||||
<repositories>
|
||||
<!-- ... -->
|
||||
|
||||
<repository>
|
||||
<id>spring-milestone</id>
|
||||
<url>https://repo.spring.io/libs-milestone</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
----
|
||||
endif::[]
|
||||
|
||||
// tag::config[]
|
||||
[[httpsession-spring-java-configuration]]
|
||||
== Spring Boot Configuration
|
||||
|
||||
After adding the required dependencies and repository declarations, we can create our Spring configuration
|
||||
for both the GemFire client and server using _Spring Boot_. The Spring configuration is responsible for
|
||||
creating a Servlet Filter that replaces the `HttpSession` with an implementation backed by _Spring Session_
|
||||
and GemFire.
|
||||
|
||||
=== Spring Boot-based GemFire Server
|
||||
|
||||
We start with the _Spring Boot_ application for configuring and bootstrapping a GemFire Server process...
|
||||
|
||||
[source,java]
|
||||
----
|
||||
include::{samples-dir}httpsession-gemfire-boot/src/main/java/sample/server/GemFireServer.java[tags=class]
|
||||
----
|
||||
|
||||
<1> The `@EnableGemFireHttpSession` annotation is used on the GemFire Server to mainly define the corresponding
|
||||
Region (e.g. `ClusteredSpringSessions`, the default) in which Session state information will be stored
|
||||
and managed by GemFire. As well, we have specified an arbitrary expiration attribute (i.e. `maxInactiveIntervalInSeconds`)
|
||||
for when the Session will timeout, which is triggered by a GemFire Region entry expiration event that also invalidates
|
||||
the Session object in the Region.
|
||||
<2> Next, we define a few `Properties` that allow us to configure certain aspects of the GemFire Server using
|
||||
http://gemfire.docs.pivotal.io/docs-gemfire/reference/topics/gemfire_properties.html[GemFire's System properties].
|
||||
<3> Then, we create an instance of the GemFire `Cache` using our defined `Properties`.
|
||||
<4> Finally, we configure and start a `CacheServer` running in the GemFire Server to listen for connections
|
||||
from cache clients. The `CacheServer's` `Socket` will be used to connect our GemFire cache client,
|
||||
_Spring Boot_ web application to the server.
|
||||
|
||||
The sample also makes use of a `PropertySourcesPlaceholderConfigurer` bean in order to externalize the sample application
|
||||
configuration to affect GemFire and application configuration/behavior from the command-line (e.g. such as GemFire's
|
||||
`log-level` using the `gemfire.log.level` System property; more details below).
|
||||
|
||||
=== Spring Boot-based GemFire cache client Web application
|
||||
|
||||
Now, we create our _Spring Boot_ Web application exposing our Web service with Spring MVC, running as a
|
||||
GemFire cache client connected to our _Spring Boot_-based GemFire Server, using Spring Session backed by GemFire
|
||||
to manage Session state in a clustered, replicated fashion.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
include::{samples-dir}httpsession-gemfire-boot/src/main/java/sample/client/Application.java[tags=class]
|
||||
----
|
||||
|
||||
<1> Here, again, we use the `@EnableGemFireHttpSession` annotation to not only configure the GemFire cache client,
|
||||
but to also override the (HTTP) Web application container's `HttpSession` and replace it with a Session implementation
|
||||
backed by _Spring Session_ and GemFire. Also notice, we did not define any Session expiration timeout with the
|
||||
`maxInactiveIntervalInSeconds` attribute this time. That is because the Session expiration is managed by GemFire,
|
||||
on the server, which will appropriately notify the cache client when the Session times out. Again, we have just
|
||||
resorted to using the default named Region, `ClusteredSpringSessions`. Of course, we can change the Region name,
|
||||
but we must do so on both the client and the server. That is a GemFire requirement, not a
|
||||
_Spring Session Data GemFire_ requirement.
|
||||
<2> Similarly to the server configuration, we set a few basic GemFire System `Properties` on the client.
|
||||
<3> Although, this time, an instance of `ClientCache` is created with the `ClientCacheFactoryBean`
|
||||
from _Spring Data GemFire_.
|
||||
<4> However, in order to connect to the GemFire Server we must define a GemFire `Pool` bean containing a
|
||||
pool of connections to the server. Whenever a client Region entry operation corresponding to a Session update occurs,
|
||||
the client-side Region will use an existing, pooled connection to route the operation to the server.
|
||||
<5> The following _Spring_ `BeanPostProcessor` (along with some utility methods) are only needed for testing purposes
|
||||
and are not required by any production code. Specifically, the `BeanPostProcessor` along with the code referenced in *6*
|
||||
is useful in integration test cases where the client and server processes are forked by the test framework. It is pretty
|
||||
easy to figure out that a race condition is imminent without proper coordination between the client and the server,
|
||||
therefore, the BPP and `ClientMembershipListener` help sync the interaction between the client and the server
|
||||
on startup during automated testing.
|
||||
<6> Navigates the Web application to the home page (`index.html`), which uses **Thymeleaf** templates for server-side
|
||||
pages.
|
||||
<7> Heartbeat Web service endpoint (useful for manual testing purposes).
|
||||
<8> Web service endpoint allowing the user to add a Session attribute using the Web application UI. In addition,
|
||||
the webapp stores an additional Session attribute (`requestCount`) to keep track of how many HTTP requests the user
|
||||
has sent during the current "session".
|
||||
|
||||
There are many other utility methods, so please refer to the actual source code for full details.
|
||||
|
||||
TIP: In typical GemFire deployments, where the cluster includes potentially hundreds or thousands of GemFire data nodes
|
||||
(servers), it is more common for clients to connect to one or more GemFire Locators running in the cluster. A Locator
|
||||
passes meta-data to clients about the servers available, their load and which servers have the client's data of interest,
|
||||
which is particularly important in direct, single-hop data access and latency-sensitive operations. See more details
|
||||
about the http://gemfire.docs.pivotal.io/docs-gemfire/latest/topologies_and_comm/cs_configuration/chapter_overview.html[Client/Server Topology in GemFire's User Guide].
|
||||
|
||||
NOTE: For more information on configuring _Spring Data GemFire_, refer to the http://docs.spring.io/spring-data-gemfire/docs/current/reference/html/[reference guide].
|
||||
|
||||
The `@EnableGemFireHttpSession` annotation enables a developer to configure certain aspects of both _Spring Session_
|
||||
and GemFire out-of-the-box using the following attributes:
|
||||
|
||||
* `maxInactiveIntervalInSeconds` - controls _HttpSession_ idle-timeout expiration (defaults to **30 minutes**).
|
||||
* `regionName` - specifies the name of the GemFire Region used to store `HttpSession` state (defaults is "*ClusteredSpringSessions*").
|
||||
* `clientRegionShort` - specifies GemFire's http://gemfire.docs.pivotal.io/docs-gemfire/latest/developing/management_all_region_types/chapter_overview.html[data management policy]
|
||||
with a GemFire http://gemfire.docs.pivotal.io/docs-gemfire/latest/javadocs/japi/com/gemstone/gemfire/cache/client/ClientRegionShortcut.html[ClientRegionShortcut]
|
||||
(default is `PROXY`). This attribute is only used when configuring client Region.
|
||||
* `poolName` - name of the dedicated GemFire Pool used to connect a client to the cluster of servers. The attribute
|
||||
is only used when the application is a GemFire cache client. Defaults to `gemfirePool`.
|
||||
* `serverRegionShort` - specifies GemFire's http://gemfire.docs.pivotal.io/docs-gemfire/latest/developing/management_all_region_types/chapter_overview.html[data management policy]
|
||||
using a GemFire http://data-docs-samples.cfapps.io/docs-gemfire/latest/javadocs/japi/com/gemstone/gemfire/cache/RegionShortcut.html[RegionShortcut]
|
||||
(default is `PARTITION`). This attribute is only used when configuring server Regions, or when a p2p topology is employed.
|
||||
|
||||
NOTE: It is important to remember that the GemFire client Region name must match a server Region by the same name if
|
||||
the client Region is a `PROXY` or `CACHING_PROXY`. Client and server Region names are not required to match if
|
||||
the client Region used to store Spring Sessions is `LOCAL`. However, keep in mind that your session state will not
|
||||
be propagated to the server and you lose all the benefits of using GemFire to store and manage distributed, replicated
|
||||
session state information in a cluster.
|
||||
|
||||
[[httpsession-gemfire-boot-sample]]
|
||||
== HttpSession with GemFire using Spring Boot Sample Application
|
||||
|
||||
=== Running the httpsession-gemfire-boot Sample Application
|
||||
|
||||
You can run the sample by obtaining the {download-url}[source code] and invoking the following commands.
|
||||
|
||||
First, you must run the server:
|
||||
|
||||
----
|
||||
$ ./gradlew :samples:httpsession-gemfire-boot:run [-Dgemfire.log-level=config]
|
||||
----
|
||||
|
||||
Then, in a separate terminal, run the client:
|
||||
|
||||
----
|
||||
$ ./gradlew :samples:httpsession-gemfire-boot:bootRun [-Dgemfire.log-level=config]
|
||||
----
|
||||
|
||||
You should now be able to access the application at http://localhost:8080/. In this sample, the web application
|
||||
is the client cache and the server is standalone.
|
||||
|
||||
=== Exploring the httpsession-gemfire-boot Sample Application
|
||||
|
||||
Try using the application. Fill out the form with the following information:
|
||||
|
||||
* **Attribute Name:** _username_
|
||||
* **Attribute Value:** _test_
|
||||
|
||||
Now click the **Set Attribute** button. You should now see the attribute name and value displayed in the table
|
||||
along with an additional attribute (`requestCount`) indicating the number of Session interactions (via HTTP requests).
|
||||
|
||||
=== How does it work?
|
||||
|
||||
We interact with the standard `HttpSession` in the the Spring MVC web service endpoint, shown here for convenience:
|
||||
|
||||
.src/main/java/sample/SessionServlet.java
|
||||
[source,java]
|
||||
----
|
||||
@RequestMapping(method = RequestMethod.POST, path = "/session")
|
||||
public String session(HttpSession session, ModelMap modelMap,
|
||||
@RequestParam(name = "attributeName", required = false) String name,
|
||||
@RequestParam(name = "attributeValue", required = false) String value) {
|
||||
|
||||
modelMap.addAttribute("sessionAttributes",
|
||||
attributes(setAttribute(updateRequestCount(session), name, value)));
|
||||
|
||||
return INDEX_TEMPLATE_VIEW_NAME;
|
||||
}
|
||||
----
|
||||
|
||||
Instead of using the embedded HTTP server's `HttpSession`, we are actually persisting the Session state in GemFire.
|
||||
_Spring Session_ creates a cookie named SESSION in your browser that contains the id of your session.
|
||||
Go ahead and view the cookies (click for help with https://developer.chrome.com/devtools/docs/resources#cookies[Chrome]
|
||||
or https://getfirebug.com/wiki/index.php/Cookies_Panel#Cookies_List[Firefox]).
|
||||
|
||||
NOTE: The following instructions assume you have a local GemFire installation. For more information on installation,
|
||||
see http://gemfire.docs.pivotal.io/docs-gemfire/latest/getting_started/installation/install_intro.html[Installing Pivotal GemFire].
|
||||
|
||||
If you like, you can easily remove the session using `gfsh`. For example, on a Linux-based system type the following
|
||||
at the command-line:
|
||||
|
||||
$ gfsh
|
||||
|
||||
Then, enter the following commands in _Gfsh_ ensuring to replace `70002719-3c54-4c20-82c3-e7faa6b718f3` with the value
|
||||
of your SESSION cookie, or the session ID returned by the GemFire OQL query (which should match):
|
||||
|
||||
....
|
||||
gfsh>connect --jmx-manager=localhost[1099]
|
||||
|
||||
gfsh>query --query='SELECT * FROM /ClusteredSpringSessions.keySet'
|
||||
|
||||
Result : true
|
||||
startCount : 0
|
||||
endCount : 20
|
||||
Rows : 1
|
||||
|
||||
Result
|
||||
------------------------------------
|
||||
70002719-3c54-4c20-82c3-e7faa6b718f3
|
||||
|
||||
NEXT_STEP_NAME : END
|
||||
|
||||
gfsh>remove --region=/ClusteredSpringSessions --key="70002719-3c54-4c20-82c3-e7faa6b718f3"
|
||||
....
|
||||
|
||||
NOTE: The _GemFire User Guide_ has more detailed instructions on using http://gemfire.docs.pivotal.io/docs-gemfire/latest/tools_modules/gfsh/chapter_overview.html[gfsh].
|
||||
|
||||
Now visit the application at http://localhost:8080/ again and observe that the attribute we added is no longer displayed.
|
||||
|
||||
Alternatively, you can wait **20 seconds** for the session to expire and timeout, and then refresh the page. The attribute
|
||||
we added should no longer be displayed in the table.
|
||||
@@ -1,273 +0,0 @@
|
||||
= Spring Session - HttpSession with GemFire Client/Server using XML (Quick Start)
|
||||
John Blum
|
||||
:toc:
|
||||
|
||||
This guide describes how to configure Spring Session to transparently leverage Pivotal GemFire to back a web application's
|
||||
`HttpSession` using XML Configuration.
|
||||
|
||||
NOTE: The completed guide can be found in the <<httpsession-gemfire-clientserver-xml-sample-app,HttpSession with GemFire (Client/Server) using XML Sample Application>>.
|
||||
|
||||
== Updating Dependencies
|
||||
Before using Spring Session, you must ensure that the required dependencies are included.
|
||||
If you are using Maven, include the following `dependencies` in your _pom.xml_:
|
||||
|
||||
.pom.xml
|
||||
[source,xml]
|
||||
[subs="verbatim,attributes"]
|
||||
----
|
||||
<dependencies>
|
||||
<!-- ... -->
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.session</groupId>
|
||||
<artifactId>spring-session-data-gemfire</artifactId>
|
||||
<version>{spring-session-version}</version>
|
||||
<type>pom</type>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-web</artifactId>
|
||||
<version>{spring-version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
----
|
||||
|
||||
ifeval::["{version-snapshot}" == "true"]
|
||||
Since we are using a SNAPSHOT version, we need to add the Spring Snapshot Maven Repository.
|
||||
If you are using Maven, include the following `repository` declaration in your _pom.xml_:
|
||||
|
||||
.pom.xml
|
||||
[source,xml]
|
||||
----
|
||||
<repositories>
|
||||
<!-- ... -->
|
||||
|
||||
<repository>
|
||||
<id>spring-snapshot</id>
|
||||
<url>https://repo.spring.io/libs-snapshot</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
----
|
||||
endif::[]
|
||||
|
||||
ifeval::["{version-milestone}" == "true"]
|
||||
Since we are using a Milestone version, we need to add the Spring Milestone Maven Repository.
|
||||
If you are using Maven, include the following `repository` declaration in your _pom.xml_:
|
||||
|
||||
.pom.xml
|
||||
[source,xml]
|
||||
----
|
||||
<repositories>
|
||||
<!-- ... -->
|
||||
|
||||
<repository>
|
||||
<id>spring-milestone</id>
|
||||
<url>https://repo.spring.io/libs-milestone</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
----
|
||||
endif::[]
|
||||
|
||||
// tag::config[]
|
||||
[[httpsession-spring-xml-configuration]]
|
||||
== Spring XML Configuration
|
||||
|
||||
After adding the required dependencies and repository declarations, we can create our Spring configuration.
|
||||
The Spring configuration is responsible for creating a Servlet Filter that replaces the `HttpSession`
|
||||
with an implementation backed by Spring Session and GemFire.
|
||||
|
||||
Add the following Spring Configuration:
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
include::{samples-dir}httpsession-gemfire-clientserver-xml/src/main/webapp/WEB-INF/spring/session-client.xml[tags=beans]
|
||||
----
|
||||
|
||||
<1> Spring annotation configuration support is enabled with `<context:annotation-config/>` element so that any
|
||||
Spring beans declared in the XML config that are annotated with either Spring or Standard Java annotations supported
|
||||
by Spring will be configured appropriately.
|
||||
<2> The `META-INF/spring/application.properties` file are used along with the `PropertySourcesPlaceholderConfigurer`
|
||||
bean to replace placeholders in the Spring XML configuration meta-data with the approrpriate property values.
|
||||
<3> Then the `GemFireCacheSeverReadyBeanPostProcessor`is registered to determine whether a GemFire Server
|
||||
at the designated host/port is running and listening for client connections, blocking client startup until
|
||||
the server is available and ready.
|
||||
<4> Next, we include a `Properties` bean to configure certain aspects of the GemFire client cache using
|
||||
http://gemfire.docs.pivotal.io/docs-gemfire/reference/topics/gemfire_properties.html[GemFire's System Properties].
|
||||
In this case, we are just setting GemFire's `log-level` from a application-specific System property, defaulting
|
||||
to `warning` if unspecified.
|
||||
<5> Then we create a instance of a GemFire `ClientCache` initialized with our `gemfireProperties`.
|
||||
<6> We configure a Pool of client connections to talk to the GemFire Server in our Client/Server topology.
|
||||
In our configuration, we use sensible settings for timeouts, number of connections and so on. Also, our `Pool`
|
||||
has been configured to connect directly to a server.
|
||||
<7> Finally, the `GemFireHttpSessionConfiguration` is registered to enable Spring Session functionality.
|
||||
|
||||
TIP: In typical GemFire deployments, where the cluster includes potentially hundreds of GemFire data nodes (servers),
|
||||
it is more common for clients to connect to one or more GemFire Locators running in the cluster. A Locator passes meta-data
|
||||
to clients about the servers available, load and which servers have the client's data of interest, which is particularly
|
||||
important for single-hop, direct data access. See more details about the http://gemfire.docs.pivotal.io/docs-gemfire/latest/topologies_and_comm/cs_configuration/chapter_overview.html[Client/Server Topology in GemFire's User Guide].
|
||||
|
||||
NOTE: For more information on configuring _Spring Data GemFire_, refer to the http://docs.spring.io/spring-data-gemfire/docs/current/reference/html/[reference guide].
|
||||
|
||||
=== Server Configuration
|
||||
|
||||
We have only covered one side of the equation. We also need a GemFire Server for our client to talk to and send
|
||||
session state information to the server to manage.
|
||||
|
||||
In this sample, we will use the following GemFire Server Java Configuration:
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
include::{samples-dir}httpsession-gemfire-clientserver-xml/src/main/resources/META-INF/spring/session-server.xml[tags=beans]
|
||||
----
|
||||
|
||||
<1> First, we enable Spring annotation config support with the `<context:annotation-config>` element so that any
|
||||
Spring beans declared in the XML config that are annotated with either Spring or Standard Java annotations supported
|
||||
by Spring will be configured appropriately.
|
||||
<2> A `PropertySourcesPlaceholderConfigurer` is registered to replace placeholders in our Spring XML configuration
|
||||
meta-data with property values in the `META-INF/spring/application.properties` file.
|
||||
<3> Next, we configure the GemFire Server using GemFire System Properties very much like our P2P samples.
|
||||
With the `mcast-port` set to 0 and no `locators` property specified, our server will be standalone. We also allow a
|
||||
JMX client (e.g. _Gfsh_) to connect to our server with the use of the GemFire-specific JMX System properties.
|
||||
<4> Then we create an instance of a GemFire peer `Cache` initialized with our GemFire System Properties.
|
||||
<5> We also setup a GemFire `CacheServer` instance running on *localhost*, listening to port **11235**,
|
||||
ready to accept our client connection.
|
||||
<6> Finally, we enable the same Spring Session functionality we used on the client by registering an instance of
|
||||
`GemFireHttpSessionConfiguration`, except that we set the session expiration timeout to **30 seconds**.
|
||||
We will explain later what this means.
|
||||
|
||||
The GemFire Server configuration gets bootstrapped with the following:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
include::{samples-dir}httpsession-gemfire-clientserver-xml/src/main/java/sample/Application.java[tags=class]
|
||||
----
|
||||
|
||||
TIP: Instead of a simple Java class with a main method, you could also use _Spring Boot_.
|
||||
|
||||
<1> The `@Configuration` annotation designates this Java class as a source for Spring configuration meta-data using
|
||||
Spring's annotation configuration support.
|
||||
<2> Primarily, the configuration comes from the `META-INF/spring/session-server.xml` file, which is also the reason
|
||||
why _Spring Boot_ was not used in this sample, since using XML seemingly defeats the purpose and benefits
|
||||
of using Spring Boot. However, this sample is about demonstrating how to use Spring XML to configure
|
||||
the GemFire client and server.
|
||||
|
||||
== XML Servlet Container Initialization
|
||||
|
||||
Our <<httpsession-spring-xml-configuration,Spring XML Configuration>> created a Spring bean named `springSessionRepositoryFilter`
|
||||
that implements `Filter`. The `springSessionRepositoryFilter` bean is responsible for replacing the `HttpSession` with
|
||||
a custom implementation that is backed by Spring Session and GemFire.
|
||||
|
||||
In order for our `Filter` to do its magic, we need to instruct Spring to load our `session-client.xml` configuration file.
|
||||
We do this with the following configuration:
|
||||
|
||||
.src/main/webapp/WEB-INF/web.xml
|
||||
[source,xml,indent=0]
|
||||
----
|
||||
include::{samples-dir}httpsession-gemfire-clientserver-xml/src/main/webapp/WEB-INF/web.xml[tags=context-param]
|
||||
include::{samples-dir}httpsession-gemfire-clientserver-xml/src/main/webapp/WEB-INF/web.xml[tags=listeners]
|
||||
----
|
||||
|
||||
The http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#context-create[ContextLoaderListener]
|
||||
reads the `contextConfigLocation` context parameter value and picks up our _session-client.xml_ configuration file.
|
||||
|
||||
Finally, we need to ensure that our Servlet Container (i.e. Tomcat) uses our `springSessionRepositoryFilter`
|
||||
for every request.
|
||||
|
||||
The following snippet performs this last step for us:
|
||||
|
||||
.src/main/webapp/WEB-INF/web.xml
|
||||
[source,xml,indent=0]
|
||||
----
|
||||
include::{samples-dir}httpsession-gemfire-clientserver-xml/src/main/webapp/WEB-INF/web.xml[tags=springSessionRepositoryFilter]
|
||||
----
|
||||
|
||||
The http://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/filter/DelegatingFilterProxy.html[DelegatingFilterProxy]
|
||||
will look up a bean by the name of `springSessionRepositoryFilter` and cast it to a `Filter`. For every request that `DelegatingFilterProxy`
|
||||
is invoked, the `springSessionRepositoryFilter` will be invoked.
|
||||
// end::config[]
|
||||
|
||||
[[httpsession-gemfire-clientserver-xml-sample-app]]
|
||||
== HttpSession with GemFire (Client/Server) using XML Sample Application
|
||||
|
||||
|
||||
=== Running the httpsession-gemfire-clientserver-xml Sample Application
|
||||
|
||||
You can run the sample by obtaining the {download-url}[source code] and invoking the following commands.
|
||||
|
||||
First, you need to run the server using:
|
||||
|
||||
----
|
||||
$ ./gradlew :samples:httpsession-gemfire-clientserver-xml:run [-Dsample.httpsession.gemfire.log-level=info]
|
||||
----
|
||||
|
||||
Now, in a separate terminal, you can run the client using:
|
||||
|
||||
----
|
||||
$ ./gradlew :samples:httpsession-gemfire-clientserver-xml:tomcatRun [-Dsample.httpsession.gemfire.log-level=info]
|
||||
----
|
||||
|
||||
You should now be able to access the application at http://localhost:8080/. In this sample, the web application
|
||||
is the client cache and the server is standalone.
|
||||
|
||||
=== Exploring the httpsession-gemfire-clientserver-xml Sample Application
|
||||
|
||||
Try using the application. Fill out the form with the following information:
|
||||
|
||||
* **Attribute Name:** _username_
|
||||
* **Attribute Value:** _john_
|
||||
|
||||
Now click the **Set Attribute** button. You should now see the values displayed in the table.
|
||||
|
||||
=== How does it work?
|
||||
|
||||
We interact with the standard `HttpSession` in the `SessionServlet` shown below:
|
||||
|
||||
.src/main/java/sample/SessionServlet.java
|
||||
[source,java]
|
||||
----
|
||||
include::{samples-dir}httpsession-gemfire-clientserver/src/main/java/sample/SessionServlet.java[tags=class]
|
||||
----
|
||||
|
||||
Instead of using Tomcat's `HttpSession`, we are actually persisting the values in GemFire.
|
||||
Spring Session creates a cookie named SESSION in your browser that contains the id of your session.
|
||||
Go ahead and view the cookies (click for help with https://developer.chrome.com/devtools/docs/resources#cookies[Chrome]
|
||||
or https://getfirebug.com/wiki/index.php/Cookies_Panel#Cookies_List[Firefox]).
|
||||
|
||||
NOTE: The following instructions assume you have a local GemFire installation. For more information on installation,
|
||||
see http://gemfire.docs.pivotal.io/docs-gemfire/latest/getting_started/installation/install_intro.html[Installing Pivotal GemFire].
|
||||
|
||||
If you like, you can easily remove the session using `gfsh`. For example, on a Linux-based system type the following
|
||||
at the command-line:
|
||||
|
||||
$ gfsh
|
||||
|
||||
Then, enter the following commands in _Gfsh_ ensuring to replace `70002719-3c54-4c20-82c3-e7faa6b718f3` with the value
|
||||
of your SESSION cookie, or the session ID returned by the GemFire OQL query (which should match):
|
||||
|
||||
....
|
||||
gfsh>connect --jmx-manager=localhost[1099]
|
||||
|
||||
gfsh>query --query='SELECT * FROM /ClusteredSpringSessions.keySet'
|
||||
|
||||
Result : true
|
||||
startCount : 0
|
||||
endCount : 20
|
||||
Rows : 1
|
||||
|
||||
Result
|
||||
------------------------------------
|
||||
70002719-3c54-4c20-82c3-e7faa6b718f3
|
||||
|
||||
NEXT_STEP_NAME : END
|
||||
|
||||
gfsh>remove --region=/ClusteredSpringSessions --key="70002719-3c54-4c20-82c3-e7faa6b718f3"
|
||||
....
|
||||
|
||||
NOTE: The _GemFire User Guide_ has more detailed instructions on using http://gemfire.docs.pivotal.io/docs-gemfire/latest/tools_modules/gfsh/chapter_overview.html[gfsh].
|
||||
|
||||
Now visit the application at http://localhost:8080/ again and observe that the attribute we added is no longer displayed.
|
||||
|
||||
Alternatively, you can wait *30 seconds* for the session to timeout (i.e. expire) and refresh the page. Again, the
|
||||
attribute we added should no longer be displayed in the table. However, keep in mind, that by refreshing the page,
|
||||
you will inadvertently create a new (empty) session. If you run the query again, you will also see two session IDs,
|
||||
the new and the old, since GemFire keeps a "tombstone" of the old session around.
|
||||
@@ -1,272 +0,0 @@
|
||||
= Spring Session - HttpSession with GemFire Client/Server (Quick Start)
|
||||
John Blum
|
||||
:toc:
|
||||
|
||||
This guide describes how to configure Spring Session to transparently leverage Pivotal GemFire to back a web application's
|
||||
`HttpSession` using Java Configuration.
|
||||
|
||||
NOTE: The completed guide can be found in the <<httpsession-gemfire-clientserver-java-sample-app,HttpSession with GemFire (Client/Server) Sample Application>>.
|
||||
|
||||
== Updating Dependencies
|
||||
Before using Spring Session, you must ensure that the required dependencies are included.
|
||||
If you are using Maven, include the following `dependencies` in your _pom.xml_:
|
||||
|
||||
.pom.xml
|
||||
[source,xml]
|
||||
[subs="verbatim,attributes"]
|
||||
----
|
||||
<dependencies>
|
||||
<!-- ... -->
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.session</groupId>
|
||||
<artifactId>spring-session-data-gemfire</artifactId>
|
||||
<version>{spring-session-version}</version>
|
||||
<type>pom</type>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-web</artifactId>
|
||||
<version>{spring-version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
----
|
||||
|
||||
ifeval::["{version-snapshot}" == "true"]
|
||||
Since we are using a SNAPSHOT version, we need to add the Spring Snapshot Maven Repository.
|
||||
If you are using Maven, include the following `repository` declaration in your _pom.xml_:
|
||||
|
||||
.pom.xml
|
||||
[source,xml]
|
||||
----
|
||||
<repositories>
|
||||
<!-- ... -->
|
||||
|
||||
<repository>
|
||||
<id>spring-snapshot</id>
|
||||
<url>https://repo.spring.io/libs-snapshot</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
----
|
||||
endif::[]
|
||||
|
||||
ifeval::["{version-milestone}" == "true"]
|
||||
Since we are using a Milestone version, we need to add the Spring Milestone Maven Repository.
|
||||
If you are using Maven, include the following `repository` declaration in your _pom.xml_:
|
||||
|
||||
.pom.xml
|
||||
[source,xml]
|
||||
----
|
||||
<repositories>
|
||||
<!-- ... -->
|
||||
|
||||
<repository>
|
||||
<id>spring-milestone</id>
|
||||
<url>https://repo.spring.io/libs-milestone</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
----
|
||||
endif::[]
|
||||
|
||||
// tag::config[]
|
||||
[[httpsession-spring-java-configuration]]
|
||||
== Spring Java Configuration
|
||||
|
||||
After adding the required dependencies and repository declarations, we can create our Spring configuration.
|
||||
The Spring configuration is responsible for creating a Servlet Filter that replaces the `HttpSession`
|
||||
with an implementation backed by Spring Session and GemFire.
|
||||
|
||||
Add the following Spring Configuration:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
include::{samples-dir}httpsession-gemfire-clientserver/src/main/java/sample/ClientConfig.java[tags=class]
|
||||
----
|
||||
|
||||
<1> The `@EnableGemFireHttpSession` annotation creates a Spring bean named `springSessionRepositoryFilter` that
|
||||
implements `Filter`. The filter is what replaces the `HttpSession` with an implementation backed by Spring Session
|
||||
and GemFire.
|
||||
<2> Next, we register a `Properties` bean that allows us to configure certain aspects of the GemFire client cache
|
||||
using http://gemfire.docs.pivotal.io/docs-gemfire/reference/topics/gemfire_properties.html[GemFire's System properties].
|
||||
<3> We use the `Properties` to configure an instance of a GemFire `ClientCache`.
|
||||
<4> Then, we configure a `Pool` of client connections to talk to the GemFire Server in our Client/Server topology. In our
|
||||
configuration, we have used sensible settings for timeouts, number of connections and so on. Also, the `Pool` has been
|
||||
configured to connect directly to a server. Learn more about various `Pool` configuration settings from the
|
||||
http://gemfire.docs.pivotal.io/docs-gemfire/latest/javadocs/japi/com/gemstone/gemfire/cache/client/PoolFactory.html[PoolFactory API].
|
||||
<56> Finally, we include a Spring `BeanPostProcessor` to block the client until our GemFire Server is up and running,
|
||||
listening for and accepting client connections.
|
||||
|
||||
The `gemfireCacheServerReadyBeanPostProcessor` is necessary in order to coordinate the client and server in
|
||||
an automated fashion during testing, but unnecessary in situations where the GemFire cluster is already presently
|
||||
running, such as in production.
|
||||
|
||||
The `BeanPostProcessor` uses a GemFire http://gemfire.docs.pivotal.io/docs-gemfire/latest/javadocs/japi/com/gemstone/gemfire/management/membership/ClientMembershipListener.html[ClientMembershipListener]
|
||||
that will be notified when the client has successfully connected to the server. Once a connection has been established,
|
||||
the listener releases the latch that the `BeanPostProcessor` will wait on (up to the specified timeout) in the
|
||||
`postProcessAfterInitialization` callback to block the client.
|
||||
|
||||
TIP: In typical GemFire deployments, where the cluster includes potentially hundreds of GemFire data nodes (servers),
|
||||
it is more common for clients to connect to one or more GemFire Locators running in the cluster. A Locator passes meta-data
|
||||
to clients about the servers available, load and which servers have the client's data of interest, which is particularly
|
||||
important for single-hop, direct data access. See more details about the http://gemfire.docs.pivotal.io/docs-gemfire/latest/topologies_and_comm/cs_configuration/chapter_overview.html[Client/Server Topology in GemFire's User Guide].
|
||||
|
||||
NOTE: For more information on configuring _Spring Data GemFire_, refer to the http://docs.spring.io/spring-data-gemfire/docs/current/reference/html/[reference guide].
|
||||
|
||||
The `@EnableGemFireHttpSession` annotation enables a developer to configure certain aspects of both Spring Session
|
||||
and GemFire out-of-the-box using the following attributes:
|
||||
|
||||
* `maxInactiveIntervalInSeconds` - controls _HttpSession_ idle-timeout expiration (defaults to **30 minutes**).
|
||||
* `regionName` - specifies the name of the GemFire Region used to store `HttpSession` state (defaults is "*ClusteredSpringSessions*").
|
||||
* `clientRegionShort` - specifies GemFire's http://gemfire.docs.pivotal.io/docs-gemfire/latest/developing/management_all_region_types/chapter_overview.html[data management policy]
|
||||
with a GemFire http://gemfire.docs.pivotal.io/docs-gemfire/latest/javadocs/japi/com/gemstone/gemfire/cache/client/ClientRegionShortcut.html[ClientRegionShortcut]
|
||||
(default is `PROXY`). This attribute is only used when configuring client Region.
|
||||
* `poolName` - name of the dedicated GemFire Pool used to connect a client to the cluster of servers. The attribute
|
||||
is only used when the application is a GemFire cache client. Defaults to `gemfirePool`.
|
||||
* `serverRegionShort` - specifies GemFire's http://gemfire.docs.pivotal.io/docs-gemfire/latest/developing/management_all_region_types/chapter_overview.html[data management policy]
|
||||
using a GemFire http://data-docs-samples.cfapps.io/docs-gemfire/latest/javadocs/japi/com/gemstone/gemfire/cache/RegionShortcut.html[RegionShortcut]
|
||||
(default is `PARTITION`). This attribute is only used when configuring server Regions, or when a p2p topology is employed.
|
||||
|
||||
NOTE: It is important to note that the GemFire client Region name must match a server Region by the same name if
|
||||
the client Region is a `PROXY` or `CACHING_PROXY`. Names are not required to match if the client Region used to
|
||||
store Spring Sessions is `LOCAL`, however, keep in mind that your session state will not be propagated to the server
|
||||
and you lose all benefits of using GemFire to store and manage distributed, replicated session state information
|
||||
in a cluster.
|
||||
|
||||
NOTE: `serverRegionShort` is ignored in a client/server cache configuration and only applies when
|
||||
a peer-to-peer (P2P) topology, and more specifically, a GemFire peer cache is used.
|
||||
|
||||
=== Server Configuration
|
||||
|
||||
We have only covered one side of the equation. We also need a GemFire Server for our client to talk to and send
|
||||
session state to the server to manage.
|
||||
|
||||
In this sample, we will use the following GemFire Server Java Configuration:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
include::{samples-dir}httpsession-gemfire-clientserver/src/main/java/sample/ServerConfig.java[tags=class]
|
||||
----
|
||||
|
||||
<1> On the server, we also configure Spring Session using the `@EnableGemFireHttpSession` annotation. This ensures
|
||||
the Region names on both the client and server match (in this sample, we use the default "_ClusteredSpringSessions_").
|
||||
We have also set the session timeout to **30 seconds**. Later, we will see how this timeout is used.
|
||||
<2> Next, we configure the GemFire Server using GemFire System Properties very much like our P2P samples.
|
||||
With the `mcast-port` set to 0 and no `locators` property specified, our server will be standalone. We also allow a
|
||||
JMX client (e.g. _Gfsh_) to connect to our server with the use of the GemFire-specific JMX System properties.
|
||||
<3> Then, we create an instance of a GemFire peer `Cache` initialized with our GemFire System Properties.
|
||||
<4> We also setup a GemFire `CacheServer` instance running on **localhost**, listening to port **12480**,
|
||||
ready to accept our client connection.
|
||||
<5> Finally, we declare a `main` method as an entry point for launching and running our GemFire Server
|
||||
from the command-line.
|
||||
|
||||
== Java Servlet Container Initialization
|
||||
|
||||
Our <<httpsession-spring-java-configuration,Spring Java Configuration>> created a Spring bean named `springSessionRepositoryFilter`
|
||||
that implements `Filter`. The `springSessionRepositoryFilter` bean is responsible for replacing the `HttpSession`
|
||||
with a custom implementation backed by Spring Session and GemFire.
|
||||
|
||||
In order for our `Filter` to do its magic, Spring needs to load our `ClientConfig` class. We also need to ensure our
|
||||
Servlet Container (i.e. Tomcat) uses our `springSessionRepositoryFilter` for every request. Fortunately, Spring Session
|
||||
provides a utility class named `AbstractHttpSessionApplicationInitializer` to make both of these steps extremely easy.
|
||||
|
||||
You can find an example below:
|
||||
|
||||
.src/main/java/sample/Initializer.java
|
||||
[source,java]
|
||||
----
|
||||
include::{samples-dir}httpsession-gemfire-clientserver/src/main/java/sample/Initializer.java[tags=class]
|
||||
----
|
||||
|
||||
NOTE: The name of our class (`Initializer`) does not matter. What is important is that we extend `AbstractHttpSessionApplicationInitializer`.
|
||||
|
||||
<1> The first step is to extend `AbstractHttpSessionApplicationInitializer`.
|
||||
This ensures that a Spring bean named `springSessionRepositoryFilter` is registered with our Servlet Container
|
||||
and used for every request.
|
||||
<2> `AbstractHttpSessionApplicationInitializer` also provides a mechanism to easily allow Spring to load our `ClientConfig`.
|
||||
// end::config[]
|
||||
|
||||
[[httpsession-gemfire-clientserver-java-sample-app]]
|
||||
== HttpSession with GemFire (Client/Server) Sample Application
|
||||
|
||||
|
||||
=== Running the httpsession-gemfire-clientserver Sample Application
|
||||
|
||||
You can run the sample by obtaining the {download-url}[source code] and invoking the following commands.
|
||||
|
||||
First, you need to run the server using:
|
||||
|
||||
----
|
||||
$ ./gradlew :samples:httpsession-gemfire-clientserver:run [-Dsample.httpsession.gemfire.log-level=info]
|
||||
----
|
||||
|
||||
Then, in a separate terminal, you run the client using:
|
||||
|
||||
----
|
||||
$ ./gradlew :samples:httpsession-gemfire-clientserver:tomcatRun [-Dsample.httpsession.gemfire.log-level=info]
|
||||
----
|
||||
|
||||
You should now be able to access the application at http://localhost:8080/. In this sample, the web application
|
||||
is the client cache and the server is standalone.
|
||||
|
||||
=== Exploring the httpsession-gemfire-clientserver Sample Application
|
||||
|
||||
Try using the application. Fill out the form with the following information:
|
||||
|
||||
* **Attribute Name:** _username_
|
||||
* **Attribute Value:** _john_
|
||||
|
||||
Now click the **Set Attribute** button. You should now see the values displayed in the table.
|
||||
|
||||
=== How does it work?
|
||||
|
||||
We interact with the standard `HttpSession` in the `SessionServlet` shown below:
|
||||
|
||||
.src/main/java/sample/SessionServlet.java
|
||||
[source,java]
|
||||
----
|
||||
include::{samples-dir}httpsession-gemfire-clientserver/src/main/java/sample/SessionServlet.java[tags=class]
|
||||
----
|
||||
|
||||
Instead of using Tomcat's `HttpSession`, we are actually persisting the values in GemFire.
|
||||
Spring Session creates a cookie named SESSION in your browser that contains the id of your session.
|
||||
Go ahead and view the cookies (click for help with https://developer.chrome.com/devtools/docs/resources#cookies[Chrome]
|
||||
or https://getfirebug.com/wiki/index.php/Cookies_Panel#Cookies_List[Firefox]).
|
||||
|
||||
NOTE: The following instructions assume you have a local GemFire installation. For more information on installation,
|
||||
see http://gemfire.docs.pivotal.io/docs-gemfire/latest/getting_started/installation/install_intro.html[Installing Pivotal GemFire].
|
||||
|
||||
If you like, you can easily remove the session using `gfsh`. For example, on a Linux-based system type the following
|
||||
at the command-line:
|
||||
|
||||
$ gfsh
|
||||
|
||||
Then, enter the following commands in _Gfsh_ ensuring to replace `70002719-3c54-4c20-82c3-e7faa6b718f3` with the value
|
||||
of your SESSION cookie, or the session ID returned by the GemFire OQL query (which should match):
|
||||
|
||||
....
|
||||
gfsh>connect --jmx-manager=localhost[1099]
|
||||
|
||||
gfsh>query --query='SELECT * FROM /ClusteredSpringSessions.keySet'
|
||||
|
||||
Result : true
|
||||
startCount : 0
|
||||
endCount : 20
|
||||
Rows : 1
|
||||
|
||||
Result
|
||||
------------------------------------
|
||||
70002719-3c54-4c20-82c3-e7faa6b718f3
|
||||
|
||||
NEXT_STEP_NAME : END
|
||||
|
||||
gfsh>remove --region=/ClusteredSpringSessions --key="70002719-3c54-4c20-82c3-e7faa6b718f3"
|
||||
....
|
||||
|
||||
NOTE: The _GemFire User Guide_ has more detailed instructions on using http://gemfire.docs.pivotal.io/docs-gemfire/latest/tools_modules/gfsh/chapter_overview.html[gfsh].
|
||||
|
||||
Now visit the application at http://localhost:8080/ again and observe that the attribute we added is no longer displayed.
|
||||
|
||||
Alternatively, you can wait **30 seconds** for the session to expire and timeout, and then refresh the page. The attribute
|
||||
we added should no longer be displayed in the table. However, keep in mind, that by refreshing the page, you will inadvertently
|
||||
create a new (empty) session. If you run the query again, you will also see two session IDs, the new and the old,
|
||||
since GemFire keeps a "tombstone" of the old session around.
|
||||
@@ -1,210 +0,0 @@
|
||||
= Spring Session - HttpSession with GemFire P2P using XML (Quick Start)
|
||||
John Blum, Rob Winch
|
||||
:toc:
|
||||
|
||||
This guide describes how to configure Pivotal GemFire as a provider in Spring Session to transparently back
|
||||
a web application's `HttpSession` using XML Configuration.
|
||||
|
||||
NOTE: The completed guide can be found in the <<httpsession-gemfire-p2p-xml-sample-app,HttpSession with GemFire (P2P) using XML Sample Application>>.
|
||||
|
||||
== Updating Dependencies
|
||||
Before using Spring Session, you must ensure that the required dependencies are included.
|
||||
If you are using Maven, include the following `dependencies` in your _pom.xml_:
|
||||
|
||||
.pom.xml
|
||||
[source,xml]
|
||||
[subs="verbatim,attributes"]
|
||||
----
|
||||
<dependencies>
|
||||
<!-- ... -->
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.session</groupId>
|
||||
<artifactId>spring-session-data-gemfire</artifactId>
|
||||
<version>{spring-session-version}</version>
|
||||
<type>pom</type>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-web</artifactId>
|
||||
<version>{spring-version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
----
|
||||
|
||||
ifeval::["{version-snapshot}" == "true"]
|
||||
Since we are using a SNAPSHOT version, we need to add the Spring Snapshot Maven Repository.
|
||||
If you are using Maven, include the following `repository` declaration in your _pom.xml_:
|
||||
|
||||
.pom.xml
|
||||
[source,xml]
|
||||
----
|
||||
<repositories>
|
||||
<!-- ... -->
|
||||
|
||||
<repository>
|
||||
<id>spring-snapshot</id>
|
||||
<url>https://repo.spring.io/libs-snapshot</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
----
|
||||
endif::[]
|
||||
|
||||
ifeval::["{version-milestone}" == "true"]
|
||||
Since we are using a Milestone version, we need to add the Spring Milestone Maven Repository.
|
||||
If you are using Maven, include the following `repository` declaration in your _pom.xml_:
|
||||
|
||||
.pom.xml
|
||||
[source,xml]
|
||||
----
|
||||
<repositories>
|
||||
<!-- ... -->
|
||||
|
||||
<repository>
|
||||
<id>spring-milestone</id>
|
||||
<url>https://repo.spring.io/libs-milestone</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
----
|
||||
endif::[]
|
||||
|
||||
// tag::config[]
|
||||
[[httpsession-spring-xml-configuration]]
|
||||
== Spring XML Configuration
|
||||
|
||||
After adding the required dependencies and repository declarations, we can create our Spring configuration.
|
||||
The Spring configuration is responsible for creating a Servlet Filter that replaces the `HttpSession`
|
||||
with an implementation backed by Spring Session and GemFire.
|
||||
|
||||
Add the following Spring Configuration:
|
||||
|
||||
.src/main/webapp/WEB-INF/spring/session.xml
|
||||
[source,xml,indent=0]
|
||||
----
|
||||
include::{samples-dir}httpsession-gemfire-p2p-xml/src/main/webapp/WEB-INF/spring/session.xml[tags=beans]
|
||||
----
|
||||
|
||||
<1> We use the combination of `<context:annotation-config/>` and `GemFireHttpSessionConfiguration` because Spring Session
|
||||
does not yet provide XML Namespace support (see https://github.com/spring-projects/spring-session/issues/104[gh-104]).
|
||||
This creates a Spring bean with the name of `springSessionRepositoryFilter` that implements `Filter`. The filter is what
|
||||
replaces the `HttpSession` with an implementation backed by Spring Session.
|
||||
In this instance, Spring Session is backed by GemFire.
|
||||
<2> Then, we configure a GemFire peer cache using standard GemFire System properties. We give the GemFire data node
|
||||
a name using the `name` property and set `mcast-port` to 0. With the absence of a `locators` property, this data node
|
||||
will be a standalone server. GemFire's `log-level` is set using an application-specific System property
|
||||
(`sample.httpsession.gemfire.log-level`) that a user can specify on the command-line when running this application
|
||||
using either Maven or Gradle (default is "_warning_").
|
||||
<3> Finally, we create an instance of the GemFire peer cache that embeds GemFire in the same JVM process as the running
|
||||
Spring Session sample application.
|
||||
|
||||
TIP: Additionally, we have configured this data node (server) as a GemFire Manager as well using GemFire-specific
|
||||
JMX System properties that enable JMX client (e.g. _Gfsh_) to connect to this running data node.
|
||||
|
||||
NOTE: For more information on configuring _Spring Data GemFire_, refer to the http://docs.spring.io/spring-data-gemfire/docs/current/reference/html/[reference guide].
|
||||
|
||||
== XML Servlet Container Initialization
|
||||
|
||||
Our <<httpsession-spring-xml-configuration,Spring XML Configuration>> created a Spring bean named `springSessionRepositoryFilter`
|
||||
that implements `Filter`. The `springSessionRepositoryFilter` bean is responsible for replacing the `HttpSession` with
|
||||
a custom implementation that is backed by Spring Session and GemFire.
|
||||
|
||||
In order for our `Filter` to do its magic, we need to instruct Spring to load our `session.xml` configuration file.
|
||||
We do this with the following configuration:
|
||||
|
||||
.src/main/webapp/WEB-INF/web.xml
|
||||
[source,xml,indent=0]
|
||||
----
|
||||
include::{samples-dir}httpsession-gemfire-p2p-xml/src/main/webapp/WEB-INF/web.xml[tags=context-param]
|
||||
include::{samples-dir}httpsession-gemfire-p2p-xml/src/main/webapp/WEB-INF/web.xml[tags=listeners]
|
||||
----
|
||||
|
||||
The http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#context-create[ContextLoaderListener]
|
||||
reads the `contextConfigLocation` context parameter value and picks up our _session.xml_ configuration file.
|
||||
|
||||
Finally, we need to ensure that our Servlet Container (i.e. Tomcat) uses our `springSessionRepositoryFilter`
|
||||
for every request.
|
||||
|
||||
The following snippet performs this last step for us:
|
||||
|
||||
.src/main/webapp/WEB-INF/web.xml
|
||||
[source,xml,indent=0]
|
||||
----
|
||||
include::{samples-dir}httpsession-gemfire-p2p-xml/src/main/webapp/WEB-INF/web.xml[tags=springSessionRepositoryFilter]
|
||||
----
|
||||
|
||||
The http://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/filter/DelegatingFilterProxy.html[DelegatingFilterProxy]
|
||||
will look up a bean by the name of `springSessionRepositoryFilter` and cast it to a `Filter`. For every request that `DelegatingFilterProxy`
|
||||
is invoked, the `springSessionRepositoryFilter` will be invoked.
|
||||
// end::config[]
|
||||
|
||||
[[httpsession-gemfire-p2p-xml-sample-app]]
|
||||
== HttpSession with GemFire (P2P) using XML Sample Application
|
||||
|
||||
|
||||
=== Running the httpsession-gemfire-p2p-xml Sample Application
|
||||
|
||||
You can run the sample by obtaining the {download-url}[source code] and invoking the following command:
|
||||
|
||||
----
|
||||
$ ./gradlew :samples:httpsession-gemfire-p2p-xml:tomcatRun [-Dsample.httpsession.gemfire.log-level=info]
|
||||
----
|
||||
|
||||
You should now be able to access the application at http://localhost:8080/
|
||||
|
||||
=== Exploring the httpsession-gemfire-p2p-xml Sample Application
|
||||
|
||||
Try using the application. Fill out the form with the following information:
|
||||
|
||||
* **Attribute Name:** _username_
|
||||
* **Attribute Value:** _john_
|
||||
|
||||
Now click the **Set Attribute** button. You should now see the values displayed in the table.
|
||||
|
||||
=== How does it work?
|
||||
|
||||
We interact with the standard `HttpSession` in the `SessionServlet` shown below:
|
||||
|
||||
.src/main/java/sample/SessionServlet.java
|
||||
[source,java]
|
||||
----
|
||||
include::{samples-dir}httpsession-gemfire-p2p-xml/src/main/java/sample/SessionServlet.java[tags=class]
|
||||
----
|
||||
|
||||
Instead of using Tomcat's `HttpSession`, we are actually persisting the values in GemFire.
|
||||
Spring Session creates a cookie named SESSION in your browser that contains the id of your session.
|
||||
Go ahead and view the cookies (click for help with https://developer.chrome.com/devtools/docs/resources#cookies[Chrome]
|
||||
or https://getfirebug.com/wiki/index.php/Cookies_Panel#Cookies_List[Firefox]).
|
||||
|
||||
NOTE: The following instructions assume you have a local GemFire installation. For more information on installation,
|
||||
see http://gemfire.docs.pivotal.io/docs-gemfire/latest/getting_started/installation/install_intro.html[Installing Pivotal GemFire].
|
||||
|
||||
If you like, you can easily remove the session using `gfsh`. For example, on a Linux-based system type the following
|
||||
at the command-line:
|
||||
|
||||
$ gfsh
|
||||
|
||||
Then, enter the following into _Gfsh_ ensuring to replace `70002719-3c54-4c20-82c3-e7faa6b718f3` with the value
|
||||
of your SESSION cookie, or the session ID returned by the GemFire OQL query (which should match):
|
||||
|
||||
....
|
||||
gfsh>connect --jmx-manager=localhost[1099]
|
||||
|
||||
gfsh>query --query='SELECT * FROM /ClusteredSpringSessions.keySet'
|
||||
|
||||
Result : true
|
||||
startCount : 0
|
||||
endCount : 20
|
||||
Rows : 1
|
||||
|
||||
Result
|
||||
------------------------------------
|
||||
70002719-3c54-4c20-82c3-e7faa6b718f3
|
||||
|
||||
NEXT_STEP_NAME : END
|
||||
|
||||
gfsh>remove --region=/ClusteredSpringSessions --key="70002719-3c54-4c20-82c3-e7faa6b718f3"
|
||||
....
|
||||
|
||||
NOTE: The _GemFire User Guide_ has more detailed instructions on using http://gemfire.docs.pivotal.io/docs-gemfire/latest/tools_modules/gfsh/chapter_overview.html[gfsh].
|
||||
|
||||
Now visit the application at http://localhost:8080/ and observe that the attribute we added is no longer displayed.
|
||||
@@ -1,209 +0,0 @@
|
||||
= Spring Session - HttpSession with GemFire P2P (Quick Start)
|
||||
John Blum, Rob Winch
|
||||
:toc:
|
||||
|
||||
This guide describes how to configure Pivotal GemFire as a provider in Spring Session to transparently back
|
||||
a web application's `HttpSession` using Java Configuration.
|
||||
|
||||
NOTE: The completed guide can be found in the <<httpsession-gemfire-p2p-java-sample-app,HttpSession with GemFire (P2P) Sample Application>>.
|
||||
|
||||
== Updating Dependencies
|
||||
Before using Spring Session, you must ensure that the required dependencies are included.
|
||||
If you are using Maven, include the following `dependencies` in your _pom.xml_:
|
||||
|
||||
.pom.xml
|
||||
[source,xml]
|
||||
[subs="verbatim,attributes"]
|
||||
----
|
||||
<dependencies>
|
||||
<!-- ... -->
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.session</groupId>
|
||||
<artifactId>spring-session-data-gemfire</artifactId>
|
||||
<version>{spring-session-version}</version>
|
||||
<type>pom</type>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-web</artifactId>
|
||||
<version>{spring-version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
----
|
||||
|
||||
ifeval::["{version-snapshot}" == "true"]
|
||||
Since we are using a SNAPSHOT version, we need to add the Spring Snapshot Maven Repository.
|
||||
If you are using Maven, include the following `repository` declaration in your _pom.xml_:
|
||||
|
||||
.pom.xml
|
||||
[source,xml]
|
||||
----
|
||||
<repositories>
|
||||
<!-- ... -->
|
||||
|
||||
<repository>
|
||||
<id>spring-snapshot</id>
|
||||
<url>https://repo.spring.io/libs-snapshot</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
----
|
||||
endif::[]
|
||||
|
||||
ifeval::["{version-milestone}" == "true"]
|
||||
Since we are using a Milestone version, we need to add the Spring Milestone Maven Repository.
|
||||
If you are using Maven, include the following `repository` declaration in your _pom.xml_:
|
||||
|
||||
.pom.xml
|
||||
[source,xml]
|
||||
----
|
||||
<repositories>
|
||||
<!-- ... -->
|
||||
|
||||
<repository>
|
||||
<id>spring-milestone</id>
|
||||
<url>https://repo.spring.io/libs-milestone</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
----
|
||||
endif::[]
|
||||
|
||||
// tag::config[]
|
||||
[[httpsession-spring-java-configuration]]
|
||||
== Spring Java Configuration
|
||||
|
||||
After adding the required dependencies and repository declarations, we can create our Spring configuration.
|
||||
The Spring configuration is responsible for creating a Servlet Filter that replaces the `HttpSession`
|
||||
with an implementation backed by Spring Session and GemFire.
|
||||
|
||||
Add the following Spring Configuration:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
include::{samples-dir}httpsession-gemfire-p2p/src/main/java/sample/Config.java[tags=class]
|
||||
----
|
||||
|
||||
<1> The `@EnableGemFireHttpSession` annotation creates a Spring bean named `springSessionRepositoryFilter` that
|
||||
implements `Filter`. The filter is what replaces the `HttpSession` with an implementation backed by Spring Session.
|
||||
In this instance, Spring Session is backed by GemFire.
|
||||
<2> Then, we configure a GemFire peer cache using standard GemFire System properties. We give the GemFire data node
|
||||
a name using the `name` property and set `mcast-port` to 0. With the absence of a `locators` property, this data node
|
||||
will be a standalone server. GemFire's `log-level` is set using an application-specific System property (`sample.httpsession.gemfire.log-level`)
|
||||
that a user can specify on the command-line when running this sample application using either Maven or Gradle (default is "_warning_").
|
||||
<3> Finally, we create an instance of the GemFire peer cache that embeds GemFire in the same JVM process as the running
|
||||
Spring Session sample application.
|
||||
|
||||
TIP: Additionally, we have configured this data node (server) as a GemFire Manager as well using GemFire-specific
|
||||
JMX System properties that enable JMX client (e.g. _Gfsh_) to connect to this running data node.
|
||||
|
||||
NOTE: For more information on configuring _Spring Data GemFire_, refer to the http://docs.spring.io/spring-data-gemfire/docs/current/reference/html/[reference guide].
|
||||
|
||||
The `@EnableGemFireHttpSession` annotation enables a developer to configure certain aspects of Spring Session
|
||||
and GemFire out-of-the-box using the following attributes:
|
||||
|
||||
* `maxInactiveIntervalInSeconds` - controls HttpSession idle-timeout expiration (defaults to **30 minutes**).
|
||||
* `regionName` - specifies the name of the GemFire Region used to store `HttpSession` state (defaults is "_ClusteredSpringSessions_").
|
||||
* `serverRegionShort` - specifies GemFire http://gemfire.docs.pivotal.io/docs-gemfire/latest/developing/management_all_region_types/chapter_overview.html[data management policies]
|
||||
with a GemFire http://gemfire.docs.pivotal.io/docs-gemfire/latest/javadocs/japi/com/gemstone/gemfire/cache/RegionShortcut.html[RegionShortcut]
|
||||
(default is `PARTITION`).
|
||||
|
||||
NOTE: `clientRegionShort` is ignored in a peer cache configuration and only applies when a client-server topology,
|
||||
and more specifically, a GemFire client cache is used.
|
||||
|
||||
== Java Servlet Container Initialization
|
||||
|
||||
Our <<httpsession-spring-java-configuration,Spring Java Configuration>> created a Spring bean named `springSessionRepositoryFilter`
|
||||
that implements `Filter`. The `springSessionRepositoryFilter` bean is responsible for replacing the `HttpSession`
|
||||
with a custom implementation backed by Spring Session and GemFire.
|
||||
|
||||
In order for our `Filter` to do its magic, Spring needs to load our `Config` class. We also need to ensure our
|
||||
Servlet Container (i.e. Tomcat) uses our `springSessionRepositoryFilter` for every request. Fortunately, Spring Session
|
||||
provides a utility class named `AbstractHttpSessionApplicationInitializer` to make both of these steps extremely easy.
|
||||
|
||||
You can find an example below:
|
||||
|
||||
.src/main/java/sample/Initializer.java
|
||||
[source,java]
|
||||
----
|
||||
include::{samples-dir}httpsession-gemfire-p2p/src/main/java/sample/Initializer.java[tags=class]
|
||||
----
|
||||
|
||||
NOTE: The name of our class (`Initializer`) does not matter. What is important is that we extend `AbstractHttpSessionApplicationInitializer`.
|
||||
|
||||
<1> The first step is to extend `AbstractHttpSessionApplicationInitializer`.
|
||||
This ensures that a Spring bean named `springSessionRepositoryFilter` is registered with our Servlet Container
|
||||
and used for every request.
|
||||
<2> `AbstractHttpSessionApplicationInitializer` also provides a mechanism to easily allow Spring to load our `Config`.
|
||||
// end::config[]
|
||||
|
||||
[[httpsession-gemfire-p2p-java-sample-app]]
|
||||
== HttpSession with GemFire (P2P) Sample Application
|
||||
|
||||
|
||||
=== Running the httpsession-gemfire-p2p Sample Application
|
||||
|
||||
You can run the sample by obtaining the {download-url}[source code] and invoking the following command:
|
||||
|
||||
----
|
||||
$ ./gradlew :samples:httpsession-gemfire-p2p:tomcatRun [-Dsample.httpsession.gemfire.log-level=info]
|
||||
----
|
||||
|
||||
You should now be able to access the application at http://localhost:8080/
|
||||
|
||||
=== Exploring the httpsession-gemfire-p2p Sample Application
|
||||
|
||||
Try using the application. Fill out the form with the following information:
|
||||
|
||||
* **Attribute Name:** _username_
|
||||
* **Attribute Value:** _john_
|
||||
|
||||
Now click the **Set Attribute** button. You should now see the values displayed in the table.
|
||||
|
||||
=== How does it work?
|
||||
|
||||
We interact with the standard `HttpSession` in the `SessionServlet` shown below:
|
||||
|
||||
.src/main/java/sample/SessionServlet.java
|
||||
[source,java]
|
||||
----
|
||||
include::{samples-dir}httpsession-gemfire-p2p/src/main/java/sample/SessionServlet.java[tags=class]
|
||||
----
|
||||
|
||||
Instead of using Tomcat's `HttpSession`, we are actually persisting the values in GemFire.
|
||||
Spring Session creates a cookie named SESSION in your browser that contains the id of your session.
|
||||
Go ahead and view the cookies (click for help with https://developer.chrome.com/devtools/docs/resources#cookies[Chrome]
|
||||
or https://getfirebug.com/wiki/index.php/Cookies_Panel#Cookies_List[Firefox]).
|
||||
|
||||
NOTE: The following instructions assume you have a local GemFire installation. For more information on installation,
|
||||
see http://gemfire.docs.pivotal.io/docs-gemfire/latest/getting_started/installation/install_intro.html[Installing Pivotal GemFire].
|
||||
|
||||
If you like, you can easily remove the session using `gfsh`. For example, on a Linux-based system type the following
|
||||
at the command-line:
|
||||
|
||||
$ gfsh
|
||||
|
||||
Then, enter the following into _Gfsh_ ensuring to replace `70002719-3c54-4c20-82c3-e7faa6b718f3` with the value
|
||||
of your SESSION cookie, or the session ID returned by the GemFire OQL query (which should match):
|
||||
|
||||
....
|
||||
gfsh>connect --jmx-manager=localhost[1099]
|
||||
|
||||
gfsh>query --query='SELECT * FROM /ClusteredSpringSessions.keySet'
|
||||
|
||||
Result : true
|
||||
startCount : 0
|
||||
endCount : 20
|
||||
Rows : 1
|
||||
|
||||
Result
|
||||
------------------------------------
|
||||
70002719-3c54-4c20-82c3-e7faa6b718f3
|
||||
|
||||
NEXT_STEP_NAME : END
|
||||
|
||||
gfsh>remove --region=/ClusteredSpringSessions --key="70002719-3c54-4c20-82c3-e7faa6b718f3"
|
||||
....
|
||||
|
||||
NOTE: The _GemFire User Guide_ has more detailed instructions on using http://gemfire.docs.pivotal.io/docs-gemfire/latest/tools_modules/gfsh/chapter_overview.html[gfsh].
|
||||
|
||||
Now visit the application at http://localhost:8080/ and observe that the attribute we added is no longer displayed.
|
||||
@@ -17,7 +17,7 @@ You can find an example of customizing Spring Session's cookie below:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
include::{samples-dir}custom-cookie/src/main/java/sample/Config.java[tags=cookie-serializer]
|
||||
include::{samples-dir}javaconfig/custom-cookie/src/main/java/sample/Config.java[tags=cookie-serializer]
|
||||
----
|
||||
|
||||
<1> We customize the name of the cookie to be JSESSIONID
|
||||
@@ -80,6 +80,7 @@ You can run the sample by obtaining the {download-url}[source code] and invoking
|
||||
====
|
||||
For the sample to work, you must http://redis.io/download[install Redis 2.8+] on localhost and run it with the default port (6379).
|
||||
Alternatively, you can update the `LettuceConnectionFactory` to point to a Redis server.
|
||||
Another option is to use https://www.docker.com/[Docker] to run Redis on localhost. See https://hub.docker.com/_/redis/[Docker Redis repository] for detailed instructions.
|
||||
====
|
||||
|
||||
----
|
||||
@@ -98,4 +99,4 @@ Try using the application. Fill out the form with the following information:
|
||||
Now click the **Set Attribute** button.
|
||||
You should now see the values displayed in the table.
|
||||
|
||||
If you look at the cookies for the application, you can see the cookie is saved to the custom name of JSESSIONID
|
||||
If you look at the cookies for the application, you can see the cookie is saved to the custom name of JSESSIONID
|
||||
@@ -98,7 +98,7 @@ Since our application is already loading Spring configuration using our `Securit
|
||||
.src/main/java/sample/SecurityInitializer.java
|
||||
[source,java]
|
||||
----
|
||||
include::{samples-dir}hazelcast-spring/src/main/java/sample/SecurityInitializer.java[tags=class]
|
||||
include::{samples-dir}javaconfig/hazelcast/src/main/java/sample/SecurityInitializer.java[tags=class]
|
||||
----
|
||||
|
||||
Last we need to ensure that our Servlet Container (i.e. Tomcat) uses our `springSessionRepositoryFilter` for every request.
|
||||
@@ -110,7 +110,7 @@ You can find an example below:
|
||||
.src/main/java/sample/Initializer.java
|
||||
[source,java]
|
||||
----
|
||||
include::{samples-dir}hazelcast-spring/src/main/java/sample/Initializer.java[tags=class]
|
||||
include::{samples-dir}javaconfig/hazelcast/src/main/java/sample/Initializer.java[tags=class]
|
||||
----
|
||||
|
||||
NOTE: The name of our class (Initializer) does not matter. What is important is that we extend `AbstractHttpSessionApplicationInitializer`.
|
||||
@@ -75,7 +75,7 @@ Add the following Spring Configuration:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
include::{samples-dir}httpsession-jdbc/src/main/java/sample/Config.java[tags=class]
|
||||
include::{samples-dir}javaconfig/jdbc/src/main/java/sample/Config.java[tags=class]
|
||||
----
|
||||
|
||||
<1> The `@EnableJdbcHttpSession` annotation creates a Spring Bean with the name of `springSessionRepositoryFilter` that implements Filter.
|
||||
@@ -100,7 +100,7 @@ You can find an example below:
|
||||
.src/main/java/sample/Initializer.java
|
||||
[source,java]
|
||||
----
|
||||
include::{samples-dir}httpsession/src/main/java/sample/Initializer.java[tags=class]
|
||||
include::{samples-dir}javaconfig/jdbc/src/main/java/sample/Initializer.java[tags=class]
|
||||
----
|
||||
|
||||
NOTE: The name of our class (Initializer) does not matter. What is important is that we extend `AbstractHttpSessionApplicationInitializer`.
|
||||
@@ -140,7 +140,7 @@ We interact with the standard `HttpSession` in the `SessionServlet` shown below:
|
||||
.src/main/java/sample/SessionServlet.java
|
||||
[source,java]
|
||||
----
|
||||
include::{samples-dir}httpsession-jdbc/src/main/java/sample/SessionServlet.java[tags=class]
|
||||
include::{samples-dir}javaconfig/jdbc/src/main/java/sample/SessionServlet.java[tags=class]
|
||||
----
|
||||
|
||||
Instead of using Tomcat's `HttpSession`, we are actually persisting the values in H2 database.
|
||||
@@ -24,8 +24,8 @@ If you are using Maven, ensure to add the following dependencies:
|
||||
<type>pom</type>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>biz.paluch.redis</groupId>
|
||||
<artifactId>lettuce</artifactId>
|
||||
<groupId>io.lettuce</groupId>
|
||||
<artifactId>lettuce-core</artifactId>
|
||||
<version>{lettuce-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -80,7 +80,7 @@ Add the following Spring Configuration:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
include::{samples-dir}httpsession/src/main/java/sample/Config.java[tags=class]
|
||||
include::{samples-dir}javaconfig/redis/src/main/java/sample/Config.java[tags=class]
|
||||
----
|
||||
|
||||
<1> The `@EnableRedisHttpSession` annotation creates a Spring Bean with the name of `springSessionRepositoryFilter` that implements Filter.
|
||||
@@ -103,7 +103,7 @@ You can find an example below:
|
||||
.src/main/java/sample/Initializer.java
|
||||
[source,java]
|
||||
----
|
||||
include::{samples-dir}httpsession/src/main/java/sample/Initializer.java[tags=class]
|
||||
include::{samples-dir}javaconfig/redis/src/main/java/sample/Initializer.java[tags=class]
|
||||
----
|
||||
|
||||
NOTE: The name of our class (Initializer) does not matter. What is important is that we extend `AbstractHttpSessionApplicationInitializer`.
|
||||
@@ -127,6 +127,7 @@ You can run the sample by obtaining the {download-url}[source code] and invoking
|
||||
====
|
||||
For the sample to work, you must http://redis.io/download[install Redis 2.8+] on localhost and run it with the default port (6379).
|
||||
Alternatively, you can update the `LettuceConnectionFactory` to point to a Redis server.
|
||||
Another option is to use https://www.docker.com/[Docker] to run Redis on localhost. See https://hub.docker.com/_/redis/[Docker Redis repository] for detailed instructions.
|
||||
====
|
||||
|
||||
----
|
||||
@@ -151,7 +152,7 @@ We interact with the standard `HttpSession` in the `SessionServlet` shown below:
|
||||
.src/main/java/sample/SessionServlet.java
|
||||
[source,java]
|
||||
----
|
||||
include::{samples-dir}httpsession/src/main/java/sample/SessionServlet.java[tags=class]
|
||||
include::{samples-dir}javaconfig/redis/src/main/java/sample/SessionServlet.java[tags=class]
|
||||
----
|
||||
|
||||
Instead of using Tomcat's `HttpSession`, we are actually persisting the values in Redis.
|
||||
@@ -168,4 +169,4 @@ Alternatively, you can also delete the explicit key. Enter the following into yo
|
||||
|
||||
$ redis-cli del spring:session:sessions:7e8383a4-082c-4ffe-a4bc-c40fd3363c5e
|
||||
|
||||
Now visit the application at http://localhost:8080/ and observe that the attribute we added is no longer displayed.
|
||||
Now visit the application at http://localhost:8080/ and observe that the attribute we added is no longer displayed.
|
||||
@@ -24,8 +24,8 @@ If you are using Maven, ensure to add the following dependencies:
|
||||
<type>pom</type>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>biz.paluch.redis</groupId>
|
||||
<artifactId>lettuce</artifactId>
|
||||
<groupId>io.lettuce</groupId>
|
||||
<artifactId>lettuce-core</artifactId>
|
||||
<version>{lettuce-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -80,7 +80,7 @@ Add the following Spring Configuration:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
include::{samples-dir}rest/src/main/java/sample/HttpSessionConfig.java[tags=class]
|
||||
include::{samples-dir}javaconfig/rest/src/main/java/sample/HttpSessionConfig.java[tags=class]
|
||||
----
|
||||
|
||||
<1> The `@EnableRedisHttpSession` annotation creates a Spring Bean with the name of `springSessionRepositoryFilter` that implements `Filter`.
|
||||
@@ -101,7 +101,7 @@ In order for our `Filter` to do its magic, Spring needs to load our `Config` cla
|
||||
.src/main/java/sample/mvc/MvcInitializer.java
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::{samples-dir}rest/src/main/java/sample/mvc/MvcInitializer.java[tags=config]
|
||||
include::{samples-dir}javaconfig/rest/src/main/java/sample/mvc/MvcInitializer.java[tags=config]
|
||||
----
|
||||
|
||||
Last we need to ensure that our Servlet Container (i.e. Tomcat) uses our `springSessionRepositoryFilter` for every request.
|
||||
@@ -110,7 +110,7 @@ Fortunately, Spring Session provides a utility class named `AbstractHttpSessionA
|
||||
.src/main/java/sample/Initializer.java
|
||||
[source,java]
|
||||
----
|
||||
include::{samples-dir}rest/src/main/java/sample/Initializer.java[tags=class]
|
||||
include::{samples-dir}javaconfig/rest/src/main/java/sample/Initializer.java[tags=class]
|
||||
----
|
||||
|
||||
NOTE: The name of our class (Initializer) does not matter. What is important is that we extend `AbstractHttpSessionApplicationInitializer`.
|
||||
@@ -128,6 +128,7 @@ You can run the sample by obtaining the {download-url}[source code] and invoking
|
||||
====
|
||||
For the sample to work, you must http://redis.io/download[install Redis 2.8+] on localhost and run it with the default port (6379).
|
||||
Alternatively, you can update the `LettuceConnectionFactory` to point to a Redis server.
|
||||
Another option is to use https://www.docker.com/[Docker] to run Redis on localhost. See https://hub.docker.com/_/redis/[Docker Redis repository] for detailed instructions.
|
||||
====
|
||||
|
||||
----
|
||||
@@ -154,7 +155,7 @@ In the output you will notice the following:
|
||||
----
|
||||
HTTP/1.1 200 OK
|
||||
...
|
||||
x-auth-token: 0dc1f6e1-c7f1-41ac-8ce2-32b6b3e57aa3
|
||||
X-Auth-Token: 0dc1f6e1-c7f1-41ac-8ce2-32b6b3e57aa3
|
||||
|
||||
{"username":"user"}
|
||||
----
|
||||
@@ -162,25 +163,25 @@ x-auth-token: 0dc1f6e1-c7f1-41ac-8ce2-32b6b3e57aa3
|
||||
Specifically, we notice the following things about our response:
|
||||
|
||||
* The HTTP Status is now a 200
|
||||
* We have a header with the name of *x-auth-token* which contains a new session id
|
||||
* We have a header with the name of *X-Auth-Token* which contains a new session id
|
||||
* The current username is displayed
|
||||
|
||||
We can now use the *x-auth-token* to make another request without providing the username and password again. For example, the following outputs the username just as before:
|
||||
We can now use the *X-Auth-Token* to make another request without providing the username and password again. For example, the following outputs the username just as before:
|
||||
|
||||
$ curl -v http://localhost:8080/ -H "x-auth-token: 0dc1f6e1-c7f1-41ac-8ce2-32b6b3e57aa3"
|
||||
$ curl -v http://localhost:8080/ -H "X-Auth-Token: 0dc1f6e1-c7f1-41ac-8ce2-32b6b3e57aa3"
|
||||
|
||||
The only difference is that the session id is not provided in the response headers because we are reusing an existing session.
|
||||
|
||||
If we invalidate the session, then the x-auth-token is displayed in the response with an empty value. For example, the following will invalidate our session:
|
||||
If we invalidate the session, then the X-Auth-Token is displayed in the response with an empty value. For example, the following will invalidate our session:
|
||||
|
||||
$ curl -v http://localhost:8080/logout -H "x-auth-token: 0dc1f6e1-c7f1-41ac-8ce2-32b6b3e57aa3"
|
||||
$ curl -v http://localhost:8080/logout -H "X-Auth-Token: 0dc1f6e1-c7f1-41ac-8ce2-32b6b3e57aa3"
|
||||
|
||||
You will see in the output that the x-auth-token provides an empty String indicating that the previous session was invalidated.
|
||||
You will see in the output that the X-Auth-Token provides an empty String indicating that the previous session was invalidated.
|
||||
|
||||
----
|
||||
HTTP/1.1 204 No Content
|
||||
...
|
||||
x-auth-token:
|
||||
X-Auth-Token:
|
||||
----
|
||||
|
||||
=== How does it work?
|
||||
@@ -188,7 +189,7 @@ x-auth-token:
|
||||
Spring Security interacts with the standard `HttpSession` in `SecurityContextPersistenceFilter`.
|
||||
|
||||
Instead of using Tomcat's `HttpSession`, Spring Security is now persisting the values in Redis.
|
||||
Spring Session creates a header named x-auth-token in your browser that contains the id of your session.
|
||||
Spring Session creates a header named X-Auth-Token in your browser that contains the id of your session.
|
||||
|
||||
If you like, you can easily see that the session is created in Redis. First create a session using the following:
|
||||
|
||||
@@ -199,7 +200,7 @@ In the output you will notice the following:
|
||||
----
|
||||
HTTP/1.1 200 OK
|
||||
...
|
||||
x-auth-token: 7e8383a4-082c-4ffe-a4bc-c40fd3363c5e
|
||||
X-Auth-Token: 7e8383a4-082c-4ffe-a4bc-c40fd3363c5e
|
||||
|
||||
{"username":"user"}
|
||||
----
|
||||
@@ -214,6 +215,6 @@ Alternatively, you can also delete the explicit key. Enter the following into yo
|
||||
|
||||
$ redis-cli del spring:session:sessions:7e8383a4-082c-4ffe-a4bc-c40fd3363c5e
|
||||
|
||||
We can now use the *x-auth-token* to make another request with the session we deleted and observe we are prompted for a authentication. For example, the following returns an HTTP 401:
|
||||
We can now use the *X-Auth-Token* to make another request with the session we deleted and observe we are prompted for a authentication. For example, the following returns an HTTP 401:
|
||||
|
||||
$ curl -v http://localhost:8080/ -H "x-auth-token: 0dc1f6e1-c7f1-41ac-8ce2-32b6b3e57aa3"
|
||||
$ curl -v http://localhost:8080/ -H "X-Auth-Token: 0dc1f6e1-c7f1-41ac-8ce2-32b6b3e57aa3"
|
||||
@@ -25,8 +25,8 @@ If you are using Maven, ensure to add the following dependencies:
|
||||
<type>pom</type>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>biz.paluch.redis</groupId>
|
||||
<artifactId>lettuce</artifactId>
|
||||
<groupId>io.lettuce</groupId>
|
||||
<artifactId>lettuce-core</artifactId>
|
||||
<version>{lettuce-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -79,7 +79,7 @@ Add the following Spring Configuration:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
include::{samples-dir}security/src/main/java/sample/Config.java[tags=class]
|
||||
include::{samples-dir}javaconfig/security/src/main/java/sample/Config.java[tags=class]
|
||||
----
|
||||
|
||||
<1> The `@EnableRedisHttpSession` annotation creates a Spring Bean with the name of `springSessionRepositoryFilter` that implements Filter.
|
||||
@@ -100,7 +100,7 @@ Since our application is already loading Spring configuration using our `Securit
|
||||
.src/main/java/sample/SecurityInitializer.java
|
||||
[source,java]
|
||||
----
|
||||
include::{samples-dir}security/src/main/java/sample/SecurityInitializer.java[tags=class]
|
||||
include::{samples-dir}javaconfig/security/src/main/java/sample/SecurityInitializer.java[tags=class]
|
||||
----
|
||||
|
||||
Last we need to ensure that our Servlet Container (i.e. Tomcat) uses our `springSessionRepositoryFilter` for every request.
|
||||
@@ -112,7 +112,7 @@ You can find an example below:
|
||||
.src/main/java/sample/Initializer.java
|
||||
[source,java]
|
||||
----
|
||||
include::{samples-dir}security/src/main/java/sample/Initializer.java[tags=class]
|
||||
include::{samples-dir}javaconfig/security/src/main/java/sample/Initializer.java[tags=class]
|
||||
----
|
||||
|
||||
NOTE: The name of our class (Initializer) does not matter. What is important is that we extend `AbstractHttpSessionApplicationInitializer`.
|
||||
@@ -132,6 +132,7 @@ You can run the sample by obtaining the {download-url}[source code] and invoking
|
||||
====
|
||||
For the sample to work, you must http://redis.io/download[install Redis 2.8+] on localhost and run it with the default port (6379).
|
||||
Alternatively, you can update the `LettuceConnectionFactory` to point to a Redis server.
|
||||
Another option is to use https://www.docker.com/[Docker] to run Redis on localhost. See https://hub.docker.com/_/redis/[Docker Redis repository] for detailed instructions.
|
||||
====
|
||||
|
||||
----
|
||||
@@ -170,4 +171,4 @@ Alternatively, you can also delete the explicit key. Enter the following into yo
|
||||
|
||||
$ redis-cli del spring:session:sessions:7e8383a4-082c-4ffe-a4bc-c40fd3363c5e
|
||||
|
||||
Now visit the application at http://localhost:8080/ and observe that we are no longer authenticated.
|
||||
Now visit the application at http://localhost:8080/ and observe that we are no longer authenticated.
|
||||
@@ -21,6 +21,7 @@ You can run the sample by obtaining the {download-url}[source code] and invoking
|
||||
====
|
||||
For the sample to work, you must http://redis.io/download[install Redis 2.8+] on localhost and run it with the default port (6379).
|
||||
Alternatively, you can update the `LettuceConnectionFactory` to point to a Redis server.
|
||||
Another option is to use https://www.docker.com/[Docker] to run Redis on localhost. See https://hub.docker.com/_/redis/[Docker Redis repository] for detailed instructions.
|
||||
====
|
||||
|
||||
----
|
||||
@@ -99,7 +100,7 @@ We can obtain the `HttpSessionManager` from the `HttpServletRequest` using the f
|
||||
.src/main/java/sample/UserAccountsFilter.java
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::{samples-dir}users/src/main/java/sample/UserAccountsFilter.java[tags=HttpSessionManager]
|
||||
include::{samples-dir}javaconfig/users/src/main/java/sample/UserAccountsFilter.java[tags=HttpSessionManager]
|
||||
----
|
||||
|
||||
We can now use it to create a URL to add another session.
|
||||
@@ -107,7 +108,7 @@ We can now use it to create a URL to add another session.
|
||||
.src/main/java/sample/UserAccountsFilter.java
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::{samples-dir}users/src/main/java/sample/UserAccountsFilter.java[tags=addAccountUrl]
|
||||
include::{samples-dir}javaconfig/users/src/main/java/sample/UserAccountsFilter.java[tags=addAccountUrl]
|
||||
----
|
||||
|
||||
<1> We have an existing variable named `unauthenticatedAlias`.
|
||||
@@ -148,7 +149,7 @@ For example, if we are currently using the session with the alias of *1*, then t
|
||||
.src/main/webapp/index.jsp
|
||||
[source,xml,indent=0]
|
||||
----
|
||||
include::{samples-dir}users/src/main/webapp/index.jsp[tags=link]
|
||||
include::{samples-dir}javaconfig/users/src/main/webapp/index.jsp[tags=link]
|
||||
----
|
||||
|
||||
will output a link of:
|
||||
@@ -1,169 +0,0 @@
|
||||
= Spring Session - Mongo Repositories
|
||||
Jakub Kubrynski
|
||||
:toc:
|
||||
|
||||
This guide describes how to use Spring Session backed by Mongo.
|
||||
|
||||
NOTE: The completed guide can be found in the <<mongo-sample, mongo sample application>>.
|
||||
|
||||
== Updating Dependencies
|
||||
Before you use Spring Session, you must ensure to update your dependencies.
|
||||
We assume you are working with a working Spring Boot web application.
|
||||
If you are using Maven, ensure to add the following dependencies:
|
||||
|
||||
.pom.xml
|
||||
[source,xml]
|
||||
[subs="verbatim,attributes"]
|
||||
----
|
||||
<dependencies>
|
||||
<!-- ... -->
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.session</groupId>
|
||||
<artifactId>spring-session-data-mongo</artifactId>
|
||||
<version>{spring-session-version}</version>
|
||||
<type>pom</type>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-mongodb</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
----
|
||||
|
||||
ifeval::["{version-snapshot}" == "true"]
|
||||
Since We are using a SNAPSHOT version, we need to ensure to add the Spring Snapshot Maven Repository.
|
||||
Ensure you have the following in your pom.xml:
|
||||
|
||||
.pom.xml
|
||||
[source,xml]
|
||||
----
|
||||
<repositories>
|
||||
|
||||
<!-- ... -->
|
||||
|
||||
<repository>
|
||||
<id>spring-snapshot</id>
|
||||
<url>https://repo.spring.io/libs-snapshot</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
----
|
||||
endif::[]
|
||||
|
||||
ifeval::["{version-milestone}" == "true"]
|
||||
Since We are using a Milestone version, we need to ensure to add the Spring Milestone Maven Repository.
|
||||
Ensure you have the following in your pom.xml:
|
||||
|
||||
.pom.xml
|
||||
[source,xml]
|
||||
----
|
||||
<repository>
|
||||
<id>spring-milestone</id>
|
||||
<url>https://repo.spring.io/libs-milestone</url>
|
||||
</repository>
|
||||
----
|
||||
endif::[]
|
||||
|
||||
[[mongo-spring-configuration]]
|
||||
== Spring Configuration
|
||||
|
||||
After adding the required dependencies, we can create our Spring configuration.
|
||||
The Spring configuration is responsible for creating a Servlet Filter that replaces the `HttpSession` implementation with an implementation backed by Spring Session.
|
||||
|
||||
// tag::config[]
|
||||
All you have to do is to add the following Spring Configuration:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
include::{samples-dir}mongo/src/main/java/sample/config/HttpSessionConfig.java[tags=class]
|
||||
----
|
||||
|
||||
<1> The `@EnableMongoHttpSession` annotation creates a Spring Bean with the name of `springSessionRepositoryFilter` that implements Filter.
|
||||
The filter is what is in charge of replacing the `HttpSession` implementation to be backed by Spring Session.
|
||||
In this instance Spring Session is backed by Mongo.
|
||||
|
||||
<2> We explicitly configure `JdkMongoSessionConverter` since Spring Security's objects cannot be automatically persisted using Jackson (the default if Jackson is on the classpath).
|
||||
|
||||
// end::config[]
|
||||
|
||||
[[boot-mongo-configuration]]
|
||||
== Configuring the Mongo Connection
|
||||
|
||||
Spring Boot automatically creates a `MongoClient` that connects Spring Session to a Mongo Server on localhost on port 27017 (default port).
|
||||
In a production environment you need to ensure to update your configuration to point to your Mongo server.
|
||||
For example, you can include the following in your *application.properties*
|
||||
|
||||
.src/main/resources/application.properties
|
||||
----
|
||||
spring.data.mongodb.host=mongo-srv
|
||||
spring.data.mongodb.port=27018
|
||||
spring.data.mongodb.database=prod
|
||||
----
|
||||
|
||||
For more information, refer to http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-connecting-to-mongodb[Connecting to MongoDB] portion of the Spring Boot documentation.
|
||||
|
||||
[[boot-servlet-configuration]]
|
||||
== Servlet Container Initialization
|
||||
|
||||
Our <<boot-spring-configuration,Spring Configuration>> created a Spring Bean named `springSessionRepositoryFilter` that implements `Filter`.
|
||||
The `springSessionRepositoryFilter` bean is responsible for replacing the `HttpSession` with a custom implementation that is backed by Spring Session.
|
||||
|
||||
In order for our `Filter` to do its magic, Spring needs to load our `Config` class.
|
||||
Last we need to ensure that our Servlet Container (i.e. Tomcat) uses our `springSessionRepositoryFilter` for every request.
|
||||
Fortunately, Spring Boot takes care of both of these steps for us.
|
||||
|
||||
[[mongo-sample]]
|
||||
== Mongo Sample Application
|
||||
|
||||
The Mongo Sample Application demonstrates how to use Spring Session to transparently leverage Mongo to back a web application's `HttpSession` when using Spring Boot.
|
||||
|
||||
[[mongo-running]]
|
||||
=== Running the Mongo Sample Application
|
||||
|
||||
You can run the sample by obtaining the {download-url}[source code] and invoking the following command:
|
||||
|
||||
----
|
||||
$ ./gradlew :samples:mongo:bootRun
|
||||
----
|
||||
|
||||
You should now be able to access the application at http://localhost:8080/
|
||||
|
||||
[[boot-explore]]
|
||||
=== Exploring the security Sample Application
|
||||
|
||||
Try using the application. Enter the following to log in:
|
||||
|
||||
* **Username** _user_
|
||||
* **Password** _password_
|
||||
|
||||
Now click the **Login** button.
|
||||
You should now see a message indicating your are logged in with the user entered previously.
|
||||
The user's information is stored in Mongo rather than Tomcat's `HttpSession` implementation.
|
||||
|
||||
[[mongo-how]]
|
||||
=== How does it work?
|
||||
|
||||
Instead of using Tomcat's `HttpSession`, we are actually persisting the values in Mongo.
|
||||
Spring Session replaces the `HttpSession` with an implementation that is backed by Mongo.
|
||||
When Spring Security's `SecurityContextPersistenceFilter` saves the `SecurityContext` to the `HttpSession` it is then persisted into Mongo.
|
||||
|
||||
When a new `HttpSession` is created, Spring Session creates a cookie named SESSION in your browser that contains the id of your session.
|
||||
Go ahead and view the cookies (click for help with https://developer.chrome.com/devtools/docs/resources#cookies[Chrome] or https://getfirebug.com/wiki/index.php/Cookies_Panel#Cookies_List[Firefox]).
|
||||
|
||||
If you like, you can easily inspect the session using mongo client. For example, on a Linux based system you can type:
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
The sample application uses an embedded MongoDB instance that listens on a randomly allocated port.
|
||||
The port used by embedded MongoDB together with exact command to connect to it is logged during application startup.
|
||||
====
|
||||
|
||||
$ mongo --port ...
|
||||
> use test
|
||||
> db.sessions.find().pretty()
|
||||
|
||||
Alternatively, you can also delete the explicit key. Enter the following into your terminal ensuring to replace `60f17293-839b-477c-bb92-07a9c3658843` with the value of your SESSION cookie:
|
||||
|
||||
> db.sessions.remove({"_id":"60f17293-839b-477c-bb92-07a9c3658843"})
|
||||
|
||||
Now visit the application at http://localhost:8080/ and observe that we are no longer authenticated.
|
||||
@@ -76,7 +76,7 @@ Add the following Spring Configuration:
|
||||
.src/main/webapp/WEB-INF/spring/session.xml
|
||||
[source,xml,indent=0]
|
||||
----
|
||||
include::{samples-dir}httpsession-jdbc-xml/src/main/webapp/WEB-INF/spring/session.xml[tags=beans]
|
||||
include::{samples-dir}xml/jdbc/src/main/webapp/WEB-INF/spring/session.xml[tags=beans]
|
||||
----
|
||||
|
||||
<1> We use the combination of `<context:annotation-config/>` and `JdbcHttpSessionConfiguration` because Spring Session does not yet provide XML Namespace support (see https://github.com/spring-projects/spring-session/issues/104[gh-104]).
|
||||
@@ -101,8 +101,8 @@ We do this with the following configuration:
|
||||
.src/main/webapp/WEB-INF/web.xml
|
||||
[source,xml,indent=0]
|
||||
----
|
||||
include::{samples-dir}httpsession-xml/src/main/webapp/WEB-INF/web.xml[tags=context-param]
|
||||
include::{samples-dir}httpsession-xml/src/main/webapp/WEB-INF/web.xml[tags=listeners]
|
||||
include::{samples-dir}xml/jdbc/src/main/webapp/WEB-INF/web.xml[tags=context-param]
|
||||
include::{samples-dir}xml/jdbc/src/main/webapp/WEB-INF/web.xml[tags=listeners]
|
||||
----
|
||||
|
||||
The http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#context-create[ContextLoaderListener] reads the contextConfigLocation and picks up our session.xml configuration.
|
||||
@@ -113,7 +113,7 @@ The following snippet performs this last step for us:
|
||||
.src/main/webapp/WEB-INF/web.xml
|
||||
[source,xml,indent=0]
|
||||
----
|
||||
include::{samples-dir}httpsession-xml/src/main/webapp/WEB-INF/web.xml[tags=springSessionRepositoryFilter]
|
||||
include::{samples-dir}xml/jdbc/src/main/webapp/WEB-INF/web.xml[tags=springSessionRepositoryFilter]
|
||||
----
|
||||
|
||||
The http://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/filter/DelegatingFilterProxy.html[DelegatingFilterProxy] will look up a Bean by the name of `springSessionRepositoryFilter` and cast it to a `Filter`.
|
||||
@@ -150,7 +150,7 @@ We interact with the standard `HttpSession` in the `SessionServlet` shown below:
|
||||
.src/main/java/sample/SessionServlet.java
|
||||
[source,java]
|
||||
----
|
||||
include::{samples-dir}httpsession-jdbc-xml/src/main/java/sample/SessionServlet.java[tags=class]
|
||||
include::{samples-dir}xml/jdbc/src/main/java/sample/SessionServlet.java[tags=class]
|
||||
----
|
||||
|
||||
Instead of using Tomcat's `HttpSession`, we are actually persisting the values in H2 database.
|
||||
@@ -24,8 +24,8 @@ If you are using Maven, ensure to add the following dependencies:
|
||||
<type>pom</type>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>biz.paluch.redis</groupId>
|
||||
<artifactId>lettuce</artifactId>
|
||||
<groupId>io.lettuce</groupId>
|
||||
<artifactId>lettuce-core</artifactId>
|
||||
<version>{lettuce-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -81,7 +81,7 @@ Add the following Spring Configuration:
|
||||
.src/main/webapp/WEB-INF/spring/session.xml
|
||||
[source,xml,indent=0]
|
||||
----
|
||||
include::{samples-dir}httpsession-xml/src/main/webapp/WEB-INF/spring/session.xml[tags=beans]
|
||||
include::{samples-dir}xml/redis/src/main/webapp/WEB-INF/spring/session.xml[tags=beans]
|
||||
----
|
||||
|
||||
<1> We use the combination of `<context:annotation-config/>` and `RedisHttpSessionConfiguration` because Spring Session does not yet provide XML Namespace support (see https://github.com/spring-projects/spring-session/issues/104[gh-104]).
|
||||
@@ -104,8 +104,8 @@ We do this with the following configuration:
|
||||
.src/main/webapp/WEB-INF/web.xml
|
||||
[source,xml,indent=0]
|
||||
----
|
||||
include::{samples-dir}httpsession-xml/src/main/webapp/WEB-INF/web.xml[tags=context-param]
|
||||
include::{samples-dir}httpsession-xml/src/main/webapp/WEB-INF/web.xml[tags=listeners]
|
||||
include::{samples-dir}xml/redis/src/main/webapp/WEB-INF/web.xml[tags=context-param]
|
||||
include::{samples-dir}xml/redis/src/main/webapp/WEB-INF/web.xml[tags=listeners]
|
||||
----
|
||||
|
||||
The http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#context-create[ContextLoaderListener] reads the contextConfigLocation and picks up our session.xml configuration.
|
||||
@@ -116,7 +116,7 @@ The following snippet performs this last step for us:
|
||||
.src/main/webapp/WEB-INF/web.xml
|
||||
[source,xml,indent=0]
|
||||
----
|
||||
include::{samples-dir}httpsession-xml/src/main/webapp/WEB-INF/web.xml[tags=springSessionRepositoryFilter]
|
||||
include::{samples-dir}xml/redis/src/main/webapp/WEB-INF/web.xml[tags=springSessionRepositoryFilter]
|
||||
----
|
||||
|
||||
The http://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/filter/DelegatingFilterProxy.html[DelegatingFilterProxy] will look up a Bean by the name of `springSessionRepositoryFilter` and cast it to a `Filter`.
|
||||
@@ -135,6 +135,7 @@ You can run the sample by obtaining the {download-url}[source code] and invoking
|
||||
====
|
||||
For the sample to work, you must http://redis.io/download[install Redis 2.8+] on localhost and run it with the default port (6379).
|
||||
Alternatively, you can update the `LettuceConnectionFactory` to point to a Redis server.
|
||||
Another option is to use https://www.docker.com/[Docker] to run Redis on localhost. See https://hub.docker.com/_/redis/[Docker Redis repository] for detailed instructions.
|
||||
====
|
||||
|
||||
----
|
||||
@@ -159,7 +160,7 @@ We interact with the standard `HttpSession` in the `SessionServlet` shown below:
|
||||
.src/main/java/sample/SessionServlet.java
|
||||
[source,java]
|
||||
----
|
||||
include::{samples-dir}httpsession-xml/src/main/java/sample/SessionServlet.java[tags=class]
|
||||
include::{samples-dir}xml/redis/src/main/java/sample/SessionServlet.java[tags=class]
|
||||
----
|
||||
|
||||
Instead of using Tomcat's `HttpSession`, we are actually persisting the values in Redis.
|
||||
@@ -176,4 +177,4 @@ Alternatively, you can also delete the explicit key. Enter the following into yo
|
||||
|
||||
$ redis-cli del spring:session:sessions:7e8383a4-082c-4ffe-a4bc-c40fd3363c5e
|
||||
|
||||
Now visit the application at http://localhost:8080/ and observe that the attribute we added is no longer displayed.
|
||||
Now visit the application at http://localhost:8080/ and observe that the attribute we added is no longer displayed.
|
||||
@@ -23,112 +23,113 @@ Additional features include:
|
||||
|
||||
* <<websocket,WebSocket>> - provides the ability to keep the `HttpSession` alive when receiving WebSocket messages
|
||||
|
||||
== What's New in 1.2
|
||||
== What's New in 1.3
|
||||
|
||||
Below are the highlights of what is new in Spring Session 1.2. You can find a complete list of what's new in https://github.com/spring-projects/spring-session/issues?utf8=%E2%9C%93&q=milestone%3A%221.2.0+RC1%22[1.2.0 RC1] by referring to the changelog.
|
||||
Below are the highlights of what is new in Spring Session 1.3. You can find a complete list of what's new by referring to the changelogs of
|
||||
https://github.com/spring-projects/spring-session/milestone/6?closed=1[1.3.0.M1],
|
||||
https://github.com/spring-projects/spring-session/milestone/18?closed=1[1.3.0.M2],
|
||||
https://github.com/spring-projects/spring-session/milestone/16?closed=1[1.3.0.RC1], and
|
||||
https://github.com/spring-projects/spring-session/milestone/19?closed=1[1.3.0.RELEASE].
|
||||
|
||||
* First class support for http://docs.spring.io/spring-session/docs/1.3.0.RELEASE/reference/html5/#httpsession-hazelcast[Hazelcast]
|
||||
* First class support for http://docs.spring.io/spring-session/docs/1.3.0.RELEASE/reference/html5/#spring-security-concurrent-sessions-how[Spring Security's concurrent session management]
|
||||
* Added https://github.com/maseev/spring-session-orientdb[OrientDB Community Extension]
|
||||
* Added <<httpsession-jdbc,JdbcOperationsSessionRepository>> (See https://github.com/spring-projects/spring-session/issues/364[#364]).
|
||||
* Added <<httpsession-mongo,MongoOperationsSessionRepository>> (See https://github.com/spring-projects/spring-session/pull/371[#371]).
|
||||
* SessionRepositoryFilter caches null session lookup (See https://github.com/spring-projects/spring-session/issues/423[#423])
|
||||
* link:guides/grails3.html[Grails 3 Sample & Guide]
|
||||
* Improved Workspace Setup (See https://github.com/spring-projects/spring-session/pull/417[#417])
|
||||
* https://github.com/spring-projects/spring-session/tree/1.3.0.RELEASE/samples/httpsession-redis-json[GenericJackson2JsonRedisSerializer sample] with Spring Security's new Jackson Support
|
||||
* Guides now https://github.com/spring-projects/spring-session/pull/652[use Lettuce]
|
||||
* `spring.session.cleanup.cron.expression` can be used to override the cleanup task’s cron expression
|
||||
* Lots of performance improvements and bug fixes
|
||||
|
||||
[[samples]]
|
||||
== Samples and Guides (Start Here)
|
||||
|
||||
If you are looking to get started with Spring Session, the best place to start is our Sample Applications.
|
||||
|
||||
.Sample Applications
|
||||
.Sample Applications using Spring Boot
|
||||
|===
|
||||
| Source | Description | Guide
|
||||
|
||||
| {gh-samples-url}httpsession[HttpSession]
|
||||
| Demonstrates how to use Spring Session to replace the `HttpSession` with a Redis store.
|
||||
| link:guides/httpsession.html[HttpSession Guide]
|
||||
| {gh-samples-url}boot/redis[HttpSession with Redis]
|
||||
| Demonstrates how to use Spring Session to replace the `HttpSession` with Redis.
|
||||
| link:guides/boot-redis.html[HttpSession with Redis Guide]
|
||||
|
||||
| {gh-samples-url}httpsession-xml[HttpSession XML]
|
||||
| Demonstrates how to use Spring Session to replace the `HttpSession` with a Redis store using XML based configuration.
|
||||
| link:guides/httpsession-xml.html[HttpSession XML Guide]
|
||||
| {gh-samples-url}boot/jdbc[HttpSession with JDBC]
|
||||
| Demonstrates how to use Spring Session to replace the `HttpSession` with a relational database store.
|
||||
| link:guides/boot-jdbc.html[HttpSession with JDBC Guide]
|
||||
|
||||
| {gh-samples-url}httpsession-gemfire-boot[HttpSession with GemFire using Spring Boot]
|
||||
| Demonstrates how to use Spring Session to replace the `HttpSession` with GemFire using a Client/Server topology in a Spring Boot application.
|
||||
| link:guides/httpsession-gemfire-boot.html[HttpSession GemFire Boot Guide]
|
||||
| {gh-samples-url}boot/findbyusername[Find by Username]
|
||||
| Demonstrates how to use Spring Session to find sessions by username.
|
||||
| link:guides/boot-findbyusername.html[Find by Username Guide]
|
||||
|
||||
| {gh-samples-url}httpsession-gemfire-clientserver[HttpSession with GemFire (Client/Server)]
|
||||
| Demonstrates how to use Spring Session to replace the `HttpSession` with GemFire using a Client/Server topology.
|
||||
| link:guides/httpsession-gemfire-clientserver.html[HttpSession GemFire Client/Server Guide]
|
||||
| {gh-samples-url}boot/websocket[WebSockets]
|
||||
| Demonstrates how to use Spring Session with WebSockets.
|
||||
| link:guides/boot-websocket.html[WebSockets Guide]
|
||||
|
||||
| {gh-samples-url}httpsession-gemfire-clientserver-xml[HttpSession with GemFire (Client/Server) using XML]
|
||||
| Demonstrates how to use Spring Session to replace the `HttpSession` with GemFire using a Client/Server topology configured with XML.
|
||||
| link:guides/httpsession-gemfire-clientserver-xml.html[HttpSession GemFire Client/Server XML Guide]
|
||||
| {gh-samples-url}boot/redis-json[HttpSession with Redis JSON serialization]
|
||||
| Demonstrates how to use Spring Session to replace the `HttpSession` with Redis using JSON serialization.
|
||||
| TBD
|
||||
|
||||
| {gh-samples-url}httpsession-gemfire-p2p[HttpSession with GemFire (P2P)]
|
||||
| Demonstrates how to use Spring Session to replace the `HttpSession` with GemFire using a P2P topology.
|
||||
| link:guides/httpsession-gemfire-p2p.html[HttpSession GemFire P2P Guide]
|
||||
|===
|
||||
|
||||
| {gh-samples-url}httpsession-gemfire-p2p-xml[HttpSession with GemFire (P2P) using XML]
|
||||
| Demonstrates how to use Spring Session to replace the `HttpSession` with GemFire using a P2P topology configured with XML.
|
||||
| link:guides/httpsession-gemfire-p2p-xml.html[HttpSession GemFire P2P XML Guide]
|
||||
.Sample Applications using Spring Java based configuration
|
||||
|===
|
||||
| Source | Description | Guide
|
||||
|
||||
| {gh-samples-url}custom-cookie[Custom Cookie]
|
||||
| {gh-samples-url}javaconfig/redis[HttpSession with Redis]
|
||||
| Demonstrates how to use Spring Session to replace the `HttpSession` with Redis.
|
||||
| link:guides/java-redis.html[HttpSession with Redis Guide]
|
||||
|
||||
| {gh-samples-url}javaconfig/jdbc[HttpSession with JDBC]
|
||||
| Demonstrates how to use Spring Session to replace the `HttpSession` with a relational database store.
|
||||
| link:guides/java-jdbc.html[HttpSession with JDBC Guide]
|
||||
|
||||
| {gh-samples-url}javaconfig/hazelcast[HttpSession with Hazelcast]
|
||||
| Demonstrates how to use Spring Session to replace the `HttpSession` with Hazelcast.
|
||||
| link:guides/java-hazelcast.html[HttpSession with Hazelcast Guide]
|
||||
|
||||
| {gh-samples-url}javaconfig/custom-cookie[Custom Cookie]
|
||||
| Demonstrates how to use Spring Session and customize the cookie.
|
||||
| link:guides/custom-cookie.html[Custom Cookie Guide]
|
||||
| link:guides/java-custom-cookie.html[Custom Cookie Guide]
|
||||
|
||||
| {gh-samples-url}boot[Spring Boot]
|
||||
| Demonstrates how to use Spring Session with Spring Boot.
|
||||
| link:guides/boot.html[Spring Boot Guide]
|
||||
| {gh-samples-url}javaconfig/security[Spring Security]
|
||||
| Demonstrates how to use Spring Session with an existing Spring Security application.
|
||||
| link:guides/java-security.html[Spring Security Guide]
|
||||
|
||||
| {gh-samples-url}grails3[Grails 3]
|
||||
| {gh-samples-url}javaconfig/rest[REST]
|
||||
| Demonstrates how to use Spring Session in a REST application to support authenticating with a header.
|
||||
| link:guides/java-rest.html[REST Guide]
|
||||
|
||||
| {gh-samples-url}javaconfig/users[Multiple Users]
|
||||
| Demonstrates how to use Spring Session to manage multiple simultaneous browser sessions (i.e Google Accounts).
|
||||
| link:guides/java-users.html[Multiple Users Guide]
|
||||
|
||||
|===
|
||||
|
||||
.Sample Applications using Spring XML based configuration
|
||||
|===
|
||||
| Source | Description | Guide
|
||||
|
||||
| {gh-samples-url}xml/redis[HttpSession with Redis]
|
||||
| Demonstrates how to use Spring Session to replace the `HttpSession` with a Redis store.
|
||||
| link:guides/xml-redis.html[HttpSession with Redis Guide]
|
||||
|
||||
| {gh-samples-url}xml/jdbc[HttpSession with JDBC]
|
||||
| Demonstrates how to use Spring Session to replace the `HttpSession` with a relational database store.
|
||||
| link:guides/xml-jdbc.html[HttpSession with JDBC Guide]
|
||||
|
||||
|===
|
||||
|
||||
.Misc sample Applications
|
||||
|===
|
||||
| Source | Description | Guide
|
||||
|
||||
| {gh-samples-url}misc/grails3[Grails 3]
|
||||
| Demonstrates how to use Spring Session with Grails 3.
|
||||
| link:guides/grails3.html[Grails 3 Guide]
|
||||
|
||||
| {gh-samples-url}security[Spring Security]
|
||||
| Demonstrates how to use Spring Session with an existing Spring Security application.
|
||||
| link:guides/security.html[Spring Security Guide]
|
||||
|
||||
| {gh-samples-url}rest[REST]
|
||||
| Demonstrates how to use Spring Session in a REST application to support authenticating with a header.
|
||||
| link:guides/rest.html[REST Guide]
|
||||
|
||||
| {gh-samples-url}findbyusername[Find by Username]
|
||||
| Demonstrates how to use Spring Session to find sessions by username.
|
||||
| link:guides/findbyusername.html[Find by Username]
|
||||
|
||||
| {gh-samples-url}users[Multiple Users]
|
||||
| Demonstrates how to use Spring Session to manage multiple simultaneous browser sessions (i.e Google Accounts).
|
||||
| link:guides/users.html[Manage Multiple Users Guide]
|
||||
|
||||
| {gh-samples-url}websocket[WebSocket]
|
||||
| Demonstrates how to use Spring Session with WebSockets.
|
||||
| link:guides/websocket.html[WebSocket Guide]
|
||||
|
||||
| {gh-samples-url}mongo[Mongo]
|
||||
| Demonstrates how to use Spring Session with Mongo.
|
||||
| link:guides/mongo.html[Mongo Guide]
|
||||
|
||||
[[samples-hazelcast]]
|
||||
| {gh-samples-url}hazelcast[Hazelcast]
|
||||
| Demonstrates how to use Spring Session with Hazelcast.
|
||||
| {gh-samples-url}misc/hazelcast[Hazelcast]
|
||||
| Demonstrates how to use Spring Session with Hazelcast in a Java EE application.
|
||||
| TBD
|
||||
|
||||
[[samples-hazelcast-spring]]
|
||||
| {gh-samples-url}hazelcast-spring[Hazelcast Spring]
|
||||
| Demonstrates how to use Spring Session and Hazelcast with an existing Spring Security application.
|
||||
| link:guides/hazelcast-spring.html[Hazelcast Spring Guide]
|
||||
|
||||
| {gh-samples-url}httpsession-jdbc[HttpSession JDBC]
|
||||
| Demonstrates how to use Spring Session to replace the `HttpSession` with a relational database store.
|
||||
| link:guides/httpsession-jdbc.html[HttpSession JDBC Guide]
|
||||
|
||||
| {gh-samples-url}httpsession-jdbc-xml[HttpSession JDBC XML]
|
||||
| Demonstrates how to use Spring Session to replace the `HttpSession` with a relational database store using XML based configuration.
|
||||
| link:guides/httpsession-jdbc-xml.html[HttpSession JDBC XML Guide]
|
||||
|
||||
| {gh-samples-url}httpsession-jdbc-boot[HttpSession JDBC Spring Boot]
|
||||
| Demonstrates how to use Spring Session to replace the `HttpSession` with a relational database store when using Spring Boot.
|
||||
| link:guides/httpsession-jdbc-boot.html[HttpSession JDBC Spring Boot Guide]
|
||||
|
||||
|===
|
||||
|
||||
[[httpsession]]
|
||||
@@ -163,7 +164,7 @@ This section describes how to use Redis to back `HttpSession` using Java based c
|
||||
NOTE: The <<samples, HttpSession Sample>> provides a working sample on how to integrate Spring Session and `HttpSession` using Java configuration.
|
||||
You can read the basic steps for integration below, but you are encouraged to follow along with the detailed HttpSession Guide when integrating with your own application.
|
||||
|
||||
include::guides/httpsession.adoc[tags=config,leveloffset=+3]
|
||||
include::guides/java-redis.adoc[tags=config,leveloffset=+3]
|
||||
|
||||
[[httpsession-redis-xml]]
|
||||
==== Redis XML Based Configuration
|
||||
@@ -173,111 +174,7 @@ This section describes how to use Redis to back `HttpSession` using XML based co
|
||||
NOTE: The <<samples, HttpSession XML Sample>> provides a working sample on how to integrate Spring Session and `HttpSession` using XML configuration.
|
||||
You can read the basic steps for integration below, but you are encouraged to follow along with the detailed HttpSession XML Guide when integrating with your own application.
|
||||
|
||||
include::guides/httpsession-xml.adoc[tags=config,leveloffset=+3]
|
||||
|
||||
[[httpsession-gemfire]]
|
||||
=== HttpSession with Pivotal GemFire
|
||||
|
||||
When https://pivotal.io/big-data/pivotal-gemfire[Pivotal GemFire] is used with Spring Session, a web application's
|
||||
`HttpSession` can be replaced with a **clustered** implementation managed by GemFire and conveniently accessed
|
||||
with Spring Session's API.
|
||||
|
||||
The two most common topologies to manage Spring Sessions using GemFire include:
|
||||
|
||||
* <<httpsession-gemfire-clientserver,Client-Server>>
|
||||
* <<httpsession-gemfire-p2p,Peer-To-Peer (P2P)>>
|
||||
|
||||
Additionally, GemFire supports site-to-site replication using http://gemfire.docs.pivotal.io/docs-gemfire/topologies_and_comm/multi_site_configuration/chapter_overview.html[WAN functionality].
|
||||
The ability to configure and use GemFire's WAN support is independent of Spring Session, and is beyond the scope
|
||||
of this document. More details on GemFire WAN functionality can be found http://docs.spring.io/spring-data-gemfire/docs/current/reference/html/#bootstrap:gateway[here].
|
||||
|
||||
[[httpsession-gemfire-clientserver]]
|
||||
==== GemFire Client-Server
|
||||
|
||||
The http://gemfire.docs.pivotal.io/docs-gemfire/latest/topologies_and_comm/cs_configuration/chapter_overview.html[Client-Server]
|
||||
topology will probably be the more common configuration preference for users when using GemFire as a provider in
|
||||
Spring Session since a GemFire server will have significantly different and unique JVM heap requirements when compared
|
||||
to the application. Using a client-server topology enables an application to manage (e.g. replicate) application state
|
||||
independently from other application processes.
|
||||
|
||||
In a client-server topology, an application using Spring Session will open a client cache connection to a (remote)
|
||||
GemFire server cluster to manage and provide consistent access to all `HttpSession` state.
|
||||
|
||||
You can configure a Client-Server topology with either:
|
||||
|
||||
* <<httpsession-gemfire-clientserver-java,Java-based Configuration>>
|
||||
* <<httpsession-gemfire-clientserver-xml,XML-based Configuration>>
|
||||
|
||||
[[httpsession-gemfire-clientserver-java]]
|
||||
===== GemFire Client-Server Java-based Configuration
|
||||
|
||||
This section describes how to use GemFire's Client-Server topology to back an `HttpSession` with Java-based configuration.
|
||||
|
||||
NOTE: The <<samples,HttpSession with GemFire (Client-Server) Sample>> provides a working sample on how to integrate
|
||||
Spring Session and GemFire to replace the HttpSession using Java configuration. You can read the basic steps for
|
||||
integration below, but you are encouraged to follow along with the detailed HttpSession with GemFire (Client-Server)
|
||||
Guide when integrating with your own application.
|
||||
|
||||
include::guides/httpsession-gemfire-clientserver.adoc[tags=config,leveloffset=+3]
|
||||
|
||||
[[http-session-gemfire-clientserver-xml]]
|
||||
===== GemFire Client-Server XML-based Configuration
|
||||
|
||||
This section describes how to use GemFire's Client-Server topology to back an `HttpSession` with XML-based configuration.
|
||||
|
||||
NOTE: The <<samples,HttpSession with GemFire (Client-Server) using XML Sample>> provides a working sample on how to
|
||||
integrate Spring Session and GemFire to replace the `HttpSession` using XML configuration. You can read the basic steps
|
||||
for integration below, but you are encouraged to follow along with the detailed HttpSession with GemFire (Client-Server)
|
||||
using XML Guide when integrating with your own application.
|
||||
|
||||
include::guides/httpsession-gemfire-clientserver-xml.adoc[tags=config,leveloffset=+3]
|
||||
|
||||
[[httpsession-gemfire-p2p]]
|
||||
==== GemFire Peer-To-Peer (P2P)
|
||||
|
||||
Perhaps less common would be to configure the Spring Session application as a peer member in the GemFire cluster using
|
||||
the http://gemfire.docs.pivotal.io/docs-gemfire/latest/topologies_and_comm/p2p_configuration/chapter_overview.html[Peer-To-Peer (P2P)] topology.
|
||||
In this configuration, a Spring Session application would be an actual data node (server) in the GemFire cluster,
|
||||
and **not** a cache client as before.
|
||||
|
||||
One advantage to this approach is the proximity of the application to the application's state (i.e. it's data). However,
|
||||
there are other effective means of accomplishing similar data dependent computations, such as using GemFire's
|
||||
http://gemfire.docs.pivotal.io/docs-gemfire/latest/developing/function_exec/chapter_overview.html[Function Execution].
|
||||
Any of GemFire's other http://gemfire.docs.pivotal.io/docs-gemfire/latest/getting_started/product_intro.html[features]
|
||||
can be used when GemFire is serving as a provider in Spring Session.
|
||||
|
||||
P2P is very useful for both testing purposes as well as smaller, more focused and self-contained applications,
|
||||
such as those found in a microservices architecture, and will most certainly improve on your application's latency,
|
||||
throughput and consistency needs.
|
||||
|
||||
You can configure a Peer-To-Peer (P2P) topology with either:
|
||||
|
||||
* <<httpsession-gemfire-p2p-java,Java-based Configuration>>
|
||||
* <<httpsession-gemfire-p2p-xml,XML-based Configuration>>
|
||||
|
||||
[[httpsession-gemfire-p2p-java]]
|
||||
===== GemFire Peer-To-Peer (P2P) Java-based Configuration
|
||||
|
||||
This section describes how to use GemFire's Peer-To-Peer (P2P) topology to back an `HttpSession` using Java-based configuration.
|
||||
|
||||
NOTE: The <<samples, HttpSession with GemFire (P2P) Sample>> provides a working sample on how to integrate
|
||||
Spring Session and GemFire to replace the `HttpSession` using Java configuration. You can read the basic steps
|
||||
for integration below, but you are encouraged to follow along with the detailed HttpSession with GemFire (P2P) Guide
|
||||
when integrating with your own application.
|
||||
|
||||
include::guides/httpsession-gemfire-p2p.adoc[tags=config,leveloffset=+3]
|
||||
|
||||
[[httpsession-gemfire-p2p-xml]]
|
||||
===== GemFire Peer-To-Peer (P2P) XML-based Configuration
|
||||
|
||||
This section describes how to use GemFire's Peer-To-Peer (P2P) topology to back an `HttpSession` using XML-based configuration.
|
||||
|
||||
NOTE: The <<samples, HttpSession with GemFire (P2P) using XML Sample>> provides a working sample on how to integrate
|
||||
Spring Session and GemFire to replace the `HttpSession` using XML configuration. You can read the basic steps for
|
||||
integration below, but you are encouraged to follow along with the detailed HttpSession with GemFire (P2P) using XML
|
||||
Guide when integrating with your own application.
|
||||
|
||||
include::guides/httpsession-gemfire-p2p-xml.adoc[tags=config,leveloffset=+3]
|
||||
include::guides/xml-redis.adoc[tags=config,leveloffset=+3]
|
||||
|
||||
[[httpsession-jdbc]]
|
||||
=== HttpSession with JDBC
|
||||
@@ -297,7 +194,7 @@ This section describes how to use a relational database to back `HttpSession` us
|
||||
NOTE: The <<samples, HttpSession JDBC Sample>> provides a working sample on how to integrate Spring Session and `HttpSession` using Java configuration.
|
||||
You can read the basic steps for integration below, but you are encouraged to follow along with the detailed HttpSession JDBC Guide when integrating with your own application.
|
||||
|
||||
include::guides/httpsession-jdbc.adoc[tags=config,leveloffset=+3]
|
||||
include::guides/java-jdbc.adoc[tags=config,leveloffset=+3]
|
||||
|
||||
[[httpsession-jdbc-xml]]
|
||||
==== JDBC XML Based Configuration
|
||||
@@ -307,7 +204,7 @@ This section describes how to use a relational database to back `HttpSession` us
|
||||
NOTE: The <<samples, HttpSession JDBC XML Sample>> provides a working sample on how to integrate Spring Session and `HttpSession` using XML configuration.
|
||||
You can read the basic steps for integration below, but you are encouraged to follow along with the detailed HttpSession JDBC XML Guide when integrating with your own application.
|
||||
|
||||
include::guides/httpsession-jdbc-xml.adoc[tags=config,leveloffset=+3]
|
||||
include::guides/xml-jdbc.adoc[tags=config,leveloffset=+3]
|
||||
|
||||
[[httpsession-jdbc-boot]]
|
||||
==== JDBC Spring Boot Based Configuration
|
||||
@@ -317,58 +214,7 @@ This section describes how to use a relational database to back `HttpSession` wh
|
||||
NOTE: The <<samples, HttpSession JDBC Spring Boot Sample>> provides a working sample on how to integrate Spring Session and `HttpSession` using Spring Boot.
|
||||
You can read the basic steps for integration below, but you are encouraged to follow along with the detailed HttpSession JDBC Spring Boot Guide when integrating with your own application.
|
||||
|
||||
include::guides/httpsession-jdbc-boot.adoc[tags=config,leveloffset=+3]
|
||||
|
||||
[[httpsession-mongo]]
|
||||
=== HttpSession with Mongo
|
||||
|
||||
Using Spring Session with `HttpSession` is enabled by adding a Servlet Filter before anything that uses the `HttpSession`.
|
||||
|
||||
This section describes how to use Mongo to back `HttpSession` using Java based configuration.
|
||||
|
||||
NOTE: The <<samples, HttpSession Mongo Sample>> provides a working sample on how to integrate Spring Session and `HttpSession` using Java configuration.
|
||||
You can read the basic steps for integration below, but you are encouraged to follow along with the detailed HttpSession Guide when integrating with your own application.
|
||||
|
||||
include::guides/mongo.adoc[tags=config,leveloffset=+3]
|
||||
|
||||
==== Session serialization mechanisms
|
||||
|
||||
To be able to persist session objects in MongoDB we need to provide the serialization/deserialization mechanism.
|
||||
Depending on your classpath Spring Session will choose one of two build-in converters:
|
||||
|
||||
* `JacksonMongoSessionConverter` when `ObjectMapper` class is available, or
|
||||
* `JdkMongoSessionConverter` otherwise.
|
||||
|
||||
===== JacksonMongoSessionConverter
|
||||
|
||||
This mechanism uses Jackson to serialize session objects to/from JSON.
|
||||
`JacksonMongoSessionConverter` will be the default when Jackson is detected on the classpath and the user has not explicitly registered a `AbstractMongoSessionConverter` Bean.
|
||||
|
||||
If you would like to provide custom Jackson modules you can do it by explicitly registering `JacksonMongoSessionConverter`:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::{docs-test-dir}docs/http/MongoJacksonSessionConfiguration.java[tags=config]
|
||||
----
|
||||
|
||||
==== JdkMongoSessionConverter
|
||||
|
||||
`JdkMongoSessionConverter` uses standard Java serialization to persist session attributes map to MongoDB in a binary form.
|
||||
However, standard session elements like id, access time, etc are still written as a plain Mongo objects and can be read and queried without additional effort.
|
||||
`JdkMongoSessionConverter` is used if Jackson is not on the classpath and no explicit `AbstractMongoSessionConverter` Bean has been defined.
|
||||
You can explicitly register `JdkMongoSessionConverter` by defining it as a Bean.
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::{docs-test-dir}docs/http/MongoJdkSessionConfiguration.java[tags=config]
|
||||
----
|
||||
|
||||
There is also a constructor taking `Serializer` and `Deserializer` objects, allowing you to pass custom implementations, which is especially important when you want to use non-default classloader.
|
||||
|
||||
==== Using custom converters
|
||||
|
||||
You can create your own session converter by extending `AbstractMongoSessionConverter` class.
|
||||
The implementation will be used for serializing, deserializing your objects and for providing queries to access the session.
|
||||
include::guides/boot-jdbc.adoc[tags=config,leveloffset=+3]
|
||||
|
||||
[[httpsession-hazelcast]]
|
||||
=== HttpSession with Hazelcast
|
||||
@@ -380,7 +226,7 @@ This section describes how to use Hazelcast to back `HttpSession` using Java bas
|
||||
NOTE: The <<samples, Hazelcast Spring Sample>> provides a working sample on how to integrate Spring Session and `HttpSession` using Java configuration.
|
||||
You can read the basic steps for integration below, but you are encouraged to follow along with the detailed Hazelcast Spring Guide when integrating with your own application.
|
||||
|
||||
include::guides/hazelcast-spring.adoc[tags=config,leveloffset=+2]
|
||||
include::guides/java-hazelcast.adoc[tags=config,leveloffset=+2]
|
||||
|
||||
[[httpsession-how]]
|
||||
=== How HttpSession Integration Works
|
||||
@@ -447,7 +293,7 @@ This provides the ability to support authenticating with multiple users in the s
|
||||
NOTE: The <<samples,Manage Multiple Users Guide>> provides a complete working example of managing multiple users in the same browser instance.
|
||||
You can follow the basic steps for integration below, but you are encouraged to follow along with the detailed Manage Multiple Users Guide when integrating with your own application.
|
||||
|
||||
include::guides/users.adoc[tags=how-does-it-work,leveloffset=+1]
|
||||
include::guides/java-users.adoc[tags=how-does-it-work,leveloffset=+1]
|
||||
|
||||
[[httpsession-rest]]
|
||||
=== HttpSession & RESTful APIs
|
||||
@@ -458,7 +304,7 @@ Spring Session can work with RESTful APIs by allowing the session to be provided
|
||||
NOTE: The <<samples, REST Sample>> provides a working sample on how to use Spring Session in a REST application to support authenticating with a header.
|
||||
You can follow the basic steps for integration below, but you are encouraged to follow along with the detailed REST Guide when integrating with your own application.
|
||||
|
||||
include::guides/rest.adoc[tags=config,leveloffset=+2]
|
||||
include::guides/java-rest.adoc[tags=config,leveloffset=+2]
|
||||
|
||||
[[httpsession-httpsessionlistener]]
|
||||
=== HttpSessionListener
|
||||
@@ -491,7 +337,7 @@ include::{docs-test-resources-dir}docs/http/HttpSessionListenerXmlTests-context.
|
||||
|
||||
Spring Session provides transparent integration with Spring's WebSocket support.
|
||||
|
||||
include::guides/websocket.adoc[tags=disclaimer,leveloffset=+1]
|
||||
include::guides/boot-websocket.adoc[tags=disclaimer,leveloffset=+1]
|
||||
|
||||
[[websocket-why]]
|
||||
=== Why Spring Session & WebSockets?
|
||||
@@ -517,7 +363,7 @@ You can follow the basic steps for integration below, but you are encouraged to
|
||||
|
||||
Before using WebSocket integration, you should be sure that you have <<httpsession>> working first.
|
||||
|
||||
include::guides/websocket.adoc[tags=config,leveloffset=+2]
|
||||
include::guides/boot-websocket.adoc[tags=config,leveloffset=+2]
|
||||
|
||||
[[spring-security]]
|
||||
== Spring Security Integration
|
||||
@@ -570,7 +416,7 @@ include::{docs-test-dir}docs/security/SecurityConfiguration.java[tags=class]
|
||||
----
|
||||
|
||||
This assumes that you've also configured Spring Session to provide a `FindByIndexNameSessionRepository` that
|
||||
returns `ExpiringSession` instances.
|
||||
returns `Session` instances.
|
||||
|
||||
When using XML configuration, it would look something like this:
|
||||
[source,xml,indent=0]
|
||||
@@ -579,7 +425,7 @@ include::{docs-test-resources-dir}docs/security/security-config.xml[tags=config]
|
||||
----
|
||||
|
||||
This assumes that your Spring Session `SessionRegistry` bean is called `sessionRegistry`, which is the name used by all
|
||||
`SpringHttpSessionConfiguration` subclasses except for the one for MongoDB: there it's called `mongoSessionRepository`.
|
||||
`SpringHttpSessionConfiguration` subclasses.
|
||||
|
||||
[[spring-security-concurrent-sessions-limitations]]
|
||||
=== Limitations
|
||||
@@ -612,11 +458,7 @@ include::{indexdoc-tests}[tags=repository-demo]
|
||||
<5> We retrieve the `Session` from the `SessionRepository`.
|
||||
<6> We obtain the persisted `User` from our `Session` without the need for explicitly casting our attribute.
|
||||
|
||||
[[api-expiringsession]]
|
||||
=== ExpiringSession
|
||||
|
||||
An `ExpiringSession` extends a `Session` by providing attributes related to the `Session` instance's expiration.
|
||||
If there is no need to interact with the expiration information, prefer using the more simple `Session` API.
|
||||
`Session` API also provides attributes related to the `Session` instance's expiration.
|
||||
|
||||
Typical usage might look like the following:
|
||||
|
||||
@@ -625,17 +467,17 @@ Typical usage might look like the following:
|
||||
include::{indexdoc-tests}[tags=expire-repository-demo]
|
||||
----
|
||||
|
||||
<1> We create a `SessionRepository` instance with a generic type, `S`, that extends `ExpiringSession`. The generic type is defined in our class.
|
||||
<2> We create a new `ExpiringSession` using our `SessionRepository` and assign it to a variable of type `S`.
|
||||
<3> We interact with the `ExpiringSession`.
|
||||
In our example, we demonstrate updating the amount of time the `ExpiringSession` can be inactive before it expires.
|
||||
<4> We now save the `ExpiringSession`.
|
||||
<1> We create a `SessionRepository` instance with a generic type, `S`, that extends `Session`. The generic type is defined in our class.
|
||||
<2> We create a new `Session` using our `SessionRepository` and assign it to a variable of type `S`.
|
||||
<3> We interact with the `Session`.
|
||||
In our example, we demonstrate updating the amount of time the `Session` can be inactive before it expires.
|
||||
<4> We now save the `Session`.
|
||||
This is why we needed the generic type `S`.
|
||||
The `SessionRepository` only allows saving `ExpiringSession` instances that were created or retrieved using the same `SessionRepository`.
|
||||
The `SessionRepository` only allows saving `Session` instances that were created or retrieved using the same `SessionRepository`.
|
||||
This allows for the `SessionRepository` to make implementation specific optimizations (i.e. only writing attributes that have changed).
|
||||
The last accessed time is automatically updated when the `ExpiringSession` is saved.
|
||||
<5> We retrieve the `ExpiringSession` from the `SessionRepository`.
|
||||
If the `ExpiringSession` were expired, the result would be null.
|
||||
The last accessed time is automatically updated when the `Session` is saved.
|
||||
<5> We retrieve the `Session` from the `SessionRepository`.
|
||||
If the `Session` were expired, the result would be null.
|
||||
|
||||
[[api-sessionrepository]]
|
||||
=== SessionRepository
|
||||
@@ -794,7 +636,7 @@ HMSET spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe sessionAttr:a
|
||||
[[api-redisoperationssessionrepository-expiration]]
|
||||
===== Session Expiration
|
||||
|
||||
An expiration is associated to each session using the EXPIRE command based upon the `ExpiringSession.getMaxInactiveInterval()`.
|
||||
An expiration is associated to each session using the EXPIRE command based upon the `Session.getMaxInactiveInterval()`.
|
||||
For example:
|
||||
|
||||
----
|
||||
@@ -807,7 +649,7 @@ An expiration is set on the session itself five minutes after it actually expire
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
The `SessionRepository.getSession(String)` method ensures that no expired sessions will be returned.
|
||||
The `SessionRepository.findById(String)` method ensures that no expired sessions will be returned.
|
||||
This means there is no need to check the expiration before using a session.
|
||||
====
|
||||
|
||||
@@ -824,12 +666,12 @@ EXPIRE spring:session:sessions:expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe 1800
|
||||
|
||||
When a session expires key is deleted or expires, the keyspace notification triggers a lookup of the actual session and a SessionDestroyedEvent is fired.
|
||||
|
||||
One problem with relying on Redis expiration exclusively is that Redis makes no guarantee of when the expired event will be fired if they key has not been accessed.
|
||||
One problem with relying on Redis expiration exclusively is that Redis makes no guarantee of when the expired event will be fired if the key has not been accessed.
|
||||
Specifically the background task that Redis uses to clean up expired keys is a low priority task and may not trigger the key expiration.
|
||||
For additional details see http://redis.io/topics/notifications[Timing of expired events] section in the Redis documentation.
|
||||
|
||||
To circumvent the fact that expired events are not guaranteed to happen we can ensure that each key is accessed when it is expected to expire.
|
||||
This means that if the TTL is expired on the key, Redis will remove the key and fire the expired event when we try to access they key.
|
||||
This means that if the TTL is expired on the key, Redis will remove the key and fire the expired event when we try to access the key.
|
||||
|
||||
For this reason, each session expiration is also tracked to the nearest minute.
|
||||
This allows a background task to access the potentially expired sessions to ensure that Redis expired events are fired in a more deterministic fashion.
|
||||
@@ -841,7 +683,7 @@ EXPIRE spring:session:expirations1439245080000 2100
|
||||
----
|
||||
|
||||
The background task will then use these mappings to explicitly request each key.
|
||||
By accessing they key, rather than deleting it, we ensure that Redis deletes the key for us only if the TTL is expired.
|
||||
By accessing the key, rather than deleting it, we ensure that Redis deletes the key for us only if the TTL is expired.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
@@ -927,93 +769,10 @@ redis 127.0.0.1:6379> hget spring:session:sessions:4fc39ce3-63b3-4e17-b1c4-5e1ed
|
||||
"\xac\xed\x00\x05t\x00\x03rob"
|
||||
----
|
||||
|
||||
[[api-gemfireoperationssessionrepository]]
|
||||
=== GemFireOperationsSessionRepository
|
||||
|
||||
`GemFireOperationsSessionRepository` is a `SessionRepository` that is implemented using Spring Data's `GemFireOperationsSessionRepository`.
|
||||
In a web environment, this is typically used in combination with `SessionRepositoryFilter`.
|
||||
The implementation supports `SessionDestroyedEvent` and `SessionCreatedEvent` through `SessionMessageListener`.
|
||||
|
||||
[[api-gemfireoperationssessionrepository-indexing]]
|
||||
==== Using Indexes with GemFire
|
||||
|
||||
While best practices concerning the proper definition of indexes that positively impact GemFire's performance is beyond
|
||||
the scope of this document, it is important to realize that Spring Session Data GemFire creates and uses indexes to
|
||||
query and find Sessions efficiently.
|
||||
|
||||
Out-of-the-box, Spring Session Data GemFire creates 1 Hash-typed Index on the principal name. There are two different buit in
|
||||
strategies for finding the principal name. The first strategy is that the value of the session attribute with the name
|
||||
`FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME` will be indexed to the same index name. For example:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::{docs-itest-dir}docs/http/HttpSessionGemFireIndexingITests.java[tags=findbyindexname-set]
|
||||
include::{docs-itest-dir}docs/http/HttpSessionGemFireIndexingITests.java[tags=findbyindexname-get]
|
||||
----
|
||||
|
||||
[[api-gemfireoperationssessionrepository-indexing-security]]
|
||||
==== Using Indexes with GemFire & Spring Security
|
||||
|
||||
Alternatively, Spring Session Data GemFire will map Spring Security's current `Authentication#getName()` to the index
|
||||
`FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME`. For example, if you are using Spring Security you can
|
||||
find the current user's sessions using:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::{docs-itest-dir}docs/http/HttpSessionGemFireIndexingITests.java[tags=findbyspringsecurityindexname-context]
|
||||
include::{docs-itest-dir}docs/http/HttpSessionGemFireIndexingITests.java[tags=findbyspringsecurityindexname-get]
|
||||
----
|
||||
|
||||
[[api-gemfireoperationssessionrepository-indexing-custom]]
|
||||
==== Using Custom Indexes with GemFire
|
||||
|
||||
This enables developers using the `GemFireOperationsSessionRepository` programmatically to query and find all Sessions
|
||||
with a given principal name efficiently.
|
||||
|
||||
Additionally, Spring Session Data GemFire will create a Range-based Index on the implementing Session's Map-type
|
||||
`attributes` property (i.e. on any arbitrary Session attribute) when a developer identifies 1 or more named Session
|
||||
attributes that should be indexed by GemFire.
|
||||
|
||||
Sessions attributes to index can be specified with the `indexableSessionAttributes` attribute on the `@EnableGemFireHttpSession`
|
||||
annotation. A developer adds this annotation to their Spring application `@Configuration` class when s/he wishes to
|
||||
enable Spring Session support for HttpSession backed by GemFire.
|
||||
|
||||
For example, the following configuration:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::{docs-itest-dir}docs/http/gemfire/indexablesessionattributes/GemFireHttpSessionConfig.java[tags=class-start]
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
will allow searching for sessions using the following:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::{docs-itest-dir}docs/http/gemfire/indexablesessionattributes/HttpSessionGemFireIndexingCustomITests.java[tags=findbyindexname-set]
|
||||
include::{docs-itest-dir}docs/http/gemfire/indexablesessionattributes/HttpSessionGemFireIndexingCustomITests.java[tags=findbyindexname-get]
|
||||
----
|
||||
|
||||
NOTE: Only Session attribute names identified in the `@EnableGemFireHttpSession` annotation's `indexableSessionAttributes`
|
||||
attribute will have an Index defined. All other Session attributes will not be indexed.
|
||||
|
||||
However, there is one caveat. Any values stored in indexable Session attributes must implement the `java.lang.Comparable<T>`
|
||||
interface. If those object values do not implement `Comparable`, then GemFire will throw an error on startup when the
|
||||
Index is defined for Regions with persistent Session data, or when an attempt is made at runtime to assign the indexable
|
||||
Session attribute a value that is not `Comparable` and the Session is saved to GemFire.
|
||||
|
||||
NOTE: Any Session attribute that is not indexed may store non-`Comparable` values.
|
||||
|
||||
To learn more about GemFire's Range-based Indexes, see http://gemfire.docs.pivotal.io/docs-gemfire/latest/developing/query_index/creating_map_indexes.html[Creating Indexes on Map Fields].
|
||||
|
||||
To learn more about GemFire Indexing in general, see http://gemfire.docs.pivotal.io/docs-gemfire/latest/developing/query_index/query_index.html[Working with Indexes].
|
||||
|
||||
|
||||
[[api-mapsessionrepository]]
|
||||
=== MapSessionRepository
|
||||
|
||||
The `MapSessionRepository` allows for persisting `ExpiringSession` in a `Map` with the key being the `ExpiringSession` id and the value being the `ExpiringSession`.
|
||||
The `MapSessionRepository` allows for persisting `Session` in a `Map` with the key being the `Session` id and the value being the `Session`.
|
||||
The implementation can be used with a `ConcurrentHashMap` as a testing or convenience mechanism.
|
||||
Alternatively, it can be used with distributed `Map` implementations. For example, it can be used with Hazelcast.
|
||||
|
||||
@@ -1097,14 +856,14 @@ For example, with PostgreSQL database you would use the following schema script:
|
||||
|
||||
[source,sql,indent=0]
|
||||
----
|
||||
include::{session-main-resources-dir}org/springframework/session/jdbc/schema-postgresql.sql[]
|
||||
include::{session-jdbc-main-resources-dir}org/springframework/session/jdbc/schema-postgresql.sql[]
|
||||
----
|
||||
|
||||
And with MySQL database:
|
||||
|
||||
[source,sql,indent=0]
|
||||
----
|
||||
include::{session-main-resources-dir}org/springframework/session/jdbc/schema-mysql.sql[]
|
||||
include::{session-jdbc-main-resources-dir}org/springframework/session/jdbc/schema-mysql.sql[]
|
||||
----
|
||||
|
||||
==== Transaction management
|
||||
@@ -1138,16 +897,6 @@ can be added to an `@Configuration` class. This extends the functionality provid
|
||||
You must provide a single `HazelcastInstance` bean for the configuration to work.
|
||||
Complete configuration example can be found in the <<samples>>
|
||||
|
||||
[[api-enablehazelcasthttpsession-storage]]
|
||||
==== Storage Details
|
||||
|
||||
Sessions will be stored in a distributed `IMap` in Hazelcast using a <<api-mapsessionrepository,MapSessionRepository>>.
|
||||
The `IMap` interface methods will be used to `get()` and `put()` Sessions.
|
||||
Additionally, `values()` method is used to support `FindByIndexNameSessionRepository#findByIndexNameAndIndexValue` operation, together with appropriate `ValueExtractor` that needs to be registered with Hazelcast. Refer to <<samples, Hazelcast Spring Sample>> for more details on this configuration.
|
||||
The expiration of a session in the `IMap` is handled by Hazelcast's support for setting the time to live on an entry when it is `put()` into the `IMap`. Entries (sessions) that have been idle longer than the time to live will be automatically removed from the `IMap`.
|
||||
|
||||
You shouldn't need to configure any settings such as `max-idle-seconds` or `time-to-live-seconds` for the `IMap` within the Hazelcast configuration.
|
||||
|
||||
[[api-enablehazelcasthttpsession-customize]]
|
||||
==== Basic Customization
|
||||
You can use the following attributes on `@EnableHazelcastHttpSession` to customize the configuration:
|
||||
@@ -1158,7 +907,22 @@ You can use the following attributes on `@EnableHazelcastHttpSession` to customi
|
||||
[[api-enablehazelcasthttpsession-events]]
|
||||
==== Session Events
|
||||
Using a `MapListener` to respond to entries being added, evicted, and removed from the distributed `Map`, these events will trigger
|
||||
publishing SessionCreatedEvent, SessionExpiredEvent, and SessionDeletedEvent events respectively using the `ApplicationEventPublisher`.
|
||||
publishing `SessionCreatedEvent`, `SessionExpiredEvent`, and `SessionDeletedEvent` events respectively using the `ApplicationEventPublisher`.
|
||||
|
||||
[[api-enablehazelcasthttpsession-storage]]
|
||||
==== Storage Details
|
||||
|
||||
Sessions will be stored in a distributed `IMap` in Hazelcast.
|
||||
The `IMap` interface methods will be used to `get()` and `put()` Sessions.
|
||||
Additionally, `values()` method is used to support `FindByIndexNameSessionRepository#findByIndexNameAndIndexValue` operation, together with appropriate `ValueExtractor` that needs to be registered with Hazelcast. Refer to <<samples, Hazelcast Spring Sample>> for more details on this configuration.
|
||||
The expiration of a session in the `IMap` is handled by Hazelcast's support for setting the time to live on an entry when it is `put()` into the `IMap`. Entries (sessions) that have been idle longer than the time to live will be automatically removed from the `IMap`.
|
||||
|
||||
You shouldn't need to configure any settings such as `max-idle-seconds` or `time-to-live-seconds` for the `IMap` within the Hazelcast configuration.
|
||||
|
||||
Note that if you use Hazelcast's `MapStore` to persist your sessions `IMap` there are some limitations when reloading the sessions from `MapStore`:
|
||||
|
||||
* reload triggers `EntryAddedListener` which results in `SessionCreatedEvent` being re-published
|
||||
* reload uses default TTL for a given `IMap` which results in sessions losing their original TTL
|
||||
|
||||
[[community]]
|
||||
== Spring Session Community
|
||||
@@ -1194,21 +958,31 @@ Spring Session is Open Source software released under the http://www.apache.org/
|
||||
|
||||
[[community-extensions]]
|
||||
=== Community Extensions
|
||||
https://github.com/maseev/spring-session-orientdb[Spring Session OrientDB]
|
||||
|
||||
|===
|
||||
| Name | Location
|
||||
|
||||
| Spring Session OrientDB
|
||||
| https://github.com/maseev/spring-session-orientdb
|
||||
|
||||
| Spring Session Infinispan
|
||||
| http://infinispan.org/docs/dev/user_guide/user_guide.html#externalizing_session_using_spring_session
|
||||
|
||||
|===
|
||||
|
||||
[[minimum-requirements]]
|
||||
== Minimum Requirements
|
||||
|
||||
The minimum requirements for Spring Session are:
|
||||
|
||||
* Java 5+
|
||||
* If you are running in a Servlet Container (not required), Servlet 2.5+
|
||||
* If you are using other Spring libraries (not required), the minimum required version is Spring 3.2.14.
|
||||
While we re-run all unit tests against Spring 3.2.x, we recommend using the latest Spring 4.x version when possible.
|
||||
* Java 8+
|
||||
* If you are running in a Servlet Container (not required), Servlet 3.1+
|
||||
* If you are using other Spring libraries (not required), the minimum required version is Spring 5.0.x.
|
||||
* `@EnableRedisHttpSession` requires Redis 2.8+. This is necessary to support <<api-redisoperationssessionrepository-expiration,Session Expiration>>
|
||||
* `@EnableHazelcastHttpSession` requires Hazelcast 3.6+. This is necessary to support <<api-enablehazelcasthttpsession-storage,`FindByIndexNameSessionRepository`>>
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
At its core Spring Session only has a required dependency on commons-logging.
|
||||
At its core Spring Session only has a required dependency on `spring-jcl`.
|
||||
For an example of using Spring Session without any other Spring dependencies, refer to the <<samples,hazelcast sample>> application.
|
||||
====
|
||||
|
||||
@@ -1,467 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2016 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 docs;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.Socket;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import com.gemstone.gemfire.cache.Cache;
|
||||
import com.gemstone.gemfire.cache.CacheClosedException;
|
||||
import com.gemstone.gemfire.cache.DataPolicy;
|
||||
import com.gemstone.gemfire.cache.ExpirationAction;
|
||||
import com.gemstone.gemfire.cache.ExpirationAttributes;
|
||||
import com.gemstone.gemfire.cache.GemFireCache;
|
||||
import com.gemstone.gemfire.cache.Region;
|
||||
import com.gemstone.gemfire.cache.client.ClientCache;
|
||||
import com.gemstone.gemfire.cache.client.ClientCacheFactory;
|
||||
import com.gemstone.gemfire.cache.query.Index;
|
||||
import com.gemstone.gemfire.cache.server.CacheServer;
|
||||
import org.junit.Before;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.session.ExpiringSession;
|
||||
import org.springframework.session.data.gemfire.GemFireOperationsSessionRepository;
|
||||
import org.springframework.session.data.gemfire.support.GemFireUtils;
|
||||
import org.springframework.session.events.AbstractSessionEvent;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* AbstractGemFireIntegrationTests is an abstract base class encapsulating common
|
||||
* operations for writing Spring Session GemFire integration tests.
|
||||
*
|
||||
* @author John Blum
|
||||
* @since 1.1.0
|
||||
* @see org.springframework.session.ExpiringSession
|
||||
* @see org.springframework.session.events.AbstractSessionEvent
|
||||
* @see com.gemstone.gemfire.cache.Cache
|
||||
* @see com.gemstone.gemfire.cache.DataPolicy
|
||||
* @see com.gemstone.gemfire.cache.ExpirationAttributes
|
||||
* @see com.gemstone.gemfire.cache.GemFireCache
|
||||
* @see com.gemstone.gemfire.cache.Region
|
||||
* @see com.gemstone.gemfire.cache.client.ClientCache
|
||||
* @see com.gemstone.gemfire.cache.server.CacheServer
|
||||
*/
|
||||
public class AbstractGemFireIntegrationTests {
|
||||
public static final String GEMFIRE_LOG_LEVEL = System
|
||||
.getProperty("spring.session.data.gemfire.log-level", "warning");
|
||||
|
||||
protected static final boolean DEFAULT_ENABLE_QUERY_DEBUGGING = false;
|
||||
protected static final boolean GEMFIRE_QUERY_DEBUG = Boolean
|
||||
.getBoolean("spring.session.data.gemfire.query.debug");
|
||||
|
||||
protected static final int DEFAULT_GEMFIRE_SERVER_PORT = CacheServer.DEFAULT_PORT;
|
||||
|
||||
protected static final long DEFAULT_WAIT_DURATION = TimeUnit.SECONDS.toMillis(20);
|
||||
protected static final long DEFAULT_WAIT_INTERVAL = 500L;
|
||||
|
||||
protected static final File WORKING_DIRECTORY = new File(
|
||||
System.getProperty("user.dir"));
|
||||
|
||||
protected static final String DEFAULT_PROCESS_CONTROL_FILENAME = "process.ctl";
|
||||
|
||||
protected static final String GEMFIRE_LOG_FILE_NAME = System
|
||||
.getProperty("spring.session.data.gemfire.log-file", "server.log");
|
||||
|
||||
@Autowired
|
||||
protected Cache gemfireCache;
|
||||
|
||||
@Autowired
|
||||
protected GemFireOperationsSessionRepository sessionRepository;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
System.setProperty("gemfire.Query.VERBOSE",
|
||||
String.valueOf(isQueryDebuggingEnabled()));
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
protected static File createDirectory(String pathname) {
|
||||
File directory = new File(WORKING_DIRECTORY, pathname);
|
||||
|
||||
assertThat(directory.isDirectory() || directory.mkdirs())
|
||||
.as(String.format("Failed to create directory (%1$s)", directory))
|
||||
.isTrue();
|
||||
|
||||
directory.deleteOnExit();
|
||||
|
||||
return directory;
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
protected static List<String> createJavaProcessCommandLine(Class<?> type,
|
||||
String... args) {
|
||||
List<String> commandLine = new ArrayList<String>();
|
||||
|
||||
String javaHome = System.getProperty("java.home");
|
||||
String javaExe = new File(new File(javaHome, "bin"), "java").getAbsolutePath();
|
||||
|
||||
commandLine.add(javaExe);
|
||||
commandLine.add("-server");
|
||||
commandLine.add("-ea");
|
||||
commandLine.add(String.format("-Dgemfire.log-file=%1$s", GEMFIRE_LOG_FILE_NAME));
|
||||
commandLine.add(String.format("-Dgemfire.log-level=%1$s", GEMFIRE_LOG_LEVEL));
|
||||
commandLine
|
||||
.add(String.format("-Dgemfire.Query.VERBOSE=%1$s", GEMFIRE_QUERY_DEBUG));
|
||||
commandLine.addAll(extractJvmArguments(args));
|
||||
commandLine.add("-classpath");
|
||||
commandLine.add(System.getProperty("java.class.path"));
|
||||
commandLine.add(type.getName());
|
||||
commandLine.addAll(extractProgramArguments(args));
|
||||
|
||||
// System.err.printf("Java process command-line is (%1$s)%n", commandLine);
|
||||
|
||||
return commandLine;
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
protected static List<String> extractJvmArguments(final String... args) {
|
||||
List<String> jvmArgs = new ArrayList<String>(args.length);
|
||||
|
||||
for (String arg : args) {
|
||||
if (arg.startsWith("-")) {
|
||||
jvmArgs.add(arg);
|
||||
}
|
||||
}
|
||||
|
||||
return jvmArgs;
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
protected static List<String> extractProgramArguments(final String... args) {
|
||||
List<String> jvmArgs = new ArrayList<String>(args.length);
|
||||
|
||||
for (String arg : args) {
|
||||
if (!arg.startsWith("-")) {
|
||||
jvmArgs.add(arg);
|
||||
}
|
||||
}
|
||||
|
||||
return jvmArgs;
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
protected static Process run(Class<?> type, File directory, String... args)
|
||||
throws IOException {
|
||||
return new ProcessBuilder().command(createJavaProcessCommandLine(type, args))
|
||||
.directory(directory).start();
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
protected static boolean waitForCacheServerToStart(CacheServer cacheServer) {
|
||||
return waitForCacheServerToStart(cacheServer, DEFAULT_WAIT_DURATION);
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
protected static boolean waitForCacheServerToStart(CacheServer cacheServer,
|
||||
long duration) {
|
||||
return waitForCacheServerToStart(cacheServer.getBindAddress(),
|
||||
cacheServer.getPort(), duration);
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
protected static boolean waitForCacheServerToStart(String host, int port) {
|
||||
return waitForCacheServerToStart(host, port, DEFAULT_WAIT_DURATION);
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
protected static boolean waitForCacheServerToStart(final String host, final int port,
|
||||
long duration) {
|
||||
return waitOnCondition(new Condition() {
|
||||
AtomicBoolean connected = new AtomicBoolean(false);
|
||||
|
||||
public boolean evaluate() {
|
||||
Socket socket = null;
|
||||
|
||||
try {
|
||||
if (!connected.get()) {
|
||||
socket = new Socket(host, port);
|
||||
connected.set(true);
|
||||
}
|
||||
}
|
||||
catch (IOException ignore) {
|
||||
}
|
||||
finally {
|
||||
GemFireUtils.close(socket);
|
||||
}
|
||||
|
||||
return connected.get();
|
||||
}
|
||||
}, duration);
|
||||
}
|
||||
|
||||
// NOTE this method would not be necessary except Spring Sessions' build does not fork
|
||||
// the test JVM
|
||||
// for every test class.
|
||||
/* (non-Javadoc) */
|
||||
protected static boolean waitForClientCacheToClose() {
|
||||
return waitForClientCacheToClose(DEFAULT_WAIT_DURATION);
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
protected static boolean waitForClientCacheToClose(long duration) {
|
||||
try {
|
||||
final ClientCache clientCache = ClientCacheFactory.getAnyInstance();
|
||||
|
||||
clientCache.close();
|
||||
|
||||
waitOnCondition(new Condition() {
|
||||
public boolean evaluate() {
|
||||
return clientCache.isClosed();
|
||||
}
|
||||
}, duration);
|
||||
|
||||
return clientCache.isClosed();
|
||||
}
|
||||
catch (CacheClosedException ignore) {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
protected static boolean waitForProcessToStart(Process process, File directory) {
|
||||
return waitForProcessToStart(process, directory, DEFAULT_WAIT_DURATION);
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
@SuppressWarnings("all")
|
||||
protected static boolean waitForProcessToStart(Process process, File directory,
|
||||
long duration) {
|
||||
final File processControl = new File(directory, DEFAULT_PROCESS_CONTROL_FILENAME);
|
||||
|
||||
waitOnCondition(new Condition() {
|
||||
public boolean evaluate() {
|
||||
return processControl.isFile();
|
||||
}
|
||||
}, duration);
|
||||
|
||||
return process.isAlive();
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
protected static int waitForProcessToStop(Process process, File directory) {
|
||||
return waitForProcessToStop(process, directory, DEFAULT_WAIT_DURATION);
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
protected static int waitForProcessToStop(Process process, File directory,
|
||||
long duration) {
|
||||
final long timeout = (System.currentTimeMillis() + duration);
|
||||
|
||||
try {
|
||||
while (process.isAlive() && System.currentTimeMillis() < timeout) {
|
||||
if (process.waitFor(DEFAULT_WAIT_INTERVAL, TimeUnit.MILLISECONDS)) {
|
||||
return process.exitValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
|
||||
return (process.isAlive() ? -1 : process.exitValue());
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
protected static boolean waitOnCondition(Condition condition) {
|
||||
return waitOnCondition(condition, DEFAULT_WAIT_DURATION);
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
@SuppressWarnings("all")
|
||||
protected static boolean waitOnCondition(Condition condition, long duration) {
|
||||
final long timeout = (System.currentTimeMillis() + duration);
|
||||
|
||||
try {
|
||||
while (!condition.evaluate() && System.currentTimeMillis() < timeout) {
|
||||
synchronized (condition) {
|
||||
TimeUnit.MILLISECONDS.timedWait(condition, DEFAULT_WAIT_INTERVAL);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
|
||||
return condition.evaluate();
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
protected static File writeProcessControlFile(File path) throws IOException {
|
||||
assertThat(path != null && path.isDirectory()).isTrue();
|
||||
|
||||
File processControl = new File(path, DEFAULT_PROCESS_CONTROL_FILENAME);
|
||||
|
||||
assertThat(processControl.createNewFile()).isTrue();
|
||||
|
||||
processControl.deleteOnExit();
|
||||
|
||||
return processControl;
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
protected void assertRegion(Region<?, ?> actualRegion, String expectedName,
|
||||
DataPolicy expectedDataPolicy) {
|
||||
assertThat(actualRegion).isNotNull();
|
||||
assertThat(actualRegion.getName()).isEqualTo(expectedName);
|
||||
assertThat(actualRegion.getFullPath())
|
||||
.isEqualTo(GemFireUtils.toRegionPath(expectedName));
|
||||
assertThat(actualRegion.getAttributes()).isNotNull();
|
||||
assertThat(actualRegion.getAttributes().getDataPolicy())
|
||||
.isEqualTo(expectedDataPolicy);
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
protected void assertIndex(Index index, String expectedExpression,
|
||||
String expectedFromClause) {
|
||||
assertThat(index).isNotNull();
|
||||
assertThat(index.getIndexedExpression()).isEqualTo(expectedExpression);
|
||||
assertThat(index.getFromClause()).isEqualTo(expectedFromClause);
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
protected void assertEntryIdleTimeout(Region<?, ?> region,
|
||||
ExpirationAction expectedAction, int expectedTimeout) {
|
||||
assertEntryIdleTimeout(region.getAttributes().getEntryIdleTimeout(),
|
||||
expectedAction, expectedTimeout);
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
protected void assertEntryIdleTimeout(ExpirationAttributes actualExpirationAttributes,
|
||||
ExpirationAction expectedAction, int expectedTimeout) {
|
||||
assertThat(actualExpirationAttributes).isNotNull();
|
||||
assertThat(actualExpirationAttributes.getAction()).isEqualTo(expectedAction);
|
||||
assertThat(actualExpirationAttributes.getTimeout()).isEqualTo(expectedTimeout);
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
protected boolean enableQueryDebugging() {
|
||||
return DEFAULT_ENABLE_QUERY_DEBUGGING;
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
protected boolean isQueryDebuggingEnabled() {
|
||||
return (GEMFIRE_QUERY_DEBUG || enableQueryDebugging());
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
protected List<String> listRegions(GemFireCache gemfireCache) {
|
||||
Set<Region<?, ?>> regions = gemfireCache.rootRegions();
|
||||
|
||||
List<String> regionList = new ArrayList<String>(regions.size());
|
||||
|
||||
for (Region<?, ?> region : regions) {
|
||||
regionList.add(region.getFullPath());
|
||||
}
|
||||
|
||||
return regionList;
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
@SuppressWarnings("unchecked")
|
||||
protected <T extends ExpiringSession> T createSession() {
|
||||
T expiringSession = (T) this.sessionRepository.createSession();
|
||||
assertThat(expiringSession).isNotNull();
|
||||
return expiringSession;
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
@SuppressWarnings("unchecked")
|
||||
protected <T extends ExpiringSession> T createSession(String principalName) {
|
||||
GemFireOperationsSessionRepository.GemFireSession session = createSession();
|
||||
session.setPrincipalName(principalName);
|
||||
return (T) session;
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
protected <T extends ExpiringSession> T expire(T session) {
|
||||
session.setLastAccessedTime(0L);
|
||||
return session;
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
@SuppressWarnings("unchecked")
|
||||
protected <T extends ExpiringSession> T get(String sessionId) {
|
||||
return (T) this.sessionRepository.getSession(sessionId);
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
protected <T extends ExpiringSession> T save(T session) {
|
||||
this.sessionRepository.save(session);
|
||||
return session;
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
protected <T extends ExpiringSession> T touch(T session) {
|
||||
session.setLastAccessedTime(System.currentTimeMillis());
|
||||
return session;
|
||||
}
|
||||
|
||||
/**
|
||||
* The SessionEventListener class is a Spring {@link ApplicationListener} listening
|
||||
* for Spring HTTP Session application events.
|
||||
*
|
||||
* @see org.springframework.context.ApplicationListener
|
||||
* @see org.springframework.session.events.AbstractSessionEvent
|
||||
*/
|
||||
public static class SessionEventListener
|
||||
implements ApplicationListener<AbstractSessionEvent> {
|
||||
|
||||
private volatile AbstractSessionEvent sessionEvent;
|
||||
|
||||
/* (non-Javadoc) */
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends AbstractSessionEvent> T getSessionEvent() {
|
||||
T sessionEvent = (T) this.sessionEvent;
|
||||
this.sessionEvent = null;
|
||||
return sessionEvent;
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public void onApplicationEvent(AbstractSessionEvent event) {
|
||||
this.sessionEvent = event;
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
public <T extends AbstractSessionEvent> T waitForSessionEvent(long duration) {
|
||||
waitOnCondition(new Condition() {
|
||||
public boolean evaluate() {
|
||||
return (SessionEventListener.this.sessionEvent != null);
|
||||
}
|
||||
}, duration);
|
||||
|
||||
return getSessionEvent();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The Condition interface defines a logical condition that must be satisfied before
|
||||
* it is safe to proceed.
|
||||
*/
|
||||
protected interface Condition {
|
||||
boolean evaluate();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2016 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 docs.http;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import docs.AbstractGemFireIntegrationTests;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.gemfire.CacheFactoryBean;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.test.context.support.WithMockUser;
|
||||
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
|
||||
import org.springframework.session.ExpiringSession;
|
||||
import org.springframework.session.FindByIndexNameSessionRepository;
|
||||
import org.springframework.session.data.gemfire.config.annotation.web.http.EnableGemFireHttpSession;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
*
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration
|
||||
public class HttpSessionGemFireIndexingITests extends AbstractGemFireIntegrationTests {
|
||||
|
||||
@Test
|
||||
public void findByIndexName() {
|
||||
ExpiringSession session = sessionRepository.createSession();
|
||||
String username = "HttpSessionGemFireIndexingITests-findByIndexName-username";
|
||||
|
||||
// tag::findbyindexname-set[]
|
||||
String indexName = FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME;
|
||||
session.setAttribute(indexName, username);
|
||||
// end::findbyindexname-set[]
|
||||
|
||||
sessionRepository.save(session);
|
||||
|
||||
// tag::findbyindexname-get[]
|
||||
Map<String, ExpiringSession> idToSessions = sessionRepository
|
||||
.findByIndexNameAndIndexValue(indexName, username);
|
||||
// end::findbyindexname-get[]
|
||||
|
||||
assertThat(idToSessions.keySet()).containsOnly(session.getId());
|
||||
|
||||
sessionRepository.delete(session.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser("HttpSessionGemFireIndexingITests-findBySpringSecurityIndexName")
|
||||
public void findBySpringSecurityIndexName() {
|
||||
ExpiringSession session = sessionRepository.createSession();
|
||||
|
||||
// tag::findbyspringsecurityindexname-context[]
|
||||
SecurityContext context = SecurityContextHolder.getContext();
|
||||
Authentication authentication = context.getAuthentication();
|
||||
// end::findbyspringsecurityindexname-context[]
|
||||
|
||||
session.setAttribute(
|
||||
HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY,
|
||||
context);
|
||||
sessionRepository.save(session);
|
||||
|
||||
// tag::findbyspringsecurityindexname-get[]
|
||||
String indexName = FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME;
|
||||
Map<String, ExpiringSession> idToSessions = sessionRepository
|
||||
.findByIndexNameAndIndexValue(indexName, authentication.getName());
|
||||
// end::findbyspringsecurityindexname-get[]
|
||||
|
||||
assertThat(idToSessions.keySet()).containsOnly(session.getId());
|
||||
|
||||
sessionRepository.delete(session.getId());
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableGemFireHttpSession
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
Properties gemfireProperties() {
|
||||
Properties gemfireProperties = new Properties();
|
||||
|
||||
gemfireProperties.setProperty("name", Config.class.getName());
|
||||
gemfireProperties.setProperty("mcast-port", "0");
|
||||
gemfireProperties.setProperty("log-level", GEMFIRE_LOG_LEVEL);
|
||||
|
||||
return gemfireProperties;
|
||||
}
|
||||
|
||||
@Bean
|
||||
CacheFactoryBean gemfireCache() {
|
||||
CacheFactoryBean gemfireCache = new CacheFactoryBean();
|
||||
|
||||
gemfireCache.setClose(true);
|
||||
gemfireCache.setProperties(gemfireProperties());
|
||||
|
||||
return gemfireCache;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2016 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 docs.http.gemfire.indexablesessionattributes;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import docs.AbstractGemFireIntegrationTests;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.data.gemfire.CacheFactoryBean;
|
||||
import org.springframework.session.data.gemfire.config.annotation.web.http.EnableGemFireHttpSession;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
*
|
||||
*/
|
||||
// tag::class-start[]
|
||||
@EnableGemFireHttpSession(indexableSessionAttributes = { "name1", "name2", "name3" })
|
||||
public class GemFireHttpSessionConfig {
|
||||
// end::class-start[]
|
||||
|
||||
@Bean
|
||||
Properties gemfireProperties() {
|
||||
Properties gemfireProperties = new Properties();
|
||||
|
||||
gemfireProperties.setProperty("name", GemFireHttpSessionConfig.class.getName());
|
||||
gemfireProperties.setProperty("mcast-port", "0");
|
||||
gemfireProperties.setProperty("log-level",
|
||||
AbstractGemFireIntegrationTests.GEMFIRE_LOG_LEVEL);
|
||||
|
||||
return gemfireProperties;
|
||||
}
|
||||
|
||||
@Bean
|
||||
CacheFactoryBean gemfireCache() {
|
||||
CacheFactoryBean gemfireCache = new CacheFactoryBean();
|
||||
|
||||
gemfireCache.setClose(true);
|
||||
gemfireCache.setProperties(gemfireProperties());
|
||||
|
||||
return gemfireCache;
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2016 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 docs.http.gemfire.indexablesessionattributes;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import docs.AbstractGemFireIntegrationTests;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.session.ExpiringSession;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
*
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration(classes = GemFireHttpSessionConfig.class)
|
||||
public class HttpSessionGemFireIndexingCustomITests
|
||||
extends AbstractGemFireIntegrationTests {
|
||||
|
||||
@Test
|
||||
public void findByIndexName() {
|
||||
ExpiringSession session = sessionRepository.createSession();
|
||||
String attrValue = "HttpSessionGemFireIndexingCustomITests-findByIndexName";
|
||||
|
||||
// tag::findbyindexname-set[]
|
||||
String indexName = "name1";
|
||||
session.setAttribute(indexName, attrValue);
|
||||
// end::findbyindexname-set[]
|
||||
|
||||
sessionRepository.save(session);
|
||||
|
||||
// tag::findbyindexname-get[]
|
||||
Map<String, ExpiringSession> idToSessions = sessionRepository
|
||||
.findByIndexNameAndIndexValue(indexName, attrValue);
|
||||
// end::findbyindexname-get[]
|
||||
|
||||
assertThat(idToSessions.keySet()).containsOnly(session.getId());
|
||||
|
||||
sessionRepository.delete(session.getId());
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2016 the original author or authors.
|
||||
* Copyright 2014-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.
|
||||
@@ -21,7 +21,7 @@ import java.util.Map;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import org.springframework.session.FindByIndexNameSessionRepository;
|
||||
import org.springframework.session.Session;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2016 the original author or authors.
|
||||
* Copyright 2014-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.
|
||||
@@ -21,7 +21,7 @@ import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.session.ExpiringSession;
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.session.web.http.SessionRepositoryFilter;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
@@ -38,7 +38,7 @@ import static org.mockito.Mockito.mock;
|
||||
@WebAppConfiguration
|
||||
public class HttpSessionConfigurationNoOpConfigureRedisActionXmlTests {
|
||||
@Autowired
|
||||
SessionRepositoryFilter<? extends ExpiringSession> filter;
|
||||
SessionRepositoryFilter<? extends Session> filter;
|
||||
|
||||
@Test
|
||||
public void redisConnectionFactoryNotUsedSinceNoValidation() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2016 the original author or authors.
|
||||
* Copyright 2014-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.
|
||||
@@ -16,6 +16,9 @@
|
||||
|
||||
package docs;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import com.hazelcast.config.Config;
|
||||
import com.hazelcast.core.Hazelcast;
|
||||
import com.hazelcast.core.HazelcastInstance;
|
||||
@@ -26,7 +29,6 @@ import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactor
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
|
||||
import org.springframework.mock.web.MockServletContext;
|
||||
import org.springframework.session.ExpiringSession;
|
||||
import org.springframework.session.MapSession;
|
||||
import org.springframework.session.MapSessionRepository;
|
||||
import org.springframework.session.Session;
|
||||
@@ -49,8 +51,8 @@ public class IndexDocTests {
|
||||
|
||||
@Test
|
||||
public void repositoryDemo() {
|
||||
RepositoryDemo<ExpiringSession> demo = new RepositoryDemo<ExpiringSession>();
|
||||
demo.repository = new MapSessionRepository();
|
||||
RepositoryDemo<MapSession> demo = new RepositoryDemo<>();
|
||||
demo.repository = new MapSessionRepository(new ConcurrentHashMap<>());
|
||||
|
||||
demo.demo();
|
||||
}
|
||||
@@ -68,7 +70,7 @@ public class IndexDocTests {
|
||||
|
||||
this.repository.save(toSave); // <4>
|
||||
|
||||
S session = this.repository.getSession(toSave.getId()); // <5>
|
||||
S session = this.repository.findById(toSave.getId()); // <5>
|
||||
|
||||
// <6>
|
||||
User user = session.getAttribute(ATTR_USER);
|
||||
@@ -81,24 +83,24 @@ public class IndexDocTests {
|
||||
|
||||
@Test
|
||||
public void expireRepositoryDemo() {
|
||||
ExpiringRepositoryDemo<ExpiringSession> demo = new ExpiringRepositoryDemo<ExpiringSession>();
|
||||
demo.repository = new MapSessionRepository();
|
||||
ExpiringRepositoryDemo<MapSession> demo = new ExpiringRepositoryDemo<>();
|
||||
demo.repository = new MapSessionRepository(new ConcurrentHashMap<>());
|
||||
|
||||
demo.demo();
|
||||
}
|
||||
|
||||
// tag::expire-repository-demo[]
|
||||
public class ExpiringRepositoryDemo<S extends ExpiringSession> {
|
||||
public class ExpiringRepositoryDemo<S extends Session> {
|
||||
private SessionRepository<S> repository; // <1>
|
||||
|
||||
public void demo() {
|
||||
S toSave = this.repository.createSession(); // <2>
|
||||
// ...
|
||||
toSave.setMaxInactiveIntervalInSeconds(30); // <3>
|
||||
toSave.setMaxInactiveInterval(Duration.ofSeconds(30)); // <3>
|
||||
|
||||
this.repository.save(toSave); // <4>
|
||||
|
||||
S session = this.repository.getSession(toSave.getId()); // <5>
|
||||
S session = this.repository.findById(toSave.getId()); // <5>
|
||||
// ...
|
||||
}
|
||||
|
||||
@@ -111,7 +113,7 @@ public class IndexDocTests {
|
||||
public void newRedisOperationsSessionRepository() {
|
||||
// tag::new-redisoperationssessionrepository[]
|
||||
LettuceConnectionFactory factory = new LettuceConnectionFactory();
|
||||
SessionRepository<? extends ExpiringSession> repository = new RedisOperationsSessionRepository(
|
||||
SessionRepository<? extends Session> repository = new RedisOperationsSessionRepository(
|
||||
factory);
|
||||
// end::new-redisoperationssessionrepository[]
|
||||
}
|
||||
@@ -120,7 +122,8 @@ public class IndexDocTests {
|
||||
@SuppressWarnings("unused")
|
||||
public void mapRepository() {
|
||||
// tag::new-mapsessionrepository[]
|
||||
SessionRepository<? extends ExpiringSession> repository = new MapSessionRepository();
|
||||
SessionRepository<? extends Session> repository = new MapSessionRepository(
|
||||
new ConcurrentHashMap<>());
|
||||
// end::new-mapsessionrepository[]
|
||||
}
|
||||
|
||||
@@ -136,7 +139,7 @@ public class IndexDocTests {
|
||||
|
||||
// ... configure transactionManager ...
|
||||
|
||||
SessionRepository<? extends ExpiringSession> repository =
|
||||
SessionRepository<? extends Session> repository =
|
||||
new JdbcOperationsSessionRepository(jdbcTemplate, transactionManager);
|
||||
// end::new-jdbcoperationssessionrepository[]
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2016 the original author or authors.
|
||||
* Copyright 2014-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.
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package docs;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.session.MapSessionRepository;
|
||||
@@ -27,7 +29,7 @@ import org.springframework.session.config.annotation.web.http.EnableSpringHttpSe
|
||||
public class SpringHttpSessionConfig {
|
||||
@Bean
|
||||
public MapSessionRepository sessionRepository() {
|
||||
return new MapSessionRepository();
|
||||
return new MapSessionRepository(new ConcurrentHashMap<>());
|
||||
}
|
||||
}
|
||||
// end::class[]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2016 the original author or authors.
|
||||
* Copyright 2014-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.
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package docs.http;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@@ -31,11 +33,13 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.context.web.WebAppConfiguration;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
* @author Mark Paluch
|
||||
* @since 1.2
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@@ -63,6 +67,7 @@ public abstract class AbstractHttpSessionListenerTests {
|
||||
RedisConnection connection = mock(RedisConnection.class);
|
||||
|
||||
given(factory.getConnection()).willReturn(connection);
|
||||
given(connection.getConfig(anyString())).willReturn(new Properties());
|
||||
return factory;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2016 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 docs.http;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import com.fasterxml.jackson.databind.Module;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.session.data.mongo.AbstractMongoSessionConverter;
|
||||
import org.springframework.session.data.mongo.JacksonMongoSessionConverter;
|
||||
import org.springframework.session.data.mongo.config.annotation.web.http.EnableMongoHttpSession;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Jakub Kubrynski
|
||||
* @author Rob Winch
|
||||
*/
|
||||
// tag::config[]
|
||||
@Configuration
|
||||
@EnableMongoHttpSession
|
||||
public class MongoJacksonSessionConfiguration {
|
||||
|
||||
@Bean
|
||||
public AbstractMongoSessionConverter mongoSessionConverter() {
|
||||
return new JacksonMongoSessionConverter(getJacksonModules());
|
||||
}
|
||||
|
||||
public Iterable<Module> getJacksonModules() {
|
||||
return Collections.<Module>singletonList(new MyJacksonModule());
|
||||
}
|
||||
}
|
||||
// end::config[]
|
||||
@@ -1,40 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2016 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 docs.http;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.session.data.mongo.AbstractMongoSessionConverter;
|
||||
import org.springframework.session.data.mongo.JdkMongoSessionConverter;
|
||||
import org.springframework.session.data.mongo.config.annotation.web.http.EnableMongoHttpSession;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Jakub Kubrynski
|
||||
* @author Rob Winch
|
||||
*/
|
||||
// tag::config[]
|
||||
@Configuration
|
||||
@EnableMongoHttpSession
|
||||
public class MongoJdkSessionConfiguration {
|
||||
|
||||
@Bean
|
||||
public AbstractMongoSessionConverter mongoSessionConverter() {
|
||||
return new JdkMongoSessionConverter();
|
||||
}
|
||||
}
|
||||
// end::config[]
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2016 the original author or authors.
|
||||
* Copyright 2014-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.
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package docs.security;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
@@ -73,7 +75,7 @@ public class RememberMeSecurityConfiguration extends WebSecurityConfigurerAdapte
|
||||
|
||||
@Bean
|
||||
MapSessionRepository sessionRepository() {
|
||||
return new MapSessionRepository();
|
||||
return new MapSessionRepository(new ConcurrentHashMap<>());
|
||||
}
|
||||
}
|
||||
// end::class[]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2016 the original author or authors.
|
||||
* Copyright 2014-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.
|
||||
@@ -16,7 +16,8 @@
|
||||
|
||||
package docs.security;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.time.Duration;
|
||||
import java.util.Base64;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
|
||||
@@ -25,7 +26,7 @@ import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.session.ExpiringSession;
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.session.SessionRepository;
|
||||
import org.springframework.session.web.http.SessionRepositoryFilter;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
@@ -47,7 +48,7 @@ import static org.springframework.security.test.web.servlet.setup.SecurityMockMv
|
||||
@ContextConfiguration(classes = RememberMeSecurityConfiguration.class)
|
||||
@WebAppConfiguration
|
||||
@SuppressWarnings("rawtypes")
|
||||
public class RememberMeSecurityConfigurationTests<T extends ExpiringSession> {
|
||||
public class RememberMeSecurityConfigurationTests<T extends Session> {
|
||||
@Autowired
|
||||
WebApplicationContext context;
|
||||
@Autowired
|
||||
@@ -79,9 +80,10 @@ public class RememberMeSecurityConfigurationTests<T extends ExpiringSession> {
|
||||
|
||||
Cookie cookie = result.getResponse().getCookie("SESSION");
|
||||
assertThat(cookie.getMaxAge()).isEqualTo(Integer.MAX_VALUE);
|
||||
T session = this.sessions.getSession(cookie.getValue());
|
||||
assertThat(session.getMaxInactiveIntervalInSeconds())
|
||||
.isEqualTo((int) TimeUnit.DAYS.toSeconds(30));
|
||||
T session = this.sessions
|
||||
.findById(new String(Base64.getDecoder().decode(cookie.getValue())));
|
||||
assertThat(session.getMaxInactiveInterval())
|
||||
.isEqualTo(Duration.ofDays(30));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2016 the original author or authors.
|
||||
* Copyright 2014-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.
|
||||
@@ -16,7 +16,8 @@
|
||||
|
||||
package docs.security;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.time.Duration;
|
||||
import java.util.Base64;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
|
||||
@@ -25,7 +26,7 @@ import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.session.ExpiringSession;
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.session.SessionRepository;
|
||||
import org.springframework.session.web.http.SessionRepositoryFilter;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
@@ -47,7 +48,7 @@ import static org.springframework.security.test.web.servlet.setup.SecurityMockMv
|
||||
@ContextConfiguration
|
||||
@WebAppConfiguration
|
||||
@SuppressWarnings("rawtypes")
|
||||
public class RememberMeSecurityConfigurationXmlTests<T extends ExpiringSession> {
|
||||
public class RememberMeSecurityConfigurationXmlTests<T extends Session> {
|
||||
@Autowired
|
||||
WebApplicationContext context;
|
||||
@Autowired
|
||||
@@ -79,9 +80,10 @@ public class RememberMeSecurityConfigurationXmlTests<T extends ExpiringSession>
|
||||
|
||||
Cookie cookie = result.getResponse().getCookie("SESSION");
|
||||
assertThat(cookie.getMaxAge()).isEqualTo(Integer.MAX_VALUE);
|
||||
T session = this.sessions.getSession(cookie.getValue());
|
||||
assertThat(session.getMaxInactiveIntervalInSeconds())
|
||||
.isEqualTo((int) TimeUnit.DAYS.toSeconds(30));
|
||||
T session = this.sessions
|
||||
.findById(new String(Base64.getDecoder().decode(cookie.getValue())));
|
||||
assertThat(session.getMaxInactiveInterval())
|
||||
.isEqualTo(Duration.ofDays(30));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2016 the original author or authors.
|
||||
* Copyright 2014-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.
|
||||
@@ -21,8 +21,8 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
import org.springframework.session.ExpiringSession;
|
||||
import org.springframework.session.FindByIndexNameSessionRepository;
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.session.security.SpringSessionBackedSessionRegistry;
|
||||
|
||||
/**
|
||||
@@ -33,20 +33,22 @@ import org.springframework.session.security.SpringSessionBackedSessionRegistry;
|
||||
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Autowired
|
||||
FindByIndexNameSessionRepository<ExpiringSession> sessionRepository;
|
||||
private FindByIndexNameSessionRepository<Session> sessionRepository;
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
// other config goes here...
|
||||
.sessionManagement()
|
||||
.maximumSessions(2)
|
||||
.sessionRegistry(sessionRegistry());
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Bean
|
||||
SpringSessionBackedSessionRegistry sessionRegistry() {
|
||||
return new SpringSessionBackedSessionRegistry(this.sessionRepository);
|
||||
return new SpringSessionBackedSessionRegistry<>(this.sessionRepository);
|
||||
}
|
||||
}
|
||||
// end::class[]
|
||||
|
||||
@@ -24,5 +24,9 @@
|
||||
</security:user-service>
|
||||
|
||||
<bean class="org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration"/>
|
||||
<bean id="springSessionRepository" class="org.springframework.session.MapSessionRepository"/>
|
||||
<bean id="springSessionRepository" class="org.springframework.session.MapSessionRepository">
|
||||
<constructor-arg>
|
||||
<bean class="java.util.concurrent.ConcurrentHashMap"/>
|
||||
</constructor-arg>
|
||||
</bean>
|
||||
</beans>
|
||||
|
||||
@@ -59,7 +59,7 @@ cleanup_profile=_Spring Boot Cleanup Conventions
|
||||
cleanup_settings_version=2
|
||||
eclipse.preferences.version=1
|
||||
editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
|
||||
formatter_profile=_Spring Boot Java Conventions
|
||||
formatter_profile=Spring Session Java Conventions
|
||||
formatter_settings_version=12
|
||||
org.eclipse.jdt.ui.exception.name=e
|
||||
org.eclipse.jdt.ui.gettersetter.use.is=true
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
<module name="AvoidStarImport"/>
|
||||
<module name="AvoidStaticImport">
|
||||
<property name="excludes"
|
||||
value="org.assertj.core.api.Assertions.*, org.mockito.Mockito.*, org.mockito.BDDMockito.*, org.mockito.AdditionalMatchers.*, org.mockito.Matchers.*, org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*, org.springframework.test.web.servlet.result.MockMvcResultHandlers.*, org.springframework.test.web.servlet.result.MockMvcResultMatchers.*, org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*, org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.*, org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.*, org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo"/>
|
||||
value="org.assertj.core.api.Assertions.*, org.mockito.Mockito.*, org.mockito.BDDMockito.*, org.mockito.AdditionalMatchers.*, org.mockito.ArgumentMatchers.*, org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*, org.springframework.test.web.servlet.result.MockMvcResultHandlers.*, org.springframework.test.web.servlet.result.MockMvcResultMatchers.*, org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*, org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.*, org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.*, org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo"/>
|
||||
</module>
|
||||
<module name="IllegalImport"/>
|
||||
<module name="RedundantImport"/>
|
||||
@@ -4,6 +4,7 @@
|
||||
<suppressions>
|
||||
<suppress files=".+Application\.java" checks="HideUtilityClassConstructor"/>
|
||||
<suppress files=".+Configuration\.java" checks="HideUtilityClassConstructor"/>
|
||||
<suppress files=".+SSEFluxWebConfig\.java" checks="RegexpHeader"/>
|
||||
|
||||
<suppress files="[\\/]src[\\/]test[\\/]java[\\/]" checks="Javadoc"/>
|
||||
<suppress files="[\\/]src[\\/]integration-test[\\/]java[\\/]" checks="Javadoc"/>
|
||||
@@ -1,30 +1,2 @@
|
||||
bootstrapVersion=2.3.2
|
||||
commonsPoolVersion=2.4.2
|
||||
jacksonVersion=2.6.5
|
||||
jspApiVersion=2.0
|
||||
servletApiVersion=3.0.1
|
||||
jstlelVersion=1.2.5
|
||||
version=1.3.0.RC1
|
||||
springDataRedisVersion=1.7.1.RELEASE
|
||||
html5ShivVersion=3.7.3
|
||||
commonsLoggingVersion=1.2
|
||||
junitVersion=4.12
|
||||
lettuceVersion=3.5.0.Final
|
||||
gebVersion=0.13.1
|
||||
mockitoVersion=1.10.19
|
||||
hazelcastVersion=3.6.5
|
||||
seleniumVersion=2.52.0
|
||||
springDataGeodeVersion=1.0.0.APACHE-GEODE-INCUBATING-M2
|
||||
springSecurityVersion=4.2.0.RELEASE
|
||||
springVersion=4.3.4.RELEASE
|
||||
httpClientVersion=4.5.1
|
||||
jedisVersion=2.8.1
|
||||
h2Version=1.4.192
|
||||
springDataMongoVersion=1.9.4.RELEASE
|
||||
springShellVersion=1.1.0.RELEASE
|
||||
springDataGemFireVersion=1.8.5.RELEASE
|
||||
assertjVersion=2.5.0
|
||||
spockVersion=1.0-groovy-2.4
|
||||
webjarsTaglibVersion=0.3
|
||||
jstlVersion=1.2.1
|
||||
groovyVersion=2.4.4
|
||||
springBootVersion=2.0.0.M3
|
||||
version=2.0.0.M4
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
sonarqube {
|
||||
skipProject = true
|
||||
}
|
||||
56
gradle/dependency-management.gradle
Normal file
@@ -0,0 +1,56 @@
|
||||
dependencyManagement {
|
||||
imports {
|
||||
mavenBom 'io.projectreactor:reactor-bom:Bismuth-M4'
|
||||
mavenBom 'org.springframework:spring-framework-bom:5.0.0.RC4'
|
||||
mavenBom 'org.springframework.data:spring-data-releasetrain:Kay-RC3'
|
||||
mavenBom 'org.springframework.security:spring-security-bom:5.0.0.M4'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
dependencySet(group: 'com.hazelcast', version: '3.8.3') {
|
||||
entry 'hazelcast'
|
||||
entry 'hazelcast-client'
|
||||
}
|
||||
|
||||
dependencySet(group: 'org.testcontainers', version: '1.4.2') {
|
||||
entry 'mysql'
|
||||
entry 'postgresql'
|
||||
entry 'testcontainers'
|
||||
}
|
||||
|
||||
dependency 'ch.qos.logback:logback-classic:1.2.3'
|
||||
dependency 'com.fasterxml.jackson.core:jackson-databind:2.9.0.pr4'
|
||||
dependency 'com.h2database:h2:1.4.196'
|
||||
dependency 'com.maxmind.geoip2:geoip2:2.3.1'
|
||||
dependency 'commons-codec:commons-codec:1.10'
|
||||
dependency 'edu.umd.cs.mtc:multithreadedtc:1.01'
|
||||
dependency 'io.lettuce:lettuce-core:5.0.0.RC2'
|
||||
dependency 'javax.servlet.jsp.jstl:javax.servlet.jsp.jstl-api:1.2.1'
|
||||
dependency 'javax.servlet.jsp:javax.servlet.jsp-api:2.3.2-b02'
|
||||
dependency 'javax.servlet:javax.servlet-api:3.1.0'
|
||||
dependency 'junit:junit:4.12'
|
||||
dependency 'mysql:mysql-connector-java:5.1.43'
|
||||
dependency 'org.apache.derby:derby:10.13.1.1'
|
||||
dependency 'org.apache.httpcomponents:httpclient:4.5.3'
|
||||
dependency 'org.apache.taglibs:taglibs-standard-jstlel:1.2.5'
|
||||
dependency 'org.assertj:assertj-core:3.8.0'
|
||||
dependency 'org.hsqldb:hsqldb:2.4.0'
|
||||
dependency 'org.mariadb.jdbc:mariadb-java-client:2.0.3'
|
||||
dependency 'org.mockito:mockito-core:2.8.47'
|
||||
dependency 'org.postgresql:postgresql:42.1.3'
|
||||
dependency 'org.seleniumhq.selenium:htmlunit-driver:2.27'
|
||||
dependency 'org.slf4j:jcl-over-slf4j:1.7.25'
|
||||
dependency 'org.slf4j:log4j-over-slf4j:1.7.25'
|
||||
dependency 'org.testcontainers:mariadb:1.3.0'
|
||||
dependency 'org.thymeleaf.extras:thymeleaf-extras-java8time:3.0.0.RELEASE'
|
||||
dependency 'org.thymeleaf:thymeleaf-spring5:3.0.7.RC1'
|
||||
dependency 'org.webjars:bootstrap:2.3.2'
|
||||
dependency 'org.webjars:html5shiv:3.7.3'
|
||||
dependency 'org.webjars:jquery:1.9.0'
|
||||
dependency 'org.webjars:knockout:2.3.0'
|
||||
dependency 'org.webjars:sockjs-client:0.3.4'
|
||||
dependency 'org.webjars:stomp-websocket:2.3.0'
|
||||
dependency 'org.webjars:webjars-taglib:0.3'
|
||||
dependency 'redis.clients:jedis:2.9.0'
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
import org.gradle.plugins.ide.eclipse.model.ProjectDependency
|
||||
import org.gradle.plugins.ide.eclipse.model.SourceFolder
|
||||
|
||||
|
||||
apply plugin: "propdeps-eclipse"
|
||||
apply plugin: "propdeps-idea"
|
||||
|
||||
eclipse.project.buildCommand "net.sf.eclipsecs.core.CheckstyleBuilder"
|
||||
eclipse.project.natures "net.sf.eclipsecs.core.CheckstyleNature"
|
||||
|
||||
// Include project specific settings
|
||||
task eclipseCheckstyle(type: Copy) {
|
||||
from rootProject.files(
|
||||
"eclipse/.checkstyle")
|
||||
into project.projectDir
|
||||
expand(configDir: rootProject.file('config/checkstyle').absolutePath)
|
||||
}
|
||||
|
||||
task eclipseSettings(type: Copy) {
|
||||
from rootProject.files(
|
||||
"eclipse/org.eclipse.jdt.ui.prefs",
|
||||
"eclipse/org.eclipse.wst.common.project.facet.core.xml")
|
||||
into project.file('.settings/')
|
||||
outputs.upToDateWhen { false }
|
||||
}
|
||||
|
||||
task eclipseWstComponent(type: Copy) {
|
||||
from rootProject.files(
|
||||
"eclipse/org.eclipse.wst.common.component")
|
||||
into project.file('.settings/')
|
||||
expand(deployname: project.name)
|
||||
outputs.upToDateWhen { false }
|
||||
}
|
||||
|
||||
task eclipseJdtPrepare(type: Copy) {
|
||||
from rootProject.file("eclipse/org.eclipse.jdt.core.prefs")
|
||||
into project.file(".settings/")
|
||||
outputs.upToDateWhen { false }
|
||||
}
|
||||
|
||||
task cleanEclipseJdtUi(type: Delete) {
|
||||
delete project.file(".settings/org.eclipse.jdt.core.prefs")
|
||||
delete project.file(".settings/org.eclipse.jdt.ui.prefs")
|
||||
delete project.file(".settings/org.eclipse.wst.common.component")
|
||||
delete project.file(".settings/org.eclipse.wst.common.project.facet.core.xml")
|
||||
}
|
||||
|
||||
task eclipseConfiguration(dependsOn: [eclipseCheckstyle, eclipseSettings, eclipseWstComponent]) {
|
||||
}
|
||||
|
||||
tasks["eclipseJdt"].dependsOn(eclipseJdtPrepare)
|
||||
tasks["cleanEclipse"].dependsOn(cleanEclipseJdtUi)
|
||||
tasks["eclipse"].dependsOn(eclipseConfiguration)
|
||||
@@ -1,98 +0,0 @@
|
||||
apply plugin: 'java'
|
||||
apply plugin: 'groovy'
|
||||
apply plugin: 'javadocHotfix'
|
||||
apply plugin: 'eclipse-wtp'
|
||||
apply plugin: 'propdeps'
|
||||
apply plugin: 'propdeps-idea'
|
||||
apply plugin: 'propdeps-eclipse'
|
||||
apply plugin: 'com.github.ben-manes.versions'
|
||||
apply plugin: 'checkstyle'
|
||||
apply from: IDE_GRADLE
|
||||
|
||||
group = 'org.springframework.session'
|
||||
|
||||
sourceCompatibility = 1.5
|
||||
targetCompatibility = 1.5
|
||||
|
||||
ext.springIoVersion = project.hasProperty('platformVersion') ? platformVersion : 'Brussels-BUILD-SNAPSHOT'
|
||||
|
||||
ext.spockDependencies = [
|
||||
dependencies.create("org.spockframework:spock-core:$spockVersion") {
|
||||
exclude group: 'junit', module: 'junit-dep'
|
||||
}
|
||||
]
|
||||
|
||||
ext.gebDependencies = spockDependencies + [
|
||||
"org.seleniumhq.selenium:selenium-htmlunit-driver:$seleniumVersion",
|
||||
"org.gebish:geb-spock:$gebVersion",
|
||||
"org.codehaus.groovy:groovy:$groovyVersion"
|
||||
]
|
||||
|
||||
ext.jstlDependencies = [
|
||||
"javax.servlet.jsp.jstl:javax.servlet.jsp.jstl-api:$jstlVersion",
|
||||
"org.apache.taglibs:taglibs-standard-jstlel:1.2.1"
|
||||
]
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven { url 'https://repo.spring.io/libs-snapshot' }
|
||||
}
|
||||
|
||||
configurations.all {
|
||||
resolutionStrategy.eachDependency { DependencyResolveDetails details ->
|
||||
if (details.requested.group == 'org.springframework') {
|
||||
details.useVersion springVersion
|
||||
}
|
||||
}
|
||||
}
|
||||
// Integration test setup
|
||||
configurations {
|
||||
integrationTestCompile {
|
||||
extendsFrom testCompile, optional, provided
|
||||
}
|
||||
integrationTestRuntime {
|
||||
extendsFrom integrationTestCompile, testRuntime
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
integrationTest {
|
||||
java.srcDir file('src/integration-test/java')
|
||||
groovy.srcDirs file('src/integration-test/groovy')
|
||||
resources.srcDir file('src/integration-test/resources')
|
||||
compileClasspath = sourceSets.main.output + sourceSets.test.output + configurations.integrationTestCompile
|
||||
runtimeClasspath = output + compileClasspath + configurations.integrationTestRuntime
|
||||
}
|
||||
}
|
||||
|
||||
task integrationTest(type: Test, dependsOn: jar) {
|
||||
testClassesDir = sourceSets.integrationTest.output.classesDir
|
||||
logging.captureStandardOutput(LogLevel.INFO)
|
||||
classpath = sourceSets.integrationTest.runtimeClasspath
|
||||
maxParallelForks = 1
|
||||
reports {
|
||||
html.destination = project.file("$project.buildDir/reports/integration-tests/")
|
||||
junitXml.destination = project.file("$project.buildDir/integration-test-results/")
|
||||
}
|
||||
}
|
||||
check.dependsOn integrationTest
|
||||
|
||||
checkstyle {
|
||||
configFile = rootProject.file('config/checkstyle/checkstyle.xml')
|
||||
configProperties.configDir = configFile.parentFile
|
||||
toolVersion = '6.16.1'
|
||||
}
|
||||
|
||||
task checkstyle {
|
||||
dependsOn project.tasks.findAll { task -> task.name.matches('checkstyle\\w+') }
|
||||
}
|
||||
|
||||
eclipse {
|
||||
classpath {
|
||||
plusConfigurations += [ configurations.integrationTestCompile ]
|
||||
}
|
||||
}
|
||||
|
||||
project.idea.module {
|
||||
scopes.TEST.plus += [project.configurations.integrationTestRuntime]
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
apply plugin: 'propdeps-maven'
|
||||
|
||||
install {
|
||||
repositories.mavenInstaller {
|
||||
customizePom(pom, project)
|
||||
}
|
||||
}
|
||||
|
||||
def customizePom(pom, gradleProject) {
|
||||
pom.whenConfigured { generatedPom ->
|
||||
|
||||
// sort to make pom dependencies order consistent to ease comparison of older poms
|
||||
generatedPom.dependencies = generatedPom.dependencies.sort { dep ->
|
||||
"$dep.scope:$dep.groupId:$dep.artifactId"
|
||||
}
|
||||
|
||||
// add all items necessary for maven central publication
|
||||
generatedPom.project {
|
||||
name = gradleProject.description
|
||||
description = gradleProject.description
|
||||
url = "https://github.com/spring-projects/spring-session"
|
||||
organization {
|
||||
name = "Spring IO"
|
||||
url = "http://projects.spring.io/spring-session"
|
||||
}
|
||||
licenses {
|
||||
license {
|
||||
name "The Apache Software License, Version 2.0"
|
||||
url "http://www.apache.org/licenses/LICENSE-2.0.txt"
|
||||
distribution "repo"
|
||||
}
|
||||
}
|
||||
scm {
|
||||
url = "https://github.com/spring-projects/spring-session"
|
||||
connection = "scm:git:git://github.com/spring-projects/spring-session"
|
||||
developerConnection = "scm:git:git://github.com/spring-projects/spring-session"
|
||||
}
|
||||
developers {
|
||||
developer {
|
||||
id = "rwinch"
|
||||
name = "Rob Winch"
|
||||
email = "rwinch@pivotal.io"
|
||||
}
|
||||
}
|
||||
issueManagement {
|
||||
system = "GitHub"
|
||||
url = "https://github.com/spring-projects/spring-session/issues"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
tasks.findByPath("artifactoryPublish")?.enabled = false
|
||||
sonarqube {
|
||||
skipProject = true
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
configurations {
|
||||
spring3TestRuntime.extendsFrom testRuntime
|
||||
}
|
||||
|
||||
configurations.spring3TestRuntime {
|
||||
resolutionStrategy.eachDependency { DependencyResolveDetails details ->
|
||||
if (details.requested.group == 'org.springframework'
|
||||
&& details.requested.name != 'spring-websocket'
|
||||
&& details.requested.name != 'spring-messaging') {
|
||||
details.useVersion '3.2.14.RELEASE'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task spring3Test(type: Test) {
|
||||
jvmArgs = ['-ea', '-Xmx500m', '-XX:MaxPermSize=128M']
|
||||
classpath = sourceSets.test.output + sourceSets.main.output + configurations.spring3TestRuntime
|
||||
exclude "org/springframework/session/web/socket/**"
|
||||
reports {
|
||||
html.destination = project.file("$buildDir/spring3-test-results/")
|
||||
junitXml.destination = project.file("$buildDir/reports/spring3-tests/")
|
||||
}
|
||||
}
|
||||
check.dependsOn spring3Test
|
||||
@@ -1,63 +0,0 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
maven { url "https://repo.spring.io/plugins-release" }
|
||||
}
|
||||
dependencies {
|
||||
classpath("com.bmuschko:gradle-tomcat-plugin:2.2.5")
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'war'
|
||||
apply plugin: 'com.bmuschko.tomcat'
|
||||
|
||||
[tomcatRun,tomcatRunWar]*.contextPath = '/'
|
||||
|
||||
|
||||
task integrationTomcatRun(type: com.bmuschko.gradle.tomcat.tasks.TomcatRun) {
|
||||
onlyIf { !sourceSets.integrationTest.allSource.empty }
|
||||
contextPath = tomcatRun.contextPath
|
||||
daemon = true
|
||||
tomcatClasspath = tomcatRun.tomcatClasspath
|
||||
webAppClasspath = tomcatRun.webAppClasspath
|
||||
webAppSourceDirectory = tomcatRun.webAppSourceDirectory
|
||||
doFirst {
|
||||
def mainOutputDir = project.sourceSets.main.output.classesDir
|
||||
if(mainOutputDir) {
|
||||
classesDirectory = mainOutputDir
|
||||
}
|
||||
// delay reserving ports to ensure they are still available
|
||||
def ports = reservePorts(3)
|
||||
httpPort = ports[0]
|
||||
ajpPort = ports[1]
|
||||
stopPort = ports[2]
|
||||
|
||||
System.setProperty('spring.session.redis.namespace',project.name)
|
||||
}
|
||||
}
|
||||
|
||||
task integrationTomcatStop(type: com.bmuschko.gradle.tomcat.tasks.TomcatStop) {
|
||||
onlyIf { !sourceSets.integrationTest.allSource.empty }
|
||||
doFirst {
|
||||
stopPort = integrationTomcatRun.stopPort
|
||||
}
|
||||
}
|
||||
|
||||
integrationTest {
|
||||
dependsOn integrationTomcatRun
|
||||
doFirst {
|
||||
def host = 'localhost:' + integrationTomcatRun.httpPort
|
||||
systemProperties['geb.build.baseUrl'] = 'http://'+host+'/' + integrationTomcatRun.contextPath
|
||||
systemProperties['geb.build.reportsDir'] = 'build/geb-reports'
|
||||
}
|
||||
finalizedBy integrationTomcatStop
|
||||
}
|
||||
|
||||
def reservePorts(int count) {
|
||||
def sockets = []
|
||||
for(int i in 1..count) {
|
||||
sockets << new ServerSocket(0)
|
||||
}
|
||||
def result = sockets*.localPort
|
||||
sockets*.close()
|
||||
result
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
apply from: TOMCAT_GRADLE
|
||||
|
||||
dependencies {
|
||||
def tomcatVersion = '6.0.43'
|
||||
tomcat "org.apache.tomcat:catalina:${tomcatVersion}",
|
||||
"org.apache.tomcat:coyote:${tomcatVersion}",
|
||||
"org.apache.tomcat:jasper:${tomcatVersion}"
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
apply from: TOMCAT_GRADLE
|
||||
|
||||
dependencies {
|
||||
def tomcatVersion = '7.0.59'
|
||||
tomcat "org.apache.tomcat.embed:tomcat-embed-core:${tomcatVersion}",
|
||||
"org.apache.tomcat.embed:tomcat-embed-logging-juli:${tomcatVersion}",
|
||||
"org.apache.tomcat.embed:tomcat-embed-jasper:${tomcatVersion}"
|
||||
}
|
||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
||||
#Fri Nov 11 19:46:57 CET 2016
|
||||
#Fri Jul 07 11:11:13 CDT 2017
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-3.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-3.5.1-bin.zip
|
||||
|
||||
19
gradlew
vendored
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
#!/usr/bin/env sh
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
@@ -154,16 +154,19 @@ if $cygwin ; then
|
||||
esac
|
||||
fi
|
||||
|
||||
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
|
||||
function splitJvmOpts() {
|
||||
JVM_OPTS=("$@")
|
||||
# Escape application args
|
||||
save ( ) {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
|
||||
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
|
||||
APP_ARGS=$(save "$@")
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||
if [[ "$(uname)" == "Darwin" ]] && [[ "$HOME" == "$PWD" ]]; then
|
||||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||
cd "$(dirname "$0")"
|
||||
fi
|
||||
|
||||
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
|
||||
exec "$JAVACMD" "$@"
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath("org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion")
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'org.springframework.boot'
|
||||
|
||||
apply from: JAVA_GRADLE
|
||||
apply from: SAMPLE_GRADLE
|
||||
|
||||
group = 'samples'
|
||||
|
||||
dependencies {
|
||||
compile project(':spring-session'),
|
||||
"org.springframework.boot:spring-boot-starter-redis",
|
||||
"org.springframework.boot:spring-boot-starter-web",
|
||||
"org.springframework.boot:spring-boot-starter-thymeleaf",
|
||||
"org.springframework.boot:spring-boot-starter-security",
|
||||
"nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect",
|
||||
"org.thymeleaf.extras:thymeleaf-extras-conditionalcomments",
|
||||
"org.webjars:bootstrap:$bootstrapVersion",
|
||||
"org.webjars:html5shiv:$html5ShivVersion",
|
||||
"org.webjars:webjars-locator"
|
||||
|
||||
testCompile "org.springframework.boot:spring-boot-starter-test"
|
||||
|
||||
integrationTestCompile gebDependencies,
|
||||
"org.spockframework:spock-spring:$spockVersion"
|
||||
|
||||
}
|
||||
|
||||
integrationTest {
|
||||
doFirst {
|
||||
def port = reservePort()
|
||||
|
||||
def host = 'localhost:' + port
|
||||
systemProperties['geb.build.baseUrl'] = 'http://'+host+'/'
|
||||
systemProperties['geb.build.reportsDir'] = 'build/geb-reports'
|
||||
systemProperties['server.port'] = port
|
||||
systemProperties['management.port'] = 0
|
||||
|
||||
systemProperties['spring.session.redis.namespace'] = project.name
|
||||
}
|
||||
}
|
||||
|
||||
def reservePort() {
|
||||
def socket = new ServerSocket(0)
|
||||
def result = socket.localPort
|
||||
socket.close()
|
||||
result
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
apply plugin: 'io.spring.convention.spring-sample-boot'
|
||||
|
||||
ext['spring-data-releasetrain.version'] = 'Kay-RC3'
|
||||
|
||||
dependencies {
|
||||
compile project(':spring-session-data-redis')
|
||||
compile "org.springframework.boot:spring-boot-starter-web"
|
||||
compile "org.springframework.boot:spring-boot-starter-thymeleaf"
|
||||
compile "org.springframework.boot:spring-boot-starter-security"
|
||||
compile "org.springframework.boot:spring-boot-starter-data-redis"
|
||||
compile "org.springframework.boot:spring-boot-devtools"
|
||||
compile "nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect"
|
||||
compile "org.webjars:bootstrap"
|
||||
compile "org.webjars:html5shiv"
|
||||
compile "org.webjars:webjars-locator"
|
||||
compile "com.maxmind.geoip2:geoip2"
|
||||
compile "org.apache.httpcomponents:httpclient"
|
||||
|
||||
testCompile "org.springframework.boot:spring-boot-starter-test"
|
||||
testCompile "org.assertj:assertj-core"
|
||||
|
||||
integrationTestCompile seleniumDependencies
|
||||
integrationTestCompile "org.testcontainers:testcontainers"
|
||||
}
|
||||
|
||||
integrationTest {
|
||||
doFirst {
|
||||
systemProperties['spring.session.redis.namespace'] = project.name
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright 2014-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;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
import org.testcontainers.containers.GenericContainer;
|
||||
import sample.pages.HomePage;
|
||||
import sample.pages.LoginPage;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
|
||||
import org.springframework.boot.test.util.TestPropertyValues;
|
||||
import org.springframework.context.ApplicationContextInitializer;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.htmlunit.webdriver.MockMvcHtmlUnitDriverBuilder;
|
||||
|
||||
/**
|
||||
* @author Eddú Meléndez
|
||||
* @author Rob Winch
|
||||
* @author Vedran Pavic
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@AutoConfigureMockMvc
|
||||
@SpringBootTest(classes = FindByUsernameApplication.class, webEnvironment = WebEnvironment.MOCK)
|
||||
@ContextConfiguration(initializers = FindByUsernameTests.Initializer.class)
|
||||
public class FindByUsernameTests {
|
||||
|
||||
private static final String DOCKER_IMAGE = "redis:3.2.9";
|
||||
|
||||
@ClassRule
|
||||
public static GenericContainer redisContainer = new GenericContainer(DOCKER_IMAGE)
|
||||
.withExposedPorts(6379);
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
private WebDriver driver;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
this.driver = MockMvcHtmlUnitDriverBuilder.mockMvcSetup(this.mockMvc).build();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
this.driver.quit();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void home() {
|
||||
LoginPage login = HomePage.go(this.driver);
|
||||
login.assertAt();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void login() {
|
||||
LoginPage login = HomePage.go(this.driver);
|
||||
HomePage home = login.form().login(HomePage.class);
|
||||
home.assertAt();
|
||||
home.containCookie("SESSION");
|
||||
home.doesNotContainCookie("JSESSIONID");
|
||||
home.terminateButtonDisabled();
|
||||
}
|
||||
|
||||
static class Initializer
|
||||
implements ApplicationContextInitializer<ConfigurableApplicationContext> {
|
||||
|
||||
@Override
|
||||
public void initialize(
|
||||
ConfigurableApplicationContext configurableApplicationContext) {
|
||||
TestPropertyValues
|
||||
.of("spring.redis.host=" + redisContainer.getContainerIpAddress(),
|
||||
"spring.redis.port=" + redisContainer.getFirstMappedPort())
|
||||
.applyTo(configurableApplicationContext.getEnvironment());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2016 the original author or authors.
|
||||
* Copyright 2014-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.
|
||||
@@ -14,21 +14,28 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package sample.mvc;
|
||||
package sample.pages;
|
||||
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
|
||||
/**
|
||||
* Returns view for log in page
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @author Eddú Meléndez
|
||||
*/
|
||||
@Controller
|
||||
public class LoginController {
|
||||
public class BasePage {
|
||||
|
||||
@RequestMapping("/login")
|
||||
public String login() {
|
||||
return "login";
|
||||
private WebDriver driver;
|
||||
|
||||
public BasePage(WebDriver driver) {
|
||||
this.driver = driver;
|
||||
}
|
||||
|
||||
public WebDriver getDriver() {
|
||||
return this.driver;
|
||||
}
|
||||
|
||||
public static void get(WebDriver driver, String get) {
|
||||
String baseUrl = "http://localhost";
|
||||
driver.get(baseUrl + get);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 2014-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.pages;
|
||||
|
||||
import java.util.Base64;
|
||||
import java.util.Set;
|
||||
|
||||
import org.openqa.selenium.By;
|
||||
import org.openqa.selenium.Cookie;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.support.FindBy;
|
||||
import org.openqa.selenium.support.PageFactory;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Eddú Meléndez
|
||||
* @author Rob Winch
|
||||
*/
|
||||
public class HomePage extends BasePage {
|
||||
@FindBy(css = "input[type=\"submit\"]")
|
||||
WebElement logout;
|
||||
|
||||
public HomePage(WebDriver driver) {
|
||||
super(driver);
|
||||
}
|
||||
|
||||
public void assertAt() {
|
||||
assertThat(getDriver().getTitle())
|
||||
.isEqualTo("Spring Session Sample - Secured Content");
|
||||
}
|
||||
|
||||
public void containCookie(String cookieName) {
|
||||
Set<Cookie> cookies = getDriver().manage().getCookies();
|
||||
assertThat(cookies).extracting("name").contains(cookieName);
|
||||
}
|
||||
|
||||
public void doesNotContainCookie(String cookieName) {
|
||||
Set<Cookie> cookies = getDriver().manage().getCookies();
|
||||
assertThat(cookies).extracting("name").doesNotContain(cookieName);
|
||||
}
|
||||
|
||||
public void terminateButtonDisabled() {
|
||||
Set<Cookie> cookies = getDriver().manage().getCookies();
|
||||
String cookieValue = null;
|
||||
for (Cookie cookie : cookies) {
|
||||
if ("SESSION".equals(cookie.getName())) {
|
||||
cookieValue = new String(Base64.getDecoder().decode(cookie.getValue()));
|
||||
}
|
||||
}
|
||||
WebElement element = getDriver().findElement(By.id("terminate-" + cookieValue));
|
||||
assertThat(element.isEnabled()).isFalse();
|
||||
}
|
||||
|
||||
public HomePage logout() {
|
||||
this.logout.click();
|
||||
return PageFactory.initElements(getDriver(), HomePage.class);
|
||||
}
|
||||
|
||||
public static LoginPage go(WebDriver driver) {
|
||||
get(driver, "/");
|
||||
return PageFactory.initElements(driver, LoginPage.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2014-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.pages;
|
||||
|
||||
import org.openqa.selenium.SearchContext;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.support.FindBy;
|
||||
import org.openqa.selenium.support.PageFactory;
|
||||
import org.openqa.selenium.support.pagefactory.DefaultElementLocatorFactory;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Eddú Meléndez
|
||||
* @author Rob Winch
|
||||
*/
|
||||
public class LoginPage extends BasePage {
|
||||
|
||||
public LoginPage(WebDriver driver) {
|
||||
super(driver);
|
||||
}
|
||||
|
||||
public void assertAt() {
|
||||
assertThat(getDriver().getTitle()).isEqualTo("Spring Session Sample - Log In");
|
||||
}
|
||||
|
||||
public Form form() {
|
||||
return new Form(getDriver());
|
||||
}
|
||||
|
||||
public class Form {
|
||||
|
||||
@FindBy(name = "username")
|
||||
private WebElement username;
|
||||
|
||||
@FindBy(name = "password")
|
||||
private WebElement password;
|
||||
|
||||
@FindBy(tagName = "button")
|
||||
private WebElement button;
|
||||
|
||||
public Form(SearchContext context) {
|
||||
PageFactory.initElements(new DefaultElementLocatorFactory(context), this);
|
||||
}
|
||||
|
||||
public <T> T login(Class<T> page) {
|
||||
this.username.sendKeys("user");
|
||||
this.password.sendKeys("password");
|
||||
this.button.click();
|
||||
return PageFactory.initElements(getDriver(), page);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2016 the original author or authors.
|
||||
* Copyright 2014-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.
|
||||
@@ -17,16 +17,12 @@
|
||||
package sample;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
*/
|
||||
@Configuration
|
||||
@ComponentScan
|
||||
@EnableAutoConfiguration
|
||||
@SpringBootApplication
|
||||
public class FindByUsernameApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2016 the original author or authors.
|
||||
* Copyright 2014-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.
|
||||
@@ -16,19 +16,27 @@
|
||||
|
||||
package sample.config;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
*/
|
||||
@EnableWebSecurity
|
||||
@Configuration
|
||||
public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Autowired
|
||||
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
|
||||
auth.inMemoryAuthentication().withUser("user").password("password").roles("USER");
|
||||
// tag::config[]
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.formLogin()
|
||||
.loginPage("/login")
|
||||
.permitAll()
|
||||
.and()
|
||||
.authorizeRequests()
|
||||
.anyRequest().authenticated();
|
||||
}
|
||||
// end::config[]
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2016 the original author or authors.
|
||||
* Copyright 2014-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.
|
||||
@@ -14,22 +14,18 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package sample;
|
||||
package sample.config;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
*/
|
||||
@Configuration
|
||||
@ComponentScan
|
||||
@EnableAutoConfiguration
|
||||
public class Application {
|
||||
public class WebMvcConfig implements WebMvcConfigurer {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(Application.class, args);
|
||||
@Override
|
||||
public void addViewControllers(ViewControllerRegistry registry) {
|
||||
registry.addViewController("/login").setViewName("login");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2016 the original author or authors.
|
||||
* Copyright 2014-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.
|
||||
@@ -21,8 +21,8 @@ import java.util.Collection;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.session.ExpiringSession;
|
||||
import org.springframework.session.FindByIndexNameSessionRepository;
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
@@ -39,11 +39,11 @@ import org.springframework.web.bind.annotation.RequestMethod;
|
||||
public class IndexController {
|
||||
// tag::findbyusername[]
|
||||
@Autowired
|
||||
FindByIndexNameSessionRepository<? extends ExpiringSession> sessions;
|
||||
FindByIndexNameSessionRepository<? extends Session> sessions;
|
||||
|
||||
@RequestMapping("/")
|
||||
public String index(Principal principal, Model model) {
|
||||
Collection<? extends ExpiringSession> usersSessions = this.sessions
|
||||
Collection<? extends Session> usersSessions = this.sessions
|
||||
.findByIndexNameAndIndexValue(
|
||||
FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME,
|
||||
principal.getName())
|
||||
@@ -60,7 +60,7 @@ public class IndexController {
|
||||
FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME,
|
||||
principal.getName()).keySet();
|
||||
if (usersSessionIds.contains(sessionIdToDelete)) {
|
||||
this.sessions.delete(sessionIdToDelete);
|
||||
this.sessions.deleteById(sessionIdToDelete);
|
||||
}
|
||||
|
||||
return "redirect:/";
|
||||
|
Before Width: | Height: | Size: 30 MiB After Width: | Height: | Size: 30 MiB |
@@ -0,0 +1,2 @@
|
||||
spring.session.store-type=redis
|
||||
security.user.password=password
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,36 @@
|
||||
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout}">
|
||||
<head>
|
||||
<title>Secured Content</title>
|
||||
</head>
|
||||
<body>
|
||||
<div layout:fragment="content">
|
||||
<h1>Secured Page</h1>
|
||||
<p>This page is secured using Spring Boot, Spring Session, and Spring Security.</p>
|
||||
|
||||
<p>Your current session id is <span id="session-id" th:text="${#httpSession.id}"></span></p>
|
||||
|
||||
<table class="table table-stripped">
|
||||
<tr>
|
||||
<th>Id Suffix</th>
|
||||
<th>Location</th>
|
||||
<th>Created</th>
|
||||
<th>Last Updated</th>
|
||||
<th>Information</th>
|
||||
<th>Terminate</th>
|
||||
</tr>
|
||||
<tr th:each="sessionElement : ${sessions}" th:with="details=${sessionElement.getAttribute('SESSION_DETAILS')}">
|
||||
<td th:text="${sessionElement.id.substring(30)}"></td>
|
||||
<td th:text="${details?.location}"></td>
|
||||
<td th:text="${#temporals.format(sessionElement.creationTime.atZone(T(java.time.ZoneId).systemDefault()),'dd/MMM/yyyy HH:mm:ss')}"></td>
|
||||
<td th:text="${#temporals.format(sessionElement.lastAccessedTime.atZone(T(java.time.ZoneId).systemDefault()),'dd/MMM/yyyy HH:mm:ss')}"></td>
|
||||
<td th:text="${details?.accessType}"></td>
|
||||
<td>
|
||||
<form th:action="@{'/sessions/' + ${sessionElement.id}}" th:method="delete">
|
||||
<input th:id="'terminate-' + ${sessionElement.id}" type="submit" value="Terminate" th:disabled="${sessionElement.id == #httpSession.id}"/>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -3,8 +3,8 @@
|
||||
xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
|
||||
<head>
|
||||
<title layout:title-pattern="$DECORATOR_TITLE - $CONTENT_TITLE">Spring Session Sample</title>
|
||||
<link rel="icon" type="image/x-icon" th:href="@{/resources/img/favicon.ico}" href="../static/img/favicon.ico"/>
|
||||
<title layout:title-pattern="$LAYOUT_TITLE - $CONTENT_TITLE">Spring Session Sample</title>
|
||||
<link rel="icon" type="image/x-icon" th:href="@{/favicon.ico}" href="../static/favicon.ico"/>
|
||||
<link th:href="@{/webjars/bootstrap/css/bootstrap.min.css}" href="/webjars/bootstrap/css/bootstrap.min.css" rel="stylesheet"></link>
|
||||
<style type="text/css">
|
||||
/* Sticky footer styles
|
||||
@@ -79,7 +79,7 @@
|
||||
<div class="navbar navbar-inverse navbar-static-top">
|
||||
<div class="navbar-inner">
|
||||
<div class="container">
|
||||
<a class="brand" th:href="@{/}"><img th:src="@{/resources/img/logo.png}" alt="Spring Security Sample"/></a>
|
||||
<a class="brand" th:href="@{/}"><img th:src="@{/images/logo.png}" alt="Spring Security Sample"/></a>
|
||||
|
||||
<div class="nav-collapse collapse"
|
||||
th:with="currentUser=${#httpServletRequest.userPrincipal?.principal}">
|
||||
@@ -1,6 +1,6 @@
|
||||
<html xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||
layout:decorator="layout">
|
||||
layout:decorate="~{layout}">
|
||||
<head>
|
||||
<title>Log In</title>
|
||||
</head>
|
||||
@@ -44,4 +44,4 @@
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
19
samples/boot/jdbc/spring-session-sample-boot-jdbc.gradle
Normal file
@@ -0,0 +1,19 @@
|
||||
apply plugin: 'io.spring.convention.spring-sample-boot'
|
||||
|
||||
dependencies {
|
||||
compile project(':spring-session-jdbc')
|
||||
compile "org.springframework.boot:spring-boot-starter-web"
|
||||
compile "org.springframework.boot:spring-boot-starter-thymeleaf"
|
||||
compile "org.springframework.boot:spring-boot-starter-security"
|
||||
compile "org.springframework.boot:spring-boot-devtools"
|
||||
compile "nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect"
|
||||
compile "org.webjars:bootstrap"
|
||||
compile "org.webjars:html5shiv"
|
||||
compile "org.webjars:webjars-locator"
|
||||
compile "com.h2database:h2"
|
||||
|
||||
testCompile "org.springframework.boot:spring-boot-starter-test"
|
||||
testCompile "org.assertj:assertj-core"
|
||||
|
||||
integrationTestCompile seleniumDependencies
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright 2014-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;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
import sample.pages.HomePage;
|
||||
import sample.pages.LoginPage;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.htmlunit.webdriver.MockMvcHtmlUnitDriverBuilder;
|
||||
|
||||
/**
|
||||
* @author Eddú Meléndez
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@AutoConfigureMockMvc
|
||||
@SpringBootTest(webEnvironment = WebEnvironment.MOCK)
|
||||
public class BootTests {
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
private WebDriver driver;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
this.driver = MockMvcHtmlUnitDriverBuilder.mockMvcSetup(this.mockMvc).build();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
this.driver.quit();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void home() {
|
||||
LoginPage login = HomePage.go(this.driver);
|
||||
login.assertAt();
|
||||
HomePage home = login.form().login(HomePage.class);
|
||||
home.assertAt();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void login() {
|
||||
LoginPage login = HomePage.go(this.driver);
|
||||
HomePage home = login.form().login(HomePage.class);
|
||||
home.containCookie("SESSION");
|
||||
home.doesNotContainCookie("JSESSIONID");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void logout() {
|
||||
LoginPage login = HomePage.go(this.driver);
|
||||
HomePage home = login.form().login(HomePage.class);
|
||||
login = home.logout();
|
||||
login.assertAt();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2016 the original author or authors.
|
||||
* Copyright 2014-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.
|
||||
@@ -14,21 +14,28 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package sample.mvc;
|
||||
package sample.pages;
|
||||
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
|
||||
/**
|
||||
* Controller for sending the user to the login view.
|
||||
*
|
||||
* @author Rob Winch
|
||||
*
|
||||
* @author Eddú Meléndez
|
||||
*/
|
||||
@Controller
|
||||
public class IndexController {
|
||||
@RequestMapping("/")
|
||||
public String index() {
|
||||
return "index";
|
||||
public class BasePage {
|
||||
|
||||
private WebDriver driver;
|
||||
|
||||
public BasePage(WebDriver driver) {
|
||||
this.driver = driver;
|
||||
}
|
||||
|
||||
public WebDriver getDriver() {
|
||||
return this.driver;
|
||||
}
|
||||
|
||||
public static void get(WebDriver driver, String get) {
|
||||
String baseUrl = "http://localhost";
|
||||
driver.get(baseUrl + get);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2014-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.pages;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.openqa.selenium.By;
|
||||
import org.openqa.selenium.Cookie;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.support.PageFactory;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Eddú Meléndez
|
||||
*/
|
||||
public class HomePage extends BasePage {
|
||||
|
||||
public HomePage(WebDriver driver) {
|
||||
super(driver);
|
||||
}
|
||||
|
||||
public static LoginPage go(WebDriver driver) {
|
||||
get(driver, "/");
|
||||
return PageFactory.initElements(driver, LoginPage.class);
|
||||
}
|
||||
|
||||
public void assertAt() {
|
||||
assertThat(getDriver().getTitle()).isEqualTo("Spring Session Sample - Secured Content");
|
||||
}
|
||||
|
||||
public void containCookie(String cookieName) {
|
||||
Set<Cookie> cookies = getDriver().manage().getCookies();
|
||||
assertThat(cookies).extracting("name").contains(cookieName);
|
||||
}
|
||||
|
||||
public void doesNotContainCookie(String cookieName) {
|
||||
Set<Cookie> cookies = getDriver().manage().getCookies();
|
||||
assertThat(cookies).extracting("name").doesNotContain(cookieName);
|
||||
}
|
||||
|
||||
public LoginPage logout() {
|
||||
WebElement logout = getDriver().findElement(By.cssSelector("input[type=\"submit\"]"));
|
||||
logout.click();
|
||||
return PageFactory.initElements(getDriver(), LoginPage.class);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2014-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.pages;
|
||||
|
||||
import org.openqa.selenium.SearchContext;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.support.FindBy;
|
||||
import org.openqa.selenium.support.PageFactory;
|
||||
import org.openqa.selenium.support.pagefactory.DefaultElementLocatorFactory;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Eddú Meléndez
|
||||
* @author Rob Winch
|
||||
*/
|
||||
public class LoginPage extends BasePage {
|
||||
|
||||
public LoginPage(WebDriver driver) {
|
||||
super(driver);
|
||||
}
|
||||
|
||||
public void assertAt() {
|
||||
assertThat(getDriver().getTitle()).isEqualTo("Login Page");
|
||||
}
|
||||
|
||||
public Form form() {
|
||||
return new Form(getDriver());
|
||||
}
|
||||
|
||||
public class Form {
|
||||
|
||||
@FindBy(name = "username")
|
||||
private WebElement username;
|
||||
|
||||
@FindBy(name = "password")
|
||||
private WebElement password;
|
||||
|
||||
@FindBy(name = "submit")
|
||||
private WebElement button;
|
||||
|
||||
public Form(SearchContext context) {
|
||||
PageFactory.initElements(new DefaultElementLocatorFactory(context), this);
|
||||
}
|
||||
|
||||
public <T> T login(Class<T> page) {
|
||||
this.username.sendKeys("user");
|
||||
this.password.sendKeys("password");
|
||||
this.button.click();
|
||||
return PageFactory.initElements(getDriver(), page);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2016 the original author or authors.
|
||||
* Copyright 2014-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.
|
||||
@@ -16,30 +16,17 @@
|
||||
|
||||
package sample.config;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.web.builders.WebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
* @author Vedran Pavic
|
||||
*/
|
||||
@EnableWebSecurity
|
||||
@Configuration
|
||||
public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
// @formatter:off
|
||||
@Autowired
|
||||
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
|
||||
auth
|
||||
.inMemoryAuthentication()
|
||||
.withUser("user")
|
||||
.password("password")
|
||||
.roles("USER");
|
||||
}
|
||||
// @formatter:on
|
||||
|
||||
// @formatter:off
|
||||
@Override
|
||||
public void configure(WebSecurity web) throws Exception {
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2014-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.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
@Configuration
|
||||
public class WebMvcConfig implements WebMvcConfigurer {
|
||||
|
||||
@Override
|
||||
public void addViewControllers(ViewControllerRegistry registry) {
|
||||
registry.addViewController("/").setViewName("index");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
spring.session.store-type=jdbc
|
||||
security.user.password=password
|
||||
spring.h2.console.enabled=true
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
@@ -1,4 +1,4 @@
|
||||
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorator="layout">
|
||||
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout}">
|
||||
<head>
|
||||
<title>Secured Content</title>
|
||||
</head>
|
||||
@@ -3,8 +3,8 @@
|
||||
xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
|
||||
<head>
|
||||
<title layout:title-pattern="$DECORATOR_TITLE - $CONTENT_TITLE">Spring Session Sample</title>
|
||||
<link rel="icon" type="image/x-icon" th:href="@{/resources/img/favicon.ico}" href="../static/img/favicon.ico"/>
|
||||
<title layout:title-pattern="$LAYOUT_TITLE - $CONTENT_TITLE">Spring Session Sample</title>
|
||||
<link rel="icon" type="image/x-icon" th:href="@{/favicon.ico}" href="../static/favicon.ico"/>
|
||||
<link th:href="@{/webjars/bootstrap/css/bootstrap.min.css}" href="/webjars/bootstrap/css/bootstrap.min.css" rel="stylesheet"></link>
|
||||
<style type="text/css">
|
||||
/* Sticky footer styles
|
||||
@@ -79,7 +79,7 @@
|
||||
<div class="navbar navbar-inverse navbar-static-top">
|
||||
<div class="navbar-inner">
|
||||
<div class="container">
|
||||
<a class="brand" th:href="@{/}"><img th:src="@{/resources/img/logo.png}" alt="Spring Security Sample"/></a>
|
||||
<a class="brand" th:href="@{/}"><img th:src="@{/images/logo.png}" alt="Spring Security Sample"/></a>
|
||||
|
||||
<div class="nav-collapse collapse"
|
||||
th:with="currentUser=${#httpServletRequest.userPrincipal?.principal}">
|
||||
@@ -115,7 +115,7 @@
|
||||
|
||||
<div id="footer">
|
||||
<div class="container">
|
||||
<p class="muted credit">Visit the <a href="http://spring.io/spring-security">Spring Security</a> site for more <a href="https://github.com/spring-projects/spring-security/blob/master/samples/">samples</a>.</p>
|
||||
<p class="muted credit">Visit the <a href="https://projects.spring.io/spring-session/">Spring Session</a> site for more <a href="https://github.com/spring-projects/spring-session/tree/master/samples">samples</a>.</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
@@ -0,0 +1,24 @@
|
||||
apply plugin: 'io.spring.convention.spring-sample-boot'
|
||||
|
||||
ext['spring-data-releasetrain.version'] = 'Kay-RC3'
|
||||
|
||||
dependencies {
|
||||
compile project(':spring-session-data-redis')
|
||||
compile "org.springframework.boot:spring-boot-starter-web"
|
||||
compile "org.springframework.boot:spring-boot-starter-thymeleaf"
|
||||
compile "org.springframework.boot:spring-boot-starter-security"
|
||||
compile "org.springframework.boot:spring-boot-starter-data-redis"
|
||||
compile "org.springframework.boot:spring-boot-devtools"
|
||||
compile "nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect"
|
||||
compile "org.webjars:bootstrap"
|
||||
compile "org.webjars:html5shiv"
|
||||
compile "org.webjars:webjars-locator"
|
||||
compile "org.apache.httpcomponents:httpclient"
|
||||
|
||||
testCompile "org.springframework.boot:spring-boot-starter-test"
|
||||
testCompile "org.assertj:assertj-core"
|
||||
testCompile "org.skyscreamer:jsonassert"
|
||||
|
||||
integrationTestCompile seleniumDependencies
|
||||
integrationTestCompile "org.testcontainers:testcontainers"
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Copyright 2014-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;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
import org.testcontainers.containers.GenericContainer;
|
||||
import sample.pages.HomePage;
|
||||
import sample.pages.HomePage.Attribute;
|
||||
import sample.pages.LoginPage;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
|
||||
import org.springframework.boot.test.util.TestPropertyValues;
|
||||
import org.springframework.context.ApplicationContextInitializer;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.htmlunit.webdriver.MockMvcHtmlUnitDriverBuilder;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Eddú Meléndez
|
||||
* @author Vedran Pavic
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(classes = Application.class, webEnvironment = WebEnvironment.MOCK)
|
||||
@AutoConfigureMockMvc
|
||||
@ContextConfiguration(initializers = HttpRedisJsonTest.Initializer.class)
|
||||
public class HttpRedisJsonTest {
|
||||
|
||||
private static final String DOCKER_IMAGE = "redis:3.2.9";
|
||||
|
||||
@ClassRule
|
||||
public static GenericContainer redisContainer = new GenericContainer(DOCKER_IMAGE)
|
||||
.withExposedPorts(6379);
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
private WebDriver driver;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
this.driver = MockMvcHtmlUnitDriverBuilder.mockMvcSetup(this.mockMvc).build();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
this.driver.quit();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void goLoginRedirectToLogin() {
|
||||
LoginPage login = HomePage.go(this.driver, LoginPage.class);
|
||||
login.assertAt();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void goHomeRedirectLoginPage() {
|
||||
LoginPage login = HomePage.go(this.driver, LoginPage.class);
|
||||
login.assertAt();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void login() {
|
||||
LoginPage login = HomePage.go(this.driver, LoginPage.class);
|
||||
HomePage home = login.form().login(HomePage.class);
|
||||
home.containCookie("SESSION");
|
||||
home.doesNotContainCookie("JSESSIONID");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createAttribute() {
|
||||
LoginPage login = HomePage.go(this.driver, LoginPage.class);
|
||||
HomePage home = login.form().login(HomePage.class);
|
||||
// @formatter:off
|
||||
home = home.form()
|
||||
.attributeName("Demo Key")
|
||||
.attributeValue("Demo Value")
|
||||
.submit(HomePage.class);
|
||||
// @formatter:on
|
||||
|
||||
List<Attribute> attributes = home.attributes();
|
||||
assertThat(attributes).extracting("attributeName").contains("Demo Key");
|
||||
assertThat(attributes).extracting("attributeValue").contains("Demo Value");
|
||||
}
|
||||
|
||||
static class Initializer
|
||||
implements ApplicationContextInitializer<ConfigurableApplicationContext> {
|
||||
|
||||
@Override
|
||||
public void initialize(
|
||||
ConfigurableApplicationContext configurableApplicationContext) {
|
||||
TestPropertyValues
|
||||
.of("spring.redis.host=" + redisContainer.getContainerIpAddress(),
|
||||
"spring.redis.port=" + redisContainer.getFirstMappedPort())
|
||||
.applyTo(configurableApplicationContext.getEnvironment());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 2014-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;
|
||||
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.testcontainers.containers.GenericContainer;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.util.TestPropertyValues;
|
||||
import org.springframework.context.ApplicationContextInitializer;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author jitendra
|
||||
* @author Vedran Pavic
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(classes = Application.class)
|
||||
@ContextConfiguration(initializers = RedisSerializerTest.Initializer.class)
|
||||
public class RedisSerializerTest {
|
||||
|
||||
private static final String DOCKER_IMAGE = "redis:3.2.9";
|
||||
|
||||
@ClassRule
|
||||
public static GenericContainer redisContainer = new GenericContainer(DOCKER_IMAGE)
|
||||
.withExposedPorts(6379);
|
||||
|
||||
@Autowired
|
||||
private RedisTemplate<Object, Object> sessionRedisTemplate;
|
||||
|
||||
@Test
|
||||
public void testRedisTemplate() {
|
||||
assertThat(this.sessionRedisTemplate).isNotNull();
|
||||
assertThat(this.sessionRedisTemplate.getDefaultSerializer()).isNotNull();
|
||||
assertThat(this.sessionRedisTemplate.getDefaultSerializer())
|
||||
.isInstanceOf(GenericJackson2JsonRedisSerializer.class);
|
||||
}
|
||||
|
||||
static class Initializer
|
||||
implements ApplicationContextInitializer<ConfigurableApplicationContext> {
|
||||
|
||||
@Override
|
||||
public void initialize(
|
||||
ConfigurableApplicationContext configurableApplicationContext) {
|
||||
TestPropertyValues
|
||||
.of("spring.redis.host=" + redisContainer.getContainerIpAddress(),
|
||||
"spring.redis.port=" + redisContainer.getFirstMappedPort())
|
||||
.applyTo(configurableApplicationContext.getEnvironment());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||