/*
* 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.
*/
package org.teiid.translator.loopback;
import java.lang.reflect.Array;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Date;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import org.teiid.language.Argument;
import org.teiid.language.Argument.Direction;
import org.teiid.language.Call;
import org.teiid.language.Command;
import org.teiid.language.QueryExpression;
import org.teiid.logging.LogConstants;
import org.teiid.logging.LogManager;
import org.teiid.translator.DataNotAvailableException;
import org.teiid.translator.ProcedureExecution;
import org.teiid.translator.TranslatorException;
import org.teiid.translator.TypeFacility;
import org.teiid.translator.UpdateExecution;
/**
* Represents the execution of a command.
*/
public class LoopbackExecution implements UpdateExecution, ProcedureExecution {
// Connector resources
private LoopbackExecutionFactory config;
private Command command;
private String staticStringValue = ""; //$NON-NLS-1$
// Execution state
private Random randomNumber = new Random();
private List<Object> row;
private List<Class<?>> types;
private boolean waited = false;
private int rowsReturned = 0;
private int rowsNeeded = 1;
private BigInteger rowNumber = BigInteger.ZERO;
public LoopbackExecution(Command command, LoopbackExecutionFactory config) {
this.config = config;
this.command = command;
staticStringValue = constructIncrementedString(config.getCharacterValuesSize());
}
/**
* Creates string "ABCD...ZABC..." of length characterValueSize
* @param characterValuesSize
* @return
*/
public static String constructIncrementedString(int characterValuesSize) {
//Create string of length as defined in the translator configuration
StringBuffer alphaString = new StringBuffer();
int genAlphaSize = characterValuesSize;
char currentChar = 'A';
while (genAlphaSize-- != 0){
alphaString.append(currentChar);
if (currentChar =='Z')
currentChar = 'A';
else
currentChar += 1;
}
return alphaString.toString();
}
@Override
public List<?> next() throws TranslatorException, DataNotAvailableException {
// Wait on first batch if necessary
if(this.config.getWaitTime() > 0 && !waited) {
// Wait a random amount of time up to waitTime milliseconds
int randomTimeToWait = randomNumber.nextInt(this.config.getWaitTime());
// If we're asynch and the wait time was longer than the poll interval,
// then just say we don't have results instead
if(this.config.getPollIntervalInMilli() >= 0 && randomTimeToWait > this.config.getPollIntervalInMilli()) {
waited = true;
DataNotAvailableException dnae = new DataNotAvailableException(randomTimeToWait);
dnae.setStrict(true);
throw dnae;
}
try {
Thread.sleep(randomTimeToWait);
} catch(InterruptedException e) {
}
waited = true;
}
List<Object> resultRow = row;
if(rowsReturned < this.rowsNeeded && resultRow.size() > 0) {
rowsReturned++;
if (config.getIncrementRows()) {
rowNumber = rowNumber.add(BigInteger.ONE);
generateRow();
}
return resultRow;
}
return null;
}
@Override
public void execute() throws TranslatorException {
// Log our command
LogManager.logTrace(LogConstants.CTX_CONNECTOR, "Loopback executing command: " + command); //$NON-NLS-1$
if(this.config.isThrowError()) {
throw new TranslatorException("Failing because Error=true"); //$NON-NLS-1$
}
this.rowsNeeded = this.config.getRowCount();
if (command instanceof QueryExpression) {
QueryExpression queryCommand = (QueryExpression)command;
if (queryCommand.getLimit() != null) {
this.rowsNeeded = queryCommand.getLimit().getRowLimit();
}
}
// Prepare for execution
determineOutputTypes();
generateRow();
}
@Override
public int[] getUpdateCounts() throws DataNotAvailableException,
TranslatorException {
return new int [] {0};
}
@Override
public List<?> getOutputParameterValues() throws TranslatorException {
Call proc = (Call)this.command;
int count = proc.getReturnType() != null ? 1:0;
for (Argument param : proc.getArguments()) {
if (param.getDirection() == Direction.INOUT || param.getDirection() == Direction.OUT) {
count++;
}
}
return Arrays.asList(new Object[count]);
}
@Override
public void close() {
// nothing to do
}
@Override
public void cancel() throws TranslatorException {
}
private void determineOutputTypes() {
// Get select columns and lookup the types in metadata
if(command instanceof QueryExpression) {
QueryExpression query = (QueryExpression) command;
types = Arrays.asList(query.getColumnTypes());
return;
}
if (command instanceof Call) {
types = Arrays.asList(((Call)command).getResultSetColumnTypes());
return;
}
types = new ArrayList<Class<?>>(1);
types.add(Integer.class);
}
public static final Long DAY_SECONDS=86400L;
private static final int DATE_PERIOD = 365*(8099-1970);
/**
* Increments value in each column. If the value is bounded type (e.g. short) it will cycle the values.
*/
private void generateRow() {
List<Object> newRow = new ArrayList<Object>(types.size());
String incrementedString = incrementString(staticStringValue,rowNumber);
for (Class<?> type : types) {
Object val = getVal(incrementedString, type);
newRow.add(val);
}
row = newRow;
}
Object getVal(String incrementedString, Class<?> type) {
Object val;
if (type.equals(Integer.class)) {
val = rowNumber.intValue();
} else if (type.equals(java.lang.Short.class)) {
val = rowNumber.shortValue();
} else if (type.equals(java.lang.Long.class)) {
val = rowNumber.longValue();
} else if (type.equals(java.lang.Float.class)) {
val = rowNumber.floatValue()/10;
} else if (type.equals(java.lang.Double.class)) {
val = rowNumber.doubleValue()/10;
} else if (type.equals(java.lang.Character.class)) {
val = (char)((((rowNumber.byteValue()&0xff) + 2)%26) + 97);
} else if (type.equals(java.lang.Byte.class)) {
val = rowNumber.byteValue();
} else if (type.equals(java.lang.Boolean.class)) {
val = rowNumber.intValue()%2!=0;
} else if (type.equals(java.math.BigInteger.class)) {
val = rowNumber;
} else if (type.equals(java.math.BigDecimal.class)) {
val = new BigDecimal(rowNumber, 1);
} else if (type.equals(java.sql.Date.class)) {
val = new Date(DAY_SECONDS * 1000 * (rowNumber.intValue()%DATE_PERIOD));
} else if (type.equals(java.sql.Time.class)) {
val = new Time((rowNumber.intValue()%DAY_SECONDS) * 1000);
} else if (type.equals(java.sql.Timestamp.class)) {
val = new Timestamp(rowNumber.longValue());
} else if (type.equals(TypeFacility.RUNTIME_TYPES.CLOB)) {
val = this.config.getTypeFacility().convertToRuntimeType(incrementedString.toCharArray());
} else if (type.equals(TypeFacility.RUNTIME_TYPES.BLOB) || type.equals(TypeFacility.RUNTIME_TYPES.VARBINARY)) {
val = this.config.getTypeFacility().convertToRuntimeType(incrementedString.getBytes());
} else if (type.isArray()) {
val = Array.newInstance(type.getComponentType(), 1);
Array.set(val, 0, getVal(incrementedString, type.getComponentType()));
} else {
val = incrementedString;
}
return val;
}
/**
* Increments the string by appending unique number in sequence. Preserves string length.
* @param number Number in the sequence of strings (which row)
* @return
*/
public static String incrementString(String string, BigInteger number) {
String numberString= number.toString();
if (number.equals(BigInteger.ZERO)) {
return string;//Backward compatibility for first string
}
return string.substring(0,string.length()-numberString.length())+ numberString;
}
}