/**
* Copyright (C) 2009-2013 FoundationDB, LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.foundationdb.server.service.session;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
public final class Session implements AutoCloseable
{
private final static long UNSET_NANOS = -1;
private final static AtomicLong idGenerator = new AtomicLong(0);
private final Map<Key<?>,Object> map = new HashMap<>();
private final SessionEventListener listener;
private final long sessionId = idGenerator.getAndIncrement();
private volatile boolean cancelCurrentQuery = false;
private long startMarkerNanos = UNSET_NANOS;
private long timeoutAfterNanos = UNSET_NANOS;
public String toString()
{
return String.format("Session(%s)", sessionId);
}
Session(SessionEventListener listener) {
this.listener = listener;
}
public long sessionId() {
return sessionId;
}
public <T> T get(Session.Key<T> key) {
return cast(key, map.get(key));
}
public <T> T put(Session.Key<T> key, T item) {
return cast(key, map.put(key, item));
}
public <T> T remove(Key<T> key) {
return cast(key, map.remove(key));
}
public <K,V> V get(MapKey<K,V> mapKey, K key) {
Map<K,V> map = get( mapKey.asKey() );
if (map == null) {
return null;
}
return map.get(key);
}
public <K,V> V put(MapKey<K,V> mapKey, K key, V value) {
Map<K,V> map = get( mapKey.asKey() );
if (map == null) {
map = new HashMap<>();
put(mapKey.asKey(), map);
}
return map.put(key, value);
}
public <K,V> V remove(MapKey<K,V> mapKey, K key) {
Map<K,V> map = get( mapKey.asKey() );
if (map == null) {
return null;
}
return map.remove(key);
}
public <T> void push(StackKey<T> key, T item) {
Deque<T> deque = get( key.asKey() );
if (deque == null) {
deque = new ArrayDeque<>();
put(key.asKey(), deque);
}
deque.offerLast(item);
}
public <T> T pop(StackKey<T> key) {
Deque<T> deque = get( key.asKey() );
if (deque == null) {
return null;
}
return deque.pollLast();
}
public boolean isEmpty(StackKey<?> key) {
Deque<?> deque = get( key.asKey() );
return deque == null || deque.isEmpty();
}
// "unused" suppression: Key<T> is only used for type inference
// "unchecked" suppression: we know from the put methods that Object will be of type T
@SuppressWarnings({"unused", "unchecked"})
private static <T> T cast(Key<T> key, Object o) {
return (T) o;
}
@Override
public void close()
{
if (listener != null) {
listener.sessionClosing();
}
// For now do nothing to any cached resources.
// Later, we'll close any "resource" that is added to the session.
//
map.clear();
}
public void cancelCurrentQuery(boolean cancel)
{
cancelCurrentQuery = cancel;
}
public boolean isCurrentQueryCanceled()
{
return cancelCurrentQuery;
}
private void requireTimeoutAfterSet() {
if(!hasTimeoutAfterNanos()) {
throw new IllegalStateException("Timeout nanos not set");
}
}
public boolean hasTimeoutAfterNanos() {
return timeoutAfterNanos != UNSET_NANOS;
}
public long getElapsedMillis() {
requireTimeoutAfterSet();
return TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startMarkerNanos);
}
public long getRemainingNanosBeforeTimeout() {
requireTimeoutAfterSet();
return timeoutAfterNanos - System.nanoTime();
}
public void setTimeoutAfterMillis(long millis) {
if(millis < 0) {
this.startMarkerNanos = this.timeoutAfterNanos = UNSET_NANOS;
} else {
this.startMarkerNanos = System.nanoTime();
this.timeoutAfterNanos = startMarkerNanos + TimeUnit.MILLISECONDS.toNanos(millis);
}
}
@SuppressWarnings("unused") // for <T> parameter; it's only useful for compile-time checking
public static class Key<T> {
private final Class<?> owner;
private final String name;
/** Create a new Key with the given name. Looks up owning class, see {@link #named(String,Class)}. **/
public static <T> Key<T> named(String name) {
return new Key<>(name, 1);
}
/** Create a new Key with given name an class. This <i>must</i> be used for isolated classes, such as plugins */
public static <T> Key<T> named(String name, Class<?> owner) {
return new Key<>(name, owner);
}
private static Class<?> lookupOwner(int stackFramesToOwner) {
try {
return Class.forName(Thread.currentThread().getStackTrace()[stackFramesToOwner + 2].getClassName());
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
private Key(String name, int stackFramesToOwner) {
this(name, lookupOwner(stackFramesToOwner + 1));
}
private Key(String name, Class owner) {
this.name = String.format("%s<%s>", owner.getSimpleName(), name);
this.owner = owner;
}
@Override
public String toString() {
return name;
}
Class<?> getOwner() {
return owner;
}
}
public static final class MapKey<K,V> extends Key<Map<K,V>> {
public static <K,V> MapKey<K,V> mapNamed(String name) {
return new MapKey<>(name);
}
private MapKey(String name) {
super(name, 3);
}
Key<Map<K,V>> asKey() {
return this;
}
}
public static final class StackKey<T> extends Key<Deque<T>> {
public static <K> StackKey<K> stackNamed(String name) {
return new StackKey<>(name);
}
private StackKey(String name) {
super(name, 3);
}
public Key<Deque<T>> asKey() {
return this;
}
}
}