/*
 * JBoss, Home of Professional Open Source.
 * See the COPYRIGHT.txt file distributed with this work for information
 * regarding copyright ownership.  Some portions may be licensed
 * to Red Hat, Inc. under one or more contributor license agreements.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library 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
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301 USA.
 * Modified by
 * @author  Data Virtuality GmbH
 * @date    $Date$
 *  
 */
// https://issues.jboss.org/browse/TEIID-2153
// https://issues.jboss.org/browse/TEIID-2171
// https://issues.jboss.org/browse/TEIID-2359
// https://issues.jboss.org/browse/TEIID-2149
// https://issues.jboss.org/browse/TEIID-2358
// https://issues.jboss.org/browse/TEIID-2410
// https://issues.jboss.org/browse/TEIID-2271
// https://issues.jboss.org/browse/TEIID-2467
// https://issues.jboss.org/browse/TEIID-2429
// https://issues.jboss.org/browse/TEIID-2363
// https://issues.jboss.org/browse/TEIID-2160
// https://issues.jboss.org/browse/TEIID-2461
// https://issues.jboss.org/browse/TEIID-2797
// https://issues.jboss.org/browse/TEIID-2326
// https://issues.jboss.org/browse/TEIID-1557
// https://issues.jboss.org/browse/TEIID-3781
// https://issues.jboss.org/browse/TEIID-2255
// https://issues.jboss.org/browse/TEIID-2085
// https://issues.jboss.org/browse/TEIID-2311
// https://issues.jboss.org/browse/TEIID-2897
// https://issues.jboss.org/browse/TEIID-3390
// https://issues.jboss.org/browse/TEIID-5445
// https://issues.jboss.org/browse/TEIID-5532
// https://issues.jboss.org/browse/TEIID-2476
// https://issues.jboss.org/browse/TEIID-2381
// https://issues.jboss.org/browse/TEIID-3491
// https://issues.jboss.org/browse/TEIID-3390
// https://issues.jboss.org/browse/TEIID-1092
// https://issues.jboss.org/browse/TEIID-2507
// https://issues.jboss.org/browse/TEIID-5427
// https://issues.redhat.com/browse/TEIID-3300
// https://issues.redhat.com/browse/TEIID-2577
package org.teiid.query.util;

import com.datavirtuality.dv.core.interfaces.DVCaller;
import org.teiid.CommandListener;
import org.teiid.adminapi.DataPolicy;
import org.teiid.adminapi.impl.SessionMetadata;
import org.teiid.adminapi.impl.VDBMetaData;
import org.teiid.api.exception.query.QueryProcessingException;
import org.teiid.common.buffer.BufferManager;
import org.teiid.common.buffer.TupleSource;
import org.teiid.core.TeiidComponentException;
import org.teiid.core.TeiidException;
import org.teiid.core.types.ClobImpl;
import org.teiid.core.types.InputStreamFactory;
import org.teiid.core.util.ArgCheck;
import org.teiid.core.util.ExecutorUtils;
import org.teiid.core.util.LRUCache;
import org.teiid.dqp.internal.process.*;
import org.teiid.dqp.internal.process.TupleSourceCache;
import org.teiid.dqp.internal.process.SessionAwareCache.CacheID;
import org.teiid.dqp.message.RequestID;
import org.teiid.dqp.service.TransactionContext;
import org.teiid.dqp.service.TransactionService;
import org.teiid.jdbc.ConnectionImpl;
import org.teiid.jdbc.EmbeddedProfile;
import org.teiid.jdbc.TeiidConnection;
import org.teiid.jdbc.TeiidSQLException;
import org.teiid.logging.LogConstants;
import org.teiid.logging.LogManager;
import org.teiid.metadata.AbstractMetadataRecord;
import org.teiid.metadata.FunctionMethod.Determinism;
import org.teiid.net.ServerConnection;
import org.teiid.query.QueryPlugin;
import org.teiid.query.metadata.QueryMetadataInterface;
import org.teiid.query.metadata.TempMetadataAdapter;
import org.teiid.query.metadata.TempMetadataID;
import org.teiid.query.metadata.TempMetadataStore;
import org.teiid.query.optimizer.relational.PlanToProcessConverter;
import org.teiid.query.optimizer.relational.plantree.PlanNode;
import org.teiid.query.parser.ParseInfo;
import org.teiid.query.processor.QueryProcessor;
import org.teiid.query.sql.lang.Command;
import org.teiid.query.sql.symbol.ElementSymbol;
import org.teiid.query.sql.symbol.Expression;
import org.teiid.query.sql.symbol.GroupSymbol;
import org.teiid.query.sql.util.VariableContext;
import org.teiid.query.tempdata.GlobalTableStore;
import org.teiid.query.tempdata.TempTableStore;
import org.teiid.translator.ReusableExecution;

import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.lang.ref.WeakReference;
import java.sql.Clob;
import java.sql.SQLException;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.AbstractMap.SimpleEntry;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.regex.Pattern;
import org.wildfly.security.auth.server.SecurityIdentity;

/** 
 * Defines the context that a command is processing in.  For example, this defines
 * who is processing the command and why.  Also, this class (or subclasses) provide
 * a means to pass context-specific information between users of the query processor
 * framework.
 */
public class CommandContext implements Cloneable, org.teiid.CommandContext {
	private static final ThreadLocal<LinkedList<CommandContext>> threadLocalContext =
			ThreadLocal.withInitial(LinkedList::new);

	private static class VDBState {
	    private String vdbName = ""; //$NON-NLS-1$
	    private int vdbVersion;
	    private QueryMetadataInterface metadata; 
	    private GlobalTableStore globalTables;
		private ClassLoader classLoader;
		private DQPWorkContext dqpWorkContext;
	}
	
	private static class GlobalState {
		private WeakReference<RequestWorkItem> processorID;
	    
