/*
* (C) Copyright IBM Corp. 2010
*
* LICENSE: Eclipse Public License v1.0
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.ibm.gaiandb.udpdriver.server;
import java.sql.Connection;
import java.sql.ParameterMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSetMetaData;
import java.sql.Types;
import java.util.ArrayList;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Semaphore;
import org.apache.derby.vti.IFastPath;
import com.ibm.gaiandb.Logger;
import com.ibm.gaiandb.diags.GDBMessages;
import com.ibm.gaiandb.udpdriver.common.protocol.ExecuteQueryRequest;
import com.ibm.gaiandb.udpdriver.common.protocol.ExecuteQueryResponse;
import com.ibm.gaiandb.udpdriver.common.protocol.Message;
import com.ibm.gaiandb.udpdriver.common.protocol.MessageFactory;
import com.ibm.gaiandb.udpdriver.common.protocol.MetaData;
import com.ibm.gaiandb.udpdriver.common.protocol.NextValuesRequest;
import com.ibm.gaiandb.udpdriver.common.protocol.NextValuesResponse;
import com.ibm.gaiandb.udpdriver.common.protocol.QueryRequest;
import com.ibm.gaiandb.udpdriver.common.protocol.ResponseWithValues;
/**
* ClientState contains information about a client query.
*
* A reference to the Embedded Connection object associated with the query is stored here.
*
* Because multiple RunnableWorkers could access a ClientState at the same time and so execute
* non thread safe method at the same time, the ClientState implements a
* Semaphore. The RunnableWorkers have to acquire the permit to modify the ClientState.
*
* ClientState uses a ValuesEncoder to transform the database records into their binary format.
*
* @author lengelle
*
*/
public class ClientState
{
private static final Logger logger = new Logger( "ClientState", 30 );
// Use PROPRIETARY notice if class contains a main() method, otherwise use COPYRIGHT notice.
public static final String COPYRIGHT_NOTICE = "(c) Copyright IBM Corp. 2010";
public static int NUMBER_OF_PRESERIALIZED_MESSAGE = 1;
private PreparedStatement preparedStatement;
private ValuesEncoder valuesEncoder;
private Connection connection;
private int statementType;
private int numberOfColumns;
private int[] columnTypes;
private ArrayList<Integer> nullableColumns;
private String queryID;
private ArrayBlockingQueue<NextValuesResponse> nextResponseToSend;
private int lastMessageSequenceNumber;
private Semaphore semaphore;
public void acquirePermit() throws Exception
{
semaphore.acquire();
}
public void releasePermit() throws Exception
{
semaphore.release();
}
/**
* Instantiates a new ClientState.
*
* @param preparedStatement
* @param statementType
* @throws UDPDriverServerException
*/
public ClientState( PreparedStatement preparedStatement, int statementType ) throws UDPDriverServerException
{
try
{
this.preparedStatement = preparedStatement;
this.statementType = statementType;
queryID = null;
lastMessageSequenceNumber = -1;
valuesEncoder = null;
connection = preparedStatement.getConnection();
nextResponseToSend = new ArrayBlockingQueue<NextValuesResponse>( NUMBER_OF_PRESERIALIZED_MESSAGE );
semaphore = new Semaphore( 1 );
}
catch( Exception e )
{
throw new UDPDriverServerException( "ClientState ClientState() failed.", e );
}
}
public int getStatementType()
{
return statementType;
}
/**
* Process a QueryRequest
* Return the MetaData message
*
* @param queryRequest
* @return
* @throws UDPDriverServerException
*/
public MetaData processQueryRequest( QueryRequest queryRequest ) throws UDPDriverServerException
{
try
{
queryID = queryRequest.getQueryID();
MetaData response = MessageFactory.getMetaDataMessage( queryRequest.getQueryID() );
lastMessageSequenceNumber = queryRequest.getSequenceNumber() + 1;
response.setSequenceNumber( lastMessageSequenceNumber );
ResultSetMetaData rsmd = preparedStatement.getMetaData();
if ( null == rsmd )
throw new Exception("Failed to resolve columns definition ResultSetMetaData from underlying RDBMS for query: " + queryRequest.getQuery());
ParameterMetaData pmd = preparedStatement.getParameterMetaData();
numberOfColumns = rsmd.getColumnCount();
// Set the number of parameters in the response
response.setNumberOfParameters( pmd.getParameterCount() );
// Set the number of columns in the response
response.setNumberOfColumns( numberOfColumns );
if ( numberOfColumns > 0 )
{
// Set the column types in the response
columnTypes = new int[numberOfColumns];
for ( int i=0; i<columnTypes.length; ++i )
{
columnTypes[i] = rsmd.getColumnType( i+1 );
}
response.setColumnTypes( columnTypes );
// Set the number of nullable columns and their index in the response
nullableColumns = new ArrayList<Integer>( numberOfColumns );
for( int i=0; i<numberOfColumns; ++i )
{
if ( rsmd.isNullable( i+1 ) == 1 )
{
nullableColumns.add( i+1 );
response.addNullableColumnIndex( i+1 );
}
}
response.setNumberOfNullableColumns( nullableColumns.size() );
// Set the column names
String[] columnNames = new String[numberOfColumns];
for( int i=0; i<columnNames.length; ++i )
{
columnNames[i] = rsmd.getColumnName( i+1 );
}
response.setColumnNames( columnNames );
// Set the column scale
int[] columnScale = new int[numberOfColumns];
for ( int i=0; i<columnScale.length; ++i )
{
columnScale[i] = rsmd.getScale( i+1 );
}
response.setColumnScale( columnScale );
// Set the column precision
int[] columnPrecision = new int[numberOfColumns];
for ( int i=0; i<columnPrecision.length; ++i )
{
columnPrecision[i] = rsmd.getPrecision( i+1 );
}
response.setColumnPrecision( columnPrecision );
// Set the column display size
int[] columnDisplaySize = new int[numberOfColumns];
for ( int i=0; i<columnDisplaySize.length; ++i )
{
columnDisplaySize[i] = rsmd.getColumnDisplaySize( i+1 );
}
response.setColumnDisplaySize( columnDisplaySize );
// If simple statement, execute and send the first values
if ( queryRequest.getStatementType() == Message.STATEMENT )
{
// Execute the statement
executeStatement();
// Build the response message adding records from database
nextValues( response, valuesEncoder );
}
}
return response;
}
catch( Exception e )
{
throw new UDPDriverServerException( "ClientState processQueryRequest() failed.", e );
}
}
/**
* Process a ExecuteQueryRequest
* Return a ExecuteQueryResponse
*
* @param executeQueryRequest
* @return
* @throws UDPDriverServerException
*/
public ExecuteQueryResponse processExecuteQueryRequest( ExecuteQueryRequest executeQueryRequest ) throws UDPDriverServerException
{
try
{
// If it is a re-execution, a NextValuesResponse could remain in the queue
nextResponseToSend.clear();
if ( executeQueryRequest.getSequenceNumber() <= lastMessageSequenceNumber )
{
// Duplicate message
logger.logThreadWarning(GDBMessages.NETDRIVER_CLIENT_MESSAGE_DUPLICATE, "Detected duplicate client message (ignored). Sequence number: " + executeQueryRequest.getSequenceNumber());
return null;
}
ExecuteQueryResponse response = MessageFactory.getExecuteQueryResponseMessage( queryID );
lastMessageSequenceNumber = executeQueryRequest.getSequenceNumber() + 1;
response.setSequenceNumber( lastMessageSequenceNumber );
int[] types = executeQueryRequest.getExecutiveParameterTypes();
String[] values = executeQueryRequest.getExecutiveParameters();
if ( types!=null && values!=null ) // Query with parameters '?'
{
for ( int i=0; i<values.length; ++i )
{
this.setValueInPreparedStatement( types[i], values[i], i+1 );
}
}
// Execute the statement
executeStatement();
// Build the response message adding records from database
nextValues( response, valuesEncoder );
return response;
}
catch( Exception e )
{
throw new UDPDriverServerException( "ClientState processExecuteQueryRequest() failed.", e );
}
}
/**
* Process a NextValuesRequest
* Return a NextValuesResponse
*
* @param nextValuesRequest
* @return
* @throws UDPDriverServerException
*/
public NextValuesResponse processNextValuesRequest( NextValuesRequest nextValuesRequest ) throws UDPDriverServerException
{
try
{
if ( nextValuesRequest.getSequenceNumber() <= lastMessageSequenceNumber )
{
// Duplicate message
return null;
}
if ( connection == null )
{
// A processCloseRequest has already been executed
return null;
}
// Wait for the response
NextValuesResponse response = nextResponseToSend.take();
lastMessageSequenceNumber = nextValuesRequest.getSequenceNumber() + 1;
response.setSequenceNumber( lastMessageSequenceNumber );
return response;
}
catch( Exception e )
{
throw new UDPDriverServerException( "ClientState processNextValuesRequest() failed.", e );
}
}
/**
* Process a CloseRequest.
* Return the embedded Connection reference which is not used anymore.
*
* @return
* @throws UDPDriverServerException
*/
public Connection processCloseRequest() throws UDPDriverServerException
{
try
{
if ( connection == null )
{
// A processCloseRequest has already been executed
// This execution is probably due to a duplicate CloseRequest message
return null;
}
Connection connectionToReturn = connection;
clean();
return connectionToReturn;
}
catch( Exception e )
{
throw new UDPDriverServerException( "ClientState processCloseRequest() failed.", e );
}
}
/**
* Encode the next values of the ResultSet into the ResponseWithValues given as a parameter,
* thanks to the ValuesEncoder given as a parameter.
*
* @param response
* @param valuesEncoder
* @throws UDPDriverServerException
*/
private void nextValues( ResponseWithValues response, ValuesEncoder valuesEncoder ) throws UDPDriverServerException
{
try
{
valuesEncoder.encodeNextValues( response );
if ( response.containsLastValues() )
{
if ( ! ( preparedStatement instanceof IFastPath ) )
preparedStatement.getResultSet().close();
}
}
catch( Exception e )
{
throw new UDPDriverServerException( "ClientState nextValues() failed.", e );
}
}
/**
* Serialize the next values and place them into the buffer 'nextResponseToSend'
*
* @throws UDPDriverServerException
*/
public void serializeNextValues() throws UDPDriverServerException
{
try
{
if ( connection == null )
{
// A processCloseRequest has already been executed
return;
}
if ( valuesEncoder == null )
{
throw new UDPDriverServerException( "ValuesEncoder is null." );
}
NextValuesResponse nextValuesResponse = MessageFactory.getNextValuesResponseMessage( queryID );
nextValues( nextValuesResponse, valuesEncoder );
nextResponseToSend.add( nextValuesResponse );
}
catch( Exception e )
{
throw new UDPDriverServerException( "ClientState serializeNextValues() failed.", e );
}
}
/**
* Execute the Statement
*
* @throws UDPDriverServerException
*/
private void executeStatement() throws UDPDriverServerException
{
try
{
// ResultSet resultSet = preparedStatement.executeQuery(); // now done in ValuesEncoder
if ( null == valuesEncoder )
valuesEncoder = new ValuesEncoder( preparedStatement, UDPDriverServer.DATAGRAM_SIZE, columnTypes, nullableColumns );
else
valuesEncoder.reExecutePreparedStatementQuery();
}
catch( Exception e )
{
throw new UDPDriverServerException( "ClientState executeStatement() failed.", e );
}
}
/**
* Clean the ClientState
*
* @throws UDPDriverServerException
*/
private void clean() throws UDPDriverServerException
{
try
{
preparedStatement.close();
preparedStatement = null;
connection = null;
valuesEncoder = null;
nextResponseToSend.clear();
nextResponseToSend = null;
columnTypes = null;
}
catch( Exception e )
{
throw new UDPDriverServerException( "ClientState clean() failed.", e );
}
}
/**
* Set the parameter values to the PreapredStatement
*
* @param type
* @param value
* @param index
* @throws UDPDriverServerException
*/
private void setValueInPreparedStatement( int type, String value, int index ) throws UDPDriverServerException
{
try
{
switch ( type )
{
case Types.INTEGER : preparedStatement.setInt( index, Integer.decode( value ) ); break;
case Types.VARCHAR : preparedStatement.setString( index, value ); break;
case Types.VARBINARY : preparedStatement.setBytes( index, value.getBytes() ); break;
default :
throw new UDPDriverServerException( "PreparedStatement parameter setter for this type is not implemented: " + type );
}
}
catch( Exception e )
{
throw new UDPDriverServerException( "ClientState setValueInPreparedStatement() failed.", e );
}
}
}