/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is OpenEMRConnect.
*
* The Initial Developer of the Original Code is International Training &
* Education Center for Health (I-TECH) <http://www.go2itech.org/>
*
* Portions created by the Initial Developer are Copyright (C) 2011
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* ***** END LICENSE BLOCK ***** */
package ke.go.moh.oec.lib;
import java.util.ArrayList;
import java.util.List;
/**
* Mechanism to wait for a response to a message.
* When sending a message for which we expect a response, the message
* is put in a pending queue. Then as messages are received they are
* compared against the pending queue entries to see if they are a response.
* <p>
* The request thread will wait to see if a response comes. If it comes,
* the request thread will be woken, and the response will be returned to
* the caller. If no matching response comes, a timer will time out and
* wake the request thread. It will then return with no matching response.
*
* @author Jim Grace
*/
final class MessagePendingQueue implements Runnable {
private int replyTimeoutSeconds = 0;
class Entry {
private long timeout;
private Message request;
private Message response;
}
private List<Entry> queue = new ArrayList<Entry>();
private Thread timeoutThread = null;
private long nextTimeout = 0;
private int getReplyTimeoutSeconds() {
if (replyTimeoutSeconds == 0) {
replyTimeoutSeconds = 20; // Default message reply timeout in seconds
String replyTimeoutString = Mediator.getProperty("Reply.Timeout");
if (replyTimeoutString != null) {
replyTimeoutSeconds = Integer.parseInt(replyTimeoutString);
}
}
return replyTimeoutSeconds;
}
/**
* Sleeps until the timeout arrives for the first pending message in the
* queue. (All subsequent pending messages will have a later timeout.)
* Then looks through the queue to see if there are any messages to time out.
* (By now, hopefully they have been responded to.)
* Finally, get the timeout again for the first pending message in the
* queue (if any), and sleep again to wait for that one.
*/
public void run() {
nextTimeout = getNextTimeout();
while (nextTimeout > 0) {
long now = System.currentTimeMillis();
if (nextTimeout > now) { // If we aren't there yet:
try {
Thread.sleep(nextTimeout - now);
} catch (InterruptedException ex) {
}
}
timeOutEntries();
nextTimeout = getNextTimeout();
}
}
/**
* Notify the thread that is waiting for this queue entry.
* This is done either because a response has been found to the request,
* or we have timed out and given up waiting.
*
* @param e
*/
private void notify(Entry e) {
synchronized (e) {
e.notify();
}
}
/**
* Get the timeout time of the first entry in the pending queue,
* or zero if the queue is empty. The first entry in the queue will
* be the next one to time out, since it is the oldest.
* <p>
* This method is called by the timeout thread. If it returns 0
* then the timeout thread will exit. So if it returns 0, it will
* also set timeoutThread to null. This will signal that a new
* timeoutThread must be allocated to run the timer again.
*
* @return the system time in milliseconds when the next timeout will occur,
* or zero if there are no entries in the queue.
*/
private synchronized long getNextTimeout() {
if (queue.isEmpty()) {
timeoutThread = null;
return 0;
} else {
return queue.get(0).timeout;
}
}
/**
* Loops through all the entries in the pending queue, and time out
* any that have now expired.
*/
private synchronized void timeOutEntries() {
long now = System.currentTimeMillis();
for (Entry e : queue) {
if (e.timeout <= now) {
notify(e);
}
}
}
/**
* Adds an entry to the pending queue.
* <p>
* The caller should add the message to the pending queue before sending
* the message on the network. Then after sending the message, the caller
* can come back and wait for the response. This is done to prevent a
* race condition where the response may come back quickly, before the
* caller has the chance to wait for it.
* <p>
* By queuing the message before sending it on the network, the message
* will be matched with the response even if the response comes before
* the caller has the chance to wait for it.
*
* @param request
* @return the queue entry, for future reference.
*/
synchronized Entry enqueue(Message request) {
Entry e = new Entry();
e.request = request;
e.response = null;
e.timeout = System.currentTimeMillis() + getReplyTimeoutSeconds() * 1000;
queue.add(e);
return e;
}
/**
* Removes an entry from the pending queue.
*
* @param e the entry to remove.
*/
synchronized void dequeue(Entry e) {
queue.remove(e);
}
/**
* Waits for a response to this queue entry. The thread will be
* suspended until either a matching response is received, or
* the message times out.
*
* @param e the queue entry for which to wait.
* @return the response message if there was one, otherwise null
*/
Message waitForResponse(Entry e) {
//
// Start a timeout thread. Synchronize on the current object so that
// another process won't try to start it after we test it but before
// we try to start it -- and also so that the thread won't finish
// and go away before we look.
//
synchronized (this) {
if (timeoutThread == null) {
timeoutThread = new Thread(this);
timeoutThread.start();
}
}
//
// Wait for the timeout. Also handle the extreme case where we got
// a response before we even wait. In this case the response will
// not be null, becuase it will already be posted. In this case
// we don't have to wait.
//
synchronized (e) {
if (e.response == null) {
try {
e.wait();
} catch (InterruptedException ex) {
}
}
}
dequeue(e);
return e.response;
}
/**
* Test a received message to see if is the response to a request.
* If it is, wake the thread that is waiting for the response, and return true.
* If it is not, return false.
*
* @param response the message that might be a response
* @return true if the message was a response to something in the queue, otherwise false
*/
synchronized boolean findRequest(Message response) {
for (Entry e : queue) {
if (e.request.getMessageId().equals(response.getMessageId())) {
// To prevent a race condition, it is important that the following
// two statements are done in the right order. First set the
// response on the message. If the sending thread sees the
// response posted, they will not wait. The worst that can
// happen is that the notify() method does nothing.
//
// But if the notify method were first, then the sending thread
// might come in and look for the response after we've notified.
// Then it might sleep before we set the response. Then it would
// sleep needlessly, delaying the response to the user.
//
// Alternatively, we could have put a "synchronized (e) {" block
// around the next two statements, but Java might warn us that
// we have nested synchronizations (which is a dangerous thing
// in some situations even if it isn't here.) So to avoid the
// warning, we just do the next two statements in the right
// order and there is no problem.
//
e.response = response;
notify(e);
return true;
}
}
return false;
}
}