	    /** Identify a group of related commands, which typically get cleaned up together */
	    private String connectionID;

	    private int processorBatchSize = BufferManager.DEFAULT_PROCESSOR_BATCH_SIZE;
	    
	    private Serializable commandPayload;
	    
	    /** Indicate whether statistics should be collected for relational node processing*/
	    private boolean collectNodeStatistics;
	    
	    private Random random = null;
	    
	    private TimeZone timezone = TimeZone.getDefault();
	    
	    private PlanToProcessConverter planToProcessConverter;
	    
	    private QueryProcessor.ProcessorFactory queryProcessorFactory;

	    private Set<String> groups;
		private Map<String, String> aliasMapping;
	    
	    private long timeSliceEnd = Long.MAX_VALUE;
	    
	    private long timeoutEnd = Long.MAX_VALUE;
	    
	    private boolean validateXML;
	    	    
	    private BufferManager bufferManager;

	    private SessionAwareCache<PreparedPlan> planCache;
	    
	    private boolean resultSetCacheEnabled = true;
	    
	    private int userRequestSourceConcurrency;
	    private HashSet<Object> dataObjects;

	    private RequestID requestId;
	    				
		private TransactionContext transactionContext;
		private TransactionService transactionService;
		private Executor executor = ExecutorUtils.getDirectExecutor();
		Map<String, ReusableExecution<?>> reusableExecutions;
	    Set<CommandListener> commandListeners = null;
	    private LRUCache<String, DecimalFormat> decimalFormatCache;
		private LRUCache<String, SimpleDateFormat> dateFormatCache;
		private LRUCache<Entry<String,Integer>, Pattern> patternCache;
		private AtomicLong reuseCount = null;
	    private List<Exception> warnings = null;

	    private Map<String, String> dvoptions = new HashMap<String, String>();
	    private String schema = null;

		private Options options = null;
		private boolean returnAutoGeneratedKeys;
		private GeneratedKeysImpl generatedKeys;
		private long reservedBuffers;
		 
		private AuthorizationValidator authorizationValidator;
		
		private Map<String, TupleSource> lookups;

		private Set<InputStreamFactory> created = Collections.newSetFromMap(new WeakHashMap<InputStreamFactory, Boolean>());
		
		private LRUCache<AbstractMetadataRecord, Boolean> accessible;

		public boolean parallel;
		
		private Thread processorThread;

		private volatile boolean requestInterrupted = false;
		private String callerUser;
		private Map<String, CommandContext> userCache = new HashMap<>(2);
	}

	private GlobalState globalState = new GlobalState();

	private CommandContext parentContext;
	private SessionMetadata session;
	private String userName;
	private boolean inherited;
	private VariableContext variableContext = new VariableContext();
    private TempTableStore tempTableStore;
    private LinkedList<String> recursionStack;
    private boolean nonBlocking;
    private HashSet<Object> planningObjects;
    private HashSet<Object> dataObjects = this.globalState.dataObjects;
    private TupleSourceCache tupleSourceCache;
    private VDBState vdbState = new VDBState();
    private AtomicBoolean cancelled = new AtomicBoolean();
    private AtomicBoolean parentCancelled;
	private Determinism[] determinismLevel = new Determinism[] {Determinism.DETERMINISTIC};
	private int processorTimeslice = Integer.MAX_VALUE;

	private boolean atomicBlock;

	private Collection<TempMetadataID> accessed;
    
    // EXTENSION
 	// ISSUE: DVCORE-1107
 	// FIXED BY: Salvatore Raunich
 	// DATE: 2014.03.27
    private Object dvExt1 = null;
    
    public void setDvExt1(Object dvExt1) {
    	this.dvExt1 = dvExt1;
    }
    
    public Object getAndRemoveDvExt1() {
    	Object result = dvExt1;
    	dvExt1 = null;
    	return result;
    } 	
    // END EXTENSION
    
    private Set<String> dvExt2 = new HashSet<String>();
    
    public void addDvExt2(Collection<String> dvExt2) {
    	this.dvExt2.addAll(dvExt2);
    }
    
    public void addEntryToDvExt2(String entry) {
    	this.dvExt2.add(entry);
    }
    
    public Set<String> getDvExt2() {
    	return dvExt2;
    }

    public List<String> getDvExt2AsList() {
    	return new ArrayList<String>(dvExt2);
    }	

    private Set<String> usedMattableIds = new HashSet<String>();

    public void addUsedMattableIds(Collection<String> usedMattableIds) {
    	this.usedMattableIds.addAll(usedMattableIds);
    }

    public Set<String> getUsedMattableIds() {
    	return usedMattableIds;
    }	

	private boolean dvExt3 = false;
	
	public boolean isDvExt3() {
		return dvExt3;
	}

	public void setDvExt3(boolean dvExt3) {
		this.dvExt3 = dvExt3;
	}

	private boolean dataLineage;
	public boolean isDataLineage() {
		return dataLineage;
	}

	public void setDataLineage(boolean dataLineage) {
		this.dataLineage = dataLineage;
	}

	private boolean cte;
	public boolean isCte() {
		return cte;
	}

	public void setCte(boolean cte) {
		this.cte = cte;
	}

	private boolean runDataLineage;
	public boolean isRunDataLineage() {
		return runDataLineage;
	}

	public void setRunDataLineage(boolean runDataLineage) {
		this.runDataLineage = runDataLineage;
	}

	private boolean dataLineageSet;
	public boolean isDataLineageSkip() {
		return dataLineageSet;
	}

	public void setDataLineageSkip(boolean dataLineageSkip) {
		this.dataLineageSet = dataLineageSkip;
	}

	boolean allowXmlTextNodeWhitespace;
	public boolean isAllowXmlTextNodeWhitespace() {
		return allowXmlTextNodeWhitespace;
	}

