Skip to content
Permalink
Browse files
IGNITE-14920: Implement Spring Sessions Using Ignite As Backing Store (
  • Loading branch information
ibessonov committed Jun 23, 2021
2 parents 80ea124 + defc64a commit fbaf38cb3f51e3edb7b8dc9421a862876568fec9
Show file tree
Hide file tree
Showing 13 changed files with 2,330 additions and 0 deletions.
@@ -0,0 +1,127 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>apache-ignite-extensions</artifactId>
<groupId>org.apache.ignite</groupId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>spring-session-ext</artifactId>

<properties>
<ignite.version>2.10.0</ignite.version>
<spring.version>5.3.8</spring.version>
<spring.session.version>2.5.0</spring.session.version>
<spring.security.version>5.5.0</spring.security.version>
<javax.annotation.version>1.3.2</javax.annotation.version>
<assertj.version>3.20.0</assertj.version>
<junit.jupiter.version>5.7.2</junit.jupiter.version>
<javax.servlet.version>3.0.1</javax.servlet.version>
<mockito.version>2.22.0</mockito.version>
</properties>

<dependencies>
<dependency>
<groupId>org.apache.ignite</groupId>
<artifactId>ignite-core</artifactId>
<version>${ignite.version}</version>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>${spring.version}</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-core -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>${spring.security.version}</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework.session/spring-session-core -->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-core</artifactId>
<version>${spring.session.version}</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>

<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>${javax.annotation.version}</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.assertj/assertj-core -->
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>${assertj.version}</version>
<scope>test</scope>
</dependency>

<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit.jupiter.version}</version>
<scope>test</scope>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>${javax.servlet.version}</version>
<scope>provided</scope>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework/spring-web -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.apache.ignite/ignite-indexing -->
<dependency>
<groupId>org.apache.ignite</groupId>
<artifactId>ignite-indexing</artifactId>
<version>${ignite.version}</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.mockito/mockito-core -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,98 @@
package org.apache.ignite.spring.sessions;

/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.apache.ignite.Ignite;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.session.FlushMode;
import org.springframework.session.MapSession;
import org.springframework.session.SaveMode;
import org.springframework.session.Session;
import org.springframework.session.SessionRepository;
import org.springframework.session.web.http.SessionRepositoryFilter;

/**
* Add this annotation to an {@code @Configuration} class to expose the
* {@link SessionRepositoryFilter} as a bean named {@code springSessionRepositoryFilter}
* and backed by Ignite. In order to leverage the annotation, a single {@link Ignite} must
* be provided. For example:
*
* <pre class="code">
* &#064;Configuration
* &#064;EnableIgniteHttpSession
* public class IgniteHttpSessionConfig {
*
* &#064;Bean
* public Ignite embeddedIgnite() {
* return IgniteEx.start();
* }
*
* }
* </pre>
*
* More advanced configurations can extend {@link IgniteHttpSessionConfiguration} instead.
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(IgniteHttpSessionConfiguration.class)
@Configuration(proxyBeanMethods = false)
public @interface EnableIgniteHttpSession {
/**
* The session timeout in seconds. By default, it is set to 1800 seconds (30 minutes).
* This should be a non-negative integer.
* @return the seconds a session can be inactive before expiring
*/
int maxInactiveIntervalInSeconds() default MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS;

/**
* This is the name of the Map that will be used in Ignite to store the session data.
* Default is "spring:session:sessions".
* @return the name of the Map to store the sessions in Ignite
*/
String sessionMapName() default "spring:session:sessions";

/**
* Flush mode for the Ignite sessions. The default is {@code ON_SAVE} which only
* updates the backing Ignite when {@link SessionRepository#save(Session)} is invoked.
* In a web environment this happens just before the HTTP response is committed.
* <p>
* Setting the value to {@code IMMEDIATE} will ensure that the any updates to the
* Session are immediately written to the Ignite instance.
* @return the {@link FlushMode} to use
* @since 2.2.0
*/
FlushMode flushMode() default FlushMode.ON_SAVE;

/**
* Save mode for the session. The default is {@link SaveMode#ON_SET_ATTRIBUTE}, which
* only saves changes made to session.
* @return the save mode
* @since 2.2.0
*/
SaveMode saveMode() default SaveMode.ON_SET_ATTRIBUTE;

}
@@ -0,0 +1,164 @@
package org.apache.ignite.spring.sessions;

