/**
* 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.sql.server;
import com.foundationdb.ais.model.Routine;
import java.util.ArrayDeque;
import java.util.Deque;
public class ServerCallContextStack
{
private final Deque<Entry> stack = new ArrayDeque<>();
private final Deque<ServerSessionBase> callees = new ArrayDeque<>();
private ServerTransaction sharedTransaction;
private boolean firstCalleeNested;
private ServerCallContextStack() {
}
public static class Entry {
private ServerQueryContext context;
private ServerRoutineInvocation invocation;
private Entry(ServerQueryContext context, ServerRoutineInvocation invocation) {
this.context = context;
this.invocation = invocation;
}
public ServerQueryContext getContext() {
return context;
}
public ServerRoutineInvocation getInvocation() {
return invocation;
}
public Routine getRoutine() {
return invocation.getRoutine();
}
}
private static final ThreadLocal<ServerCallContextStack> tl = new ThreadLocal<ServerCallContextStack>() {
@Override
protected ServerCallContextStack initialValue() {
return new ServerCallContextStack();
}
};
public static ServerCallContextStack get() {
return tl.get();
}
/** Convenience for use by Routines. */
public static ServerQueryContext getCallingContext() {
return get().current().getContext();
}
public Entry current() {
return stack.peekLast();
}
public void push(ServerQueryContext context, ServerRoutineInvocation invocation) {
stack.addLast(new Entry(context, invocation));
}
public void pop(ServerQueryContext context, ServerRoutineInvocation invocation,
boolean success) {
Entry last = stack.removeLast();
assert (last.getContext() == context);
if (stack.isEmpty()) {
if (firstCalleeNested) {
// Because ResultSets can be returned while still open, we
// cannot close everything down until after the last call.
while (!callees.isEmpty()) {
ServerSessionBase callee = callees.pop();
boolean active = callee.endCall(context, invocation, true, success);
assert !active : callee;
}
if (sharedTransaction != null) {
// We took over transaction from a nested call.
if (success) {
sharedTransaction.commit();
}
else {
sharedTransaction.rollback();
}
sharedTransaction = null;
}
}
else {
// This is the case where an embedded connection was made by
// Java code, so there's no easy way to track its end-of-live.
callees.clear();
assert (sharedTransaction == null);
}
}
else {
while (true) {
ServerSessionBase callee = callees.peek();
if (callee == null) break;
boolean active = callee.endCall(context, invocation, false, success);
if (active) {
// If something is still open from this one, its transaction
// will need to be used by any subsequent ones, too.
if (sharedTransaction == null) {
sharedTransaction = callee.transaction;
}
break;
}
callees.pop();
}
}
}
public void addCallee(ServerSessionBase callee) {
if (callees.isEmpty()) {
firstCalleeNested = !stack.isEmpty();
}
callees.push(callee);
}
public void endCallee(ServerSessionBase callee) {
if (stack.isEmpty() && !firstCalleeNested) {
callees.clear();
assert (sharedTransaction == null);
}
}
public ServerSessionBase currentCallee() {
return callees.peek();
}
public ServerTransaction getSharedTransaction() {
return sharedTransaction;
}
}