	public void setAllowXmlTextNodeWhitespace(boolean allowXmlTextNodeWhitespace) {
		this.allowXmlTextNodeWhitespace = allowXmlTextNodeWhitespace;
	}

	private PlanNode planNode;
	public PlanNode getPlanNode() {
		return planNode;
	}

	public void setPlanNode(PlanNode planNode) {
		this.planNode = planNode;
	}

	private Map<GroupSymbol, List<PlanNode>> withPlanNodes = new HashMap<>();
	public Map<GroupSymbol, List<PlanNode>> getWithPlanNodes() {
		return withPlanNodes;
	}

	public List<PlanNode> getWithPlanNodesByGroup(GroupSymbol groupSymbol) {
		return this.withPlanNodes.get(groupSymbol);
	}

	public void setWithPlanNodes(GroupSymbol groupSymbol, List<PlanNode> planNodes) {
		this.withPlanNodes.put(groupSymbol, planNodes);
	}

	private Set<Object> selIntoStatements = new HashSet<Object>();
	
	public void addAllSelIntoStatement(Set<Object> selIntoStatements) {
		this.selIntoStatements.addAll(selIntoStatements);
	}
	
	public void addSelIntoStatement(Object selIntoStatement) {
		this.selIntoStatements.add(selIntoStatement);
	}
	
	public Set<Object> getSelIntoStatements() {
		return selIntoStatements;
	}

	private boolean allowResourcesByParent;

	@Override
	public boolean isAllowResourcesByParent() {
		return allowResourcesByParent;
	}

	@Override
	public void setAllowResourcesByParent(boolean allowResourcesByParent) {
		this.allowResourcesByParent = allowResourcesByParent;
	}

	/**
     * Construct a new context.
     */
    public CommandContext(String connectionID, String userName, Serializable commandPayload, 
    		String vdbName, int vdbVersion, boolean collectNodeStatistics) {
        setConnectionID(connectionID);
        setUserName(userName);
        setCommandPayload(commandPayload);
        setVdbName(vdbName);
        setVdbVersion(vdbVersion);  
        setCollectNodeStatistics(collectNodeStatistics);
    }

    /**
     * Construct a new context.
     */
    public CommandContext(Object processorID, String connectionID, String userName,
        String vdbName, int vdbVersion) {

        this(connectionID, userName, null, vdbName, vdbVersion, 
            false);            
             
    }

    public CommandContext() {
    }
    
    private CommandContext(GlobalState state) {
    	this.globalState = state;
    	this.dataObjects = this.globalState.dataObjects;
    }
    
    public Determinism getDeterminismLevel() {
		return determinismLevel[0];
	}
    
    public Determinism resetDeterminismLevel(boolean detach) {
		Determinism result = determinismLevel[0];
    	if (detach) {
			determinismLevel = new Determinism[1];
		}
		determinismLevel[0] = Determinism.DETERMINISTIC;
		return result;
    }

	public Determinism resetDeterminismLevel() {
		return resetDeterminismLevel(false);
	}
    
    public void setDeterminismLevel(Determinism level) {
		if (determinismLevel[0] == null || level.compareTo(determinismLevel[0]) < 0) {
			determinismLevel[0] = level;
    	}
    }
    
    /**
     * @return
     */
    public RequestWorkItem getWorkItem() {
	    if (globalState.processorID == null) {
		    return null;
		}
        return globalState.processorID.get();
    }

    /**
     * @param object
     */
    public void setWorkItem(RequestWorkItem object) {
        ArgCheck.isNotNull(object);
        globalState.processorID = new WeakReference<RequestWorkItem>(object);
    }

    public CommandContext clone() {
    	CommandContext clone = new CommandContext(this.globalState);
    	clone.variableContext = this.variableContext;
    	clone.userName = this.userName;
    	clone.parentContext = this.parentContext;
    	clone.session = this.session;
		clone.inherited = this.inherited;
    	clone.tempTableStore = this.tempTableStore;
    	if (this.recursionStack != null) {
            clone.recursionStack = new LinkedList<String>(this.recursionStack);
        }
    	clone.setNonBlocking(this.nonBlocking);
    	clone.tupleSourceCache = this.tupleSourceCache;
    	clone.vdbState = this.vdbState;
		clone.determinismLevel = this.determinismLevel;
		if (this.parentCancelled != null) {
            clone.parentCancelled = this.parentCancelled;
	    } else {
	        clone.parentCancelled = this.cancelled;
	    }
    	// shallow copy of select into statements
    	clone.addAllSelIntoStatement(getSelIntoStatements());
		clone.setAllowXmlTextNodeWhitespace(isAllowXmlTextNodeWhitespace());
		clone.setDataLineageSkip(this.isDataLineageSkip());
	    clone.withPlanNodes = this.withPlanNodes;
		clone.replace.set(this.replace.get());
		return clone;
    }
    
    public void setNewVDBState(DQPWorkContext newWorkContext) {
    	this.vdbState = new VDBState();
    	VDBMetaData vdb = newWorkContext.getVDB();
		GlobalTableStore actualGlobalStore = vdb.getAttachment(GlobalTableStore.class);
		this.vdbState.globalTables = actualGlobalStore;
		this.session = newWorkContext.getSession();
		this.vdbState.classLoader = vdb.getAttachment(ClassLoader.class);
		this.vdbState.vdbName = vdb.getName();
		this.vdbState.vdbVersion = vdb.getVersion();
		this.vdbState.dqpWorkContext = newWorkContext;
		//we still have to wrap in a temp, but we don't need the session data
		this.vdbState.metadata = new TempMetadataAdapter(vdb.getAttachment(QueryMetadataInterface.class), new TempMetadataStore());
    }
    
    public String toString() {
        return "CommandContext: {" + globalState.processorID + "} " + getContextPath(); //$NON-NLS-1$
    }
    
    public String getConnectionId() {
        return globalState.connectionID;
    }
    
    public String getConnectionID() {
        return globalState.connectionID;
    }

