package ch.ethz.syslab.telesto.client;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import ch.ethz.syslab.telesto.client.exception.ProcessingException;
import ch.ethz.syslab.telesto.client.network.ClientConnection;
import ch.ethz.syslab.telesto.common.config.CONFIG;
import ch.ethz.syslab.telesto.common.model.Client;
import ch.ethz.syslab.telesto.common.model.ClientMode;
import ch.ethz.syslab.telesto.common.model.Message;
import ch.ethz.syslab.telesto.common.model.Queue;
import ch.ethz.syslab.telesto.common.model.ReadMode;
import ch.ethz.syslab.telesto.common.protocol.CreateQueuePacket;
import ch.ethz.syslab.telesto.common.protocol.CreateQueueResponsePacket;
import ch.ethz.syslab.telesto.common.protocol.DeleteQueuePacket;
import ch.ethz.syslab.telesto.common.protocol.GetActiveQueuesPacket;
import ch.ethz.syslab.telesto.common.protocol.GetActiveQueuesResponsePacket;
import ch.ethz.syslab.telesto.common.protocol.GetMessagesPacket;
import ch.ethz.syslab.telesto.common.protocol.GetMessagesResponsePacket;
import ch.ethz.syslab.telesto.common.protocol.GetQueueIdPacket;
import ch.ethz.syslab.telesto.common.protocol.GetQueueIdResponsePacket;
import ch.ethz.syslab.telesto.common.protocol.GetQueueNamePacket;
import ch.ethz.syslab.telesto.common.protocol.GetQueueNameResponsePacket;
import ch.ethz.syslab.telesto.common.protocol.GetQueuesPacket;
import ch.ethz.syslab.telesto.common.protocol.GetQueuesResponsePacket;
import ch.ethz.syslab.telesto.common.protocol.IdentifyClientPacket;
import ch.ethz.syslab.telesto.common.protocol.IdentifyClientResponsePacket;
import ch.ethz.syslab.telesto.common.protocol.Packet;
import ch.ethz.syslab.telesto.common.protocol.PingPacket;
import ch.ethz.syslab.telesto.common.protocol.PutMessagePacket;
import ch.ethz.syslab.telesto.common.protocol.ReadMessagePacket;
import ch.ethz.syslab.telesto.common.protocol.ReadMessageResponsePacket;
import ch.ethz.syslab.telesto.common.protocol.ReadResponsePacket;
import ch.ethz.syslab.telesto.common.protocol.RegisterClientPacket;
import ch.ethz.syslab.telesto.common.protocol.RegisterClientResponsePacket;
import ch.ethz.syslab.telesto.common.util.ErrorType;
import ch.ethz.syslab.telesto.profile.BenchmarkLog;
public class TelestoClient {
ClientConnection connection;
private AtomicInteger context = new AtomicInteger(1);
public TelestoClient() throws ProcessingException {
this(new BenchmarkLog("client"));
}
public TelestoClient(BenchmarkLog log) throws ProcessingException {
try {
connection = new ClientConnection(log);
} catch (IOException e) {
throw new ProcessingException(ErrorType.IO_ERROR, "Error while establishing connection", e);
}
}
/**
* Bounce an empty packet off the middleware to measure latency.
*
* @return the elapsed time in nanoseconds
*
* @throws ProcessingException
*/
public long ping() throws ProcessingException {
long start = System.nanoTime();
connection.sendPacket(new PingPacket());
return System.nanoTime() - start;
}
/**
* Registers the client with the specified {@link ClientMode} and name and returns the complete {@link Client}
* instance
*
* @param name
* the name of the new client
* @param mode
* the client mode to use
* @return the {@link Client} object representing the new client
* @throws ProcessingException
*/
public Client connect(String name, ClientMode mode) throws ProcessingException {
Packet packet = new RegisterClientPacket(name, mode.getByteValue());
RegisterClientResponsePacket response = (RegisterClientResponsePacket) connection.sendPacket(packet);
return new Client(response.clientId, name, mode);
}
/**
* Connect using the given client id. The returned Client instance contains the {@link ClientMode} of the identified
* client which has to be considered for future API calls in order to avoid raising errors.
*
* @param queueId
* client id to identify the client
* @return the {@link Client} object representing the new client
* @throws ProcessingException
*/
public Client connect(int clientId) throws ProcessingException {
Packet packet = new IdentifyClientPacket(clientId);
IdentifyClientResponsePacket response = (IdentifyClientResponsePacket) connection.sendPacket(packet);
return new Client(clientId, response.name, ClientMode.fromByteValue(response.mode));
}
/**
* creates a new queue with the given name. The name must be unique.
*
* @param queueName
* a unique name for the queue to be generated
* @return the representing {@link Queue} instance
* @throws ProcessingException
*/
public Queue createQueue(String queueName) throws ProcessingException {
Packet packet = new CreateQueuePacket(queueName);
CreateQueueResponsePacket response = (CreateQueueResponsePacket) connection.sendPacket(packet);
return new Queue(response.queueId, queueName);
}
/**
* remove a queue including all its messages from the system
*
* @param queueId
* the id of the queue to delete
* @throws ProcessingException
*/
public void deleteQueue(int queueId) throws ProcessingException {
Packet packet = new DeleteQueuePacket(queueId);
connection.sendPacket(packet);
}
/**
* retrieve the {@link Queue} by its name
*
* @param queueName
* the name of the queue
* @return a full {@link Queue} object representing the requested queue
* @throws ProcessingException
*/
public Queue getQueueByName(String queueName) throws ProcessingException {
Packet packet = new GetQueueIdPacket(queueName);
GetQueueIdResponsePacket response = (GetQueueIdResponsePacket) connection.sendPacket(packet);
return new Queue(response.queueId, queueName);
}
/**
* retrieve the {@link Queue} by its id
*
* @param queueId
* the id of the queue
* @return a full {@link Queue} object representing the requested queue
* @throws ProcessingException
*/
public Queue getQueueById(int queueId) throws ProcessingException {
Packet packet = new GetQueueNamePacket(queueId);
GetQueueNameResponsePacket response = (GetQueueNameResponsePacket) connection.sendPacket(packet);
return new Queue(queueId, response.name);
}
/**
* retrieve a list of all queues in the system
*
* @return list of all queues in the system
* @throws ProcessingException
*/
public List<Queue> getQueues() throws ProcessingException {
Packet packet = new GetQueuesPacket();
GetQueuesResponsePacket response = (GetQueuesResponsePacket) connection.sendPacket(packet);
return Arrays.asList(response.queues);
}
/**
* Retrieve all queues where messages for this client are waiting
*
* @return a list of queues that contain messages for the client
* @throws ProcessingException
*/
public List<Queue> getActiveQueues() throws ProcessingException {
Packet packet = new GetActiveQueuesPacket();
GetActiveQueuesResponsePacket response = (GetActiveQueuesResponsePacket) connection.sendPacket(packet);
return Arrays.asList(response.queues);
}
/**
* Query for all messages in a queue without removing any of them.
*
* Note that there is no guarantee that a message is still in a queue upon subsequent retrieving operations.
*
* @param queueId
* the queue to read from
* @return a list of all messages for the client in the specified queue
* @throws ProcessingException
*/
public List<Message> readMessages(int queueId) throws ProcessingException {
Packet packet = new GetMessagesPacket(queueId);
GetMessagesResponsePacket response = (GetMessagesResponsePacket) connection.sendPacket(packet);
return Arrays.asList(response.messages);
}
/**
* Put a single message into the queue specified in the given {@link Message} instance.
*
* This method considers the following fields of the Message instance:
* <ul>
* <li>{@link Message#queueId}
* <li>{@link Message#receiverId}
* <li>{@link Message#context}
* <li>{@link Message#priority}
* <li>{@link Message#message}
* </ul>
*
* @param message
* the message to insert
* @throws ProcessingException
* @see {@link #putMessage(Message, int[])}
* @see {@link #sendRequestResponseMessage(Message)}
*/
public void putMessage(Message message) throws ProcessingException {
Packet packet = new PutMessagePacket(message, new int[0]);
connection.sendPacket(packet);
}
/**
* Put a message into multiple queues. When this method is used, the queue specified in {@link Message#queueId} will
* be ignored.
*
* This method considers the following fields of the Message instance:
* <ul>
* <li>{@link Message#receiverId}
* <li>{@link Message#context}
* <li>{@link Message#priority}
* <li>{@link Message#message}
* </ul>
*
* @param message
* the message to be inserted
* @param queueId
* an array of queue ids specifying the queues to insert to
* @throws ProcessingException
* @see {@link #putMessage(Message)}
* @see {@link #sendRequestResponseMessage(Message)}
*/
public void putMessage(Message message, int[] queueIds) throws ProcessingException {
int[] additionalQueues = new int[queueIds.length - 1];
System.arraycopy(queueIds, 1, additionalQueues, 0, queueIds.length - 1);
Packet packet = new PutMessagePacket(message, additionalQueues);
connection.sendPacket(packet);
}
/**
* send a Message that waits for a response. This method is blocking until the response arrives.
*
* @param message
* the message to be sent
* @return The response Message
* @throws ProcessingException
* @see {@link #putMessage(Message)}
* @see {@link #putMessage(Message, int[])}
*/
public Message sendRequestResponseMessage(Message message) throws ProcessingException {
message.context = context.getAndIncrement();
Packet request = new PutMessagePacket(message, new int[0]);
connection.sendPacket(request);
// wait for response
Packet response = new ReadResponsePacket(message.queueId, message.context);
return retryUntilMessageAvailable(response);
}
/**
* retrieve (and remove) a single Message from the specified queue by priority.
*
* This method is blocking until a message is available.
*
* @param queueId
* the queue to read from
* @return the {@link Message} retrieved
* @throws ProcessingException
* @see {@link #retrieveMessage(int, int)}
* @see {@link #retrieveMessage(int, ReadMode)}
* @see {@link #retrieveMessage(int, int, ReadMode)}
*/
public Message retrieveMessage(int queueId) throws ProcessingException {
Packet packet = new ReadMessagePacket(queueId, 0, ReadMode.TIME.getByteValue());
return retryUntilMessageAvailable(packet);
}
/**
* retrieve (and remove) a single Message from the specified queue using the given {@link ReadMode}
*
* This method is blocking until a message is available.
*
* @param queueId
* the queue to read from
* @param mode
* the {@link ReadMode} to be used
* @return the {@link Message} retrieved
* @throws ProcessingException
* @see {@link #retrieveMessage(int)}
* @see {@link #retrieveMessage(int, int)}
* @see {@link #retrieveMessage(int, int, ReadMode)}
*/
public Message retrieveMessage(int queueId, ReadMode mode) throws ProcessingException {
Packet packet = new ReadMessagePacket(queueId, 0, mode.getByteValue());
return retryUntilMessageAvailable(packet);
}
/**
* retrieve (and remove) a single Message from the specified queue that was sent by the specified sender
*
* This method is blocking until a message is available.
*
* @param queueId
* the queue to read from
* @param sender
* the sender of the message
* @return the {@link Message} retrieved
* @throws ProcessingException
* @see {@link #retrieveMessage(int)}
* @see {@link #retrieveMessage(int, ReadMode)}
* @see {@link #retrieveMessage(int, int, ReadMode)}
*/
public Message retrieveMessage(int queueId, int sender) throws ProcessingException {
Packet packet = new ReadMessagePacket(queueId, sender, ReadMode.TIME.getByteValue());
return retryUntilMessageAvailable(packet);
}
/**
* retrieve (and remove) a single Message from the specified queue that was sent by the specified sender using the
* given {@link ReadMode}
*
* This method is blocking until a message is available.
*
* @param queueId
* the queue to read from
* @param sender
* the sender of the message
* @param mode
* the {@link ReadMode} to be used
* @return the {@link Message} retrieved
* @throws ProcessingException
* @see {@link #retrieveMessage(int)}
* @see {@link #retrieveMessage(int, int)}
* @see {@link #retrieveMessage(int, ReadMode)}
*/
public Message retrieveMessage(int queueId, int sender, ReadMode mode) throws ProcessingException {
Packet packet = new ReadMessagePacket(queueId, sender, mode.getByteValue());
return retryUntilMessageAvailable(packet);
}
private Message retryUntilMessageAvailable(Packet packet) throws ProcessingException {
ReadMessageResponsePacket response;
while (true) {
try {
response = (ReadMessageResponsePacket) connection.sendPacket(packet);
break;
} catch (ProcessingException e) {
if (e.type == ErrorType.NO_MESSAGES_RETRIEVED) {
try {
Thread.sleep(CONFIG.CLI_RETRY_DELAY);
} catch (InterruptedException e1) {
// Ignore
}
continue;
}
throw e;
}
}
return response.message;
}
}