/*
* 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 }
}