Compare commits
40 Commits
2.0.4.RELE
...
2.0.6.RELE
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f190da3757 | ||
|
|
34060cf0f6 | ||
|
|
ff10709f18 | ||
|
|
6e471f6441 | ||
|
|
a03be43450 | ||
|
|
7e8f500df0 | ||
|
|
b27742ce3e | ||
|
|
af44e71af0 | ||
|
|
055c2bcb93 | ||
|
|
3335deb5d5 | ||
|
|
1079e9e016 | ||
|
|
5fa52be8d1 | ||
|
|
c90952031f | ||
|
|
ce308ca513 | ||
|
|
c8f78e510e | ||
|
|
748ba70a01 | ||
|
|
dc1c7cdf02 | ||
|
|
3a972bef76 | ||
|
|
6b7fc3af08 | ||
|
|
535160bc92 | ||
|
|
606e08007e | ||
|
|
06fa33e48b | ||
|
|
d7c2e8e79c | ||
|
|
6bec95a298 | ||
|
|
9249a140c9 | ||
|
|
7f6dc801e0 | ||
|
|
83d46ad685 | ||
|
|
21cef2b7fa | ||
|
|
db31527c8c | ||
|
|
3d2a742328 | ||
|
|
7ac6e458e0 | ||
|
|
9adf0a6e0c | ||
|
|
58219fa016 | ||
|
|
83cbff5ce2 | ||
|
|
936fc853df | ||
|
|
dba475c48f | ||
|
|
9956e91b93 | ||
|
|
c902981eba | ||
|
|
2e26c6e9d3 | ||
|
|
b9cd3865c5 |
92
Jenkinsfile
vendored
92
Jenkinsfile
vendored
@@ -1,9 +1,9 @@
|
||||
def projectProperties = [
|
||||
[$class: 'BuildDiscarderProperty',
|
||||
strategy: [$class: 'LogRotator', numToKeepStr: '5']],
|
||||
pipelineTriggers([cron('@daily')])
|
||||
]
|
||||
properties(projectProperties)
|
||||
properties([
|
||||
buildDiscarder(logRotator(numToKeepStr: '10')),
|
||||
pipelineTriggers([
|
||||
cron('@daily')
|
||||
]),
|
||||
])
|
||||
|
||||
def SUCCESS = hudson.model.Result.SUCCESS.toString()
|
||||
currentBuild.result = SUCCESS
|
||||
@@ -11,15 +11,19 @@ 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'
|
||||
timeout(time: 30, unit: 'MINUTES') {
|
||||
node {
|
||||
checkout scm
|
||||
try {
|
||||
sh './gradlew clean check --no-daemon --refresh-dependencies'
|
||||
}
|
||||
catch (e) {
|
||||
currentBuild.result = 'FAILED: check'
|
||||
throw e
|
||||
}
|
||||
finally {
|
||||
junit '**/build/test-results/*/*.xml'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,31 +33,39 @@ try {
|
||||
node {
|
||||
checkout scm
|
||||
try {
|
||||
sh "./gradlew clean springIoCheck -PplatformVersion=Cairo-BUILD-SNAPSHOT -PexcludeProjects='**/samples/**' --refresh-dependencies --no-daemon --stacktrace"
|
||||
} catch(Exception e) {
|
||||
sh "./gradlew clean springIoCheck --stacktrace --no-daemon --refresh-dependencies -PplatformVersion=Cairo-BUILD-SNAPSHOT -PexcludeProjects='**/samples/**'"
|
||||
}
|
||||
catch(e) {
|
||||
currentBuild.result = 'FAILED: springio'
|
||||
throw e
|
||||
} finally {
|
||||
}
|
||||
finally {
|
||||
junit '**/build/spring-io*-results/*.xml'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(currentBuild.result == 'SUCCESS') {
|
||||
if (currentBuild.result == 'SUCCESS') {
|
||||
parallel artifacts: {
|
||||
stage('Deploy Artifacts') {
|
||||
node {
|
||||
checkout scm
|
||||
withCredentials([file(credentialsId: 'spring-signing-secring.gpg', variable: 'SIGNING_KEYRING_FILE')]) {
|
||||
withCredentials([string(credentialsId: 'spring-gpg-passphrase', variable: 'SIGNING_PASSWORD')]) {
|
||||
withCredentials([usernamePassword(credentialsId: 'oss-token', passwordVariable: 'OSSRH_PASSWORD', usernameVariable: 'OSSRH_USERNAME')]) {
|
||||
withCredentials([usernamePassword(credentialsId: '02bd1690-b54f-4c9f-819d-a77cb7a9822c', usernameVariable: 'ARTIFACTORY_USERNAME', passwordVariable: 'ARTIFACTORY_PASSWORD')]) {
|
||||
sh "./gradlew deployArtifacts finalizeDeployArtifacts -Psigning.secretKeyRingFile=$SIGNING_KEYRING_FILE -Psigning.keyId=$SPRING_SIGNING_KEYID -Psigning.password='$SIGNING_PASSWORD' -PossrhUsername=$OSSRH_USERNAME -PossrhPassword=$OSSRH_PASSWORD -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace"
|
||||
try {
|
||||
withCredentials([file(credentialsId: 'spring-signing-secring.gpg', variable: 'SIGNING_KEYRING_FILE')]) {
|
||||
withCredentials([string(credentialsId: 'spring-gpg-passphrase', variable: 'SIGNING_PASSWORD')]) {
|
||||
withCredentials([usernamePassword(credentialsId: 'oss-token', passwordVariable: 'OSSRH_PASSWORD', usernameVariable: 'OSSRH_USERNAME')]) {
|
||||
withCredentials([usernamePassword(credentialsId: '02bd1690-b54f-4c9f-819d-a77cb7a9822c', usernameVariable: 'ARTIFACTORY_USERNAME', passwordVariable: 'ARTIFACTORY_PASSWORD')]) {
|
||||
sh './gradlew deployArtifacts finalizeDeployArtifacts --stacktrace --no-daemon --refresh-dependencies -Psigning.secretKeyRingFile=$SIGNING_KEYRING_FILE -Psigning.keyId=$SPRING_SIGNING_KEYID -Psigning.password=$SIGNING_PASSWORD -PossrhUsername=$OSSRH_USERNAME -PossrhPassword=$OSSRH_PASSWORD -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
currentBuild.result = 'FAILED: artifacts'
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -61,32 +73,38 @@ try {
|
||||
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"
|
||||
try {
|
||||
withCredentials([file(credentialsId: 'docs.spring.io-jenkins_private_ssh_key', variable: 'DEPLOY_SSH_KEY')]) {
|
||||
sh './gradlew deployDocs --stacktrace --no-daemon --refresh-dependencies -PdeployDocsSshKeyPath=$DEPLOY_SSH_KEY -PdeployDocsSshUsername=$SPRING_DOCS_USERNAME'
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
currentBuild.result = 'FAILED: docs'
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
}
|
||||
finally {
|
||||
def buildStatus = currentBuild.result
|
||||
def buildNotSuccess = !SUCCESS.equals(buildStatus)
|
||||
def buildNotSuccess = !SUCCESS.equals(buildStatus)
|
||||
def lastBuildNotSuccess = !SUCCESS.equals(currentBuild.previousBuild?.result)
|
||||
|
||||
if(buildNotSuccess || lastBuildNotSuccess) {
|
||||
|
||||
stage('Notifiy') {
|
||||
if (buildNotSuccess || lastBuildNotSuccess) {
|
||||
stage('Notify') {
|
||||
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}"""
|
||||
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"
|
||||
emailext(
|
||||
subject: subject,
|
||||
body: details,
|
||||
recipientProviders: RECIPIENTS,
|
||||
to: "$SPRING_SESSION_TEAM_EMAILS"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
buildscript {
|
||||
dependencies {
|
||||
classpath 'io.spring.gradle:spring-build-conventions:0.0.16.RELEASE'
|
||||
classpath 'io.spring.gradle:spring-build-conventions:0.0.18.RELEASE'
|
||||
classpath "org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion"
|
||||
}
|
||||
repositories {
|
||||
|
||||
@@ -1200,7 +1200,7 @@ Note that these two have moved to separate repositories, and will continue to be
|
||||
=== Dropped Support
|
||||
|
||||
As a part of the changes to `HttpSessionStrategy` and it's alignment to the counterpart from the reactive world, the support for managing multiple users' sessions in a single browser instance has been removed.
|
||||
This introduction of new API to replace this functionality in consideration for future releases.
|
||||
The introduction of a new API to replace this functionality is under consideration for future releases.
|
||||
|
||||
[[community]]
|
||||
== Spring Session Community
|
||||
|
||||
@@ -2,165 +2,11 @@
|
||||
<!DOCTYPE module PUBLIC "-//Puppy Crawl//DTD Check Configuration 1.3//EN"
|
||||
"http://www.puppycrawl.com/dtds/configuration_1_3.dtd">
|
||||
<module name="Checker">
|
||||
<!-- Suppressions -->
|
||||
<!-- Supressions -->
|
||||
<module name="SuppressionFilter">
|
||||
<property name="file" value="${configDir}/suppressions.xml"/>
|
||||
<property name="file" value="${config_loc}/suppressions.xml"/>
|
||||
</module>
|
||||
|
||||
<!-- Root Checks -->
|
||||
<module name="RegexpHeader">
|
||||
<property name="headerFile" value="${configDir}/header.txt"/>
|
||||
<property name="fileExtensions" value="java"/>
|
||||
</module>
|
||||
<module name="NewlineAtEndOfFile">
|
||||
<property name="lineSeparator" value="lf"/>
|
||||
<property name="fileExtensions" value="java,xml"/>
|
||||
</module>
|
||||
|
||||
<!-- TreeWalker Checks -->
|
||||
<module name="TreeWalker">
|
||||
<!-- Annotations -->
|
||||
<module name="AnnotationUseStyle">
|
||||
<property name="elementStyle" value="compact"/>
|
||||
</module>
|
||||
<module name="MissingOverride"/>
|
||||
<module name="PackageAnnotation"/>
|
||||
<module name="AnnotationLocation">
|
||||
<property name="allowSamelineSingleParameterlessAnnotation" value="false" />
|
||||
</module>
|
||||
|
||||
<!-- Block Checks -->
|
||||
<module name="EmptyBlock">
|
||||
<property name="option" value="text"/>
|
||||
</module>
|
||||
<module name="LeftCurly"/>
|
||||
<module name="RightCurly">
|
||||
<property name="option" value="alone"/>
|
||||
</module>
|
||||
<module name="NeedBraces"/>
|
||||
<module name="AvoidNestedBlocks"/>
|
||||
|
||||
<!-- Class Design -->
|
||||
<module name="FinalClass"/>
|
||||
<module name="InterfaceIsType"/>
|
||||
<module name="HideUtilityClassConstructor"/>
|
||||
<module name="MutableException"/>
|
||||
<module name="InnerTypeLast"/>
|
||||
<module name="OneTopLevelClass"/>
|
||||
|
||||
<!-- Coding -->
|
||||
<module name="CovariantEquals"/>
|
||||
<module name="EmptyStatement"/>
|
||||
<module name="EqualsHashCode"/>
|
||||
<module name="InnerAssignment"/>
|
||||
<module name="SimplifyBooleanExpression"/>
|
||||
<module name="SimplifyBooleanReturn"/>
|
||||
<module name="StringLiteralEquality"/>
|
||||
<module name="NestedForDepth">
|
||||
<property name="max" value="3"/>
|
||||
</module>
|
||||
<module name="NestedIfDepth">
|
||||
<property name="max" value="3"/>
|
||||
</module>
|
||||
<module name="NestedTryDepth">
|
||||
<property name="max" value="3"/>
|
||||
</module>
|
||||
<module name="MultipleVariableDeclarations"/>
|
||||
<module name="RequireThis">
|
||||
<property name="checkMethods" value="false"/>
|
||||
</module>
|
||||
<module name="OneStatementPerLine"/>
|
||||
|
||||
<!-- Imports -->
|
||||
<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.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"/>
|
||||
<module name="UnusedImports">
|
||||
<property name="processJavadoc" value="true"/>
|
||||
</module>
|
||||
<module name="ImportOrder">
|
||||
<property name="groups" value="java,/^javax?\./,*,org.springframework"/>
|
||||
<property name="ordered" value="true"/>
|
||||
<property name="separated" value="true"/>
|
||||
<property name="option" value="bottom"/>
|
||||
<property name="sortStaticImportsAlphabetically" value="true"/>
|
||||
</module>
|
||||
|
||||
<!-- Javadoc Comments -->
|
||||
<module name="JavadocType">
|
||||
<property name="scope" value="package"/>
|
||||
<property name="authorFormat" value=".+\s.+"/>
|
||||
</module>
|
||||
<module name="JavadocMethod">
|
||||
<property name="allowMissingJavadoc" value="true"/>
|
||||
</module>
|
||||
<module name="JavadocVariable">
|
||||
<property name="scope" value="public"/>
|
||||
</module>
|
||||
<module name="JavadocStyle">
|
||||
<property name="checkEmptyJavadoc" value="true"/>
|
||||
</module>
|
||||
<module name="NonEmptyAtclauseDescription"/>
|
||||
<module name="JavadocTagContinuationIndentation">
|
||||
<property name="offset" value="0"/>
|
||||
</module>
|
||||
<module name="AtclauseOrder">
|
||||
<property name="target" value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF"/>
|
||||
<property name="tagOrder" value="@param, @author, @since, @see, @version, @serial, @deprecated"/>
|
||||
</module>
|
||||
<module name="AtclauseOrder">
|
||||
<property name="target" value="METHOD_DEF, CTOR_DEF, VARIABLE_DEF"/>
|
||||
<property name="tagOrder" value="@param, @return, @throws, @since, @deprecated, @see"/>
|
||||
</module>
|
||||
|
||||
<!-- Miscellaneous -->
|
||||
<module name="CommentsIndentation"/>
|
||||
<module name="UpperEll"/>
|
||||
<module name="ArrayTypeStyle"/>
|
||||
<module name="OuterTypeFilename"/>
|
||||
|
||||
<!-- Modifiers -->
|
||||
<module name="RedundantModifier"/>
|
||||
|
||||
<!-- Regexp -->
|
||||
<module name="RegexpSinglelineJava">
|
||||
<property name="format" value="^\t* +\t*\S"/>
|
||||
<property name="message" value="Line has leading space characters; indentation should be performed with tabs only."/>
|
||||
<property name="ignoreComments" value="true"/>
|
||||
</module>
|
||||
<module name="RegexpSinglelineJava">
|
||||
<property name="maximum" value="0"/>
|
||||
<property name="format" value="org\.mockito\.Mockito\.(when|doThrow|doAnswer)"/>
|
||||
<property name="message"
|
||||
value="Please use BDDMockto imports."/>
|
||||
<property name="ignoreComments" value="true"/>
|
||||
</module>
|
||||
<module name="RegexpSinglelineJava">
|
||||
<property name="maximum" value="0"/>
|
||||
<property name="format" value="org\.junit\.Assert\.assert"/>
|
||||
<property name="message" value="Please use AssertJ imports."/>
|
||||
<property name="ignoreComments" value="true"/>
|
||||
</module>
|
||||
<module name="Regexp">
|
||||
<property name="format" value="[ \t]+$"/>
|
||||
<property name="illegalPattern" value="true"/>
|
||||
<property name="message" value="Trailing whitespace"/>
|
||||
</module>
|
||||
|
||||
<!-- Whitespace -->
|
||||
<module name="GenericWhitespace"/>
|
||||
<module name="MethodParamPad"/>
|
||||
<module name="NoWhitespaceAfter">
|
||||
<property name="tokens" value="BNOT, DEC, DOT, INC, LNOT, UNARY_MINUS, UNARY_PLUS, ARRAY_DECLARATOR"/>
|
||||
</module>
|
||||
<module name="NoWhitespaceBefore"/>
|
||||
<module name="ParenPad"/>
|
||||
<module name="TypecastParenPad"/>
|
||||
<module name="WhitespaceAfter"/>
|
||||
<module name="WhitespaceAround"/>
|
||||
</module>
|
||||
<module name="io.spring.javaformat.checkstyle.SpringChecks"/>
|
||||
</module>
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
^\Q/*\E$
|
||||
^\Q * Copyright 2014-\E20\d\d\Q the original author or authors.\E$
|
||||
^\Q *\E$
|
||||
^\Q * Licensed under the Apache License, Version 2.0 (the "License");\E$
|
||||
^\Q * you may not use this file except in compliance with the License.\E$
|
||||
^\Q * You may obtain a copy of the License at\E$
|
||||
^\Q *\E$
|
||||
^\Q * http://www.apache.org/licenses/LICENSE-2.0\E$
|
||||
^\Q *\E$
|
||||
^\Q * Unless required by applicable law or agreed to in writing, software\E$
|
||||
^\Q * distributed under the License is distributed on an "AS IS" BASIS,\E$
|
||||
^\Q * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\E$
|
||||
^\Q * See the License for the specific language governing permissions and\E$
|
||||
^\Q * limitations under the License.\E$
|
||||
^\Q */\E$
|
||||
^.*$
|
||||
@@ -2,17 +2,14 @@
|
||||
<!DOCTYPE suppressions PUBLIC "-//Puppy Crawl//DTD Suppressions 1.1//EN"
|
||||
"http://www.puppycrawl.com/dtds/suppressions_1_1.dtd">
|
||||
<suppressions>
|
||||
<suppress files=".+Application\.java" checks="HideUtilityClassConstructor"/>
|
||||
<suppress files=".+Configuration\.java" checks="HideUtilityClassConstructor"/>
|
||||
<!-- global -->
|
||||
<suppress files="[\\/]src[\\/]integration-test[\\/]java[\\/]" checks="Javadoc*"/>
|
||||
|
||||
<suppress files="[\\/]src[\\/]test[\\/]java[\\/]" checks="Javadoc"/>
|
||||
<suppress files="[\\/]src[\\/]integration-test[\\/]java[\\/]" checks="Javadoc"/>
|
||||
|
||||
<suppress files="[\\/]docs[\\/]" checks="Javadoc"/>
|
||||
<suppress files="[\\/]docs[\\/]" checks="CommentsIndentation"/>
|
||||
<!-- docs -->
|
||||
<suppress files="[\\/]docs[\\/]" checks="Javadoc*"/>
|
||||
<suppress files="[\\/]docs[\\/]" checks="InnerTypeLast"/>
|
||||
|
||||
<suppress files="[\\/]samples[\\/]" checks="Javadoc"/>
|
||||
<suppress files="[\\/]samples[\\/]" checks="CommentsIndentation"/>
|
||||
<suppress files="[\\/]samples[\\/]" checks="InnerTypeLast"/>
|
||||
<!-- samples -->
|
||||
<suppress files="[\\/]samples[\\/]" checks="Javadoc*"/>
|
||||
<suppress files="[\\/]samples[\\/].+Application\.java" checks="HideUtilityClassConstructor"/>
|
||||
</suppressions>
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
springBootVersion=2.0.2.RELEASE
|
||||
version=2.0.4.RELEASE
|
||||
springBootVersion=2.0.4.RELEASE
|
||||
version=2.0.6.RELEASE
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
dependencyManagement {
|
||||
imports {
|
||||
mavenBom 'com.fasterxml.jackson:jackson-bom:2.9.6'
|
||||
mavenBom 'io.projectreactor:reactor-bom:Bismuth-SR10'
|
||||
mavenBom 'org.springframework:spring-framework-bom:5.0.7.RELEASE'
|
||||
mavenBom 'org.springframework.data:spring-data-releasetrain:Kay-SR8'
|
||||
mavenBom 'org.springframework.security:spring-security-bom:5.0.6.RELEASE'
|
||||
mavenBom 'org.testcontainers:testcontainers-bom:1.7.3'
|
||||
mavenBom 'io.projectreactor:reactor-bom:Bismuth-SR11'
|
||||
mavenBom 'org.springframework:spring-framework-bom:5.0.9.RELEASE'
|
||||
mavenBom 'org.springframework.data:spring-data-releasetrain:Kay-SR10'
|
||||
mavenBom 'org.springframework.security:spring-security-bom:5.0.8.RELEASE'
|
||||
mavenBom 'org.testcontainers:testcontainers-bom:1.8.3'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@@ -15,17 +15,18 @@ dependencyManagement {
|
||||
}
|
||||
|
||||
dependency 'com.h2database:h2:1.4.197'
|
||||
dependency 'com.microsoft.sqlserver:mssql-jdbc:6.4.0.jre8'
|
||||
dependency 'com.microsoft.sqlserver:mssql-jdbc:7.0.0.jre8'
|
||||
dependency 'edu.umd.cs.mtc:multithreadedtc:1.01'
|
||||
dependency 'io.lettuce:lettuce-core:5.0.4.RELEASE'
|
||||
dependency 'io.lettuce:lettuce-core:5.0.5.RELEASE'
|
||||
dependency 'javax.annotation:javax.annotation-api:1.3.2'
|
||||
dependency 'javax.servlet:javax.servlet-api:3.1.0'
|
||||
dependency 'junit:junit:4.12'
|
||||
dependency 'mysql:mysql-connector-java:8.0.11'
|
||||
dependency 'mysql:mysql-connector-java:8.0.12'
|
||||
dependency 'org.apache.derby:derby:10.14.2.0'
|
||||
dependency 'org.assertj:assertj-core:3.10.0'
|
||||
dependency 'org.assertj:assertj-core:3.11.1'
|
||||
dependency 'org.hsqldb:hsqldb:2.4.1'
|
||||
dependency 'org.mariadb.jdbc:mariadb-java-client:2.2.5'
|
||||
dependency 'org.mockito:mockito-core:2.18.3'
|
||||
dependency 'org.postgresql:postgresql:42.2.2'
|
||||
dependency 'org.mariadb.jdbc:mariadb-java-client:2.3.0'
|
||||
dependency 'org.mockito:mockito-core:2.22.0'
|
||||
dependency 'org.postgresql:postgresql:42.2.5'
|
||||
}
|
||||
}
|
||||
|
||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.8-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
@@ -46,7 +46,7 @@ import org.springframework.test.web.servlet.htmlunit.webdriver.MockMvcHtmlUnitDr
|
||||
@SpringBootTest(webEnvironment = WebEnvironment.MOCK)
|
||||
public class FindByUsernameTests {
|
||||
|
||||
private static final String DOCKER_IMAGE = "redis:4.0.9";
|
||||
private static final String DOCKER_IMAGE = "redis:4.0.11";
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2016 the original author or authors.
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -92,7 +92,7 @@ public class SessionDetailsFilter extends OncePerRequestFilter {
|
||||
}
|
||||
return cityName + ", " + countryName;
|
||||
}
|
||||
catch (Exception e) {
|
||||
catch (Exception ex) {
|
||||
return UNKNOWN;
|
||||
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
@AutoConfigureMockMvc
|
||||
public class HttpRedisJsonTest {
|
||||
|
||||
private static final String DOCKER_IMAGE = "redis:4.0.9";
|
||||
private static final String DOCKER_IMAGE = "redis:4.0.11";
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@@ -39,7 +39,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
@SpringBootTest
|
||||
public class RedisSerializerTest {
|
||||
|
||||
private static final String DOCKER_IMAGE = "redis:4.0.9";
|
||||
private static final String DOCKER_IMAGE = "redis:4.0.11";
|
||||
|
||||
@SpringSessionRedisOperations
|
||||
private RedisTemplate<Object, Object> sessionRedisTemplate;
|
||||
|
||||
@@ -45,7 +45,7 @@ import org.springframework.test.web.servlet.htmlunit.webdriver.MockMvcHtmlUnitDr
|
||||
@SpringBootTest(webEnvironment = WebEnvironment.MOCK)
|
||||
public class BootTests {
|
||||
|
||||
private static final String DOCKER_IMAGE = "redis:4.0.9";
|
||||
private static final String DOCKER_IMAGE = "redis:4.0.11";
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@@ -47,7 +47,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
|
||||
public class AttributeTests {
|
||||
|
||||
private static final String DOCKER_IMAGE = "redis:4.0.9";
|
||||
private static final String DOCKER_IMAGE = "redis:4.0.11";
|
||||
|
||||
@LocalServerPort
|
||||
private int port;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2016 the original author or authors.
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -59,7 +59,7 @@ public class UserRepositoryUserDetailsService implements UserDetailsService {
|
||||
return new CustomUserDetails(user);
|
||||
}
|
||||
|
||||
private final static class CustomUserDetails extends User implements UserDetails {
|
||||
private static final class CustomUserDetails extends User implements UserDetails {
|
||||
|
||||
private CustomUserDetails(User user) {
|
||||
super(user);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2017 the original author or authors.
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -42,7 +42,7 @@ public class WebSocketDisconnectHandler<S>
|
||||
if (id == null) {
|
||||
return;
|
||||
}
|
||||
this.repository.findById(id).ifPresent(user -> {
|
||||
this.repository.findById(id).ifPresent((user) -> {
|
||||
this.repository.deleteById(id);
|
||||
this.messagingTemplate.convertAndSend("/topic/friends/signout",
|
||||
Arrays.asList(user.getUsername()));
|
||||
|
||||
@@ -52,7 +52,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
|
||||
public class ApplicationTests {
|
||||
|
||||
private static final String DOCKER_IMAGE = "redis:4.0.9";
|
||||
private static final String DOCKER_IMAGE = "redis:4.0.11";
|
||||
|
||||
@Value("${local.server.port}")
|
||||
private String port;
|
||||
|
||||
@@ -28,7 +28,7 @@ import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactor
|
||||
@Profile("embedded-redis")
|
||||
public class EmbeddedRedisConfig {
|
||||
|
||||
private static final String DOCKER_IMAGE = "redis:4.0.9";
|
||||
private static final String DOCKER_IMAGE = "redis:4.0.11";
|
||||
|
||||
@Bean
|
||||
public GenericContainer redisContainer() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2016 the original author or authors.
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -55,8 +55,8 @@ public class ObjectStreamSerializer implements StreamSerializer<Object> {
|
||||
try {
|
||||
return in.readObject();
|
||||
}
|
||||
catch (ClassNotFoundException e) {
|
||||
throw new IOException(e);
|
||||
catch (ClassNotFoundException ex) {
|
||||
throw new IOException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactor
|
||||
@Profile("embedded-redis")
|
||||
public class EmbeddedRedisConfig {
|
||||
|
||||
private static final String DOCKER_IMAGE = "redis:4.0.9";
|
||||
private static final String DOCKER_IMAGE = "redis:4.0.11";
|
||||
|
||||
@Bean
|
||||
public GenericContainer redisContainer() {
|
||||
|
||||
@@ -54,7 +54,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
||||
@WebAppConfiguration
|
||||
public class RestMockMvcTests {
|
||||
|
||||
private static final String DOCKER_IMAGE = "redis:4.0.9";
|
||||
private static final String DOCKER_IMAGE = "redis:4.0.11";
|
||||
|
||||
@Autowired
|
||||
private SessionRepositoryFilter<? extends Session> sessionRepositoryFilter;
|
||||
|
||||
@@ -59,7 +59,7 @@ public class RestTests {
|
||||
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
|
||||
assertThatThrownBy(() -> getForUser(this.baseUrl + "/", headers, String.class))
|
||||
.isInstanceOf(HttpClientErrorException.class)
|
||||
.satisfies(e -> assertThat(((HttpClientErrorException) e).getStatusCode())
|
||||
.satisfies((e) -> assertThat(((HttpClientErrorException) e).getStatusCode())
|
||||
.isEqualTo(HttpStatus.UNAUTHORIZED));
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactor
|
||||
@Profile("embedded-redis")
|
||||
public class EmbeddedRedisConfig {
|
||||
|
||||
private static final String DOCKER_IMAGE = "redis:4.0.9";
|
||||
private static final String DOCKER_IMAGE = "redis:4.0.11";
|
||||
|
||||
@Bean
|
||||
public GenericContainer redisContainer() {
|
||||
|
||||
@@ -28,7 +28,7 @@ import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactor
|
||||
@Profile("embedded-redis")
|
||||
public class EmbeddedRedisConfig {
|
||||
|
||||
private static final String DOCKER_IMAGE = "redis:4.0.9";
|
||||
private static final String DOCKER_IMAGE = "redis:4.0.11";
|
||||
|
||||
@Bean
|
||||
public GenericContainer redisContainer() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2017 the original author or authors.
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -78,14 +78,14 @@ public class Initializer implements ServletContextListener {
|
||||
socket = new ServerSocket(0);
|
||||
return socket.getLocalPort();
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
catch (IOException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
finally {
|
||||
try {
|
||||
socket.close();
|
||||
}
|
||||
catch (IOException e) {
|
||||
catch (IOException ex) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactor
|
||||
@Profile("embedded-redis")
|
||||
public class EmbeddedRedisConfig {
|
||||
|
||||
private static final String DOCKER_IMAGE = "redis:4.0.9";
|
||||
private static final String DOCKER_IMAGE = "redis:4.0.11";
|
||||
|
||||
@Bean
|
||||
public GenericContainer redisContainer() {
|
||||
|
||||
@@ -10,7 +10,7 @@ String rootDirPath = rootDir.absolutePath + File.separator
|
||||
buildFiles.each { File buildFile ->
|
||||
if (buildFile.name == 'build.gradle') {
|
||||
String buildFilePath = buildFile.parentFile.absolutePath
|
||||
String projectPath = buildFilePath.replace(rootDirPath, '').replaceAll(File.separator, ':')
|
||||
String projectPath = buildFilePath.replace(rootDirPath, '').replace(File.separator, ':')
|
||||
include projectPath
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2017 the original author or authors.
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -20,6 +20,7 @@ import java.io.Serializable;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
@@ -177,7 +178,7 @@ public final class MapSession implements Session, Serializable {
|
||||
|
||||
@Override
|
||||
public Set<String> getAttributeNames() {
|
||||
return this.sessionAttrs.keySet();
|
||||
return new HashSet<>(this.sessionAttrs.keySet());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2017 the original author or authors.
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -86,7 +86,7 @@ public class ReactiveMapSessionRepository implements ReactiveSessionRepository<M
|
||||
public Mono<MapSession> findById(String id) {
|
||||
// @formatter:off
|
||||
return Mono.defer(() -> Mono.justOrEmpty(this.sessions.get(id))
|
||||
.filter(session -> !session.isExpired())
|
||||
.filter((session) -> !session.isExpired())
|
||||
.map(MapSession::new)
|
||||
.switchIfEmpty(deleteById(id).then(Mono.empty())));
|
||||
// @formatter:on
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2017 the original author or authors.
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -47,7 +47,7 @@ public interface Session {
|
||||
* Gets the Object associated with the specified name or null if no Object is
|
||||
* associated to that name.
|
||||
*
|
||||
* @param <T> The return type of the attribute
|
||||
* @param <T> the return type of the attribute
|
||||
* @param attributeName the name of the attribute to get
|
||||
* @return the Object associated with the specified name or null if no Object is
|
||||
* associated to that name
|
||||
@@ -81,7 +81,7 @@ public interface Session {
|
||||
@SuppressWarnings("unchecked")
|
||||
default <T> T getAttributeOrDefault(String name, T defaultValue) {
|
||||
T result = getAttribute(name);
|
||||
return result == null ? defaultValue : result;
|
||||
return (result != null) ? result : defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2017 the original author or authors.
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -110,8 +110,9 @@ public class SpringHttpSessionConfiguration implements ApplicationContextAware {
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
CookieSerializer cookieSerializer = this.cookieSerializer != null
|
||||
? this.cookieSerializer : createDefaultCookieSerializer();
|
||||
CookieSerializer cookieSerializer = (this.cookieSerializer != null)
|
||||
? this.cookieSerializer
|
||||
: createDefaultCookieSerializer();
|
||||
this.defaultHttpSessionIdResolver.setCookieSerializer(cookieSerializer);
|
||||
}
|
||||
|
||||
@@ -169,9 +170,9 @@ public class SpringHttpSessionConfiguration implements ApplicationContextAware {
|
||||
try {
|
||||
sessionCookieConfig = this.servletContext.getSessionCookieConfig();
|
||||
}
|
||||
catch (UnsupportedOperationException e) {
|
||||
catch (UnsupportedOperationException ex) {
|
||||
this.logger
|
||||
.warn("Unable to obtain SessionCookieConfig: " + e.getMessage());
|
||||
.warn("Unable to obtain SessionCookieConfig: " + ex.getMessage());
|
||||
}
|
||||
if (sessionCookieConfig != null) {
|
||||
if (sessionCookieConfig.getName() != null) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2017 the original author or authors.
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -45,7 +45,7 @@ public abstract class AbstractSessionEvent extends ApplicationEvent {
|
||||
* implementations it may not be possible to get the original session in which case
|
||||
* this may be null.
|
||||
*
|
||||
* @param <S> The type of Session
|
||||
* @param <S> the type of Session
|
||||
* @return the expired {@link Session} or null if the data store does not support
|
||||
* obtaining it
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2017 the original author or authors.
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -63,7 +63,7 @@ class SpringSessionBackedSessionInformation<S extends Session>
|
||||
/**
|
||||
* Tries to determine the principal's name from the given Session.
|
||||
*
|
||||
* @param session Spring Session session
|
||||
* @param session the session
|
||||
* @return the principal's name, or empty String if it couldn't be determined
|
||||
*/
|
||||
private static String resolvePrincipal(Session session) {
|
||||
|
||||
@@ -26,6 +26,9 @@ import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
/**
|
||||
* The default implementation of {@link CookieSerializer}.
|
||||
*
|
||||
@@ -36,6 +39,8 @@ import javax.servlet.http.HttpServletResponse;
|
||||
*/
|
||||
public class DefaultCookieSerializer implements CookieSerializer {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(DefaultCookieSerializer.class);
|
||||
|
||||
private String cookieName = "SESSION";
|
||||
|
||||
private Boolean useSecureCookie;
|
||||
@@ -69,8 +74,8 @@ public class DefaultCookieSerializer implements CookieSerializer {
|
||||
if (cookies != null) {
|
||||
for (Cookie cookie : cookies) {
|
||||
if (this.cookieName.equals(cookie.getName())) {
|
||||
String sessionId = this.useBase64Encoding
|
||||
? base64Decode(cookie.getValue()) : cookie.getValue();
|
||||
String sessionId = (this.useBase64Encoding
|
||||
? base64Decode(cookie.getValue()) : cookie.getValue());
|
||||
if (sessionId == null) {
|
||||
continue;
|
||||
}
|
||||
@@ -97,8 +102,9 @@ public class DefaultCookieSerializer implements CookieSerializer {
|
||||
HttpServletResponse response = cookieValue.getResponse();
|
||||
|
||||
String requestedCookieValue = cookieValue.getCookieValue();
|
||||
String actualCookieValue = this.jvmRoute == null ? requestedCookieValue
|
||||
: requestedCookieValue + this.jvmRoute;
|
||||
String actualCookieValue = (this.jvmRoute != null)
|
||||
? requestedCookieValue + this.jvmRoute
|
||||
: requestedCookieValue;
|
||||
|
||||
Cookie sessionCookie = new Cookie(this.cookieName, this.useBase64Encoding
|
||||
? base64Encode(actualCookieValue) : actualCookieValue);
|
||||
@@ -140,7 +146,8 @@ public class DefaultCookieSerializer implements CookieSerializer {
|
||||
byte[] decodedCookieBytes = Base64.getDecoder().decode(base64Value);
|
||||
return new String(decodedCookieBytes);
|
||||
}
|
||||
catch (Exception e) {
|
||||
catch (Exception ex) {
|
||||
logger.debug("Unable to Base64 decode value: " + base64Value);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2017 the original author or authors.
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -98,7 +98,7 @@ public class HeaderHttpSessionIdResolver implements HttpSessionIdResolver {
|
||||
@Override
|
||||
public List<String> resolveSessionIds(HttpServletRequest request) {
|
||||
String headerValue = request.getHeader(this.headerName);
|
||||
return headerValue != null ? Collections.singletonList(headerValue)
|
||||
return (headerValue != null) ? Collections.singletonList(headerValue)
|
||||
: Collections.emptyList();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2016 the original author or authors.
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -174,11 +174,11 @@ abstract class OnCommittedResponseWrapper extends HttpServletResponseWrapper {
|
||||
}
|
||||
|
||||
private void trackContentLength(byte[] content) {
|
||||
checkContentLength(content == null ? 0 : content.length);
|
||||
checkContentLength((content != null) ? content.length : 0);
|
||||
}
|
||||
|
||||
private void trackContentLength(char[] content) {
|
||||
checkContentLength(content == null ? 0 : content.length);
|
||||
checkContentLength((content != null) ? content.length : 0);
|
||||
}
|
||||
|
||||
private void trackContentLength(int content) {
|
||||
@@ -257,13 +257,13 @@ abstract class OnCommittedResponseWrapper extends HttpServletResponseWrapper {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.delegate.hashCode();
|
||||
public boolean equals(Object obj) {
|
||||
return this.delegate.equals(obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return this.delegate.equals(obj);
|
||||
public int hashCode() {
|
||||
return this.delegate.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -502,13 +502,13 @@ abstract class OnCommittedResponseWrapper extends HttpServletResponseWrapper {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.delegate.hashCode();
|
||||
public boolean equals(Object obj) {
|
||||
return this.delegate.equals(obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return this.delegate.equals(obj);
|
||||
public int hashCode() {
|
||||
return this.delegate.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -352,7 +352,7 @@ public class SessionRepositoryFilter<S extends Session> extends OncePerRequestFi
|
||||
@Override
|
||||
public String getRequestedSessionId() {
|
||||
S requestedSession = getRequestedSession();
|
||||
return (requestedSession != null ? requestedSession.getId() : null);
|
||||
return (requestedSession != null) ? requestedSession.getId() : null;
|
||||
}
|
||||
|
||||
private S getRequestedSession() {
|
||||
|
||||
@@ -96,7 +96,7 @@ public class SpringSessionWebSessionStore<S extends Session> implements WebSessi
|
||||
@Override
|
||||
public Mono<WebSession> retrieveSession(String sessionId) {
|
||||
return this.sessions.findById(sessionId)
|
||||
.doOnNext(session -> session.setLastAccessedTime(this.clock.instant()))
|
||||
.doOnNext((session) -> session.setLastAccessedTime(this.clock.instant()))
|
||||
.map(this::existingSession);
|
||||
}
|
||||
|
||||
@@ -164,6 +164,7 @@ public class SpringSessionWebSessionStore<S extends Session> implements WebSessi
|
||||
|
||||
@Override
|
||||
public Mono<Void> invalidate() {
|
||||
this.state.set(State.EXPIRED);
|
||||
return SpringSessionWebSessionStore.this.sessions.deleteById(this.session.getId());
|
||||
}
|
||||
|
||||
@@ -174,7 +175,14 @@ public class SpringSessionWebSessionStore<S extends Session> implements WebSessi
|
||||
|
||||
@Override
|
||||
public boolean isExpired() {
|
||||
return this.session.isExpired();
|
||||
if (this.state.get().equals(State.EXPIRED)) {
|
||||
return true;
|
||||
}
|
||||
if (this.session.isExpired()) {
|
||||
this.state.set(State.EXPIRED);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -199,7 +207,7 @@ public class SpringSessionWebSessionStore<S extends Session> implements WebSessi
|
||||
}
|
||||
|
||||
private enum State {
|
||||
NEW, STARTED
|
||||
NEW, STARTED, EXPIRED
|
||||
}
|
||||
|
||||
private static class SpringSessionMap implements Map<String, Object> {
|
||||
@@ -231,7 +239,7 @@ public class SpringSessionWebSessionStore<S extends Session> implements WebSessi
|
||||
@Override
|
||||
public boolean containsValue(Object value) {
|
||||
return this.session.getAttributeNames().stream()
|
||||
.anyMatch(attrName -> this.session.getAttribute(attrName) != null);
|
||||
.anyMatch((attrName) -> this.session.getAttribute(attrName) != null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2017 the original author or authors.
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -71,8 +71,9 @@ public final class WebSocketRegistryListener
|
||||
SessionDisconnectEvent e = (SessionDisconnectEvent) event;
|
||||
Map<String, Object> sessionAttributes = SimpMessageHeaderAccessor
|
||||
.getSessionAttributes(e.getMessage().getHeaders());
|
||||
String httpSessionId = sessionAttributes == null ? null
|
||||
: SessionRepositoryMessageInterceptor.getSessionId(sessionAttributes);
|
||||
String httpSessionId = (sessionAttributes != null)
|
||||
? SessionRepositoryMessageInterceptor.getSessionId(sessionAttributes)
|
||||
: null;
|
||||
afterConnectionClosed(httpSessionId, e.getSessionId());
|
||||
}
|
||||
}
|
||||
@@ -140,10 +141,10 @@ public final class WebSocketRegistryListener
|
||||
try {
|
||||
toClose.close(SESSION_EXPIRED_STATUS);
|
||||
}
|
||||
catch (IOException e) {
|
||||
catch (IOException ex) {
|
||||
logger.debug(
|
||||
"Failed to close WebSocketSession (this is nothing to worry about but for debugging only)",
|
||||
e);
|
||||
ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,8 +117,9 @@ public final class SessionRepositoryMessageInterceptor<S extends Session>
|
||||
}
|
||||
Map<String, Object> sessionHeaders = SimpMessageHeaderAccessor
|
||||
.getSessionAttributes(message.getHeaders());
|
||||
String sessionId = sessionHeaders == null ? null
|
||||
: (String) sessionHeaders.get(SPRING_SESSION_ID_ATTR_NAME);
|
||||
String sessionId = (sessionHeaders != null)
|
||||
? (String) sessionHeaders.get(SPRING_SESSION_ID_ATTR_NAME)
|
||||
: null;
|
||||
if (sessionId != null) {
|
||||
S session = this.sessionRepository.findById(sessionId);
|
||||
if (session != null) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2017 the original author or authors.
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -100,4 +100,17 @@ public class MapSessionRepositoryTests {
|
||||
assertThat(this.repository.findById(createSession.getId())).isNotNull();
|
||||
}
|
||||
|
||||
@Test // gh-1120
|
||||
public void getAttributeNamesAndRemove() {
|
||||
MapSession session = this.repository.createSession();
|
||||
session.setAttribute("attribute1", "value1");
|
||||
session.setAttribute("attribute2", "value2");
|
||||
|
||||
for (String attributeName : session.getAttributeNames()) {
|
||||
session.removeAttribute(attributeName);
|
||||
}
|
||||
|
||||
assertThat(session.getAttributeNames()).isEmpty();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -133,6 +133,18 @@ public class MapSessionTests {
|
||||
assertThat(this.session.isExpired(now)).isTrue();
|
||||
}
|
||||
|
||||
@Test // gh-1120
|
||||
public void getAttributeNamesAndRemove() {
|
||||
this.session.setAttribute("attribute1", "value1");
|
||||
this.session.setAttribute("attribute2", "value2");
|
||||
|
||||
for (String attributeName : this.session.getAttributeNames()) {
|
||||
this.session.removeAttribute(attributeName);
|
||||
}
|
||||
|
||||
assertThat(this.session.getAttributeNames()).isEmpty();
|
||||
}
|
||||
|
||||
static class CustomSession implements Session {
|
||||
|
||||
@Override
|
||||
|
||||
@@ -144,4 +144,17 @@ public class ReactiveMapSessionRepositoryTests {
|
||||
assertThat(this.repository.findById(createSession.getId()).block()).isNotNull();
|
||||
}
|
||||
|
||||
@Test // gh-1120
|
||||
public void getAttributeNamesAndRemove() {
|
||||
MapSession session = this.repository.createSession().block();
|
||||
session.setAttribute("attribute1", "value1");
|
||||
session.setAttribute("attribute2", "value2");
|
||||
|
||||
for (String attributeName : session.getAttributeNames()) {
|
||||
session.removeAttribute(attributeName);
|
||||
}
|
||||
|
||||
assertThat(session.getAttributeNames()).isEmpty();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -291,4 +291,21 @@ public class SpringSessionWebSessionStoreTests<S extends Session> {
|
||||
.hasMessage("clock cannot be null");
|
||||
}
|
||||
|
||||
@Test // gh-1114
|
||||
public void createSessionThenSessionIsNotExpired() {
|
||||
WebSession createdWebSession = this.webSessionStore.createWebSession().block();
|
||||
|
||||
assertThat(createdWebSession.isExpired()).isFalse();
|
||||
}
|
||||
|
||||
@Test // gh-1114
|
||||
public void invalidateSessionThenSessionIsExpired() {
|
||||
WebSession createdWebSession = this.webSessionStore.createWebSession().block();
|
||||
given(createdWebSession.invalidate()).willReturn(Mono.empty());
|
||||
|
||||
createdWebSession.invalidate().block();
|
||||
|
||||
assertThat(createdWebSession.isExpired()).isTrue();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2017 the original author or authors.
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -66,6 +66,6 @@ public class SessionEventRegistry implements ApplicationListener<AbstractSession
|
||||
}
|
||||
|
||||
private Object getLock(String sessionId) {
|
||||
return this.locks.computeIfAbsent(sessionId, k -> new Object());
|
||||
return this.locks.computeIfAbsent(sessionId, (k) -> new Object());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactor
|
||||
*/
|
||||
public abstract class AbstractRedisITests {
|
||||
|
||||
private static final String DOCKER_IMAGE = "redis:4.0.9";
|
||||
private static final String DOCKER_IMAGE = "redis:4.0.11";
|
||||
|
||||
protected static class BaseConfig {
|
||||
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.session.data.redis;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@@ -191,6 +193,28 @@ public class ReactiveRedisOperationsSessionRepositoryITests extends AbstractRedi
|
||||
assertThat(this.repository.findById(originalId).block()).isNull();
|
||||
}
|
||||
|
||||
// gh-1111
|
||||
@Test
|
||||
public void changeSessionSaveOldSessionInstance() {
|
||||
ReactiveRedisOperationsSessionRepository.RedisSession toSave = this.repository
|
||||
.createSession().block();
|
||||
String sessionId = toSave.getId();
|
||||
|
||||
this.repository.save(toSave).block();
|
||||
|
||||
ReactiveRedisOperationsSessionRepository.RedisSession session = this.repository
|
||||
.findById(sessionId).block();
|
||||
session.changeSessionId();
|
||||
session.setLastAccessedTime(Instant.now());
|
||||
this.repository.save(session).block();
|
||||
|
||||
toSave.setLastAccessedTime(Instant.now());
|
||||
this.repository.save(toSave).block();
|
||||
|
||||
assertThat(this.repository.findById(sessionId).block()).isNull();
|
||||
assertThat(this.repository.findById(session.getId()).block()).isNotNull();
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableRedisWebSession
|
||||
static class Config extends BaseConfig {
|
||||
|
||||
@@ -581,6 +581,22 @@ public class RedisOperationsSessionRepositoryITests extends AbstractRedisITests
|
||||
assertThat(this.repository.findById(originalId)).isNull();
|
||||
}
|
||||
|
||||
// gh-1137
|
||||
@Test
|
||||
public void changeSessionIdWhenSessionIsDeleted() {
|
||||
RedisSession toSave = this.repository.createSession();
|
||||
String sessionId = toSave.getId();
|
||||
this.repository.save(toSave);
|
||||
|
||||
this.repository.deleteById(sessionId);
|
||||
|
||||
toSave.changeSessionId();
|
||||
this.repository.save(toSave);
|
||||
|
||||
assertThat(this.repository.findById(toSave.getId())).isNull();
|
||||
assertThat(this.repository.findById(sessionId)).isNull();
|
||||
}
|
||||
|
||||
private String getSecurityName() {
|
||||
return this.context.getAuthentication().getName();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2017 the original author or authors.
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -95,7 +95,7 @@ public class RedisListenerContainerTaskExecutorITests extends AbstractRedisITest
|
||||
synchronized (this.lock) {
|
||||
this.lock.wait(TimeUnit.SECONDS.toMillis(1));
|
||||
}
|
||||
return this.taskDispatched == null ? Boolean.FALSE : this.taskDispatched;
|
||||
return (this.taskDispatched != null) ? this.taskDispatched : Boolean.FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -134,24 +134,36 @@ public class ReactiveRedisOperationsSessionRepository implements
|
||||
|
||||
@Override
|
||||
public Mono<Void> save(RedisSession session) {
|
||||
return session.saveDelta().and(s -> {
|
||||
if (session.isNew) {
|
||||
session.setNew(false);
|
||||
}
|
||||
|
||||
s.onComplete();
|
||||
});
|
||||
Mono<Void> result = session.saveChangeSessionId().and(session.saveDelta())
|
||||
.and((s) -> {
|
||||
session.isNew = false;
|
||||
s.onComplete();
|
||||
});
|
||||
if (session.isNew) {
|
||||
return result;
|
||||
}
|
||||
else {
|
||||
String sessionKey = getSessionKey(
|
||||
session.hasChangedSessionId() ? session.originalSessionId
|
||||
: session.getId());
|
||||
return this.sessionRedisOperations.hasKey(sessionKey)
|
||||
.flatMap((exists) -> exists ? result : Mono.empty());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<RedisSession> findById(String id) {
|
||||
String sessionKey = getSessionKey(id);
|
||||
|
||||
// @formatter:off
|
||||
return this.sessionRedisOperations.opsForHash().entries(sessionKey)
|
||||
.collectMap(e -> e.getKey().toString(), Map.Entry::getValue)
|
||||
.filter(map -> !map.isEmpty()).map(new SessionMapper(id))
|
||||
.filter(session -> !session.isExpired()).map(RedisSession::new)
|
||||
.collectMap((e) -> e.getKey().toString(), Map.Entry::getValue)
|
||||
.filter((map) -> !map.isEmpty())
|
||||
.map(new SessionMapper(id))
|
||||
.filter((session) -> !session.isExpired())
|
||||
.map(RedisSession::new)
|
||||
.switchIfEmpty(Mono.defer(() -> deleteById(id).then(Mono.empty())));
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -276,12 +288,8 @@ public class ReactiveRedisOperationsSessionRepository implements
|
||||
return this.cached.isExpired();
|
||||
}
|
||||
|
||||
public void setNew(boolean isNew) {
|
||||
this.isNew = isNew;
|
||||
}
|
||||
|
||||
public boolean isNew() {
|
||||
return this.isNew;
|
||||
private boolean hasChangedSessionId() {
|
||||
return !getId().equals(this.originalSessionId);
|
||||
}
|
||||
|
||||
private void flushImmediateIfNecessary() {
|
||||
@@ -296,38 +304,35 @@ public class ReactiveRedisOperationsSessionRepository implements
|
||||
}
|
||||
|
||||
private Mono<Void> saveDelta() {
|
||||
String sessionId = getId();
|
||||
Mono<Void> changeSessionId = saveChangeSessionId(sessionId);
|
||||
|
||||
if (this.delta.isEmpty()) {
|
||||
return changeSessionId.and(Mono.empty());
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
String sessionKey = getSessionKey(sessionId);
|
||||
|
||||
String sessionKey = getSessionKey(getId());
|
||||
Mono<Boolean> update = ReactiveRedisOperationsSessionRepository.this.sessionRedisOperations
|
||||
.opsForHash().putAll(sessionKey, this.delta);
|
||||
|
||||
Mono<Boolean> setTtl = ReactiveRedisOperationsSessionRepository.this.sessionRedisOperations
|
||||
.expire(sessionKey, getMaxInactiveInterval());
|
||||
|
||||
return changeSessionId.and(update).and(setTtl).and(s -> {
|
||||
return update.and(setTtl).and((s) -> {
|
||||
this.delta.clear();
|
||||
s.onComplete();
|
||||
}).then();
|
||||
}
|
||||
|
||||
private Mono<Void> saveChangeSessionId(String sessionId) {
|
||||
if (sessionId.equals(this.originalSessionId)) {
|
||||
private Mono<Void> saveChangeSessionId() {
|
||||
if (!hasChangedSessionId()) {
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
Publisher<Void> replaceSessionId = s -> {
|
||||
String sessionId = getId();
|
||||
|
||||
Publisher<Void> replaceSessionId = (s) -> {
|
||||
this.originalSessionId = sessionId;
|
||||
s.onComplete();
|
||||
};
|
||||
|
||||
if (isNew()) {
|
||||
if (this.isNew) {
|
||||
return Mono.from(replaceSessionId);
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -28,6 +28,8 @@ import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.core.NestedExceptionUtils;
|
||||
import org.springframework.dao.NonTransientDataAccessException;
|
||||
import org.springframework.data.redis.connection.Message;
|
||||
import org.springframework.data.redis.connection.MessageListener;
|
||||
import org.springframework.data.redis.core.BoundHashOperations;
|
||||
@@ -316,7 +318,7 @@ public class RedisOperationsSessionRepository implements
|
||||
/**
|
||||
* Creates a new instance. For an example, refer to the class level javadoc.
|
||||
*
|
||||
* @param sessionRedisOperations The {@link RedisOperations} to use for managing the
|
||||
* @param sessionRedisOperations the {@link RedisOperations} to use for managing the
|
||||
* sessions. Cannot be null.
|
||||
*/
|
||||
public RedisOperationsSessionRepository(
|
||||
@@ -797,8 +799,10 @@ public class RedisOperationsSessionRepository implements
|
||||
|
||||
this.delta = new HashMap<>(this.delta.size());
|
||||
|
||||
Long originalExpiration = this.originalLastAccessTime == null ? null
|
||||
: this.originalLastAccessTime.plus(getMaxInactiveInterval()).toEpochMilli();
|
||||
Long originalExpiration = (this.originalLastAccessTime != null)
|
||||
? this.originalLastAccessTime.plus(getMaxInactiveInterval())
|
||||
.toEpochMilli()
|
||||
: null;
|
||||
RedisOperationsSessionRepository.this.expirationPolicy
|
||||
.onExpirationUpdated(originalExpiration, this);
|
||||
}
|
||||
@@ -812,8 +816,16 @@ public class RedisOperationsSessionRepository implements
|
||||
originalSessionIdKey, sessionIdKey);
|
||||
String originalExpiredKey = getExpiredKey(this.originalSessionId);
|
||||
String expiredKey = getExpiredKey(sessionId);
|
||||
RedisOperationsSessionRepository.this.sessionRedisOperations.rename(
|
||||
originalExpiredKey, expiredKey);
|
||||
try {
|
||||
RedisOperationsSessionRepository.this.sessionRedisOperations.rename(
|
||||
originalExpiredKey, expiredKey);
|
||||
}
|
||||
catch (NonTransientDataAccessException ex) {
|
||||
if (!"ERR no such key".equals(NestedExceptionUtils
|
||||
.getMostSpecificCause(ex).getMessage())) {
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.originalSessionId = sessionId;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2017 the original author or authors.
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -79,10 +79,10 @@ public class ConfigureNotifyKeyspaceEventsAction implements ConfigureRedisAction
|
||||
}
|
||||
return config.getProperty(config.stringPropertyNames().iterator().next());
|
||||
}
|
||||
catch (InvalidDataAccessApiUsageException e) {
|
||||
catch (InvalidDataAccessApiUsageException ex) {
|
||||
throw new IllegalStateException(
|
||||
"Unable to configure Redis to keyspace notifications. See http://docs.spring.io/spring-session/docs/current/reference/html5/#api-redisoperationssessionrepository-sessiondestroyedevent",
|
||||
e);
|
||||
ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2017 the original author or authors.
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -31,7 +31,7 @@ public interface ConfigureRedisAction {
|
||||
/**
|
||||
* A do nothing implementation of {@link ConfigureRedisAction}.
|
||||
*/
|
||||
ConfigureRedisAction NO_OP = connection -> {
|
||||
ConfigureRedisAction NO_OP = (connection) -> {
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -289,9 +289,9 @@ public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguratio
|
||||
try {
|
||||
connection.close();
|
||||
}
|
||||
catch (Exception e) {
|
||||
catch (Exception ex) {
|
||||
LogFactory.getLog(getClass()).error("Error closing RedisConnection",
|
||||
e);
|
||||
ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,7 +133,7 @@ public class ReactiveRedisOperationsSessionRepositoryTests {
|
||||
@Test
|
||||
public void createSessionDefaultMaxInactiveInterval() {
|
||||
StepVerifier.create(this.repository.createSession()).consumeNextWith(
|
||||
session -> assertThat(session.getMaxInactiveInterval()).isEqualTo(Duration
|
||||
(session) -> assertThat(session.getMaxInactiveInterval()).isEqualTo(Duration
|
||||
.ofSeconds(MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS)))
|
||||
.verifyComplete();
|
||||
}
|
||||
@@ -143,7 +143,7 @@ public class ReactiveRedisOperationsSessionRepositoryTests {
|
||||
this.repository.setDefaultMaxInactiveInterval(600);
|
||||
|
||||
StepVerifier.create(this.repository.createSession())
|
||||
.consumeNextWith(session -> assertThat(session.getMaxInactiveInterval())
|
||||
.consumeNextWith((session) -> assertThat(session.getMaxInactiveInterval())
|
||||
.isEqualTo(Duration.ofSeconds(600)))
|
||||
.verifyComplete();
|
||||
}
|
||||
@@ -157,7 +157,7 @@ public class ReactiveRedisOperationsSessionRepositoryTests {
|
||||
|
||||
StepVerifier
|
||||
.create(this.repository.createSession().doOnNext(this.repository::save))
|
||||
.consumeNextWith(session -> {
|
||||
.consumeNextWith((session) -> {
|
||||
verify(this.redisOperations).opsForHash();
|
||||
verify(this.hashOperations).putAll(anyString(), this.delta.capture());
|
||||
verify(this.redisOperations).expire(anyString(), any());
|
||||
@@ -183,6 +183,7 @@ public class ReactiveRedisOperationsSessionRepositoryTests {
|
||||
|
||||
@Test
|
||||
public void saveSessionNothingChanged() {
|
||||
given(this.redisOperations.hasKey(anyString())).willReturn(Mono.just(true));
|
||||
given(this.redisOperations.expire(anyString(), any()))
|
||||
.willReturn(Mono.just(true));
|
||||
|
||||
@@ -191,12 +192,14 @@ public class ReactiveRedisOperationsSessionRepositoryTests {
|
||||
|
||||
StepVerifier.create(this.repository.save(session)).verifyComplete();
|
||||
|
||||
verify(this.redisOperations).hasKey(anyString());
|
||||
verifyZeroInteractions(this.redisOperations);
|
||||
verifyZeroInteractions(this.hashOperations);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void saveLastAccessChanged() {
|
||||
given(this.redisOperations.hasKey(anyString())).willReturn(Mono.just(true));
|
||||
given(this.redisOperations.opsForHash()).willReturn(this.hashOperations);
|
||||
given(this.hashOperations.putAll(anyString(), any())).willReturn(Mono.just(true));
|
||||
given(this.redisOperations.expire(anyString(), any()))
|
||||
@@ -206,6 +209,7 @@ public class ReactiveRedisOperationsSessionRepositoryTests {
|
||||
session.setLastAccessedTime(Instant.ofEpochMilli(12345678L));
|
||||
Mono.just(session).subscribe(this.repository::save);
|
||||
|
||||
verify(this.redisOperations).hasKey(anyString());
|
||||
verify(this.redisOperations).opsForHash();
|
||||
verify(this.hashOperations).putAll(anyString(), this.delta.capture());
|
||||
verify(this.redisOperations).expire(anyString(), any());
|
||||
@@ -219,6 +223,7 @@ public class ReactiveRedisOperationsSessionRepositoryTests {
|
||||
|
||||
@Test
|
||||
public void saveSetAttribute() {
|
||||
given(this.redisOperations.hasKey(anyString())).willReturn(Mono.just(true));
|
||||
given(this.redisOperations.opsForHash()).willReturn(this.hashOperations);
|
||||
given(this.hashOperations.putAll(anyString(), any())).willReturn(Mono.just(true));
|
||||
given(this.redisOperations.expire(anyString(), any()))
|
||||
@@ -229,6 +234,7 @@ public class ReactiveRedisOperationsSessionRepositoryTests {
|
||||
session.setAttribute(attrName, "attrValue");
|
||||
Mono.just(session).subscribe(this.repository::save);
|
||||
|
||||
verify(this.redisOperations).hasKey(anyString());
|
||||
verify(this.redisOperations).opsForHash();
|
||||
verify(this.hashOperations).putAll(anyString(), this.delta.capture());
|
||||
verify(this.redisOperations).expire(anyString(), any());
|
||||
@@ -242,6 +248,7 @@ public class ReactiveRedisOperationsSessionRepositoryTests {
|
||||
|
||||
@Test
|
||||
public void saveRemoveAttribute() {
|
||||
given(this.redisOperations.hasKey(anyString())).willReturn(Mono.just(true));
|
||||
given(this.redisOperations.opsForHash()).willReturn(this.hashOperations);
|
||||
given(this.hashOperations.putAll(anyString(), any())).willReturn(Mono.just(true));
|
||||
given(this.redisOperations.expire(anyString(), any()))
|
||||
@@ -252,6 +259,7 @@ public class ReactiveRedisOperationsSessionRepositoryTests {
|
||||
session.removeAttribute(attrName);
|
||||
Mono.just(session).subscribe(this.repository::save);
|
||||
|
||||
verify(this.redisOperations).hasKey(anyString());
|
||||
verify(this.redisOperations).opsForHash();
|
||||
verify(this.hashOperations).putAll(anyString(), this.delta.capture());
|
||||
verify(this.redisOperations).expire(anyString(), any());
|
||||
@@ -325,7 +333,7 @@ public class ReactiveRedisOperationsSessionRepositoryTests {
|
||||
given(this.hashOperations.entries(anyString()))
|
||||
.willReturn(Flux.fromIterable(map.entrySet()));
|
||||
|
||||
StepVerifier.create(this.repository.findById("test")).consumeNextWith(session -> {
|
||||
StepVerifier.create(this.repository.findById("test")).consumeNextWith((session) -> {
|
||||
verify(this.redisOperations).opsForHash();
|
||||
verify(this.hashOperations).entries(anyString());
|
||||
verifyZeroInteractions(this.redisOperations);
|
||||
@@ -367,6 +375,19 @@ public class ReactiveRedisOperationsSessionRepositoryTests {
|
||||
verifyZeroInteractions(this.hashOperations);
|
||||
}
|
||||
|
||||
@Test // gh-1120
|
||||
public void getAttributeNamesAndRemove() {
|
||||
RedisSession session = this.repository.new RedisSession(this.cached);
|
||||
session.setAttribute("attribute1", "value1");
|
||||
session.setAttribute("attribute2", "value2");
|
||||
|
||||
for (String attributeName : session.getAttributeNames()) {
|
||||
session.removeAttribute(attributeName);
|
||||
}
|
||||
|
||||
assertThat(session.getAttributeNames()).isEmpty();
|
||||
}
|
||||
|
||||
private Map<String, Object> map(Object... objects) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
if (objects == null) {
|
||||
|
||||
@@ -868,6 +868,19 @@ public class RedisOperationsSessionRepositoryTests {
|
||||
.hasMessage("namespace cannot be null or empty");
|
||||
}
|
||||
|
||||
@Test // gh-1120
|
||||
public void getAttributeNamesAndRemove() {
|
||||
RedisSession session = this.redisRepository.new RedisSession(this.cached);
|
||||
session.setAttribute("attribute1", "value1");
|
||||
session.setAttribute("attribute2", "value2");
|
||||
|
||||
for (String attributeName : session.getAttributeNames()) {
|
||||
session.removeAttribute(attributeName);
|
||||
}
|
||||
|
||||
assertThat(session.getAttributeNames()).isEmpty();
|
||||
}
|
||||
|
||||
private String getKey(String id) {
|
||||
return "spring:session:sessions:" + id;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2017 the original author or authors.
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -43,10 +43,10 @@ import static org.mockito.Mockito.verify;
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class RedisSessionExpirationPolicyTests {
|
||||
// Wed Apr 15 10:28:32 CDT 2015
|
||||
final static Long NOW = 1429111712346L;
|
||||
static final Long NOW = 1429111712346L;
|
||||
|
||||
// Wed Apr 15 10:27:32 CDT 2015
|
||||
final static Long ONE_MINUTE_AGO = 1429111652346L;
|
||||
static final Long ONE_MINUTE_AGO = 1429111652346L;
|
||||
|
||||
@Mock
|
||||
RedisOperations<Object, Object> sessionRedisOperations;
|
||||
|
||||
@@ -10,4 +10,5 @@ dependencies {
|
||||
testCompile "org.springframework.security:spring-security-core"
|
||||
|
||||
integrationTestCompile "com.hazelcast:hazelcast-client"
|
||||
integrationTestCompile "org.testcontainers:testcontainers"
|
||||
}
|
||||
|
||||
@@ -18,9 +18,17 @@ package org.springframework.session.hazelcast;
|
||||
|
||||
import com.hazelcast.core.HazelcastInstance;
|
||||
import com.hazelcast.core.IMap;
|
||||
import com.hazelcast.instance.HazelcastInstanceProxy;
|
||||
import org.junit.Assume;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.session.FindByIndexNameSessionRepository;
|
||||
import org.springframework.session.MapSession;
|
||||
import org.springframework.session.hazelcast.HazelcastSessionRepository.HazelcastSession;
|
||||
|
||||
@@ -34,8 +42,10 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
*/
|
||||
public abstract class AbstractHazelcastRepositoryITests {
|
||||
|
||||
private static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT";
|
||||
|
||||
@Autowired
|
||||
private HazelcastInstance hazelcast;
|
||||
private HazelcastInstance hazelcastInstance;
|
||||
|
||||
@Autowired
|
||||
private HazelcastSessionRepository repository;
|
||||
@@ -45,7 +55,7 @@ public abstract class AbstractHazelcastRepositoryITests {
|
||||
HazelcastSession sessionToSave = this.repository.createSession();
|
||||
String sessionId = sessionToSave.getId();
|
||||
|
||||
IMap<String, MapSession> hazelcastMap = this.hazelcast
|
||||
IMap<String, MapSession> hazelcastMap = this.hazelcastInstance
|
||||
.getMap(HazelcastSessionRepository.DEFAULT_SESSION_MAP_NAME);
|
||||
|
||||
assertThat(hazelcastMap.size()).isEqualTo(0);
|
||||
@@ -61,7 +71,7 @@ public abstract class AbstractHazelcastRepositoryITests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void changeSessionIdWhenOnlyChangeId() throws Exception {
|
||||
public void changeSessionIdWhenOnlyChangeId() {
|
||||
String attrName = "changeSessionId";
|
||||
String attrValue = "changeSessionId-value";
|
||||
HazelcastSession toSave = this.repository.createSession();
|
||||
@@ -90,7 +100,7 @@ public abstract class AbstractHazelcastRepositoryITests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void changeSessionIdWhenChangeTwice() throws Exception {
|
||||
public void changeSessionIdWhenChangeTwice() {
|
||||
HazelcastSession toSave = this.repository.createSession();
|
||||
|
||||
this.repository.save(toSave);
|
||||
@@ -109,7 +119,7 @@ public abstract class AbstractHazelcastRepositoryITests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void changeSessionIdWhenSetAttributeOnChangedSession() throws Exception {
|
||||
public void changeSessionIdWhenSetAttributeOnChangedSession() {
|
||||
String attrName = "changeSessionId";
|
||||
String attrValue = "changeSessionId-value";
|
||||
|
||||
@@ -138,10 +148,7 @@ public abstract class AbstractHazelcastRepositoryITests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void changeSessionIdWhenHasNotSaved() throws Exception {
|
||||
String attrName = "changeSessionId";
|
||||
String attrValue = "changeSessionId-value";
|
||||
|
||||
public void changeSessionIdWhenHasNotSaved() {
|
||||
HazelcastSession toSave = this.repository.createSession();
|
||||
String originalId = toSave.getId();
|
||||
toSave.changeSessionId();
|
||||
@@ -167,4 +174,57 @@ public abstract class AbstractHazelcastRepositoryITests {
|
||||
assertThat(this.repository.findById(sessionId)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createAndUpdateSession() {
|
||||
HazelcastSession session = this.repository.createSession();
|
||||
String sessionId = session.getId();
|
||||
|
||||
this.repository.save(session);
|
||||
|
||||
session = this.repository.findById(sessionId);
|
||||
session.setAttribute("attributeName", "attributeValue");
|
||||
|
||||
this.repository.save(session);
|
||||
|
||||
assertThat(this.repository.findById(sessionId)).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createSessionWithSecurityContextAndFindById() {
|
||||
HazelcastSession session = this.repository.createSession();
|
||||
String sessionId = session.getId();
|
||||
|
||||
Authentication authentication = new UsernamePasswordAuthenticationToken(
|
||||
"saves-" + System.currentTimeMillis(), "password",
|
||||
AuthorityUtils.createAuthorityList("ROLE_USER"));
|
||||
SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
|
||||
securityContext.setAuthentication(authentication);
|
||||
session.setAttribute(SPRING_SECURITY_CONTEXT, securityContext);
|
||||
|
||||
this.repository.save(session);
|
||||
|
||||
assertThat(this.repository.findById(sessionId)).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createSessionWithSecurityContextAndFindByPrincipal() {
|
||||
Assume.assumeTrue("Hazelcast runs in embedded server topology",
|
||||
this.hazelcastInstance instanceof HazelcastInstanceProxy);
|
||||
|
||||
HazelcastSession session = this.repository.createSession();
|
||||
|
||||
String username = "saves-" + System.currentTimeMillis();
|
||||
Authentication authentication = new UsernamePasswordAuthenticationToken(username,
|
||||
"password", AuthorityUtils.createAuthorityList("ROLE_USER"));
|
||||
SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
|
||||
securityContext.setAuthentication(authentication);
|
||||
session.setAttribute(SPRING_SECURITY_CONTEXT, securityContext);
|
||||
|
||||
this.repository.save(session);
|
||||
|
||||
assertThat(this.repository.findByIndexNameAndIndexValue(
|
||||
FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, username))
|
||||
.isNotNull();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2016 the original author or authors.
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -22,14 +22,17 @@ import com.hazelcast.core.HazelcastInstance;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.testcontainers.containers.BindMode;
|
||||
import org.testcontainers.containers.GenericContainer;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.session.MapSession;
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.session.hazelcast.config.annotation.web.http.EnableHazelcastHttpSession;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.context.web.WebAppConfiguration;
|
||||
import org.springframework.util.SocketUtils;
|
||||
|
||||
/**
|
||||
* Integration tests that check the underlying data source - in this case Hazelcast
|
||||
@@ -44,20 +47,23 @@ import org.springframework.util.SocketUtils;
|
||||
@WebAppConfiguration
|
||||
public class HazelcastClientRepositoryITests extends AbstractHazelcastRepositoryITests {
|
||||
|
||||
private static final int PORT = SocketUtils.findAvailableTcpPort();
|
||||
|
||||
private static HazelcastInstance hazelcastInstance;
|
||||
private static GenericContainer container = new GenericContainer<>(
|
||||
"hazelcast/hazelcast:3.9.4")
|
||||
.withExposedPorts(5701)
|
||||
.withEnv("JAVA_OPTS",
|
||||
"-Dhazelcast.config=/opt/hazelcast/config_ext/hazelcast.xml")
|
||||
.withClasspathResourceMapping("/hazelcast-server.xml",
|
||||
"/opt/hazelcast/config_ext/hazelcast.xml",
|
||||
BindMode.READ_ONLY);
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() {
|
||||
hazelcastInstance = HazelcastITestUtils.embeddedHazelcastServer(PORT);
|
||||
public static void setUpClass() {
|
||||
container.start();
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void teardown() {
|
||||
if (hazelcastInstance != null) {
|
||||
hazelcastInstance.shutdown();
|
||||
}
|
||||
public static void tearDownClass() {
|
||||
container.stop();
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@@ -65,9 +71,13 @@ public class HazelcastClientRepositoryITests extends AbstractHazelcastRepository
|
||||
static class HazelcastSessionConfig {
|
||||
|
||||
@Bean
|
||||
public HazelcastInstance embeddedHazelcastClient() {
|
||||
public HazelcastInstance hazelcastInstance() {
|
||||
ClientConfig clientConfig = new ClientConfig();
|
||||
clientConfig.getNetworkConfig().addAddress("127.0.0.1:" + PORT);
|
||||
clientConfig.getNetworkConfig().addAddress(container.getContainerIpAddress()
|
||||
+ ":" + container.getFirstMappedPort());
|
||||
clientConfig.getUserCodeDeploymentConfig().setEnabled(true)
|
||||
.addClass(Session.class).addClass(MapSession.class)
|
||||
.addClass(SessionUpdateEntryProcessor.class);
|
||||
return HazelcastClient.newHazelcastClient(clientConfig);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2016 the original author or authors.
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -43,7 +43,7 @@ public class HazelcastServerRepositoryITests extends AbstractHazelcastRepository
|
||||
static class HazelcastSessionConfig {
|
||||
|
||||
@Bean
|
||||
public HazelcastInstance embeddedHazelcastServer() {
|
||||
public HazelcastInstance hazelcastInstance() {
|
||||
return HazelcastITestUtils.embeddedHazelcastServer();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2017 the original author or authors.
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -66,6 +66,6 @@ public class SessionEventRegistry implements ApplicationListener<AbstractSession
|
||||
}
|
||||
|
||||
private Object getLock(String sessionId) {
|
||||
return this.locks.computeIfAbsent(sessionId, k -> new Object());
|
||||
return this.locks.computeIfAbsent(sessionId, (k) -> new Object());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
@WebAppConfiguration
|
||||
public class EnableHazelcastHttpSessionEventsTests<S extends Session> {
|
||||
|
||||
private final static int MAX_INACTIVE_INTERVAL_IN_SECONDS = 1;
|
||||
private static final int MAX_INACTIVE_INTERVAL_IN_SECONDS = 1;
|
||||
|
||||
@Autowired
|
||||
private SessionRepository<S> repository;
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<hazelcast xmlns="http://www.hazelcast.com/schema/config"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.hazelcast.com/schema/config hazelcast-config-3.9.xsd">
|
||||
|
||||
<user-code-deployment enabled="true">
|
||||
<class-cache-mode>ETERNAL</class-cache-mode>
|
||||
<provider-mode>LOCAL_AND_CACHED_CLASSES</provider-mode>
|
||||
</user-code-deployment>
|
||||
|
||||
</hazelcast>
|
||||
@@ -31,8 +31,6 @@ import javax.annotation.PreDestroy;
|
||||
import com.hazelcast.core.EntryEvent;
|
||||
import com.hazelcast.core.HazelcastInstance;
|
||||
import com.hazelcast.core.IMap;
|
||||
import com.hazelcast.map.AbstractEntryProcessor;
|
||||
import com.hazelcast.map.EntryProcessor;
|
||||
import com.hazelcast.map.listener.EntryAddedListener;
|
||||
import com.hazelcast.map.listener.EntryEvictedListener;
|
||||
import com.hazelcast.map.listener.EntryRemovedListener;
|
||||
@@ -452,59 +450,4 @@ public class HazelcastSessionRepository implements
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Hazelcast {@link EntryProcessor} responsible for handling updates to session.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @see #save(HazelcastSession)
|
||||
*/
|
||||
private static final class SessionUpdateEntryProcessor
|
||||
extends AbstractEntryProcessor<String, MapSession> {
|
||||
|
||||
private Instant lastAccessedTime;
|
||||
|
||||
private Duration maxInactiveInterval;
|
||||
|
||||
private Map<String, Object> delta;
|
||||
|
||||
@Override
|
||||
public Object process(Map.Entry<String, MapSession> entry) {
|
||||
MapSession value = entry.getValue();
|
||||
if (value == null) {
|
||||
return Boolean.FALSE;
|
||||
}
|
||||
if (this.lastAccessedTime != null) {
|
||||
value.setLastAccessedTime(this.lastAccessedTime);
|
||||
}
|
||||
if (this.maxInactiveInterval != null) {
|
||||
value.setMaxInactiveInterval(this.maxInactiveInterval);
|
||||
}
|
||||
if (this.delta != null) {
|
||||
for (final Map.Entry<String, Object> attribute : this.delta.entrySet()) {
|
||||
if (attribute.getValue() != null) {
|
||||
value.setAttribute(attribute.getKey(), attribute.getValue());
|
||||
}
|
||||
else {
|
||||
value.removeAttribute(attribute.getKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
entry.setValue(value);
|
||||
return Boolean.TRUE;
|
||||
}
|
||||
|
||||
public void setLastAccessedTime(Instant lastAccessedTime) {
|
||||
this.lastAccessedTime = lastAccessedTime;
|
||||
}
|
||||
|
||||
public void setMaxInactiveInterval(Duration maxInactiveInterval) {
|
||||
this.maxInactiveInterval = maxInactiveInterval;
|
||||
}
|
||||
|
||||
public void setDelta(Map<String, Object> delta) {
|
||||
this.delta = delta;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session.hazelcast;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Map;
|
||||
|
||||
import com.hazelcast.map.AbstractEntryProcessor;
|
||||
import com.hazelcast.map.EntryProcessor;
|
||||
|
||||
import org.springframework.session.MapSession;
|
||||
|
||||
/**
|
||||
* Hazelcast {@link EntryProcessor} responsible for handling updates to session.
|
||||
*
|
||||
* @author Vedran Pavic
|
||||
* @since 2.0.5
|
||||
* @see HazelcastSessionRepository#save(HazelcastSessionRepository.HazelcastSession)
|
||||
*/
|
||||
class SessionUpdateEntryProcessor extends AbstractEntryProcessor<String, MapSession> {
|
||||
|
||||
private Instant lastAccessedTime;
|
||||
|
||||
private Duration maxInactiveInterval;
|
||||
|
||||
private Map<String, Object> delta;
|
||||
|
||||
@Override
|
||||
public Object process(Map.Entry<String, MapSession> entry) {
|
||||
MapSession value = entry.getValue();
|
||||
if (value == null) {
|
||||
return Boolean.FALSE;
|
||||
}
|
||||
if (this.lastAccessedTime != null) {
|
||||
value.setLastAccessedTime(this.lastAccessedTime);
|
||||
}
|
||||
if (this.maxInactiveInterval != null) {
|
||||
value.setMaxInactiveInterval(this.maxInactiveInterval);
|
||||
}
|
||||
if (this.delta != null) {
|
||||
for (final Map.Entry<String, Object> attribute : this.delta.entrySet()) {
|
||||
if (attribute.getValue() != null) {
|
||||
value.setAttribute(attribute.getKey(), attribute.getValue());
|
||||
}
|
||||
else {
|
||||
value.removeAttribute(attribute.getKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
entry.setValue(value);
|
||||
return Boolean.TRUE;
|
||||
}
|
||||
|
||||
void setLastAccessedTime(Instant lastAccessedTime) {
|
||||
this.lastAccessedTime = lastAccessedTime;
|
||||
}
|
||||
|
||||
void setMaxInactiveInterval(Duration maxInactiveInterval) {
|
||||
this.maxInactiveInterval = maxInactiveInterval;
|
||||
}
|
||||
|
||||
void setDelta(Map<String, Object> delta) {
|
||||
this.delta = delta;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -418,4 +418,17 @@ public class HazelcastSessionRepositoryTests {
|
||||
verifyZeroInteractions(this.sessions);
|
||||
}
|
||||
|
||||
@Test // gh-1120
|
||||
public void getAttributeNamesAndRemove() {
|
||||
HazelcastSession session = this.repository.createSession();
|
||||
session.setAttribute("attribute1", "value1");
|
||||
session.setAttribute("attribute2", "value2");
|
||||
|
||||
for (String attributeName : session.getAttributeNames()) {
|
||||
session.removeAttribute(attributeName);
|
||||
}
|
||||
|
||||
assertThat(session.getAttributeNames()).isEmpty();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -237,7 +237,7 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
|
||||
|
||||
assertThat(findByPrincipalName).hasSize(1);
|
||||
assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId());
|
||||
findByPrincipalName.values().forEach(session -> {
|
||||
findByPrincipalName.values().forEach((session) -> {
|
||||
assertThat(session.isChanged()).isFalse();
|
||||
assertThat(session.getDelta()).isEmpty();
|
||||
});
|
||||
@@ -263,7 +263,7 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
|
||||
|
||||
assertThat(findByPrincipalName).hasSize(1);
|
||||
assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId());
|
||||
findByPrincipalName.values().forEach(session -> {
|
||||
findByPrincipalName.values().forEach((session) -> {
|
||||
assertThat(session.isChanged()).isFalse();
|
||||
assertThat(session.getDelta()).isEmpty();
|
||||
});
|
||||
@@ -309,7 +309,7 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
|
||||
|
||||
assertThat(findByPrincipalName).hasSize(1);
|
||||
assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId());
|
||||
findByPrincipalName.values().forEach(session -> {
|
||||
findByPrincipalName.values().forEach((session) -> {
|
||||
assertThat(session.isChanged()).isFalse();
|
||||
assertThat(session.getDelta()).isEmpty();
|
||||
});
|
||||
@@ -360,7 +360,7 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
|
||||
|
||||
assertThat(findByPrincipalName).hasSize(1);
|
||||
assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId());
|
||||
findByPrincipalName.values().forEach(session -> {
|
||||
findByPrincipalName.values().forEach((session) -> {
|
||||
assertThat(session.isChanged()).isFalse();
|
||||
assertThat(session.getDelta()).isEmpty();
|
||||
});
|
||||
@@ -423,7 +423,7 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
|
||||
|
||||
assertThat(findByPrincipalName).hasSize(1);
|
||||
assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId());
|
||||
findByPrincipalName.values().forEach(session -> {
|
||||
findByPrincipalName.values().forEach((session) -> {
|
||||
assertThat(session.isChanged()).isFalse();
|
||||
assertThat(session.getDelta()).isEmpty();
|
||||
});
|
||||
@@ -448,7 +448,7 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
|
||||
|
||||
assertThat(findByPrincipalName).hasSize(1);
|
||||
assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId());
|
||||
findByPrincipalName.values().forEach(session -> {
|
||||
findByPrincipalName.values().forEach((session) -> {
|
||||
assertThat(session.isChanged()).isFalse();
|
||||
assertThat(session.getDelta()).isEmpty();
|
||||
});
|
||||
@@ -491,7 +491,7 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
|
||||
|
||||
assertThat(findByPrincipalName).hasSize(1);
|
||||
assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId());
|
||||
findByPrincipalName.values().forEach(session -> {
|
||||
findByPrincipalName.values().forEach((session) -> {
|
||||
assertThat(session.isChanged()).isFalse();
|
||||
assertThat(session.getDelta()).isEmpty();
|
||||
});
|
||||
@@ -539,7 +539,7 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
|
||||
|
||||
assertThat(findByPrincipalName).hasSize(1);
|
||||
assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId());
|
||||
findByPrincipalName.values().forEach(session -> {
|
||||
findByPrincipalName.values().forEach((session) -> {
|
||||
assertThat(session.isChanged()).isFalse();
|
||||
assertThat(session.getDelta()).isEmpty();
|
||||
});
|
||||
@@ -743,6 +743,31 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
|
||||
assertThat(session.<String>getAttribute("testName")).isEqualTo("testValue2");
|
||||
}
|
||||
|
||||
@Test // gh-1151
|
||||
public void saveDeleted() {
|
||||
JdbcOperationsSessionRepository.JdbcSession session = this.repository.createSession();
|
||||
this.repository.save(session);
|
||||
session = this.repository.findById(session.getId());
|
||||
this.repository.deleteById(session.getId());
|
||||
session.setLastAccessedTime(Instant.now());
|
||||
this.repository.save(session);
|
||||
|
||||
assertThat(this.repository.findById(session.getId())).isNull();
|
||||
}
|
||||
|
||||
@Test // gh-1151
|
||||
public void saveDeletedAddAttribute() {
|
||||
JdbcOperationsSessionRepository.JdbcSession session = this.repository.createSession();
|
||||
this.repository.save(session);
|
||||
session = this.repository.findById(session.getId());
|
||||
this.repository.deleteById(session.getId());
|
||||
session.setLastAccessedTime(Instant.now());
|
||||
session.setAttribute("testName", "testValue1");
|
||||
this.repository.save(session);
|
||||
|
||||
assertThat(this.repository.findById(session.getId())).isNull();
|
||||
}
|
||||
|
||||
private String getSecurityName() {
|
||||
return this.context.getAuthentication().getName();
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ public class MariaDb10JdbcOperationsSessionRepositoryITests
|
||||
private static class MariaDb10Container extends MariaDBContainer<MariaDb10Container> {
|
||||
|
||||
MariaDb10Container() {
|
||||
super("mariadb:10.3.7");
|
||||
super("mariadb:10.3.9");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -86,7 +86,7 @@ public class MariaDb5JdbcOperationsSessionRepositoryITests
|
||||
private static class MariaDb5Container extends MariaDBContainer<MariaDb5Container> {
|
||||
|
||||
MariaDb5Container() {
|
||||
super("mariadb:5.5.60");
|
||||
super("mariadb:5.5.61");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -85,7 +85,7 @@ public class MySql5JdbcOperationsSessionRepositoryITests
|
||||
private static class MySql5Container extends MySQLContainer<MySql5Container> {
|
||||
|
||||
MySql5Container() {
|
||||
super("mysql:5.7.22");
|
||||
super("mysql:5.7.23");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -85,7 +85,7 @@ public class MySql8JdbcOperationsSessionRepositoryITests
|
||||
private static class MySql8Container extends MySQLContainer<MySql8Container> {
|
||||
|
||||
MySql8Container() {
|
||||
super("mysql:8.0.11");
|
||||
super("mysql:8.0.12");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -86,7 +86,7 @@ public class PostgreSql10JdbcOperationsSessionRepositoryITests
|
||||
extends PostgreSQLContainer<PostgreSql10Container> {
|
||||
|
||||
PostgreSql10Container() {
|
||||
super("postgres:10.4");
|
||||
super("postgres:10.5");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ public class PostgreSql9JdbcOperationsSessionRepositoryITests
|
||||
extends PostgreSQLContainer<PostgreSql9Container> {
|
||||
|
||||
PostgreSql9Container() {
|
||||
super("postgres:9.6.9");
|
||||
super("postgres:9.6.10");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ import javax.sql.DataSource;
|
||||
import com.microsoft.sqlserver.jdbc.SQLServerDataSource;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.testcontainers.containers.MSSQLServerContainer;
|
||||
|
||||
@@ -40,7 +39,6 @@ import org.springframework.test.context.web.WebAppConfiguration;
|
||||
*
|
||||
* @author Vedran Pavic
|
||||
*/
|
||||
@Ignore
|
||||
@RunWith(SpringRunner.class)
|
||||
@WebAppConfiguration
|
||||
@ContextConfiguration
|
||||
@@ -88,12 +86,9 @@ public class SqlServerJdbcOperationsSessionRepositoryITests
|
||||
extends MSSQLServerContainer<SqlServer2007Container> {
|
||||
|
||||
SqlServer2007Container() {
|
||||
super("microsoft/mssql-server-linux:2017-CU7");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getStartupTimeoutSeconds() {
|
||||
return 240;
|
||||
super("microsoft/mssql-server-linux:2017-CU9");
|
||||
withStartupTimeoutSeconds(240);
|
||||
withConnectTimeoutSeconds(240);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
microsoft/mssql-server-linux:2017-CU9
|
||||
@@ -144,7 +144,9 @@ public class JdbcOperationsSessionRepository implements
|
||||
|
||||
private static final String CREATE_SESSION_ATTRIBUTE_QUERY =
|
||||
"INSERT INTO %TABLE_NAME%_ATTRIBUTES(SESSION_PRIMARY_ID, ATTRIBUTE_NAME, ATTRIBUTE_BYTES) " +
|
||||
"VALUES (?, ?, ?)";
|
||||
"SELECT PRIMARY_ID, ?, ? " +
|
||||
"FROM %TABLE_NAME% " +
|
||||
"WHERE SESSION_ID = ?";
|
||||
|
||||
private static final String GET_SESSION_QUERY =
|
||||
"SELECT S.PRIMARY_ID, S.SESSION_ID, S.CREATION_TIME, S.LAST_ACCESS_TIME, S.MAX_INACTIVE_INTERVAL, SA.ATTRIBUTE_NAME, SA.ATTRIBUTE_BYTES " +
|
||||
@@ -372,7 +374,7 @@ public class JdbcOperationsSessionRepository implements
|
||||
protected void doInTransactionWithoutResult(TransactionStatus status) {
|
||||
JdbcOperationsSessionRepository.this.jdbcOperations.update(
|
||||
JdbcOperationsSessionRepository.this.createSessionQuery,
|
||||
ps -> {
|
||||
(ps) -> {
|
||||
ps.setString(1, session.primaryKey);
|
||||
ps.setString(2, session.getId());
|
||||
ps.setLong(3, session.getCreationTime().toEpochMilli());
|
||||
@@ -381,9 +383,9 @@ public class JdbcOperationsSessionRepository implements
|
||||
ps.setLong(6, session.getExpiryTime().toEpochMilli());
|
||||
ps.setString(7, session.getPrincipalName());
|
||||
});
|
||||
if (!session.getAttributeNames().isEmpty()) {
|
||||
final List<String> attributeNames = new ArrayList<>(session.getAttributeNames());
|
||||
insertSessionAttributes(session, attributeNames);
|
||||
Set<String> attributeNames = session.getAttributeNames();
|
||||
if (!attributeNames.isEmpty()) {
|
||||
insertSessionAttributes(session, new ArrayList<>(attributeNames));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -397,7 +399,7 @@ public class JdbcOperationsSessionRepository implements
|
||||
if (session.isChanged()) {
|
||||
JdbcOperationsSessionRepository.this.jdbcOperations.update(
|
||||
JdbcOperationsSessionRepository.this.updateSessionQuery,
|
||||
ps -> {
|
||||
(ps) -> {
|
||||
ps.setString(1, session.getId());
|
||||
ps.setLong(2, session.getLastAccessedTime().toEpochMilli());
|
||||
ps.setInt(3, (int) session.getMaxInactiveInterval().getSeconds());
|
||||
@@ -407,20 +409,26 @@ public class JdbcOperationsSessionRepository implements
|
||||
});
|
||||
}
|
||||
List<String> addedAttributeNames = session.delta.entrySet().stream()
|
||||
.filter(entry -> entry.getValue() == DeltaValue.ADDED)
|
||||
.filter((entry) -> entry.getValue() == DeltaValue.ADDED)
|
||||
.map(Map.Entry::getKey)
|
||||
.collect(Collectors.toList());
|
||||
insertSessionAttributes(session, addedAttributeNames);
|
||||
if (!addedAttributeNames.isEmpty()) {
|
||||
insertSessionAttributes(session, addedAttributeNames);
|
||||
}
|
||||
List<String> updatedAttributeNames = session.delta.entrySet().stream()
|
||||
.filter(entry -> entry.getValue() == DeltaValue.UPDATED)
|
||||
.filter((entry) -> entry.getValue() == DeltaValue.UPDATED)
|
||||
.map(Map.Entry::getKey)
|
||||
.collect(Collectors.toList());
|
||||
updateSessionAttributes(session, updatedAttributeNames);
|
||||
if (!updatedAttributeNames.isEmpty()) {
|
||||
updateSessionAttributes(session, updatedAttributeNames);
|
||||
}
|
||||
List<String> removedAttributeNames = session.delta.entrySet().stream()
|
||||
.filter(entry -> entry.getValue() == DeltaValue.REMOVED)
|
||||
.filter((entry) -> entry.getValue() == DeltaValue.REMOVED)
|
||||
.map(Map.Entry::getKey)
|
||||
.collect(Collectors.toList());
|
||||
deleteSessionAttributes(session, removedAttributeNames);
|
||||
if (!removedAttributeNames.isEmpty()) {
|
||||
deleteSessionAttributes(session, removedAttributeNames);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
@@ -430,10 +438,10 @@ public class JdbcOperationsSessionRepository implements
|
||||
|
||||
@Override
|
||||
public JdbcSession findById(final String id) {
|
||||
final JdbcSession session = this.transactionOperations.execute(status -> {
|
||||
final JdbcSession session = this.transactionOperations.execute((status) -> {
|
||||
List<JdbcSession> sessions = JdbcOperationsSessionRepository.this.jdbcOperations.query(
|
||||
JdbcOperationsSessionRepository.this.getSessionQuery,
|
||||
ps -> ps.setString(1, id),
|
||||
(ps) -> ps.setString(1, id),
|
||||
JdbcOperationsSessionRepository.this.extractor
|
||||
);
|
||||
if (sessions.isEmpty()) {
|
||||
@@ -473,10 +481,10 @@ public class JdbcOperationsSessionRepository implements
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
List<JdbcSession> sessions = this.transactionOperations.execute(status ->
|
||||
List<JdbcSession> sessions = this.transactionOperations.execute((status) ->
|
||||
JdbcOperationsSessionRepository.this.jdbcOperations.query(
|
||||
JdbcOperationsSessionRepository.this.listSessionsByPrincipalNameQuery,
|
||||
ps -> ps.setString(1, indexValue),
|
||||
(ps) -> ps.setString(1, indexValue),
|
||||
JdbcOperationsSessionRepository.this.extractor));
|
||||
|
||||
Map<String, JdbcSession> sessionMap = new HashMap<>(
|
||||
@@ -490,18 +498,16 @@ public class JdbcOperationsSessionRepository implements
|
||||
}
|
||||
|
||||
private void insertSessionAttributes(JdbcSession session, List<String> attributeNames) {
|
||||
if (attributeNames == null || attributeNames.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Assert.notEmpty(attributeNames, "attributeNames must not be null or empty");
|
||||
if (attributeNames.size() > 1) {
|
||||
this.jdbcOperations.batchUpdate(this.createSessionAttributeQuery, new BatchPreparedStatementSetter() {
|
||||
|
||||
@Override
|
||||
public void setValues(PreparedStatement ps, int i) throws SQLException {
|
||||
String attributeName = attributeNames.get(i);
|
||||
ps.setString(1, session.primaryKey);
|
||||
ps.setString(2, attributeName);
|
||||
serialize(ps, 3, session.getAttribute(attributeName));
|
||||
ps.setString(1, attributeName);
|
||||
serialize(ps, 2, session.getAttribute(attributeName));
|
||||
ps.setString(3, session.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -512,19 +518,17 @@ public class JdbcOperationsSessionRepository implements
|
||||
});
|
||||
}
|
||||
else {
|
||||
this.jdbcOperations.update(this.createSessionAttributeQuery, ps -> {
|
||||
this.jdbcOperations.update(this.createSessionAttributeQuery, (ps) -> {
|
||||
String attributeName = attributeNames.get(0);
|
||||
ps.setString(1, session.primaryKey);
|
||||
ps.setString(2, attributeName);
|
||||
serialize(ps, 3, session.getAttribute(attributeName));
|
||||
ps.setString(1, attributeName);
|
||||
serialize(ps, 2, session.getAttribute(attributeName));
|
||||
ps.setString(3, session.getId());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void updateSessionAttributes(JdbcSession session, List<String> attributeNames) {
|
||||
if (attributeNames == null || attributeNames.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Assert.notEmpty(attributeNames, "attributeNames must not be null or empty");
|
||||
if (attributeNames.size() > 1) {
|
||||
this.jdbcOperations.batchUpdate(this.updateSessionAttributeQuery, new BatchPreparedStatementSetter() {
|
||||
|
||||
@@ -544,7 +548,7 @@ public class JdbcOperationsSessionRepository implements
|
||||
});
|
||||
}
|
||||
else {
|
||||
this.jdbcOperations.update(this.updateSessionAttributeQuery, ps -> {
|
||||
this.jdbcOperations.update(this.updateSessionAttributeQuery, (ps) -> {
|
||||
String attributeName = attributeNames.get(0);
|
||||
serialize(ps, 1, session.getAttribute(attributeName));
|
||||
ps.setString(2, session.primaryKey);
|
||||
@@ -554,9 +558,7 @@ public class JdbcOperationsSessionRepository implements
|
||||
}
|
||||
|
||||
private void deleteSessionAttributes(JdbcSession session, List<String> attributeNames) {
|
||||
if (attributeNames == null || attributeNames.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Assert.notEmpty(attributeNames, "attributeNames must not be null or empty");
|
||||
if (attributeNames.size() > 1) {
|
||||
this.jdbcOperations.batchUpdate(this.deleteSessionAttributeQuery, new BatchPreparedStatementSetter() {
|
||||
|
||||
@@ -575,7 +577,7 @@ public class JdbcOperationsSessionRepository implements
|
||||
});
|
||||
}
|
||||
else {
|
||||
this.jdbcOperations.update(this.deleteSessionAttributeQuery, ps -> {
|
||||
this.jdbcOperations.update(this.deleteSessionAttributeQuery, (ps) -> {
|
||||
String attributeName = attributeNames.get(0);
|
||||
ps.setString(1, session.primaryKey);
|
||||
ps.setString(2, attributeName);
|
||||
@@ -584,7 +586,7 @@ public class JdbcOperationsSessionRepository implements
|
||||
}
|
||||
|
||||
public void cleanUpExpiredSessions() {
|
||||
Integer deletedCount = this.transactionOperations.execute(transactionStatus ->
|
||||
Integer deletedCount = this.transactionOperations.execute((status) ->
|
||||
JdbcOperationsSessionRepository.this.jdbcOperations.update(
|
||||
JdbcOperationsSessionRepository.this.deleteSessionsByExpiryTimeQuery,
|
||||
System.currentTimeMillis()));
|
||||
@@ -739,21 +741,21 @@ public class JdbcOperationsSessionRepository implements
|
||||
}
|
||||
if (attributeExists) {
|
||||
if (attributeRemoved) {
|
||||
this.delta.merge(attributeName, DeltaValue.REMOVED,
|
||||
(oldDeltaValue, deltaValue) -> oldDeltaValue == DeltaValue.ADDED
|
||||
? null
|
||||
this.delta.merge(attributeName, DeltaValue.REMOVED, (oldDeltaValue,
|
||||
deltaValue) -> (oldDeltaValue == DeltaValue.ADDED) ? null
|
||||
: deltaValue);
|
||||
}
|
||||
else {
|
||||
this.delta.merge(attributeName, DeltaValue.UPDATED,
|
||||
(oldDeltaValue, deltaValue) -> oldDeltaValue == DeltaValue.ADDED
|
||||
? oldDeltaValue
|
||||
: deltaValue);
|
||||
(oldDeltaValue,
|
||||
deltaValue) -> (oldDeltaValue == DeltaValue.ADDED)
|
||||
? oldDeltaValue
|
||||
: deltaValue);
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.delta.merge(attributeName, DeltaValue.ADDED,
|
||||
(oldDeltaValue, deltaValue) -> oldDeltaValue == DeltaValue.ADDED
|
||||
(oldDeltaValue, deltaValue) -> (oldDeltaValue == DeltaValue.ADDED)
|
||||
? oldDeltaValue
|
||||
: DeltaValue.UPDATED);
|
||||
}
|
||||
|
||||
@@ -675,6 +675,19 @@ public class JdbcOperationsSessionRepositoryTests {
|
||||
verify(this.jdbcOperations, times(1)).update(startsWith("DELETE"), anyLong());
|
||||
}
|
||||
|
||||
@Test // gh-1120
|
||||
public void getAttributeNamesAndRemove() {
|
||||
JdbcOperationsSessionRepository.JdbcSession session = this.repository.createSession();
|
||||
session.setAttribute("attribute1", "value1");
|
||||
session.setAttribute("attribute2", "value2");
|
||||
|
||||
for (String attributeName : session.getAttributeNames()) {
|
||||
session.removeAttribute(attributeName);
|
||||
}
|
||||
|
||||
assertThat(session.getAttributeNames()).isEmpty();
|
||||
}
|
||||
|
||||
private void assertPropagationRequiresNew() {
|
||||
ArgumentCaptor<TransactionDefinition> argument =
|
||||
ArgumentCaptor.forClass(TransactionDefinition.class);
|
||||
|
||||
Reference in New Issue
Block a user