/* * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.web.server.session; import java.io.Serializable; import java.time.Clock; import java.time.Duration; import java.time.Instant; import java.util.Map; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; import reactor.core.publisher.Mono; import org.springframework.util.Assert; /** * Default implementation of {@link org.springframework.web.server.WebSession}. * * @author Rossen Stoyanchev * @since 5.0 */ public class DefaultWebSession implements ConfigurableWebSession, Serializable { private static final long serialVersionUID = -3567697426432961630L; private final String id; private final Map<String, Object> attributes; private final Clock clock; private final Instant creationTime; private volatile Instant lastAccessTime; private volatile Duration maxIdleTime; private AtomicReference<State> state = new AtomicReference<>(); private volatile transient Supplier<Mono<Void>> saveOperation; /** * Constructor to create a new session. * @param id the session id * @param clock for access to current time */ public DefaultWebSession(String id, Clock clock) { Assert.notNull(id, "'id' is required."); Assert.notNull(clock, "'clock' is required."); this.id = id; this.clock = clock; this.attributes = new ConcurrentHashMap<>(); this.creationTime = Instant.now(clock); this.lastAccessTime = this.creationTime; this.maxIdleTime = Duration.ofMinutes(30); this.state.set(State.NEW); } /** * Constructor to load existing session. * @param id the session id * @param attributes the attributes of the session * @param clock for access to current time * @param creationTime the creation time * @param lastAccessTime the last access time * @param maxIdleTime the configured maximum session idle time */ public DefaultWebSession(String id, Map<String, Object> attributes, Clock clock, Instant creationTime, Instant lastAccessTime, Duration maxIdleTime) { Assert.notNull(id, "'id' is required."); Assert.notNull(clock, "'clock' is required."); this.id = id; this.attributes = new ConcurrentHashMap<>(attributes); this.clock = clock; this.creationTime = creationTime; this.lastAccessTime = lastAccessTime; this.maxIdleTime = maxIdleTime; this.state.set(State.STARTED); } @Override public String getId() { return this.id; } @Override public Map<String, Object> getAttributes() { return this.attributes; } @Override @SuppressWarnings("unchecked") public <T> Optional<T> getAttribute(String name) { return Optional.ofNullable((T) this.attributes.get(name)); } @Override public Instant getCreationTime() { return this.creationTime; } @Override public void setLastAccessTime(Instant lastAccessTime) { this.lastAccessTime = lastAccessTime; } @Override public Instant getLastAccessTime() { return this.lastAccessTime; } /** * <p>By default this is set to 30 minutes. * @param maxIdleTime the max idle time */ @Override public void setMaxIdleTime(Duration maxIdleTime) { this.maxIdleTime = maxIdleTime; } @Override public Duration getMaxIdleTime() { return this.maxIdleTime; } @Override public void setSaveOperation(Supplier<Mono<Void>> saveOperation) { Assert.notNull(saveOperation, "'saveOperation' is required."); this.saveOperation = saveOperation; } protected Supplier<Mono<Void>> getSaveOperation() { return this.saveOperation; } @Override public void start() { this.state.compareAndSet(State.NEW, State.STARTED); } @Override public boolean isStarted() { State value = this.state.get(); return (State.STARTED.equals(value) || (State.NEW.equals(value) && !getAttributes().isEmpty())); } @Override public Mono<Void> save() { return this.saveOperation.get(); } @Override public boolean isExpired() { return (isStarted() && !this.maxIdleTime.isNegative() && Instant.now(this.clock).minus(this.maxIdleTime).isAfter(this.lastAccessTime)); } private enum State { NEW, STARTED } }