package io.vertx.ext.web.sstore;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.auth.VertxContextPRNG;
import io.vertx.ext.web.Session;
import io.vertx.ext.web.sstore.impl.SessionInternal;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiFunction;
import java.util.function.Function;
public abstract class AbstractSession implements Session, SessionInternal {
private static final char[] HEX = "0123456789abcdef".toCharArray();
private VertxContextPRNG prng;
private String id;
private long timeout;
private volatile Map<String, Object> data;
private long lastAccessed;
private int version;
protected void setId(String id) {
this.id = id;
}
protected void setTimeout(long timeout) {
this.timeout = timeout;
}
protected void setData(Map<String, Object> data) {
if (data != null) {
this.data = data;
this.crc = checksum();
}
}
protected void setData(JsonObject data) {
if (data != null) {
setData(data.getMap());
}
}
protected void setLastAccessed(long lastAccessed) {
this.lastAccessed = lastAccessed;
}
protected void setVersion(int version) {
this.version = version;
}
private boolean destroyed;
private boolean renewed;
private String oldId;
private int crc;
public AbstractSession() {
}
public AbstractSession(VertxContextPRNG random) {
this.prng = random;
}
public AbstractSession(VertxContextPRNG random, long timeout, int length) {
this.prng = random;
this.id = generateId(prng, length);
this.timeout = timeout;
this.lastAccessed = System.currentTimeMillis();
}
public void setPRNG(VertxContextPRNG prng) {
this.prng = prng;
}
@Override
public void flushed(boolean skipCrc) {
renewed = false;
if (oldId != null) {
if (!skipCrc) {
crc = checksum();
}
oldId = null;
}
}
@Override
public String id() {
return id;
}
@Override
public Session regenerateId() {
if (oldId == null) {
oldId = id;
}
id = generateId(prng, oldId.length() / 2);
renewed = true;
return this;
}
@Override
public long timeout() {
return timeout;
}
@Override
@SuppressWarnings("unchecked")
public <T> T get(String key) {
if (isEmpty()) {
return null;
}
Object obj = data().get(key);
return (T) obj;
}
@Override
public Session put(String key, Object obj) {
final Map<String, Object> data = data();
if (obj == null) {
data.remove(key);
} else {
data.put(key, obj);
}
return this;
}
@Override
public Session putIfAbsent(String key, Object obj) {
data()
.putIfAbsent(key, obj);
return this;
}
@Override
public Session computeIfAbsent(String key, Function<String, Object> mappingFunction) {
data()
.computeIfAbsent(key, mappingFunction);
return this;
}
@Override
@SuppressWarnings("unchecked")
public <T> T remove(String key) {
if (isEmpty()) {
return null;
}
Object obj = data().remove(key);
return (T) obj;
}
@Override
public Map<String, Object> data() {
if (data == null) {
synchronized (this) {
if (data == null) {
data = new ConcurrentHashMap<>();
if (destroyed) {
regenerateId();
destroyed = false;
}
}
}
}
return data;
}
@Override
public boolean isEmpty() {
return data == null || data.size() == 0;
}
@Override
public long lastAccessed() {
return lastAccessed;
}
@Override
public void setAccessed() {
this.lastAccessed = System.currentTimeMillis();
}
@Override
public void destroy() {
synchronized (this) {
destroyed = true;
data = null;
}
}
@Override
public boolean isDestroyed() {
return destroyed;
}
@Override
public boolean isRegenerated() {
return renewed;
}
@Override
public String oldId() {
return oldId;
}
public int version() {
return version;
}
public void incrementVersion() {
int old = this.crc;
crc = checksum();
if (this.crc != old) {
++version;
}
}
private static String generateId(VertxContextPRNG rng, int length) {
final byte[] bytes = new byte[length];
rng.nextBytes(bytes);
final char[] hex = new char[length * 2];
for (int j = 0; j < length; j++) {
int v = bytes[j] & 0xFF;
hex[j * 2] = HEX[v >>> 4];
hex[j * 2 + 1] = HEX[v & 0x0F];
}
return new String(hex);
}
protected int crc() {
return crc;
}
protected int checksum() {
if (isEmpty()) {
return 0x0000;
} else {
int result = 1;
for (Map.Entry<String, Object> kv : data.entrySet()) {
String key = kv.getKey();
result = 31 * result + key.hashCode();
Object value = kv.getValue();
if (value != null) {
result = 31 * result + value.hashCode();
}
}
return result;
}
}
}