/*
* Sun Public License
*
* The contents of this file are subject to the Sun Public License Version
* 1.0 (the "License"). You may not use this file except in compliance with
* the License. A copy of the License is available at http://www.sun.com/
*
* The Original Code is the SLAMD Distributed Load Generation Engine.
* The Initial Developer of the Original Code is Neil A. Wilson.
* Portions created by Neil A. Wilson are Copyright (C) 2004-2010.
* Some preexisting portions Copyright (C) 2002-2006 Sun Microsystems, Inc.
* All Rights Reserved.
*
* Contributor(s): Neil A. Wilson
*/
package com.slamd.server;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Date;
import com.slamd.admin.AccessManager;
import com.slamd.admin.AdminAccess;
import com.slamd.asn1.ASN1Element;
import com.slamd.asn1.ASN1Exception;
import com.slamd.asn1.ASN1Reader;
import com.slamd.asn1.ASN1Writer;
import com.slamd.common.Constants;
import com.slamd.common.JobClassLoader;
import com.slamd.common.SLAMDException;
import com.slamd.job.Job;
import com.slamd.job.JobClass;
import com.slamd.message.ClassTransferRequestMessage;
import com.slamd.message.ClassTransferResponseMessage;
import com.slamd.message.ClientHelloMessage;
import com.slamd.message.HelloResponseMessage;
import com.slamd.message.JobCompletedMessage;
import com.slamd.message.JobControlRequestMessage;
import com.slamd.message.JobControlResponseMessage;
import com.slamd.message.JobRequestMessage;
import com.slamd.message.JobResponseMessage;
import com.slamd.message.KeepAliveMessage;
import com.slamd.message.Message;
import com.slamd.message.ServerShutdownMessage;
import com.slamd.message.StatusRequestMessage;
import com.slamd.message.StatusResponseMessage;
/**
* This class defines a thread that is spawned by the server to handle each
* client connection. It takes care of reading messages in from the client and
* provides methods for sending messages to the client.
*
*
* @author Neil A. Wilson
*/
public class ClientConnection
extends Thread
implements Comparable
{
// The list used to hold responses to solicited messages
private ArrayList<Message> messageList;
// The reader used to read ASN.1 elements from the client.
private ASN1Reader reader;
// The writer used to write ASN.1 elements to the client.
private ASN1Writer writer;
// Indicates whether this connection should keep listening for new messages
// from the client.
private boolean keepListening;
// Indicates whether this client is operating in restricted mode. If so, then
// it should only be used to handle jobs for which it has been explicitly
// requested.
protected boolean restrictedMode;
// Indicates whether this client supports time synchronization.
private boolean supportsTimeSync;
// The client listener that accepted this connection.
private ClientListener clientListener;
// The time that this connection was established.
private Date establishedTime;
// The type of authentication performed by the client.
private int authType;
// The length of time in seconds between keepalive messages
private int keepAliveTime;
// The maximum length of time in seconds that the getResponse method will wait
// for a matching message to show up in the receive queue before returning
// null
private int maxResponseWaitTime;
// The next message ID that will be used in a message originated by the server
private int messageID;
// The job that is being processed by this connection
private Job jobInProgress;
// A mutex used to provide threadsafe access to the message list
private final Object messageListMutex;
// The SLAMD server with which this client connection is associated
private SLAMDServer slamdServer;
// The network socket used to communicate with the client
protected Socket socket;
// The authentication ID provided by the client.
private String authID;
// The authentication credentials provided by the client.
private String authCredentials;
// The client ID that the client has assigned to itself.
private String clientID;
// The IP address of the client.
private String clientIPAddress;
// The unique ID assigned by the server that identifies this connection to the
// server.
private String connectionID;
/**
* Creates a new connection that the server will use to communicate with the
* client.
*
* @param slamdServer The SLAMD server with which this connection is
* associated.
* @param clientListener The client listener that accepted this connection.
* @param socket The socket that is used to communicate with the
* client.
* @param connectionID The unique identifier associated with the client.
*
* @throws SLAMDException If there is a problem with the client connection.
*/
public ClientConnection(SLAMDServer slamdServer,
ClientListener clientListener, Socket socket,
String connectionID)
throws SLAMDException
{
slamdServer.logMessage(Constants.LOG_LEVEL_TRACE,
"In ClientConnection constructor");
setName("Client Connection " + connectionID);
this.slamdServer = slamdServer;
this.clientListener = clientListener;
this.socket = socket;
this.clientID = "(unknown)";
this.clientIPAddress = socket.getInetAddress().getHostAddress();
this.connectionID = connectionID;
this.restrictedMode = false;
this.supportsTimeSync = false;
this.establishedTime = new Date();
messageList = new ArrayList<Message>();
messageListMutex = new Object();
messageID = 1;
keepAliveTime = slamdServer.getClientListener().getKeepAliveInterval();
jobInProgress = null;
maxResponseWaitTime =
slamdServer.getClientListener().getMaxResponseWaitTime();
// Send the hello response to the client (for right now, always success)
try
{
reader = new ASN1Reader(socket.getInputStream());
writer = new ASN1Writer(socket.getOutputStream());
ClientHelloMessage helloRequest;
String respMesg = "";
try
{
ASN1Element element =
reader.readElement(Constants.MAX_BLOCKING_READ_TIME);
Message message = Message.decode(element);
if (message instanceof ClientHelloMessage)
{
helloRequest = (ClientHelloMessage) message;
slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG,
"Received hello request from client " +
toString());
this.clientID = helloRequest.getClientID();
this.restrictedMode = helloRequest.restrictedMode();
this.authID = helloRequest.getAuthID();
this.authCredentials = helloRequest.getAuthCredentials();
this.authType = helloRequest.getAuthType();
this.supportsTimeSync = helloRequest.supportsTimeSync();
if ((authID == null) || (authID.length() == 0) ||
(authCredentials == null) || (authCredentials.length() == 0))
{
if (clientListener.requireAuthentication())
{
String msg = "Authentication required but client did not " +
"provide sufficient authentication data.";
HelloResponseMessage helloResp =
new HelloResponseMessage(0,
Constants.MESSAGE_RESPONSE_SERVER_ERROR, msg, -1);
writer.writeElement(helloResp.encode());
slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT,
"Rejected new client connection " +
clientID + " -- " + msg);
try
{
socket.close();
} catch (Exception e) {}
throw new SLAMDException(msg);
}
}
else
{
if (authType != Constants.AUTH_TYPE_SIMPLE)
{
throw new SLAMDException("Invalid authentication type " +
authType);
}
AccessManager accessManager = AdminAccess.getAccessManager();
if (accessManager == null)
{
String msg = "The SLAMD server is not properly configured " +
"to perform authentication.";
HelloResponseMessage helloResp =
new HelloResponseMessage(0,
Constants.MESSAGE_RESPONSE_SERVER_ERROR, msg, -1);
writer.writeElement(helloResp.encode());
slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT,
"Rejected new client connection " +
clientID + " -- " + msg);
try
{
socket.close();
} catch (Exception e) {}
throw new SLAMDException(msg);
}
StringBuilder msgBuffer = new StringBuilder();
int resultCode =
accessManager.authenticateClient(authID, authCredentials,
msgBuffer);
if (resultCode == Constants.MESSAGE_RESPONSE_SUCCESS)
{
respMesg = "Successfully authenticated as " + authID;
}
else
{
long serverTime = (supportsTimeSync
? System.currentTimeMillis()
: -1);
String msg = msgBuffer.toString();
HelloResponseMessage helloResp =
new HelloResponseMessage(0, resultCode, msg, serverTime);
writer.writeElement(helloResp.encode());
slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT,
"Rejected new client connection " +
clientID + " -- " + msg);
try
{
socket.close();
} catch (Exception e) {}
throw new SLAMDException(msg);
}
}
}
else
{
slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG,
"Expected hello request from client but got " +
"instance of " + message.getClass().getName());
}
}
catch (ASN1Exception ae)
{
slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG,
"Unable to parse hello request message from " +
toString() + ": " + ae);
ae.printStackTrace();
}
catch (SLAMDException se)
{
slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG,
"Unable to obtain hello request message from " +
toString() + ": " + se);
se.printStackTrace();
}
long serverTime = (supportsTimeSync ? System.currentTimeMillis() : -1);
HelloResponseMessage helloResp =
new HelloResponseMessage(0, Constants.MESSAGE_RESPONSE_SUCCESS,
respMesg, serverTime);
writer.writeElement(helloResp.encode());
slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT,
"Accepted new client connection " + clientID +
" from " + socket.getInetAddress().toString());
}
catch (IOException ioe)
{
slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG,
"I/O exception in client handshake for " +
toString() + ": " + ioe);
ioe.printStackTrace();
}
slamdServer.logMessage(Constants.LOG_LEVEL_TRACE,
"Leaving ClientConnection constructor");
}
/**
* Retrieves the connection ID assigned to this client connection by the
* SLAMD server.
*
* @return The connection ID assigned to this client connection by the
* SLAMD server.
*/
public String getConnectionID()
{
return connectionID;
}
/**
* Retrieves the client ID that the client has chosen for itself.
*
* @return The client ID that the client has chosen for itself.
*/
public String getClientID()
{
return clientID;
}
/**
* Retrieves the time at which this connection was established.
*
* @return The time at which this connection was established.
*/
public Date getEstablishedTime()
{
return establishedTime;
}
/**
* Retrieves the IP address of the client associated with this connection.
*
* @return The IP address of the client associated with this connection.
*/
public String getClientIPAddress()
{
return clientIPAddress;
}
/**
* Indicates whether this client is operating in restricted mode, in which
* case it will only be used to handle jobs for which it has been explicitly
* requested.
*
* @return <CODE>true</CODE> if this client is operating in restricted mode,
* or <CODE>false</CODE> if it is not.
*/
public boolean restrictedMode()
{
return restrictedMode;
}
/**
* Retrieves the job that the client is currently processing (if any).
*
* @return The job that the client is currently processing, or
* <CODE>null</CODE> if it is not processing any job.
*/
public Job getJob()
{
return jobInProgress;
}
/**
* Listens for messages from the client and either handles them or hands them
* off to be handled elsewhere.
*/
@Override()
public void run()
{
slamdServer.logMessage(Constants.LOG_LEVEL_TRACE,
"In ClientConnection.run() for " + clientID);
slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG,
"In ClientConnection.run() for " + clientID);
keepListening = true;
// First, set a timeout on the socket. This will allow us to interrupt the
// reads periodically to send keepalive messages.
if (keepAliveTime > 0)
{
try
{
socket.setSoTimeout(keepAliveTime*1000);
slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG,
"Set socket timeout of " + keepAliveTime +
" seconds for " + clientID);
}
catch (IOException ioe)
{
slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG,
"Could not set timeout for client connection " +
clientID);
}
}
// This flag will be used to detect two consecutive failures (indicates
// that the connection has been closed without the client notifying us)
boolean consecutiveFailures = false;
// Loop infinitely (or at least until the client shuts down) and read
// messages from the client
while (keepListening)
{
try
{
ASN1Element element = reader.readElement();
if (element == null)
{
// This should only happen if the client has closed the connection.
slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT,
"Detected connection closure from client " +
clientID);
try
{
socket.close();
} catch (IOException ioe2) {}
slamdServer.getClientListener().connectionLost(this);
return;
}
slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG,
"Read a message from client " + clientID);
Message message = Message.decode(element);
slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG,
"Decoded message from client " + clientID +
" -- " + message.toString());
// We need to be able to handle two kinds of messages: solicited and
// unsolicited. Solicited messages are those that the client provides
// in response to a request from the server. Unsolicited messages are
// those that the client provides without a request from the server
// (primarily job complete messages and status response messages that
// indicate the client is shutting down). This method will handle the
// unsolicited messages, but the solicited messages will be placed into
// a queue to be picked up by the method that issued the request. It is
// possible to tell the difference between solicited and unsolicited
// messages because messages that are in response to a request from the
// server (solicited messages) will have an odd message ID and messages
// that originate from the client (unsolicited messages) will have an
// even message ID.
if ((message.getMessageID() % 2) != 0)
{
// This is a solicited message, so add it into the queue to be
// picked up by something else
synchronized (messageListMutex)
{
messageList.add(message);
slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG,
"Queueing solicited response from client " +
clientID);
}
}
else if (message instanceof JobCompletedMessage)
{
// This is a job completed message, so update the scheduler that
// the job is done
JobCompletedMessage msg = (JobCompletedMessage) message;
slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG,
"Job completed response from client " +
clientID);
if (msg.getJobID().equals(jobInProgress.getJobID()))
{
jobInProgress.clientDone(this, msg);
jobInProgress = null;
slamdServer.getClientListener().setAvailableForProcessing(this);
}
}
else if (message instanceof StatusResponseMessage)
{
// This is a status response message, but not a solicited one. It
// almost certainly means the client is shutting down
StatusResponseMessage msg = (StatusResponseMessage)
message;
slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG,
"Status response from client " + clientID);
if (msg.getClientStatusCode() ==
Constants.CLIENT_STATE_SHUTTING_DOWN)
{
// FIXME: Check for job information in the response message
// before destroying this connection
slamdServer.getClientListener().connectionLost(this);
try
{
socket.close();
} catch (IOException ioe) {}
return;
}
}
else if (message instanceof ClassTransferRequestMessage)
{
// This is a request for a Java class file.
ClassTransferRequestMessage msg = (ClassTransferRequestMessage)
message;
slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG,
"Class transfer request from client " +
clientID + " for class " + msg.getClassName());
sendClassFile(msg);
}
}
catch (InterruptedIOException iioe)
{
// If this exception was thrown, it was because the socket timeout was
// reached. We need to send a keepalive message.
try
{
KeepAliveMessage kaMsg =
new KeepAliveMessage(getMessageID());
writer.writeElement(kaMsg.encode());
slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG,
"Sent keepalive to client " + clientID);
}
catch (IOException ioe)
{
slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG,
"Unable to send keepalive to client " +
clientID + ": " + ioe);
}
}
catch (IOException ioe)
{
// Some other I/O related exception was thrown
slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG,
"I/O exception from client " + clientID +
": " + ioe);
// Some problem occurred. See if this is a second consecutive failure
// and if so, end the connection and this thread. Otherwise set a
// flag that will be used to detect a second consecutive failure
if (consecutiveFailures)
{
slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG,
"Consecutive failures on client connection " +
clientID + " -- closing");
try
{
socket.close();
} catch (IOException ioe2) {}
slamdServer.getClientListener().connectionLost(this);
return;
}
else
{
consecutiveFailures = true;
}
}
catch (SLAMDException se)
{
// The specified class could not be found
slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG,
"Exception handling message from client " +
clientID + ": " + se);
slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG,
JobClass.stackTraceToString(se));
se.printStackTrace();
}
catch (ASN1Exception ae)
{
slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG,
"Exception decoding message from client " +
clientID + ": " + ae);
slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG,
JobClass.stackTraceToString(ae));
ae.printStackTrace();
}
}
slamdServer.logMessage(Constants.LOG_LEVEL_TRACE,
"Leaving ClientConnection.run() for client " +
clientID);
slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG,
"Leaving ClientConnection.run() for client " +
clientID);
}
/**
* Sends the specified message to the client.
*
* @param message The message to send to the client.
*/
public void sendMessage(Message message)
{
slamdServer.logMessage(Constants.LOG_LEVEL_TRACE,
"In ClientConnection.sendMessage() for client " +
clientID);
slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG,
"In ClientConnection.sendMessage() for client " +
clientID);
try
{
writer.writeElement(message.encode());
slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG,
"Wrote message to client " + clientID + " -- " +
message.toString());
}
catch (IOException ioe)
{
slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG,
"Could not write message type " +
message.getMessageType() +
" to client " + clientID + ": " + ioe);
slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG,
JobClass.stackTraceToString(ioe));
ioe.printStackTrace();
}
}
/**
* Sends a class transfer response message to the client that either contains
* the requested class information or a response code that indicates why it
* could not be provided.
*
* @param transferRequest The class transfer request message for which the
* response is to be provided.
*/
public void sendClassFile(ClassTransferRequestMessage transferRequest)
{
// First, make sure that the server knows about the specified class.
String className = transferRequest.getClassName();
JobClassLoader jobClassLoader =
new JobClassLoader(getClass().getClassLoader(),
slamdServer.getClassPath());
byte[] classBytes;
try
{
classBytes = jobClassLoader.getJobClassBytes(className);
}
catch (SLAMDException se)
{
ClassTransferResponseMessage responseMessage =
new ClassTransferResponseMessage(transferRequest.getMessageID(),
Constants.MESSAGE_RESPONSE_CLASS_NOT_FOUND, className,
new byte[0]);
sendMessage(responseMessage);
return;
}
try
{
// Send the encoded response back to the client.
ClassTransferResponseMessage responseMessage =
new ClassTransferResponseMessage(transferRequest.getMessageID(),
Constants.MESSAGE_RESPONSE_SUCCESS,
className, classBytes);
sendMessage(responseMessage);
return;
}
catch (Exception e)
{
e.printStackTrace();
// An error occurred while retrieving the class data. Send a "server
// error" message back to the client.
ClassTransferResponseMessage responseMessage =
new ClassTransferResponseMessage(transferRequest.getMessageID(),
Constants.MESSAGE_RESPONSE_SERVER_ERROR, className,
new byte[0]);
sendMessage(responseMessage);
return;
}
}
/**
* Retrieves the message ID to use in the next message originating from the
* server.
*
* @return The message ID to use in the next message originating from the
* server.
*/
public synchronized int getMessageID()
{
int returnValue = messageID;
messageID += 2;
return returnValue;
}
/**
* Retrieves a message with the specified message ID and type from the queue
* used to hold responses to solicited messages. This will wait for a
* configurable maximum amount of time for the message to appear before
* returning <CODE>null</CODE>.
*
* @param messageID The message ID for the message that is expected.
* @param messageType The type of message that is expected.
*
* @return The message with the specified message ID and type from the queue
* used to hold responses to solicited messages.
*/
public Message getResponse(int messageID, int messageType)
{
slamdServer.logMessage(Constants.LOG_LEVEL_TRACE,
"In ClientConnection.getResponse(" + messageID +
", " + messageType + ") for client " + clientID);
slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG,
"In ClientConnection.getResponse(" + messageID +
", " + messageType + ") for client " + clientID);
long stopWaitingTime = System.currentTimeMillis() +
(maxResponseWaitTime * 1000);
while (System.currentTimeMillis() < stopWaitingTime)
{
synchronized (messageListMutex)
{
for (int i=0; i < messageList.size(); i++)
{
Message message = messageList.get(i);
int msgID = message.getMessageID();
int msgType = message.getMessageType();
slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG,
"Client connection for " + clientID +
" looking at message " + msgID + " of type " +
msgType + " -- " + message.toString());
if (msgID == messageID)
{
if (msgType == messageType)
{
messageList.remove(i);
slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG,
"Returning requested message " +
messageID + " of type " + messageType +
" from client " + clientID);
return message;
}
else
{
slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT,
"WARNING: Message ID " + msgID +
" was of the wrong type (expected " +
messageType + ", got " + msgType +
") for client connection " + clientID);
}
}
else
{
slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG,
"Ignoring message of type " + msgType +
" sent to client " + clientID +
" with an unexpected message ID (expected " +
messageID + ", got " + msgID + ')');
}
}
}
// The requested message wasn't found, so wait a short period of time
// before checking again
if (System.currentTimeMillis() < stopWaitingTime)
{
try
{
Thread.sleep(Constants.THREAD_BLOCK_SLEEP_TIME);
} catch (InterruptedException ie) {}
}
}
slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG,
"Timeout while waiting for message " + messageID +
" of type " + messageType + " from client " +
clientID);
return null;
}
/**
* Sends a job request message to the client.
*
* @param job The information that should be included in the job
* request.
* @param clientNumber The client number for this client.
*
* @return The response from the client.
*/
public JobResponseMessage sendJobRequest(Job job, int clientNumber)
{
slamdServer.logMessage(Constants.LOG_LEVEL_TRACE,
"In ClientConnection.sendJobRequest(" +
job.getJobID() + ") for client " + clientID);
slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG,
"In ClientConnection.sendJobRequest(" +
job.getJobID() + ") for client " + clientID);
// First, make sure that we aren't already processing a job. If we are,
// then send back a rejected message on behalf of the client.
if (jobInProgress != null)
{
JobResponseMessage response =
new JobResponseMessage(getMessageID(), job.getJobID(),
Constants.MESSAGE_RESPONSE_JOB_REQUEST_REFUSED,
"Already processing job " + jobInProgress.getJobID());
slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG,
"Client " + clientID + " already processing job " +
jobInProgress.getJobID());
return response;
}
this.jobInProgress = job;
int messageID = getMessageID();
String jobID = job.getJobID();
JobRequestMessage request =
new JobRequestMessage(messageID, jobID, job.getJobClassName(),
job.getStartTime(), job.getStopTime(),
clientNumber, job.getDuration(),
job.getThreadsPerClient(),
job.getThreadStartupDelay(),
job.getCollectionInterval(),
job.getParameterList());
try
{
writer.writeElement(request.encode());
slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG,
"Sent job request to client " + clientID + " -- " +
request.toString());
}
catch (IOException ioe)
{
slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG,
"Could not send job request to client " +
clientID + ": " + ioe);
slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG,
JobClass.stackTraceToString(ioe));
ioe.printStackTrace();
return new JobResponseMessage(messageID, jobID,
Constants.MESSAGE_RESPONSE_LOCAL_ERROR);
}
Message response = getResponse(messageID,
Constants.MESSAGE_TYPE_JOB_RESPONSE);
if (response == null)
{
slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG,
"No response to job request from client " +
clientID);
return new JobResponseMessage(messageID, jobID,
Constants.MESSAGE_RESPONSE_NO_RESPONSE);
}
else
{
// Make sure that the job request was accepted. If not, then set the
// job in progress to null.
JobResponseMessage jobResponse = (JobResponseMessage) response;
if (jobResponse.getResponseCode() != Constants.MESSAGE_RESPONSE_SUCCESS)
{
jobInProgress = null;
}
return jobResponse;
}
}
/**
* Sends a job control request to the client for the specified job.
*
* @param job The job with which the request is associated.
* @param controlType The type of operation being requested.
*
* @return The response from the client.
*/
public JobControlResponseMessage sendJobControlRequest(Job job,
int controlType)
{
slamdServer.logMessage(Constants.LOG_LEVEL_TRACE,
"In ClientConnection.sendJobControlRequest(" +
job.getJobID() + ", " + controlType +
") for client " + clientID);
slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG,
"In ClientConnection.sendJobControlRequest(" +
job.getJobID() + ", " + controlType +
") for client " + clientID);
// First, make sure that the job requested is the one that we know about.
// If not, return a failure message on behalf of the client
if ((jobInProgress == null) ||
(! jobInProgress.getJobID().equals(job.getJobID())))
{
JobControlResponseMessage response =
new JobControlResponseMessage(getMessageID(), job.getJobID(),
Constants.MESSAGE_RESPONSE_NO_SUCH_JOB,
"Job " + job.getJobID() +
" has not been defined to this client");
slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG,
"Job not known to client " + clientID);
return response;
}
int messageID = getMessageID();
String jobID = job.getJobID();
JobControlRequestMessage request =
new JobControlRequestMessage(messageID, jobID, controlType);
try
{
writer.writeElement(request.encode());
slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG,
"Sent job control request to client " +
clientID + " -- " + request.toString());
}
catch (IOException ioe)
{
slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG,
"Could not send job control request to client " +
clientID + ": " + ioe);
ioe.printStackTrace();
return new JobControlResponseMessage(messageID, jobID,
Constants.MESSAGE_RESPONSE_LOCAL_ERROR);
}
Message response =
getResponse(messageID,
Constants.MESSAGE_TYPE_JOB_CONTROL_RESPONSE);
if (response == null)
{
slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG,
"No response to job control request from client " +
clientID);
return new JobControlResponseMessage(messageID, jobID,
Constants.MESSAGE_RESPONSE_NO_RESPONSE);
}
else
{
// Make sure that the job request was accepted. If not, then set the
// job in progress to null.
JobControlResponseMessage jobControlResponse = (JobControlResponseMessage)
response;
switch (jobControlResponse.getResponseCode())
{
case Constants.MESSAGE_RESPONSE_CLASS_NOT_FOUND:
case Constants.MESSAGE_RESPONSE_CLASS_NOT_VALID:
case Constants.MESSAGE_RESPONSE_JOB_CREATION_FAILURE:
case Constants.MESSAGE_RESPONSE_NO_SUCH_JOB:
jobInProgress = null;
}
return jobControlResponse;
}
}
/**
* Sends a status request message to the client as a general status request.
* This request will not be for job-specific information.
*
* @return The status response message corresponding to this status request.
*/
public StatusResponseMessage sendStatusRequestMessage()
{
return sendStatusRequestMessage(null);
}
/**
* Sends a status request message to the client requesting information about
* the specified job.
*
* @param jobID The ID of the job for which to request status information.
*
* @return The status response message corresponding to this status request.
*/
public StatusResponseMessage sendStatusRequestMessage(String jobID)
{
slamdServer.logMessage(Constants.LOG_LEVEL_TRACE,
"In ClientConnection.sendStatusRequestMessage(" +
jobID + ") for client " + clientID);
slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG,
"In ClientConnection.sendStatusRequestMessage(" +
jobID + ") for client " + clientID);
int messageID = getMessageID();
StatusRequestMessage request = null;
if ((jobID == null) || (jobID.length() == 0))
{
request = new StatusRequestMessage(messageID);
}
else
{
request = new StatusRequestMessage(messageID, jobID);
}
try
{
writer.writeElement(request.encode());
slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG,
"Sent status request message to client " +
clientID + " -- " + request.toString());
}
catch (IOException ioe)
{
slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG,
"Could not send status request message to " +
"client " + clientID + ": " + ioe);
ioe.printStackTrace();
return new StatusResponseMessage(messageID,
Constants.MESSAGE_RESPONSE_NO_RESPONSE,
Constants.CLIENT_STATE_UNKNOWN,
"Unable to send status request to " +
"client " + clientID + ": " +
ioe);
}
Message response =
getResponse(messageID,
Constants.MESSAGE_TYPE_STATUS_RESPONSE);
if (response == null)
{
slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG,
"No response to status request from client " +
clientID);
return new StatusResponseMessage(messageID,
Constants.MESSAGE_RESPONSE_NO_RESPONSE,
Constants.CLIENT_STATE_UNKNOWN,
"Did not receive status response " +
"from client " + clientID);
}
else
{
return (StatusResponseMessage) response;
}
}
/**
* Sends a message to the client that indicates the server is shutting down,
* and then optionally closes the connection to the client.
*
* @param closeSocket Indicates whether the connection to the client should
* be closed.
*/
public void sendServerShutdownMessage(boolean closeSocket)
{
slamdServer.logMessage(Constants.LOG_LEVEL_TRACE,
"In ClientConnection.sendServerShutdownMessage() " +
"for client " + clientID);
slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG,
"In ClientConnection.sendServerShutdownMessage() " +
"for client " + clientID);
if (jobInProgress != null)
{
sendJobControlRequest(jobInProgress,
Constants.JOB_CONTROL_TYPE_STOP_DUE_TO_SHUTDOWN);
while (jobInProgress != null)
{
try
{
Thread.sleep(Constants.THREAD_BLOCK_SLEEP_TIME);
} catch (InterruptedException ie) {}
}
}
ServerShutdownMessage shutdownMessage =
new ServerShutdownMessage(getMessageID());
try
{
writer.writeElement(shutdownMessage.encode());
slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG,
"Sent shutdown message to client " + clientID +
" -- " + shutdownMessage.toString());
}
catch (IOException ioe)
{
slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG,
"Could not send shutdown message to client " +
clientID + ": " + ioe);
ioe.printStackTrace();
slamdServer.getClientListener().connectionLost(this);
}
if (closeSocket)
{
keepListening = false;
try
{
slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG,
"Closing socket for client " + clientID);
socket.close();
} catch (IOException ioe) {}
}
}
/**
* Retrieves a string with information about this client connection.
*
* @return A string with information about this client connection.
*/
@Override()
public String toString()
{
return connectionID + " (" + clientID + ' ' + clientIPAddress + ')';
}
/**
* Retrieves a string with basic status information about the current state of
* the client.
*
* @return A string with basic status information about the current state of
* the client.
*/
public String getStatusString()
{
if (jobInProgress == null)
{
if (restrictedMode)
{
return "Idle (restricted use)";
}
else
{
return "Idle";
}
}
else
{
return "Processing " + jobInProgress.getJobName() + " job " +
jobInProgress.getJobID();
}
}
/**
* Compares this client connection with the provided object. The given object
* must be a client connection object, and the comparison will be made based
* on the lexicographic ordering of the associated client IDs.
*
* @param o The client connection object to compare against this client
* connection.
*
* @return A negative value if this client connection should come before the
* provided client connection in a sorted list, a positive value if
* this client connection should come after the provided client
* connection in a sorted list, or zero if there is no difference in
* their ordering as far as this method is concerned.
*
* @throws ClassCastException If the provided object is not a client
* connection object.
*/
public int compareTo(Object o)
throws ClassCastException
{
ClientConnection c = (ClientConnection) o;
return clientID.compareTo(c.clientID);
}
}