/*
 * KeepAliveDaemonTest.java
 */
package org.codehaus.activemq.transport.reliable;

import java.net.URI;

import javax.jms.JMSException;

import junit.framework.TestCase;

import org.codehaus.activemq.io.impl.DefaultWireFormat;
import org.codehaus.activemq.message.Packet;
import org.codehaus.activemq.message.ReceiptHolder;


/**
 * KeepAliveDaemonTest.java
 *  
 */
public class KeepAliveDaemonTest extends TestCase {

	private boolean forced = false;
	private boolean sent = false;
	final long keepAliveTimeout = 1000L;
	final long checkInterval = 500L;

	protected class ReliableTransportChannelMock extends ReliableTransportChannel {
		private boolean stopped = false;
		protected long lastReceiptTime = 0;

		protected ReliableTransportChannelMock() {
			super(new DefaultWireFormat());
		}

		public ReceiptHolder asyncSendWithReceipt(Packet packet) throws JMSException {
			sent = true;
			return null;
		}

		public void forceDisconnect() {
			forced = true;
		}

		public void start() throws JMSException {
		}
		
		public void stop() {
			stopped = true;
		}

		public long getLastReceiptTimestamp() {
			return lastReceiptTime;
		}
		
		public boolean isTransportConnected() {
			return !stopped;
		}
	};

	private void reset() {
		sent = false;
		forced = false;
	}
	
	public void testKeepAliveProperty() throws Exception {
		long keepAliveTimeout = 373727;
		ReliableTransportChannel ch = (ReliableTransportChannel) new ReliableTransportChannelFactory().create(new DefaultWireFormat(), new URI("vm://localhost:6144?keepAliveTimeout=" + keepAliveTimeout));
		assertEquals(ch.getKeepAliveTimeout(), keepAliveTimeout);
	}
	
	public void testNoDisconnectOnChannelWithNoTimeout() throws Exception {
		// Test on a connection that does not respond in time

		ReliableTransportChannelMock channel = new ReliableTransportChannelMock() {

			public long getLastReceiptTimestamp() {
				return 0;
			}
		};
		channel.setKeepAliveTimeout(0);
		// Make sure it's timed out
		channel.lastReceiptTime = System.currentTimeMillis();

		KeepAliveDaemon d = new KeepAliveDaemon();
		d.setCheckInterval(checkInterval);
		d.addMonitoredChannel(channel);
		d.start();
		// Sleep so the daemon might disconnect the channel (it shouldn't)
		reset();
		Thread.sleep(checkInterval * 4);
		assertFalse("No keep-alive should have been sent since the channel has a zero timeout.", sent);
		assertFalse("Should not have been disconnected since channel has a zero timeout.", forced);

	}	
	public void testNoDisconnectOnFreshChannel() throws Exception {
		// Test on a connection that has a fresh timestamp

		ReliableTransportChannelMock channel = new ReliableTransportChannelMock() {

			public long getLastReceiptTimestamp() {
				return System.currentTimeMillis();
			}
		};
		channel.setKeepAliveTimeout(keepAliveTimeout);

		KeepAliveDaemon d = new KeepAliveDaemon();
		d.setCheckInterval(checkInterval);
		d.addMonitoredChannel(channel);
		d.start();
		// Sleep so the daemon might disconnect the channel (it shouldn't)
		reset();
		Thread.sleep(checkInterval * 2);
		assertFalse("No keep-alive should have been sent since this channel has a fresh timestamp.", sent);
		assertFalse("Should NOT have been forced to disconnect.", forced);

	}

	public void testForcedDisconnect() throws Exception {
		// Test on a connection that does not respond in time
		final KeepAliveDaemon d = new KeepAliveDaemon();
		ReliableTransportChannelMock channel = new ReliableTransportChannelMock() {
				
			// Make sure it's timed out
			public long getLastReceiptTimestamp() {
				return d.getLastCheckTime() - keepAliveTimeout - 1;
			}
		};
		channel.setKeepAliveTimeout(keepAliveTimeout);
		// Make sure it's timed out

		d.setCheckInterval(checkInterval);
		d.addMonitoredChannel(channel);
		d.start();
		// Sleep so the daemon can flag the channel as a possible zombie suspect
		// since it's
		// timeout interval has expired
		//Thread.sleep(checkInterval + (checkInterval / 2));
         Thread.sleep(checkInterval);
		assertTrue("KeepAlive packet should have been sent", sent);
		assertFalse("Should NOT have been forced to disconnect yet.", forced);

		// Now sleep another another period to check that the channel is
		// disconnected
		reset();
		Thread.sleep(checkInterval + checkInterval / 2);
		assertTrue("Should have been forced to disconnect.", forced);
	}

	public void testIdleChannelThatRespondsInTime() throws Exception {
		// Test on a connection that initially is a zombie suspect but responds
		// in time

		ReliableTransportChannelMock channel = new ReliableTransportChannelMock();
		channel.setKeepAliveTimeout(keepAliveTimeout);
		// Make sure it's timed out
		channel.lastReceiptTime = System.currentTimeMillis() - keepAliveTimeout;

		KeepAliveDaemon d = new KeepAliveDaemon();
		d.setCheckInterval(checkInterval);
		d.addMonitoredChannel(channel);
		d.start();
		// Sleep so the daemon can flag the channel as a possible zombie suspect
		// since it's
		// timeout interval has expired
		Thread.sleep(checkInterval + (checkInterval / 2));
		assertTrue("KeepAlive packet should have been sent", sent);
		assertFalse("Should NOT have been forced to disconnect yet.", forced);

		// Now pretend that the channel has received a receipt for the
		// keep-alive packet or something else
		reset();
		channel.lastReceiptTime = System.currentTimeMillis() + checkInterval;
         Thread.sleep(checkInterval);
		assertFalse("Should NOT have been forced to disconnect since the new timestamp is fresh enough.", forced);
	}

	public void testStoppedChannelsAreStoppedBeingMonitored() throws Exception {
		// Test on a connection that does not respond in time

		ReliableTransportChannelMock channel = new ReliableTransportChannelMock() {

			public long getLastReceiptTimestamp() {
				return 0;
			}
		};
		channel.setKeepAliveTimeout(keepAliveTimeout);
		// Make sure it's timed out
		channel.lastReceiptTime = System.currentTimeMillis();

		KeepAliveDaemon d = new KeepAliveDaemon();
		d.setCheckInterval(checkInterval);
		d.addMonitoredChannel(channel);
		d.start();
		channel.stop();
		// Sleep so the daemon might disconnect the channel (it shouldn't)
		Thread.sleep(checkInterval + (checkInterval / 2));
		assertFalse("No keep-alive should have been sent since the channel is stopped.", sent);
		assertFalse("Should NOT have been forced to disconnect.", forced);

	}
}
