/*
 * 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-3726
// https://issues.jboss.org/browse/TEIID-4130
// https://issues.jboss.org/browse/TEIID-2311
// https://issues.jboss.org/browse/TEIID-4449 (partially, getObjects() method should be changed after implementing Evaluatable type)
// https://issues.jboss.org/browse/TEIID-4076
// https://issues.jboss.org/browse/TEIID-4509
// https://issues.jboss.org/browse/TEIID-2316 (partially)
// https://issues.jboss.org/browse/TEIID-2316
// https://issues.jboss.org/browse/TEIID-2281
// https://issues.jboss.org/browse/TEIID-2379
// https://issues.jboss.org/browse/TEIID-2309
// https://issues.jboss.org/browse/TEIID-2555
// https://issues.jboss.org/browse/TEIID-4882
// https://github.com/teiid/teiid/commit/ac4d4be06719f8f7757870eae37ec7bc9de44a08
// https://issues.redhat.com/browse/TEIID-2316 (partially, only this class)
package org.teiid.query.processor.relational;

import static org.teiid.query.analysis.AnalysisRecord.*;

import java.util.*;

import org.teiid.api.exception.query.QueryValidatorException;
import org.teiid.client.plan.PlanNode;
import org.teiid.common.buffer.BlockedException;
import org.teiid.common.buffer.BufferManager;
import org.teiid.common.buffer.BufferManager.BufferReserveMode;
import org.teiid.common.buffer.TupleBatch;
import org.teiid.common.buffer.TupleBuffer;
import org.teiid.common.buffer.TupleSource;
import org.teiid.core.TeiidComponentException;
import org.teiid.core.TeiidProcessingException;
import org.teiid.query.QueryPlugin;
import org.teiid.query.eval.Evaluator;
import org.teiid.query.metadata.QueryMetadataInterface;
import org.teiid.query.optimizer.relational.RowBasedSecurityHelper;
import org.teiid.query.optimizer.relational.plantree.NodeConstants;
import org.teiid.query.processor.ProcessorDataManager;
import org.teiid.query.processor.QueryProcessor;
import org.teiid.query.processor.ProcessorPlan;
import org.teiid.query.processor.RegisterRequestParameter;
import org.teiid.query.processor.relational.SubqueryAwareEvaluator.SubqueryState;
import org.teiid.query.rewriter.QueryRewriter;
import org.teiid.query.sql.LanguageObject;
import org.teiid.query.sql.lang.*;
import org.teiid.query.sql.symbol.Constant;
import org.teiid.query.sql.symbol.Expression;
import org.teiid.query.sql.symbol.GroupSymbol;
import org.teiid.query.sql.symbol.ScalarSubquery;
import org.teiid.query.sql.util.SymbolMap;
import org.teiid.query.sql.visitor.ValueIteratorProviderCollectorVisitor;
import org.teiid.query.util.CommandContext;
import org.teiid.translator.ExecutionFactory.TransactionSupport;

import com.datavirtuality.dv.core.interfaces.DVCaller;
import com.datavirtuality.dv.core.interfaces.query.sql.lang.SelectInto;

public class AccessNode extends SubqueryAwareRelationalNode {

	private static final Object[] NO_PROJECTION = new Object[0];
    private static final int MAX_CONCURRENT = 10; //TODO: this could be settable via a property
	// Initialization state
    private Command command;
    private String modelName;
    private String connectorBindingId;
    private boolean shouldEvaluate = false;
	private Set<Object> conformedTo;

    // Processing state
	private ArrayList<TupleSource> tupleSources = new ArrayList<TupleSource>();
	private boolean isUpdate = false;
    private boolean returnedRows = false;
    protected Command nextCommand;
    private int reserved;
    private int schemaSize;
    
    private Object[] projection;
    private List<Expression> originalSelect;
	private Object modelId;
	private TransactionSupport transactionSupport;
	private boolean open;
	
	public RegisterRequestParameter.SharedAccessInfo info;
	private Map<GroupSymbol, RelationalPlan> subPlans;
	private Map<GroupSymbol, SubqueryState> evaluatedPlans;
    
    protected AccessNode() {
		super();
	}
    
	public AccessNode(int nodeID) {
		super(nodeID);
	}
	
	@Override
	public void initialize(CommandContext context, BufferManager bufferManager,
			ProcessorDataManager dataMgr) {
		super.initialize(context, bufferManager, dataMgr);
    	this.schemaSize = getBufferManager().getSchemaSize(getOutputElements());
	}

    public void reset() {
        super.reset();
        this.tupleSources.clear();
		isUpdate = false;
        returnedRows = false;
        nextCommand = null;
	    this.evaluatedPlans = null;
        open = false;
    }

	public void setCommand(Command command) {
		this.command = command;
	}

    public Command getCommand() {
        return this.command;
    }
    
    public void setModelId(Object id) {
    	this.modelId = id;
    }
    
    public Object getModelId() {
    	return this.modelId;
    }

	public void setModelName(String name) {
		this.modelName = name;
	}

	public String getModelName() {
		return this.modelName;
	}

    public void setShouldEvaluateExpressions(boolean shouldEvaluate) {
    	if (shouldEvaluate && projection != null) {
            //restore the original as we'll minimize each time
            ((Query)this.command).getSelect().setSymbols(this.originalSelect);
            this.projection = null;
    	}
        this.shouldEvaluate = shouldEvaluate;
    }


	public void open() throws TeiidComponentException, TeiidProcessingException {
		try {
			openInternal();
			open = true;
		} catch (BlockedException e) {
			// we don't want to let blocked exceptions bubble up during open
		}
	}

	private void openInternal() throws TeiidComponentException, TeiidProcessingException{
		//TODO: support a partitioning concept with multi-source and full dependent join pushdown
		if (subPlans != null) {
			if (this.evaluatedPlans == null) {
				this.evaluatedPlans = new HashMap<GroupSymbol, SubqueryState>();
				for (Map.Entry<GroupSymbol, RelationalPlan> entry : subPlans.entrySet()) {
					SubqueryState state = new SubqueryState();
					RelationalPlan value = entry.getValue();
					value.reset();
					state.processor = new QueryProcessor(value, getContext().clone(), getBufferManager(), getDataManager());
					state.collector = state.processor.createBatchCollector();
					this.evaluatedPlans.put(entry.getKey(), state);
				}
			}
			BlockedException be = null;
			for (SubqueryState state : evaluatedPlans.values()) {
				try {
					state.collector.collectTuples();
				} catch (BlockedException e) {
					be = e;
				}
			}
			if (be != null) {
				throw be;
			}
		}

		// Copy command and resolve references if necessary
        Command atomicCommand = command;
        boolean needProcessing = true;
        
        if (shouldEvaluate) {
	        atomicCommand = initialCommand();
        } 
        // EXTENSION
        // ISSUE: DVCORE-2739, DVCORE-2813
        // FIXED BY: Roman Pedchenko, Salvatore Raunich
        // DATE: 2015.03.18, 2015.06.05
    	if (!(atomicCommand instanceof SelectInto) && this.tupleSources.size() > 0)
    		return;
        // END EXTENSION
        do {
			atomicCommand = nextCommand();
        	if(shouldEvaluate) {
	            needProcessing = prepareNextCommand(atomicCommand);
	            nextCommand = null;
	        } else {
	            needProcessing = RelationalNodeUtil.shouldExecute(atomicCommand, true);
	        }
	        // else command will not be changed, so no reason to all this work.
	        // Removing this if block and always evaluating has a significant cost that will
	        // show up in performance tests for many simple tests that do not require it.
	        
	        isUpdate = RelationalNodeUtil.isUpdate(atomicCommand);
	        
			if(needProcessing) {
				registerRequest(atomicCommand);
			} else if (atomicCommand instanceof SelectInto) {
				// no processing is needed (e.g. a query with limit 0).
				// We just need to create the target table
				try {
					DVCaller.callWithExc(124, atomicCommand);
				} catch (Throwable e) {
					throw new TeiidProcessingException(e);
				}
			}
	        //We use an upper limit here to the currency because these commands have potentially large in-memory value sets
        } while (!processCommandsIndividually() && hasNextCommand() && this.tupleSources.size() < Math.max(Math.min(MAX_CONCURRENT, this.getContext().getUserRequestSourceConcurrency()), this.getContext().getUserRequestSourceConcurrency()/2));
	}
	
	public boolean isShouldEvaluate() {
		return shouldEvaluate;
	}

	public void minimizeProject(Command atomicCommand) {
		if (!(atomicCommand instanceof Query)) {
			return;
		}
		Query query = (Query)atomicCommand;
		Select select = query.getSelect();
		List<Expression> symbols = select.getSymbols();
		if (symbols.size() == 1) {
			return;
		}
		boolean shouldProject = false;
		LinkedHashMap<Expression, Integer> uniqueSymbols = new LinkedHashMap<Expression, Integer>();
		projection = new Object[symbols.size()];
		this.originalSelect = new ArrayList<Expression>(query.getSelect().getSymbols());
		int i = 0;
		int j = 0;
		for (Iterator<Expression> iter = symbols.iterator(); iter.hasNext(); ) {
			Expression ss = iter.next();
			Expression ex = SymbolMap.getExpression(ss);
			if (ex instanceof Constant) {
				projection[i] = ex;
				if (iter.hasNext() || j!=0) {
					iter.remove();
					shouldProject = true;
				} else {
					projection[i] = j++;
				}
			} else {
				Integer index = uniqueSymbols.get(ex);
				if (index == null) {
					uniqueSymbols.put(ex, j);
					index = j++;
				} else {
					iter.remove();
					shouldProject = true;
				}
				projection[i] = index;
			}
			i++;
		}
		if (!shouldProject) {
			this.projection = NO_PROJECTION;
		} else if (query.getOrderBy() != null) {
			for (OrderByItem item : query.getOrderBy().getOrderByItems()) {
				Integer index = uniqueSymbols.get(SymbolMap.getExpression(item.getSymbol()));
				if (index != null) {
					item.setExpressionPosition(index);
					item.setSymbol(select.getSymbols().get(index));
				}
			}
		}
	}
	
	public List<Expression> getOriginalSelect() {
		return originalSelect;
	}
	
	public Object[] getProjection() {
		return projection;
	}

	static void rewriteAndEvaluate(Command atomicCommand, Evaluator eval, CommandContext context, QueryMetadataInterface metadata)
			throws TeiidProcessingException, TeiidComponentException {
		try {
		    // Defect 16059 - Rewrite the command to replace references, etc. with values.
			QueryRewriter.evaluateAndRewrite(atomicCommand, eval, context, metadata);
		} catch (QueryValidatorException e) {
		     throw new TeiidProcessingException(QueryPlugin.Event.TEIID30174, e, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID30174, atomicCommand));
		}
	}
	
	@SuppressWarnings("unused")
	protected Command initialCommand() throws TeiidProcessingException, TeiidComponentException {
		return nextCommand();
	}

	protected Command nextCommand() {
		//it's important to save the next command
		//to ensure that the subquery ids remain stable
		if (nextCommand == null) {
			nextCommand = (Command) command.clone();
			if (evaluatedPlans != null) {
				for (WithQueryCommand with : ((QueryCommand)nextCommand).getWith()) {
					TupleBuffer tb = evaluatedPlans.get(with.getGroupSymbol()).collector.getTupleBuffer();
					with.setTupleBuffer(tb);
		}
			}
		}
		return nextCommand; 
	}

    protected boolean prepareNextCommand(Command atomicCommand) throws TeiidComponentException, TeiidProcessingException {
		rewriteAndEvaluate(atomicCommand, getEvaluator(Collections.emptyMap()), this.getContext(), this.getContext().getMetadata());
    	return RelationalNodeUtil.shouldExecute(atomicCommand, true);
    }

	public TupleBatch nextBatchDirect()
		throws BlockedException, TeiidComponentException, TeiidProcessingException {
		
		if (!open) {
			openInternal(); // -- blocked during actual open
			open = true;
		}
		
        while (!tupleSources.isEmpty() || hasNextCommand()) {
        	
        	if (tupleSources.isEmpty() && processCommandsIndividually()) {
        		registerNext();
        	}
        	
        	//drain the tuple source(s)
        	for (int i = 0; i < this.tupleSources.size(); i++) {
        		TupleSource tupleSource = tupleSources.get(i);
        		try {
	        		List<?> tuple = null;
	        		
	        		while ((tuple = tupleSource.nextTuple()) != null) {
	                    returnedRows = true;
	                    if (this.projection != null && this.projection.length > 0) {
	                    	List<Object> newTuple = new ArrayList<Object>(this.projection.length);
	                    	for (Object object : this.projection) {
								if (object instanceof Integer) {
									newTuple.add(tuple.get((Integer)object));
								} else {
									newTuple.add(((Constant)object).getValue());
								}
							}
	                    	tuple = newTuple;
	                    }
	                    addBatchRow(tuple);
	                    
	                    if (isBatchFull()) {
	                    	return pullBatch();
	                    }
	        		}
	        		
                	//end of source
                    tupleSource.closeSource();
                    tupleSources.remove(i--);
            		if (reserved > 0) {
                    	reserved -= schemaSize;
                    	getBufferManager().releaseBuffers(schemaSize);
            		}
                    if (!processCommandsIndividually()) {
                    	registerNext();
                    }
                    continue;
        		} catch (BlockedException e) {
        			if (processCommandsIndividually()) {
				        if (hasPendingRows()) {
					        return pullBatch();
				        }
        				throw e;
        			}
        			continue;
        		}
			}
        	
        	if (processCommandsIndividually()) {
        		if (hasPendingRows()) {
        			return pullBatch();
        		}
        		continue;
        	}
        	
        	if (!this.tupleSources.isEmpty()) {
		        if (hasPendingRows()) {
			        return pullBatch();
		        }
        		throw BlockedException.block(getContext().getRequestId(), "Blocking on source request(s)."); //$NON-NLS-1$
        	}
        }
        
        if(isUpdate && !returnedRows) {
			List<Integer> tuple = new ArrayList<Integer>(1);
			tuple.add(Integer.valueOf(0));
            // Add tuple to current batch
            addBatchRow(tuple);
        }
        terminateBatches();
        return pullBatch();
	}
	
	@Override
	protected void addBatchRow(List<?> row) {
		if (this.getOutputElements().isEmpty()) {
			//a dummy column was added to the query, just remove it now
			row = Collections.emptyList();
		}
		super.addBatchRow(row);
	}

	private void registerNext() throws TeiidComponentException,
			TeiidProcessingException {
		while (hasNextCommand()) {
		    Command atomicCommand = nextCommand();
		    if (prepareNextCommand(atomicCommand)) {
		    	nextCommand = null;
		        registerRequest(atomicCommand);
		        break;
		    }
		    nextCommand = null;
		}
	}

	private void registerRequest(Command atomicCommand)
			throws TeiidComponentException, TeiidProcessingException {
		if (shouldEvaluate) {
			projection = null;
			minimizeProject(atomicCommand);
		}
		int limit = -1;
		if (getParent() instanceof LimitNode) {
			LimitNode parent = (LimitNode)getParent();
			if (parent.getLimit() > 0) {
				limit = parent.getLimit() + parent.getOffset();
			}
		}
		RegisterRequestParameter param = new RegisterRequestParameter(connectorBindingId, getID(), limit, overrideFetchSize);
		param.info = info;
		param.fetchSize = this.getBatchSize();		
		RowBasedSecurityHelper.checkConstraints(atomicCommand, getEvaluator(Collections.emptyMap()));
		tupleSources.add(getDataManager().registerRequest(getContext(), atomicCommand, modelName, param));
		if (tupleSources.size() > 1) {
        	reserved += getBufferManager().reserveBuffers(schemaSize, BufferReserveMode.FORCE);
		}
	}
	
	protected boolean processCommandsIndividually() {
		return false;
	}
    
    protected boolean hasNextCommand() {
        return false;
    }
    
	public void closeDirect() {
		if (reserved > 0) {
			getBufferManager().releaseBuffers(reserved);
			reserved = 0;
		}
		if (this.evaluatedPlans != null) {
			for (SubqueryState state : this.evaluatedPlans.values()) {
				state.close(true);
			}
			this.evaluatedPlans = null;
		}
		super.closeDirect();
        closeSources();            
	}

    private void closeSources() {
    	for (TupleSource ts : this.tupleSources) {
    		ts.closeSource();			
		}
    	this.tupleSources.clear();
	}

	protected void getNodeString(StringBuffer str) {
		super.getNodeString(str);
		str.append(command);
		if (this.info != null) {
			str.append(" [SHARED ").append(this.info.id).append("]"); //$NON-NLS-1$ //$NON-NLS-2$
		}
	}

	public Object clone(){
		AccessNode clonedNode = new AccessNode();
		this.copyTo(clonedNode);
		return clonedNode;
	}

	protected void copyTo(AccessNode target){
		super.copyTo(target);
		target.modelName = modelName;
		target.modelId = modelId;
		target.connectorBindingId = connectorBindingId;
		target.shouldEvaluate = shouldEvaluate;
		if (!shouldEvaluate) {
			target.projection = projection;
			target.originalSelect = originalSelect;
		}
		target.command = command;
		target.info = info;
		target.conformedTo = this.conformedTo;
		// EXTENSION
		// ISSUE: DVCORE-476
		// FIXED BY: Salvatore Raunich
		// DATE: 2013.06.13
		target.recOptId = recOptId;
		target.matTableIds = matTableIds;
		target.isJoin = isJoin;
		// END EXTENSION
		if (this.subPlans != null) {
			target.subPlans = new HashMap<GroupSymbol, RelationalPlan>();
			for (Map.Entry<GroupSymbol, RelationalPlan> entry : this.subPlans.entrySet()) {
				target.subPlans.put(entry.getKey(), entry.getValue().clone());
	}
		}
	}

    // EXTENSION
    // ISSUE: DVCORE-414
    // FIXED BY: Roman Pedchenko
    // DATE: 2013.05.19
    private boolean isJoin = false;
    public void setIsJoin(boolean isJoin) {
    	this.isJoin = isJoin;
    }
    // END EXTENSION
    
    // EXTENSION
    // ISSUE: DVCORE-451
    // FIXED BY: Salvatore Raunich
    // DATE: 2013.05.30
	private String recOptId;
	public void setRecOptId(String recOptId) {
		this.recOptId = recOptId;
	}
	private Set<String> matTableIds;
	public void setMatTableIds(Set<String> matTableIds) {
		this.matTableIds = matTableIds;
	}
	private List<String> dvRecOptIds;
	public List<String> getDvRecOptIds() {
		return dvRecOptIds;
	}
	public void setDvRecOptIds(List<String> dvRecOptIds) {
		this.dvRecOptIds = dvRecOptIds;
	}
	// END EXTENSION
    
    public PlanNode getDescriptionProperties() {
    	PlanNode props = super.getDescriptionProperties();
        props.addProperty(PROP_SQL, this.command.toString());
        props.addProperty(PROP_MODEL_NAME, this.modelName);
	    if (this.projection != null && this.projection.length > 0 && this.originalSelect != null) {
		    props.addProperty(PROP_SELECT_COLS, this.originalSelect.toString());
	    }
	    props.addProperty(PROP_MODEL_NAME, this.modelName);
        if (this.info != null) {
        	props.addProperty(PROP_SHARING_ID, String.valueOf(this.info.id));
        }
	    if (this.subPlans != null) {
		    for (Map.Entry<GroupSymbol, RelationalPlan> entry : this.subPlans.entrySet()) {
			    props.addProperty(entry.getKey() + " Dependent Subplan", entry.getValue().getDescriptionProperties()); //$NON-NLS-1$
		    }
	    }
        // EXTENSION
        // ISSUE: DVCORE-414
        // FIXED BY: Roman Pedchenko
        // DATE: 2013.05.19
        props.addProperty(NodeConstants.Info.IS_JOIN.toString(), String.valueOf(isJoin));
        // END EXTENSION
        
        // EXTENSION
        // ISSUE: DVCORE-451
        // FIXED BY: Salvatore Raunich
        // DATE: 2013.05.30
		props.addProperty(NodeConstants.Info.REC_OPT_IDS.toString(), String.valueOf(recOptId));
		List<String> matTableIdsList = null;
		if (matTableIds != null)
			matTableIdsList = new ArrayList<String>(matTableIds);
		props.addProperty(NodeConstants.Info.DV_USED_MATTABLE_IDS.name(), matTableIdsList);
		props.addProperty(NodeConstants.Info.DV_RECOPT.name(), dvRecOptIds);
        // END EXTENSION
        return props;
    }

	public String getConnectorBindingId() {
		return connectorBindingId;
	}

	public void setConnectorBindingId(String connectorBindingId) {
		this.connectorBindingId = connectorBindingId;
	}
	
	@Override
	//public Collection<? extends SubqueryContainer<?>> getObjects() {
	public Collection<? extends LanguageObject> getObjects() {
		ArrayList<LanguageObject> list = new ArrayList<LanguageObject>();
		if (shouldEvaluate) {
			//collect any evaluatable subqueries
			collectEvaluatable(list, this.command);
		}
		return list;
	}

	//private void collectEvaluatable(ArrayList<SubqueryContainer<?>> list, Command cmd) {
	private void collectEvaluatable(ArrayList<LanguageObject> list, Command cmd) {
		for (SubqueryContainer<?> container : ValueIteratorProviderCollectorVisitor.getValueIteratorProviders(cmd)) {
			/*if (container instanceof Evaluatable<?> && ((Evaluatable<?>)container).shouldEvaluate()) {
				list.add(container);
			} else {
				collectEvaluatable(list, container.getCommand());
			}*/
			if (container instanceof ExistsCriteria && ((ExistsCriteria)container).shouldEvaluate()) {
				list.add(container);
			}
			if (container instanceof ScalarSubquery && ((ScalarSubquery)container).shouldEvaluate()) {
				list.add(container);
			}
		}
	}
	
	@Override
	public Boolean requiresTransaction(boolean transactionalReads) {
		int subqueryTxn = 0;
		for (SubqueryContainer<?> subquery : ValueIteratorProviderCollectorVisitor.getValueIteratorProviders(getObjects())) {
			if (!(subquery instanceof SubqueryContainer.Evaluatable) || !((SubqueryContainer.Evaluatable<?>)subquery).shouldEvaluate()) {
				continue;
			}
			ProcessorPlan plan = subquery.getCommand().getProcessorPlan();
			if (plan != null) {
				Boolean txn = plan.requiresTransaction(transactionalReads);
				if (txn != null) {
					if (txn) {
						return true;
					} 
				} else {
					subqueryTxn++;
				}
			}
		}
		if (subqueryTxn > 1) {
			return true;
		}
		// we dont have multiSource yet
		/*if ((multiSource && connectorBindingExpression == null) && (subqueryTxn > 0)) {
			return true;
		}*/
		if (transactionSupport == TransactionSupport.NONE) {
			return subqueryTxn > 0?null:false;
		}
		if ((command instanceof StoredExecutable && ((StoredExecutable)command).getUpdateCount() > 1)) {
			return true;
		}
		if (transactionalReads || RelationalNodeUtil.isUpdate(command) || (command instanceof StoredExecutable && ((StoredExecutable) command).getUpdateCount() != 0)) {
			// we dont have multiSource yet
			/*if ((multiSource && connectorBindingExpression == null)) {
				return true;
			}*/
			return subqueryTxn > 0 ? true : null;
		}
		return false;
	}

	public void setSubPlans(Map<GroupSymbol, RelationalPlan> plans) {
		this.subPlans = plans;
	}

	public Map<GroupSymbol, RelationalPlan> getSubPlans() {
		return subPlans;
	}

	public Set<Object> getConformedTo() {
		return conformedTo;
	}

	public void setConformedTo(Set<Object> conformedTo) {
		this.conformedTo = conformedTo;
	}

	public void setTransactionSupport(TransactionSupport transactionSupport) {
	 	this.transactionSupport = transactionSupport;
	}
	
}