    public String getUserName() {
        return userName;
    }

    public String getVdbName() {
        return vdbState.vdbName;
    }

    public int getVdbVersion() {
        return vdbState.vdbVersion;
    }

    /**
     * Sets the connectionID.
     * @param connectionID The connectionID to set
     */
    public void setConnectionID(String connectionID) {
        this.globalState.connectionID = connectionID;
    }

    /**
     * Sets the userName.
     * @param userName The userName to set
     */
    public void setUserName(String userName) {
        this.userName = userName;
    }

    /**
     * Sets the vdbName.
     * @param vdbName The vdbName to set
     */
    public void setVdbName(String vdbName) {
        this.vdbState.vdbName = vdbName;
    }

    /**
     * Sets the vdbVersion.
     * @param vdbVersion The vdbVersion to set
     */
    public void setVdbVersion(int vdbVersion) {
        this.vdbState.vdbVersion = vdbVersion;
    }

    public Serializable getCommandPayload() {
        return this.globalState.commandPayload;
    }
    public void setCommandPayload(Serializable commandPayload) {
        this.globalState.commandPayload = commandPayload;
    }    
    
    /** 
     * @param collectNodeStatistics The collectNodeStatistics to set.
     * @since 4.2
     */
    public void setCollectNodeStatistics(boolean collectNodeStatistics) {
        this.globalState.collectNodeStatistics = collectNodeStatistics;
    }
    
    public boolean getCollectNodeStatistics() {
        return this.globalState.collectNodeStatistics;
    }
    
    @Override
    public int getProcessorBatchSize() {
        return this.globalState.processorBatchSize;
    }
    
    public int getProcessorBatchSize(List<Expression> schema) {
    	return this.globalState.bufferManager.getProcessorBatchSize(schema);
    }
    
    public void setProcessorBatchSize(int processorBatchSize) {
        this.globalState.processorBatchSize = processorBatchSize;
    }
    
    public double getNextRand() {
        if (globalState.random == null) {
        	globalState.random = new Random();
        }
        return globalState.random.nextDouble();
    }
    
    public double getNextRand(long seed) {
        if (globalState.random == null) {
        	globalState.random = new Random();
        }
        globalState.random.setSeed(seed);
        return globalState.random.nextDouble();
    }
    
    void setRandom(Random random) {
        this.globalState.random = random;
    }

    public void pushCall(String value) throws QueryProcessingException {
        if (recursionStack == null) {
            recursionStack = new LinkedList<String>();
        } else if (recursionStack.contains(value)) {
			 throw new QueryProcessingException(QueryPlugin.Event.TEIID30347, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID30347, value));
        }
        