/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.apache.ignite.Ignite;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportAware;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.session.FlushMode;
import org.springframework.session.IndexResolver;
import org.springframework.session.MapSession;
import org.springframework.session.SaveMode;
import org.springframework.session.Session;
import org.springframework.session.SessionRepository;
import org.springframework.session.config.SessionRepositoryCustomizer;
import org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration;
import org.springframework.session.web.http.SessionRepositoryFilter;
import org.springframework.util.StringUtils;

/**
* Exposes the {@link SessionRepositoryFilter} as a bean named
* {@code springSessionRepositoryFilter}. In order to use this a single {@link Ignite}
* must be exposed as a Bean.
*/
@Configuration(proxyBeanMethods = false)
public class IgniteHttpSessionConfiguration extends SpringHttpSessionConfiguration implements ImportAware {
/** */
private Integer maxInactiveIntervalInSeconds = MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS;

/** */
private String sessionMapName = IgniteIndexedSessionRepository.DEFAULT_SESSION_MAP_NAME;

/** */
private FlushMode flushMode = FlushMode.ON_SAVE;

/** */
private SaveMode saveMode = SaveMode.ON_SET_ATTRIBUTE;

/** */
private Ignite ignite;

/** */
private ApplicationEventPublisher applicationEventPublisher;

/** */
private IndexResolver<Session> indexResolver;

/** */
private List<SessionRepositoryCustomizer<IgniteIndexedSessionRepository>> sessionRepositoryCustomizers;

/** */
@Bean
public SessionRepository<?> sessionRepository() {
return createIgniteIndexedSessionRepository();
}

/** */
public void setMaxInactiveIntervalInSeconds(int maxInactiveIntervalInSeconds) {
this.maxInactiveIntervalInSeconds = maxInactiveIntervalInSeconds;
}

/** */
public void setSessionMapName(String sessionMapName) {
this.sessionMapName = sessionMapName;
}

/**
*
*/
public void setFlushMode(FlushMode flushMode) {
this.flushMode = flushMode;
}

/** */
public void setSaveMode(SaveMode saveMode) {
this.saveMode = saveMode;
}

/** */
@Autowired
public void setIgnite(@SpringSessionIgnite ObjectProvider<Ignite> springSessionIgnite,
ObjectProvider<Ignite> ignite) {
Ignite igniteToUse = springSessionIgnite.getIfAvailable();
if (igniteToUse == null)
igniteToUse = ignite.getObject();

this.ignite = igniteToUse;
}

/** */
@Autowired
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}

/** */
@Autowired(required = false)
public void setIndexResolver(IndexResolver<Session> indexResolver) {
this.indexResolver = indexResolver;
}

/** */
@Autowired(required = false)
public void setSessionRepositoryCustomizer(
ObjectProvider<SessionRepositoryCustomizer<IgniteIndexedSessionRepository>> sessionRepositoryCustomizers) {
this.sessionRepositoryCustomizers = sessionRepositoryCustomizers.orderedStream().collect(Collectors.toList());
}

/** */
@Override @SuppressWarnings("deprecation") public void setImportMetadata(AnnotationMetadata importMetadata) {
Map<String, Object> attributeMap = importMetadata
.getAnnotationAttributes(EnableIgniteHttpSession.class.getName());
AnnotationAttributes attributes = AnnotationAttributes.fromMap(attributeMap);
this.maxInactiveIntervalInSeconds = attributes.getNumber("maxInactiveIntervalInSeconds");
String sessionMapNameValue = attributes.getString("sessionMapName");
if (StringUtils.hasText(sessionMapNameValue))
this.sessionMapName = sessionMapNameValue;

this.flushMode = attributes.getEnum("flushMode");
this.saveMode = attributes.getEnum("saveMode");
}

/** */
private IgniteIndexedSessionRepository createIgniteIndexedSessionRepository() {
IgniteIndexedSessionRepository sessionRepository = new IgniteIndexedSessionRepository(this.ignite);
sessionRepository.setApplicationEventPublisher(this.applicationEventPublisher);
if (this.indexResolver != null)
sessionRepository.setIndexResolver(this.indexResolver);

if (StringUtils.hasText(this.sessionMapName))
sessionRepository.setSessionMapName(this.sessionMapName);

sessionRepository.setDefaultMaxInactiveInterval(this.maxInactiveIntervalInSeconds);
sessionRepository.setFlushMode(this.flushMode);
sessionRepository.setSaveMode(this.saveMode);
this.sessionRepositoryCustomizers
.forEach((sessionRepositoryCustomizer) -> sessionRepositoryCustomizer.customize(sessionRepository));
return sessionRepository;
}
}

0 comments on commit fbaf38c

Please sign in to comment.