/* * (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.net.InetAddress; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import com.ibm.gaiandb.Logger; import com.ibm.gaiandb.diags.GDBMessages; import com.ibm.gaiandb.udpdriver.common.SocketHelper; import com.ibm.gaiandb.udpdriver.common.protocol.CloseRequest; 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; /** * RunnableWorker aims to be run on a thread from a thread pool. * * It is started by the server listener and processes a protocol Message. * * It contains a static concurrent map , shared with the other workers, containing all * the information about each client, identified by their query ID, and their current state (ClientState object). * Depending on the type of message it has to process, a RunnableWorker may need to modify this structure to * add/modify information about a client. * * Once it has processed the message, the RunnableWorker has finished its job and the thread will * be return to the pool. * * Because multiple RunnableWorker could access a ClientState at the same time, this object is * protected by a Semaphore. A RunnableWorker has to acquire the permit to modify the ClientState. * * @author lengelle * */ public class RunnableWorker implements Runnable { // Use PROPRIETARY notice if class contains a main() method, otherwise use COPYRIGHT notice. public static final String COPYRIGHT_NOTICE = "(c) Copyright IBM Corp. 2010"; private static final Logger logger = new Logger( "RunnableWorker", 25 ); /** * The concurrent structure that contains the ClientState associated with each query */ private static ConcurrentMap< String, ClientState > clientMap = new ConcurrentHashMap< String, ClientState >(); private SocketHelper socket; private Message message; private ConnectionManager connectionManagerForPreparedStatement; private ConnectionManager connectionManagerForStatement; public RunnableWorker( Message message, SocketHelper socket, ConnectionManager connectionManagerForPreparedStatement, ConnectionManager connectionManagerForStatement ) throws SQLException { this.socket = socket; this.message = message; this.connectionManagerForPreparedStatement = connectionManagerForPreparedStatement; this.connectionManagerForStatement = connectionManagerForStatement; } /** * Process the message. */ public void run() { try { switch ( message.getType() ) { case Message.QUERY_REQUEST : QueryRequest queryRequest = ( QueryRequest )message; processQueryRequest( queryRequest ); break; case Message.EXECUTE_QUERY_REQUEST : ExecuteQueryRequest executeQueryRequest = ( ExecuteQueryRequest )message; processExecuteQueryRequest( executeQueryRequest ); break; case Message.NEXT_VALUES_REQUEST : NextValuesRequest nextValuesRequest = ( NextValuesRequest )message; processNextValuesRequest( nextValuesRequest ); break; case Message.CLOSE_REQUEST : CloseRequest closeRequest = ( CloseRequest )message; processCloseRequest( closeRequest ); break; default: throw new UDPDriverServerException( "Message is not conformed." ); } } catch( Exception e ) { logger.logException( GDBMessages.NETDRIVER_SERVER_INTERRUPTED_ERROR, "RunnableWorker Interrupted End." , e ); } } /** * Process a QueryRequest. * * @param queryRequest * @throws UDPDriverServerException */ private void processQueryRequest( QueryRequest queryRequest ) throws UDPDriverServerException { try { // Create a ClientState ClientState clientState = createClientState( queryRequest ); clientState.acquirePermit(); // Build the response MetaData response = clientState.processQueryRequest( queryRequest ); if ( response == null ) { clientState.releasePermit(); return; } String key; if ( queryRequest.getStatementType() == Message.STATEMENT ) { if ( !response.containsLastValues() ) { // Register the ClientState key = getKeyFromMessage( queryRequest ); if ( !createNewEntryInMap( key, clientState, clientMap ) ) { // Probably a duplicate message clientState.releasePermit(); return; } sendMessage( response.serializeMessage(), queryRequest.getEmittingAddress(), queryRequest.getEmittingPort() ); clientState.serializeNextValues(); } else { sendMessage( response.serializeMessage(), queryRequest.getEmittingAddress(), queryRequest.getEmittingPort() ); cleanClientState( clientState, connectionManagerForStatement ); } } else { // Register the ClientState key = getKeyFromMessage( queryRequest ); if ( !createNewEntryInMap( key, clientState, clientMap ) ) { // Probably a duplicate message clientState.releasePermit(); return; } sendMessage( response.serializeMessage(), queryRequest.getEmittingAddress(), queryRequest.getEmittingPort() ); } clientState.releasePermit(); MessageFactory.returnMessage( queryRequest ); MessageFactory.returnMessage( response ); } catch( Exception e ) { throw new UDPDriverServerException( "ThreadWorker processQueryRequest() failed: " + e.getMessage(), e ); } } /** * Process a ExecuteQueryRequest. * * @param executeQueryRequest * @throws UDPDriverServerException */ private void processExecuteQueryRequest( ExecuteQueryRequest executeQueryRequest ) throws UDPDriverServerException { try { // Obtain a ClientState from the map String key = getKeyFromMessage( executeQueryRequest ); ClientState clientState = getEntryFromMap( key, clientMap ); if ( clientState == null ) { logger.logThreadWarning(GDBMessages.NETDRIVER_CLIENT_QUERY_STATE_LOOKUP_ERROR, "Unable to lookup client query state on server. " + "This node may have been recycled (ignoring client request), queryID: " + executeQueryRequest.getQueryID()); return; } clientState.acquirePermit(); // Build the response ExecuteQueryResponse response = clientState.processExecuteQueryRequest( executeQueryRequest ); if ( response == null ) { clientState.releasePermit(); logger.logThreadWarning(GDBMessages.NETDRIVER_MESSAGE_RESPONSE_BUILD_ERROR, "Unable to build response message (ignoring client request), queryID: " + executeQueryRequest.getQueryID()); return; } sendMessage( response.serializeMessage(), executeQueryRequest.getEmittingAddress(), executeQueryRequest.getEmittingPort() ); if ( !response.containsLastValues() ) { clientState.serializeNextValues(); } clientState.releasePermit(); MessageFactory.returnMessage( executeQueryRequest ); MessageFactory.returnMessage( response ); } catch( Exception e ) { throw new UDPDriverServerException( "ThreadWorker processExecuteQueryRequest() failed.", e ); } } /** * Process a NextValuesRequest * * @param nextValuesRequest * @throws UDPDriverServerException */ private void processNextValuesRequest( NextValuesRequest nextValuesRequest ) throws UDPDriverServerException { try { // Obtain a ClientState from the map String key = getKeyFromMessage( nextValuesRequest ); ClientState clientState = getEntryFromMap( key, clientMap ); if ( clientState == null ) { return; } clientState.acquirePermit(); // Build the response NextValuesResponse response = clientState.processNextValuesRequest( nextValuesRequest ); if ( response == null ) { clientState.releasePermit(); return; } if ( response.containsLastValues() ) { if ( clientState.getStatementType() == Message.STATEMENT ) { cleanClientState( clientState, connectionManagerForStatement ); removeEntryFromMap( key, clientMap ); } sendMessage( response.serializeMessage(), nextValuesRequest.getEmittingAddress(), nextValuesRequest.getEmittingPort() ); MessageFactory.returnMessage( nextValuesRequest ); MessageFactory.returnMessage( response ); } else { sendMessage( response.serializeMessage(), nextValuesRequest.getEmittingAddress(), nextValuesRequest.getEmittingPort() ); MessageFactory.returnMessage( nextValuesRequest ); MessageFactory.returnMessage( response ); clientState.serializeNextValues(); } clientState.releasePermit(); } catch( Exception e ) { throw new UDPDriverServerException( "ThreadWorker processNextValuesRequest() failed.", e ); } } /** * Process a CloseRequest * * @param closeRequest * @throws UDPDriverServerException */ private void processCloseRequest( CloseRequest closeRequest ) throws UDPDriverServerException { try { // Obtain a ClientState from the map String key = getKeyFromMessage( closeRequest ); ClientState clientState = getEntryFromMap( key, clientMap ); if ( clientState == null ) { return; } clientState.acquirePermit(); cleanClientState( clientState, connectionManagerForPreparedStatement ); removeEntryFromMap( key, clientMap ); clientState.releasePermit(); MessageFactory.returnMessage( closeRequest ); } catch( Exception e ) { throw new UDPDriverServerException( "ThreadWorker processCloseRequest() failed.", e ); } } private void cleanClientState( ClientState clientState, ConnectionManager connectionManager ) throws UDPDriverServerException { try { Connection connection = clientState.processCloseRequest(); if ( connection == null ) { return; } connectionManager.releaseConnection( connection ); } catch( Exception e ) { throw new UDPDriverServerException( "ThreadWorker removeClientState() failed.", e ); } } private ClientState createClientState( QueryRequest queryRequest ) throws UDPDriverServerException { try { ConnectionManager connectionManager = null; if ( queryRequest.getStatementType() == Message.STATEMENT ) { connectionManager = connectionManagerForStatement; } else { connectionManager = connectionManagerForPreparedStatement; } Connection connection = connectionManager.getConnection(); PreparedStatement preparedStatement = connection.prepareStatement( queryRequest.getQuery() ); ClientState clientState = new ClientState( preparedStatement, queryRequest.getStatementType() ); return clientState; } catch( Exception e ) { throw new UDPDriverServerException( "ThreadWorker createClientState() failed." + e.getMessage(), e ); } } /** * Create a new entry in the map. * Return true if successful. * * @param key * @param clientState * @param map * @return * @throws UDPDriverServerException */ private boolean createNewEntryInMap( String key, ClientState clientState, ConcurrentMap< String, ClientState > map ) throws UDPDriverServerException { if ( map == null ) { throw new UDPDriverServerException( "ThreadWorker createNewEntryInMap() failed. Map is null." ); } if ( !map.containsKey( key ) ) { map.put( key, clientState ); return true; } return false; } /** * Get a reference on a ClientState in the map. * Return null if the ClientState associated with the key is not found. * * @param key * @param map * @return * @throws UDPDriverServerException */ private ClientState getEntryFromMap( String key, ConcurrentMap< String, ClientState > map ) throws UDPDriverServerException { if ( map == null ) { throw new UDPDriverServerException( "ThreadWorker getEntryFromMap() failed. Map is null." ); } ClientState clientState = map.get( key ); return clientState; } private void removeEntryFromMap( String key, ConcurrentMap< String, ClientState > map ) throws UDPDriverServerException { if ( map == null ) { throw new UDPDriverServerException( "ThreadWorker removeEntryFromMap() failed. Map is null." ); } map.remove( key ); } /** * Create a unique key from a message. * The key is a concatenation of the queryID, the client address and the client port. * * @param message * @return */ private String getKeyFromMessage( Message message ) { String queryID = message.getQueryID(); StringBuffer sb = new StringBuffer( message.getQueryID().length() + 15 ); // Approximatively port + host sb.append( message.getEmittingAddress() ); sb.append( message.getEmittingPort() ); sb.append( queryID ); return sb.toString(); } /** * Send a message through the network. * If the message size exceeds the datagram size, a warning is logged. * * @param message * @param address * @param port * @throws UDPDriverServerException */ private void sendMessage( byte[] message, InetAddress address, int port ) throws UDPDriverServerException { try { if ( message.length > UDPDriverServer.DATAGRAM_SIZE ) { //For debug purpose, because it is an expensive operation //ResponseWithValues rwv = (ResponseWithValues)MessageFactory.getMessage( message ); //logger.logWarning( "A message longer than the current datagram size has been sent. Message type : " + message[0] +" NumberOfRows: "+rwv.getNumberOfRows()+" totalSize: "+message.length+" compared to datagramSize: "+UDPDriverServer.DATAGRAM_SIZE ); logger.logWarning( GDBMessages.NETDRIVER_MESSAGE_LONGER_THAN_DATAGRAM, "A message longer than the current datagram size has been sent. Message type : " + message[0] ); //MessageFactory.returnMessage( rwv ); } socket.send( message, address, port ); } catch( Exception e ) { throw new UDPDriverServerException( "RunnableWorker sendMessage() failed.", e ); } } }