        recursionStack.push(value);
    }
    
    public int getCallStackDepth() {
    	if (this.recursionStack == null) {
    		return 0;
    	}
    	return this.recursionStack.size();
    }

	public void popCall() {
		if ((recursionStack != null) && (recursionStack.size() > 0)) {
			recursionStack.pop();
		}
	}

    /** 
     * @param securityFunctionEvaluator The securityFunctionEvaluator to set.
     */
    public void setAuthoriziationValidator(AuthorizationValidator authorizationValidator) {
        this.globalState.authorizationValidator = authorizationValidator;
    }

	public TempTableStore getTempTableStore() {
		return tempTableStore;
	}

	public void setTempTableStore(TempTableStore tempTableStore) {
		this.tempTableStore = tempTableStore;
	}
	
	public TimeZone getServerTimeZone() {
		return globalState.timezone;
	}

	public void setPlanToProcessConverter(PlanToProcessConverter planToProcessConverter) {
		this.globalState.planToProcessConverter = planToProcessConverter;
	}

	public PlanToProcessConverter getPlanToProcessConverter() {
		return globalState.planToProcessConverter;
	}
	
	public QueryProcessor.ProcessorFactory getQueryProcessorFactory() {
		return this.globalState.queryProcessorFactory;
	}

	public void setQueryProcessorFactory(QueryProcessor.ProcessorFactory queryProcessorFactory) {
		this.globalState.queryProcessorFactory = queryProcessorFactory;
	}
	
	public VariableContext getVariableContext() {
		return variableContext;
	}
	
	public void setVariableContext(VariableContext variableContext) {
		this.variableContext = variableContext;
	}
	
	public void pushVariableContext(VariableContext toPush) {
		toPush.setParentContext(this.variableContext);
		this.variableContext = toPush;
	}
	
	public Object getFromContext(Expression expression) throws TeiidComponentException {
		if (variableContext == null || !(expression instanceof ElementSymbol)) {
			throw new TeiidComponentException(QueryPlugin.Event.TEIID30328, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID30328, expression, QueryPlugin.Util.getString("Evaluator.no_value"))); //$NON-NLS-1$
		}
		Object value = variableContext.getValue((ElementSymbol)expression);
		if (value == null && !variableContext.containsVariable((ElementSymbol)expression)) {
			throw new TeiidComponentException(QueryPlugin.Event.TEIID30328, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID30328, expression, QueryPlugin.Util.getString("Evaluator.no_value"))); //$NON-NLS-1$
		}
		return value;
	}
	
	public Set<String> getGroups() {
		if (globalState.groups == null) {
			globalState.groups = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
		}
		return globalState.groups;
	}

	public Map<String, String> getAliasMapping() {
		if (globalState.aliasMapping == null) {
			globalState.aliasMapping = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER);
		}
		return globalState.aliasMapping;
	}
	
	public long getTimeSliceEnd() {
		return globalState.timeSliceEnd;
	}
	
	public long getTimeoutEnd() {
		return globalState.timeoutEnd;
	}
	
	public void setTimeSliceEnd(long timeSliceEnd) {
		globalState.timeSliceEnd = timeSliceEnd;
	}

	/**
	 * Sets timeslice from 'now' with value of 'processorTimeslice'.<br>
	 * Timeslice provides course scheduling of long running processor plans.<br>
	 * Plans whose execution exceed a time slice will be re-queued for additional<br>
	 * processing to allow for other plans to be initiated.<br>
	 * The time slice is from the perspective of the engine processing thread.<br>
	 * This value is not honored exactly as the plan may not be at a re-startable point<br>
	 * when the time slice expires.<br>
	 * This is not a replacement for the thread scheduling performed by Java and the operating system, rather it just ensures<br>
	 * that Teiid allows other work to be started if the current set of active plans includes long running queries
	 * @see DVCORE-8554
	 */
	public void updateTimeSliceEnd() {
		setTimeSliceEnd(System.currentTimeMillis() + processorTimeslice);
	}


	/**
	 * Sets general size of timeslice for "course scheduling of long running processor plans".
	 */
	public void setProcessorTimeslice(int processorTimeSlice) {
		this.processorTimeslice = processorTimeSlice;
	}

	public void setProcessorThread(Thread processorThread) {
		globalState.processorThread = processorThread;
	}

	public Thread getProcessorThread() {
		return this.globalState.processorThread;
	}

	public void requestInterrupted() {
		this.globalState.requestInterrupted = true;
	}

	public boolean isRequestInterrupted() {
		return this.globalState.requestInterrupted;
	}

	public void throwIfRequestIsInterrupted() throws InterruptedException {
		if (isRequestInterrupted()) {
			throw new InterruptedException(QueryPlugin.Util.gs(QueryPlugin.Event.DVCORE301209, getRequestId()));
		}
	}
	
	public void setTimeoutEnd(long timeoutEnd) {
		globalState.timeoutEnd = timeoutEnd;
	}

	public void setMetadata(QueryMetadataInterface metadata) {
		vdbState.metadata = metadata;
	}
	
	public QueryMetadataInterface getMetadata() {
		return vdbState.metadata;
	}
    
    public void setValidateXML(boolean validateXML) {
    	globalState.validateXML = validateXML;
	}
    
    public boolean validateXML() {
		return globalState.validateXML;
	}
    
    public BufferManager getBufferManager() {
    	return globalState.bufferManager;
    }
    
    public void setBufferManager(BufferManager bm) {
    	globalState.bufferManager = bm;
    }
    
    public GlobalTableStore getGlobalTableStore() {
    	return vdbState.globalTables;
    }
    
    public void setGlobalTableStore(GlobalTableStore tempTableStore) {
    	vdbState.globalTables = tempTableStore;
    }
    
    public boolean isNonBlocking() {
		return nonBlocking;
	}
    
    public void setNonBlocking(boolean nonBlocking) {
		this.nonBlocking = nonBlocking;
	}
    
    public void setPreparedPlanCache(SessionAwareCache<PreparedPlan> cache) {
    	this.globalState.planCache = cache;
    }
    
    public PreparedPlan getPlan(String key) {
    	if (this.globalState.planCache == null) {
    		return null;
    	}
    	CacheID id = new CacheID(new ParseInfo(), key, getVdbName(), getVdbVersion(), getConnectionId(), getUserName());
    	PreparedPlan pp = this.globalState.planCache.get(id);
    	if (pp != null) {
    		if (id.getSessionId() != null) {
    			setDeterminismLevel(Determinism.USER_DETERMINISTIC);
    		} else if (id.getUserName() != null) {
    			setDeterminismLevel(Determinism.SESSION_DETERMINISTIC);
    		}
        	return pp;
    	}
    	return null;
    }
    
    public void putPlan(String key, PreparedPlan plan, Determinism determinismLevel) {
    	if (this.globalState.planCache == null) {
    		return;
    	}
    	CacheID id = new CacheID(new ParseInfo(), key, getVdbName(), getVdbVersion(), getConnectionId(), getUserName());
    	this.globalState.planCache.put(id, determinismLevel, plan, null);
    }
    
    public boolean isResultSetCacheEnabled() {
		return globalState.resultSetCacheEnabled;
	}
    
    public void setResultSetCacheEnabled(boolean resultSetCacheEnabled) {
		this.globalState.resultSetCacheEnabled = resultSetCacheEnabled;
	}
    
	public int getUserRequestSourceConcurrency() {
		return this.globalState.userRequestSourceConcurrency;
	}
	
	public void setUserRequestSourceConcurrency(int userRequestSourceConcurrency) {
		this.globalState.userRequestSourceConcurrency = userRequestSourceConcurrency;
	}
	
	@Override
	public SecurityIdentity getSubject() {
		return this.session == null ? null : this.session.getSubject();
	}
	
	public void accessedPlanningObject(Object id) {
		if (this.planningObjects == null) {
			this.planningObjects = new HashSet<Object>();
		}
		this.planningObjects.add(id);
	}
	
	public Set<Object> getPlanningObjects() {
		if (this.planningObjects == null) {
			return Collections.emptySet();
		}
		return planningObjects;
	}
	
	public void accessedDataObject(Object id) {
		if (this.dataObjects != null) {
			this.dataObjects.add(id);
		}
	}
	
	public Set<Object> getDataObjects() {
		return dataObjects;
	}
	
	public void setDataObjects(HashSet<Object> dataObjectsAccessed) {
		this.dataObjects = dataObjectsAccessed;
	}

	public CommandContext getTopContext() {
		if (parentContext == null)
			return this;
		return parentContext.getTopContext();
	}

	public void setParentContext(CommandContext parentContext) {
		this.parentContext = parentContext;
	}

	@Override
	public SessionMetadata getSession() {
		return this.session;
	}
	
	public void setSession(SessionMetadata session) {
		if (this.session == null)
			this.globalState.callerUser = session.getUserName();
		this.session = session;
	}

	@Override
	public String getRequestId() {
		return this.globalState.requestId != null ? this.globalState.requestId.toString() : null;
	}

	public RequestID getRequestID() {
		return this.globalState.requestId;
	}
	
	public void setRequestId(RequestID requestId) {
		this.globalState.requestId = requestId;
	}
	
	public void setDQPWorkContext(DQPWorkContext workContext) {
		this.vdbState.dqpWorkContext = workContext;
	}
	
	@Override
	public Map<String, DataPolicy> getAllowedDataPolicies() {
		final DQPWorkContext wc = this.vdbState.dqpWorkContext;
		if (wc == null)
 			return null;
		if (inherited && session != null)
			return wc.getAllowedDataPolicies(session);
		else
			return wc.getAllowedDataPolicies();
	}

	public void setInherited(boolean inherited) {
		this.inherited = inherited;
	}

	@Override
	public VDBMetaData getVdb() {
		if (this.vdbState.dqpWorkContext == null) {
			return null;
		}
		return this.vdbState.dqpWorkContext.getVDB();
	}
	
	public DQPWorkContext getDQPWorkContext() {
		return this.vdbState.dqpWorkContext;
	}
	
	public TransactionContext getTransactionContext() {
		return globalState.transactionContext;
	}
	
	public void setTransactionContext(TransactionContext transactionContext) {
		globalState.transactionContext = transactionContext;
	}
	
	public TransactionService getTransactionServer() {
		return globalState.transactionService;
	}
	
	public void setTransactionService(TransactionService transactionService) {
		globalState.transactionService = transactionService;
	}
	
	public Executor getExecutor() {
		return this.globalState.executor;
	}

	/**
	 * Submit work that will notify the request work item of more work when complete
	 * @param callable
	 * @return
	 */
	public <V> Future<V> submit(Callable<V> callable) {
		if (getWorkItem() != null) {
			return getWorkItem().addRequestWork(callable);
		}
		return ForkJoinPool.commonPool().submit(callable);
	}
	
	public void setExecutor(Executor e) {
		this.globalState.executor = e;
	}
	
	public ReusableExecution<?> getReusableExecution(String nodeId) {
		synchronized (this.globalState) {
			if (this.globalState.reusableExecutions == null) {
				return null;
			}
			return this.globalState.reusableExecutions.get(nodeId);
		}
	}
	
	public void putReusableExecution(String nodeId, ReusableExecution<?> execution) {
		synchronized (this.globalState) {
			if (this.globalState.reusableExecutions == null) {
				this.globalState.reusableExecutions = new ConcurrentHashMap<String, ReusableExecution<?>>();
			}
			this.globalState.reusableExecutions.put(nodeId, execution);
		}
	}

	public void close() {
		synchronized (this.globalState) {
			if (this.globalState.reservedBuffers > 0) {
				long toRelease = this.globalState.reservedBuffers;
				this.globalState.reservedBuffers = 0;
				this.globalState.bufferManager.releaseOrphanedBuffers(toRelease);
			}
			if (this.globalState.reusableExecutions != null) {
	
				for (ReusableExecution<?> reusableExecution : this.globalState.reusableExecutions.values()) {
					try {
						reusableExecution.dispose();
					} catch (Exception e) {
						LogManager.logWarning(LogConstants.CTX_DQP, e, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID30030));
					}
				}
				this.globalState.reusableExecutions.clear();
			}
			if (this.globalState.commandListeners != null) {
				for (CommandListener listener : this.globalState.commandListeners) {
					try {
						listener.commandClosed(this);
					} catch (Exception e) {
						LogManager.logWarning(LogConstants.CTX_DQP, e, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID30031));
					}
				}
				this.globalState.commandListeners.clear();
			}
			if (this.globalState.lookups != null) {
				for (TupleSource ts : this.globalState.lookups.values()) {
					ts.closeSource();
				}
				this.globalState.lookups = null;
			}
			if (this.globalState.created != null) {
				for (InputStreamFactory isf : this.globalState.created) {
					try {
						isf.free();
					} catch (IOException e) {
					}
				}
				this.globalState.created.clear();
			}
		}
	}

	public static LinkedList<CommandContext> getThreadLocalContextList() {
		return threadLocalContext.get();
	}

	public static CommandContext getThreadLocalContext() {
		return threadLocalContext.get().peek();
	}
	
	public static void pushThreadLocalContext(CommandContext context) {
		threadLocalContext.get().push(context);
	}
	
	public static void popThreadLocalContext() {
		threadLocalContext.get().poll();
	}

	public long addAndGetReservedBuffers(int i) {
		synchronized (this.globalState) {
			return globalState.reservedBuffers += i;
		}
	}

	@Override
    public Object setSessionVariable(String key, Object value) {
        return this.session.getSessionVariables().put(key, value);
    }

    @Override
    public Object getSessionVariable(String key) {
        return this.session.getSessionVariables().get(key);
    }

	@Override
	public void addListener(CommandListener listener) {
		if (listener != null) {
			synchronized (this.globalState) {
				if (this.globalState.commandListeners == null) {
					this.globalState.commandListeners = Collections.newSetFromMap(new IdentityHashMap<CommandListener, Boolean>());
				}
				this.globalState.commandListeners.add(listener);
			}
		}
	}

	@Override
	public void removeListener(CommandListener listener) {
		if (listener != null) {
			synchronized (this.globalState) {
				if (this.globalState.commandListeners != null) {
					this.globalState.commandListeners.remove(listener);
				}
			}
		}
	}
	
	public static DecimalFormat getDecimalFormat(CommandContext context, String format) {
		DecimalFormat result = null;
		if (context != null) {
			if (context.globalState.decimalFormatCache == null) {
				context.globalState.decimalFormatCache = new LRUCache<String, DecimalFormat>(32);
			} else {
				result = context.globalState.decimalFormatCache.get(format);
			}
		}
		if (result == null) {
			result = new DecimalFormat(format); //TODO: could be locale sensitive
			result.setParseBigDecimal(true);
			if (context != null) {
				context.globalState.decimalFormatCache.put(format, result);
			}
		}
		return result;
	}
	
	public static SimpleDateFormat getDateFormat(CommandContext context, String format) {
		SimpleDateFormat result = null;
		if (context != null) {
			if (context.globalState.dateFormatCache == null) {
				context.globalState.dateFormatCache = new LRUCache<String, SimpleDateFormat>(32);
			} else {
				result = context.globalState.dateFormatCache.get(format);
			}
		}
		if (result == null) {
			result = new SimpleDateFormat(format, Locale.ENGLISH); //TODO: could be locale sensitive
			if (context != null) {
				context.globalState.dateFormatCache.put(format, result);
			}
		}
		return result;
	}
	
	   /**
     * Compile a regular expression into a {@link java.util.regex.Pattern} and cache it in
     * the {@link CommandContext} for future use.
     *
     * @param context
     * @param regex Regular expression.
     * @param flags Bitmask flags like {@link java.util.regex.Pattern#CASE_INSENSITIVE}.
     * @return Compiled regex.
     */
   public static Pattern getPattern(CommandContext context, String regex, int flags) {
       Pattern result = null;
       if (context != null) {
           if (context.globalState.patternCache == null) {
               context.globalState.patternCache = new LRUCache<Entry<String,Integer>,Pattern>(32);
           } else {
               result = context.globalState.patternCache.get(new SimpleEntry(result, flags));
           }
       }
       if (result == null) {
           result = Pattern.compile(regex, flags);
           if (context != null) {
               context.globalState.patternCache.put(new SimpleEntry(result, flags), result);
           }
       }
       return result;
   }
	
	public void incrementReuseCount() {
		globalState.reuseCount.getAndIncrement();
	}
	
	@Override
	public long getReuseCount() {
		if (globalState.reuseCount == null) {
			return 0;
		}
		return globalState.reuseCount.get();
	}	
	
	@Override
	public boolean isContinuous() {
		return globalState.reuseCount != null;
	}
	
	public void setContinuous() {
		this.globalState.reuseCount = new AtomicLong();
	}

	@Override
	public ClassLoader getVDBClassLoader() {
		return this.vdbState.classLoader;
	}
	
	public void setVDBClassLoader(ClassLoader classLoader) {
		this.vdbState.classLoader = classLoader;
	}
	
    /**
     * Get all warnings found while processing this plan.  These warnings may
     * be detected throughout the plan lifetime, which means new ones may arrive
     * at any time.  This method returns all current warnings and clears 
     * the current warnings list.  The warnings are in order they were detected.
     * @return Current list of warnings, never null
     */
    public List<Exception> getAndClearWarnings() {
        if (globalState.warnings == null) {
            return null;
        }
        synchronized (this.globalState) {
            List<Exception> copied = globalState.warnings;
            globalState.warnings = null;
            return copied;
		}
	}
        
    public Options getOptions() {
    	if (this.globalState.options == null) {
    		this.globalState.options = new Options();
    	}
    	return this.globalState.options;
    }
    
    public void setOptions(Options options) {
    	this.globalState.options = options;
    }

    public void addWarning(Exception warning) {
    	if (warning == null) {
    		return;
    	}
    	synchronized (this.globalState) {
            if (globalState.warnings == null) {
            	globalState.warnings = new ArrayList<Exception>(1);
            }
            globalState.warnings.add(warning);
		}
        LogManager.logInfo(LogConstants.CTX_DQP, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID31105, warning.getMessage()));
    }
    
    public TupleSourceCache getTupleSourceCache() {
		return tupleSourceCache;
	}
    
    public void setTupleSourceCache(TupleSourceCache tupleSourceCache) {
		this.tupleSourceCache = tupleSourceCache;
	}

    public Map<String, String> getDvOptions() {
		return globalState.dvoptions;
	}

	public void setDvOptions(Map<String, String> options) {
		globalState.dvoptions = options;
	}

    // the map for connectors to store arbitrary values
    @SuppressWarnings("unchecked")
	public Map<String, String> getTmpValues() throws Throwable {
    	if (globalState.schema == null)
    		return null;
		return (Map<String, String>) DVCaller.callWithExc(120);
	}

	@Override
	public boolean isReturnAutoGeneratedKeys() {
		return this.globalState.returnAutoGeneratedKeys;
	}
 
	public void setReturnAutoGeneratedKeys(boolean b) {
		this.globalState.returnAutoGeneratedKeys = b;
	}
	@Override
	public GeneratedKeysImpl returnGeneratedKeys(String[] columnNames,
			Class<?>[] columnDataTypes) {
		synchronized (this.globalState) {
			this.globalState.generatedKeys = new GeneratedKeysImpl(columnNames, columnDataTypes);
			return this.globalState.generatedKeys;
		}
	}
	
	public GeneratedKeysImpl getGeneratedKeys() {
		synchronized (this.globalState) {
			return this.globalState.generatedKeys;
		}
    }

	public AuthorizationValidator getAuthorizationValidator() {
		return this.globalState.authorizationValidator;
	}

	public TupleSource getCodeLookup(String matTableName) {
		if (this.globalState.lookups != null) {
			return this.globalState.lookups.remove(matTableName);
		}
		return null;
	}

	public void putCodeLookup(String matTableName, TupleSource ts) {
		if (this.globalState.lookups == null) {
			this.globalState.lookups = new HashMap<String, TupleSource>();
		}
		this.globalState.lookups.put(matTableName, ts);
	}
	
	public void requestCancelled() {
		this.cancelled.set(true);
	}

	/**
	 * Check if this context or the parent has been cancelled.
	 * If the parent has been, then we'll propagate.
	 * @return
	 */
	public boolean isCancelled() {
		if (this.cancelled.get()) {
			return true;
		}
		if (this.parentCancelled != null && this.parentCancelled.get()) {
			return true;
		}
		return false;
	}

	public void clearGeneratedKeys() {
		synchronized (this.globalState) {
			this.globalState.generatedKeys = null;
		}
	}

	/**
	 * Used by transaction detection logic when starting an atomic block
	 * transaction
	 * @return
	 */
 	public boolean isAtomicBlock() {
		return atomicBlock;
 	}

	public void setAtomicBlock(boolean atomicBlock) {
		this.atomicBlock = atomicBlock;
 	}

	/**
	 * Used by the planner to track only what is accessed by the plan
	 */
 	public void addAccessed(TempMetadataID id) {
		if (this.accessed != null) {
			this.accessed.add(id);
		}
 	}

	public void setAccessed(Collection<TempMetadataID> accessed) {
		this.accessed = accessed;
 	}

	public String getSchema() {
		return this.globalState.schema;
	}

	public void setSchema(String schema) {
//System.out.println(" -------------------------------- schema: " + schema + "; " + this);
		this.globalState.schema = schema;
	}

	public void addCreatedLob(InputStreamFactory isf) {
		if (this.globalState.created != null) {
			this.globalState.created.add(isf);
		}
	}

	public void disableAutoCleanLobs() {
		this.globalState.created = null;
 	}
	
	public Boolean isAccessible(AbstractMetadataRecord record) {
		if (this.globalState.accessible == null) {
			return null;
		}
		return this.globalState.accessible.get(record);
	}

	public void setAccessible(AbstractMetadataRecord record, Boolean result) {
		if (this.globalState.accessible == null) {
			this.globalState.accessible = new LRUCache<AbstractMetadataRecord, Boolean>(1000);
		}
		this.globalState.accessible.put(record, result);
	}

	public boolean isParallel() {
		return this.globalState.parallel;
	}

	public boolean setParallel(boolean value) {
		boolean result = this.globalState.parallel;
		this.globalState.parallel = value;
		return result;
	}

	public String getCallerUser() {
		return globalState.callerUser;
	}

	public void setCallerUser(String callerUser) {
		this.globalState.callerUser = callerUser;
	}

	public Map<String, CommandContext> getUserCache() {
		return globalState.userCache;
	}

	private String getContextPath() {
		StringBuilder sb = new StringBuilder();
		var ctx = this;
		while (ctx != null) {
			sb.append(String.format("[%8h] ", ctx.hashCode())).append(ctx.userName);
			ctx = ctx.parentContext;
			if (ctx != null)
				sb.append(" <- ");
		}
		return sb.toString();
	}

	/**
	 * Used by the system table logic
	 * @return
	 */
	public Clob getSpatialRefSys() {
		return new ClobImpl(new InputStreamFactory() {

			@Override
			public InputStream getInputStream() throws IOException {
				return getClass().getClassLoader().getResourceAsStream("org/teiid/metadata/spatial_ref_sys.csv"); //$NON-NLS-1$
			}
		}, -1);
	}

	@Override
	public TeiidConnection getConnection() throws TeiidSQLException {
		EmbeddedProfile ep = getDQPWorkContext().getConnectionProfile();
		//TODO: this is problematic as the client properties are not conveyed
		Properties info = new Properties();
		info.put(EmbeddedProfile.DQP_WORK_CONTEXT, getDQPWorkContext());
		String url = "jdbc:datavirtuality:" + getVdbName() + "." + getVdbVersion(); //$NON-NLS-1$ //$NON-NLS-2$
		ServerConnection sc;
		try {
			sc = ep.createServerConnection(info);
		} catch (TeiidException e) {
			throw TeiidSQLException.create(e);
		}
		return new ConnectionImpl(sc, info, url) {
			@Override
			public void close() throws SQLException {
				//just ignore
			}

			@Override
			public void rollback() throws SQLException {
				//just ignore
			}

			@Override
			public void setAutoCommit(boolean autoCommit) throws SQLException {
				//TODO: detect if attempted set conflicts with current txn state
				throw new TeiidSQLException();
			}

			@Override
			public void commit() throws SQLException {
				throw new TeiidSQLException();
			}

			@Override
			public void changeUser(String userName, String newPassword)
					throws SQLException {
				throw new TeiidSQLException();
			}
		};
	}

	boolean noFail = false;
	@Override
	public void setNoFail(boolean noFail) {
		this.noFail = noFail;
	}

	@Override
	public boolean isNoFail() {
		return noFail;
	}

	public static CommandContext newChild(CommandContext parent) {
		if (parent == null)
			return null;
		CommandContext child = parent.clone();
		child.setParentContext(parent);
		child.inherit(parent);
		return child;
	}

	private AtomicBoolean replace = new AtomicBoolean();
	private AtomicBoolean replaceProcessed = new AtomicBoolean();

	public boolean isReplace() {
		return replace.get();
	}

	public void setReplace() {
		this.replace.set(true);
	}

	public void setReplaceProcessed() {
		this.replaceProcessed.set(true);
	}

	public boolean isReplaceNotProcessed() {
		return this.replace.get() && !this.replaceProcessed.get();
	}

	private void inherit(CommandContext context) {
		this.replace = context.replace;
		this.replaceProcessed = context.replaceProcessed;
	}

	/**
	 * Checks if CommandContext is used in scoped process like Virtual Procedure.
	 * @param command
	 * @return true if scoped, false by default
	 */
	public boolean isScoped(Command command) {
		try {
			Collection<Object> contextVariables = getVariableContext().getVariableMap().values();
			if (!contextVariables.isEmpty()) {
				return contextVariables
						.stream().allMatch(var -> var instanceof DVProcedureContext);
			}
		} catch (Exception e) {
			// TODO: change logging after initial testing
			System.out.println("CommandContext_failed_on_command " + command.toString());
			e.printStackTrace(System.out);
		}
		return false;
	}
}
