/*
* JBoss, Home of Professional Open Source.
* Copyright 2008, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.test.timer.test;
import java.rmi.RemoteException;
import java.rmi.ServerException;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import javax.ejb.EJBHome;
import javax.ejb.NoSuchObjectLocalException;
import javax.jms.Message;
import javax.jms.JMSException;
import javax.jms.Queue;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
import javax.jms.QueueReceiver;
import javax.jms.QueueSender;
import javax.jms.QueueSession;
import javax.jms.TextMessage;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.security.auth.login.LoginContext;
import junit.extensions.TestSetup;
import junit.framework.Test;
import junit.framework.TestSuite;
import org.jboss.test.JBossTestCase;
import org.jboss.test.JBossTestSetup;
import org.jboss.test.util.AppCallbackHandler;
import org.jboss.test.util.jms.JMSDestinationsUtil;
import org.jboss.test.timer.interfaces.TimerEntity;
import org.jboss.test.timer.interfaces.TimerEntityHome;
import org.jboss.test.timer.interfaces.TimerSFSB;
import org.jboss.test.timer.interfaces.TimerSFSBHome;
import org.jboss.test.timer.interfaces.TimerSLSBHome;
import org.jboss.test.timer.interfaces.TimerSLSB;
/**
* Simple unit tests for the EJB Timer service
*/
public class BasicTimerUnitTestCase extends JBossTestCase
{
private static final String EJB_TIMER_XAR = "ejb-timer.ear";
private static final int SHORT_PERIOD = 1 * 1000; // 1s
private static final int LONG_PERIOD = 20 * 1000; // 20s
private LoginContext lc;
/**
* Setup the test suite.
*/
public static Test suite() throws Exception
{
TestSetup wrapper = new JBossTestSetup(new TestSuite(BasicTimerUnitTestCase.class))
{
@Override
protected void setUp() throws Exception
{
super.setUp();
JMSDestinationsUtil.setupBasicDestinations();
JMSDestinationsUtil.deployQueue("QueueA");
JMSDestinationsUtil.deployQueue("QueueB");
JMSDestinationsUtil.deployQueue("QueueC");
JMSDestinationsUtil.deployQueue("QueueD");
redeploy(EJB_TIMER_XAR);
flushAuthCache();
}
@Override
protected void tearDown() throws Exception
{
undeploy(EJB_TIMER_XAR);
JMSDestinationsUtil.destroyDestinations();
super.tearDown();
}
};
return wrapper;
}
/**
* Constructor for the BasicTimerUnitTest object
*/
public BasicTimerUnitTestCase(String pName)
{
super(pName);
}
public void tearDown()
{
}
/**
* Test that a Stafeful Session Bean cannot access a Timer Service
*
* @throws Exception Unexpected Exception indicating an error
*/
public void testStatefulSessionBeanTimer()
throws Exception
{
TimerSFSBHome lHome = (TimerSFSBHome) getEJBHome(TimerSFSBHome.JNDI_NAME);
TimerSFSB lBean = lHome.create();
try
{
lBean.checkTimerService();
fail("Stateful Session Bean is not allowed to get a Timer Service");
}
catch (RemoteException re)
{
Throwable lCause = re.detail;
if (lCause instanceof ServerException)
{
lCause = ((ServerException) lCause).detail;
if (lCause instanceof IllegalStateException)
{
// This exception is expected -> ignore
}
else
{
throw re;
}
}
}
}
/**
* Test that a repetitive Stafeless Session Bean Timer
*
* @throws Exception Unexpected Exception indicating an error
*/
public void testStatelessSessionBeanTimer()
throws Exception
{
TimerSLSBHome home = (TimerSLSBHome) getEJBHome(TimerSLSBHome.JNDI_NAME);
TimerSLSB bean = home.create();
byte[] handle = bean.startTimer(SHORT_PERIOD);
Thread.sleep(12 * SHORT_PERIOD + SHORT_PERIOD);
int count = bean.getTimeoutCount(handle);
bean.stopTimer(handle);
assertTrue("Timeout was expected to be called at least 10 times but was "
+ "only called: " + count + " times",
count >= 10);
Thread.sleep(5 * SHORT_PERIOD);
int count2 = bean.getTimeoutCount(handle);
assertTrue("After the timer was stopped no timeout should happen but "
+ "it was called " + count2 + " more times",
count2 == 0);
bean.remove();
}
/**
* Test that a repetitive Stateless Session Bean Timer with a retry.
* NOTE: This test was added to test JIRA issue JBAS-1926.
* Since the same timer mechanism is used (with respects to what this test actualy tests)
* with entity and message beans, testing on those would probably be redundant, so a test
* is only run on stateless session bean.
*
*
* @throws Exception Unexpected Exception indicating an error
*/
public void testStatelessSessionBeanTimerRetry()
throws Exception
{
log.info("testStatelessSessionBeanTimerRetry(): start");
TimerSLSBHome home = (TimerSLSBHome) getEJBHome(TimerSLSBHome.JNDI_NAME);
TimerSLSB bean = home.create();
// We need to make sure that the next timer interval occurs
// while the retry timeout is STILL running in order to test JBAS-1926
final long retryMs = bean.getRetryTimeoutPeriod();
log.info("testStatelessSessionBeanTimerRetry():GOT RETRY TIME:" + retryMs);
assertFalse("Failed to get valid retry timeout!", retryMs == -1);
final HashMap info = new HashMap();
info.put(TimerSLSB.INFO_EXEC_FAIL_COUNT,new Integer(1)); // fail only once
// RE: JIRA Issue JBAS-1926
// This is the amount of time the task will take to execute
// This is intentionlly more than the time of the interval
// so that the we can be sure that the interval will fire again
// WHILE the retry is still in progress.
final int taskTime = SHORT_PERIOD * 2;
info.put(TimerSLSB.INFO_TASK_RUNTIME,new Integer(taskTime)); // the time is takes to execute the task
final byte[] handle = bean.startTimer(SHORT_PERIOD,info);
// Wait for 1 SHORT_PERIOD for the first firing
// Another retryMs for the amount of time it takes for the retry to happen
// and finally the amount of time that it takes to execute the task and 200ms to be safe.
Thread.sleep(SHORT_PERIOD + retryMs + taskTime + 200);
int count = bean.getTimeoutCount(handle);
bean.stopTimer(handle);
assertEquals("Timeout was called too many times. Should have been once for the initial" +
", and once for the retry during the time allotted.",2,count);
bean.remove();
}
/**
* Test that a single Stafeless Session Bean Timer
*
* @throws Exception Unexpected Exception indicating an error
*/
public void testStatelessSessionBeanSingleTimer()
throws Exception
{
TimerSLSBHome home = (TimerSLSBHome) getEJBHome(TimerSLSBHome.JNDI_NAME);
TimerSLSB bean = home.create();
byte[] handle = bean.startSingleTimer(SHORT_PERIOD);
Thread.sleep(5 * SHORT_PERIOD);
int lCount = bean.getTimeoutCount(handle);
assertTrue("Timeout was expected to be called only once but was called: "
+ lCount + " times",
lCount == 1);
try
{
bean.stopTimer(handle);
fail("A single timer should expire after the first event and therefore this "
+ "has to throw an NoSuchObjectLocalException");
}
catch (RemoteException re)
{
Throwable lCause = re.detail;
if (lCause instanceof ServerException)
{
lCause = ((ServerException) lCause).detail;
if (lCause instanceof NoSuchObjectLocalException)
{
// This exception is expected -> ignore
}
else
{
throw re;
}
}
}
// Test for the case where a transaction fails, the time should be retried once
// This test assumes the FixedRetryPolicy is left at the default of 200ms.
// The "fail-once" data in the timer will be used by the bean to fail the
// transaction once, to make sure that it is automatically retried.
log.info("testStatelessSessionBeanSingleTimer(): Testing retry on timer.");
final HashMap info = new HashMap(1);
info.put(TimerSLSB.INFO_EXEC_FAIL_COUNT,new Integer(1));
handle = bean.startSingleTimer(SHORT_PERIOD,info);
Thread.sleep(5 * SHORT_PERIOD);
assertEquals("Timeout was expected to be called twice, once inititially, one once for the retry.",
2,bean.getTimeoutCount(handle));
}
/**
* Test that a repetitive Entity Bean Timer
*
* @throws Exception Unexpected Exception indicating an error
*/
public void testEntityBeanTimer()
throws Exception
{
TimerEntityHome home = (TimerEntityHome) getEJBHome(TimerEntityHome.JNDI_NAME);
TimerEntity entity = home.create(new Integer(111));
entity.startTimer(SHORT_PERIOD);
Thread.sleep(12 * SHORT_PERIOD);
entity.stopTimer();
int lCount = entity.getTimeoutCount();
assertTrue("Timeout was expected to be called at least 10 times but was "
+ "only called: " + lCount + " times",
lCount >= 10);
Thread.sleep(5 * SHORT_PERIOD);
int lCount2 = entity.getTimeoutCount();
assertTrue("After the timer was stopped no timeout should happen but "
+ "it was called " + (lCount2 - lCount) + " more times",
lCount == lCount2);
entity.remove();
}
/**
* Test that a single Entity Bean Timer
*
* @throws Exception Unexpected Exception indicating an error
*/
public void testEntityBeanSingleTimer()
throws Exception
{
TimerEntityHome home = (TimerEntityHome) getEJBHome(TimerEntityHome.JNDI_NAME);
TimerEntity entity = home.create(new Integer(222));
entity.startSingleTimer(SHORT_PERIOD);
Thread.sleep(5 * SHORT_PERIOD);
int lCount = entity.getTimeoutCount();
assertTrue("Timeout was expected to be called only once but was called: "
+ lCount + " times",
lCount == 1);
try
{
entity.stopTimer();
fail("A single timer should expire after the first event and therefore this "
+ "has to throw an NoSuchObjectLocalException");
}
catch (RemoteException re)
{
Throwable lCause = re.detail;
if (lCause instanceof ServerException)
{
lCause = ((ServerException) lCause).detail;
if (lCause instanceof NoSuchObjectLocalException)
{
// This exception is expected -> ignore
}
else
{
throw re;
}
}
}
entity.remove();
}
/** Test an mdb that creates a timer for each onMessage
* @throws Exception
*/
public void testMDBTimer() throws Exception
{
log.info("+++ testMDBTimer");
InitialContext ctx = new InitialContext();
QueueConnectionFactory factory = (QueueConnectionFactory) ctx.lookup("ConnectionFactory");
QueueConnection queConn = null;
QueueSession session = null;
QueueSender sender = null;
QueueReceiver receiver = null;
try
{
queConn = factory.createQueueConnection();
queConn.start();
Queue queueA = (Queue) ctx.lookup("queue/QueueA");
Queue queueB = (Queue) ctx.lookup("queue/QueueB");
session = queConn.createQueueSession(false, QueueSession.AUTO_ACKNOWLEDGE);
sender = session.createSender(queueA);
receiver = session.createReceiver(queueB);
while (receiver.receive(1000) != null)
{
// Empty the queue
}
TextMessage message = session.createTextMessage();
message.setText("testMDBTimer");
message.setIntProperty("UNIQUE_ID", 123456789);
message.setJMSReplyTo(queueB);
sender.send(message);
// Get the initial onMessage ack
Message reply = receiver.receive(30000);
log.info("onMessage reply: " + reply);
assertTrue("onMessage reply != null", reply != null);
if (log.isDebugEnabled())
{
log.debug("Properties");
Enumeration e = reply.getPropertyNames();
while (e.hasMoreElements())
{
log.debug(e.nextElement());
}
}
int id = reply.getIntProperty("UNIQUE_ID");
log.debug("id=" + id);
assertTrue("onMessage reply.id = 123456789", id == 123456789);
// Get the initial timer reply
reply = receiver.receive(30000);
log.info("ejbTimeout reply: " + reply);
assertTrue("ejbTimeout reply != null", reply != null);
if (log.isDebugEnabled())
{
log.debug("Properties");
Enumeration e = reply.getPropertyNames();
while (e.hasMoreElements())
{
log.debug(e.nextElement());
}
}
id = reply.getIntProperty("UNIQUE_ID");
log.debug("id=" + id);
assertTrue("onMessage reply.id = 123456789", id == 123456789);
}
finally
{
if (receiver != null)
{
try
{
receiver.close();
} catch (JMSException ignore)
{
//
}
}
if (sender != null)
{
try
{
sender.close();
} catch (JMSException ignore)
{
//
}
}
if (session != null)
{
try
{
session.close();
} catch (JMSException ignore)
{
//
}
}
if (queConn != null)
{
try
{
queConn.close();
} catch (JMSException ignore)
{
//
}
}
}
}
/** Test an mdb that creates a timer in its ejbCreate method
* @throws Exception
*/
public void testOnCreateMDBTimer() throws Exception
{
log.info("+++ testOnCreateMDBTimer");
InitialContext ctx = new InitialContext();
QueueConnectionFactory factory = (QueueConnectionFactory) ctx.lookup("ConnectionFactory");
QueueConnection queConn = null;
QueueSession session = null;
QueueSender sender = null;
QueueReceiver receiver = null;
try
{
queConn = factory.createQueueConnection();
queConn.start();
Queue queueA = (Queue) ctx.lookup("queue/QueueC");
Queue queueB = (Queue) ctx.lookup("queue/QueueD");
session = queConn.createQueueSession(false, QueueSession.AUTO_ACKNOWLEDGE);
sender = session.createSender(queueA);
receiver = session.createReceiver(queueB);
while (receiver.receive(1000) != null)
{
// Empty the queue
}
TextMessage message = session.createTextMessage();
message.setText("testOnCreateMDBTimer");
message.setIntProperty("UNIQUE_ID", 123456788);
message.setJMSReplyTo(queueB);
sender.send(message);
// Get the initial onMessage ack
Message reply = receiver.receive(15000);
log.info("onMessage reply: " + reply);
assertTrue("onMessage reply != null", reply != null);
int id = reply.getIntProperty("UNIQUE_ID");
assertTrue("onMessage reply.id = 123456788", id == 123456788);
// Get the 10 ejbCreate timer replys
for(int n = 0; n < 10; n ++)
{
reply = receiver.receive(15000);
log.info("ejbTimeout reply: " + reply);
assertTrue("ejbTimeout reply != null", reply != null);
id = reply.getIntProperty("UNIQUE_ID");
assertTrue("onMessage reply.id = 123456788", id == 123456788);
long elapsed = reply.getLongProperty("Elapsed");
log.info("Elapsed: "+elapsed);
}
}
finally
{
if (receiver != null)
{
try
{
receiver.close();
} catch (JMSException ignore)
{
//
}
}
if (sender != null)
{
try
{
sender.close();
} catch (JMSException ignore)
{
//
}
}
if (session != null)
{
try
{
session.close();
} catch (JMSException ignore)
{
//
}
}
if (queConn != null)
{
try
{
queConn.close();
} catch (JMSException ignore)
{
//
}
}
}
}
/**
* Test with a repetitive timer the timer interface
*
* @throws Exception Unexpected Exception indicating an error
*/
public void testTimerImplementation()
throws Exception
{
TimerSLSBHome home = (TimerSLSBHome) getEJBHome(TimerSLSBHome.JNDI_NAME);
TimerSLSB bean = home.create();
byte[] handle = bean.startTimer(LONG_PERIOD);
Date lNextEvent = bean.getNextTimeout(handle);
long lUntilNextEvent = lNextEvent.getTime() - new Date().getTime();
Thread.sleep(SHORT_PERIOD);
long lTimeRemaining = bean.getTimeRemaining(handle);
Object info = bean.getInfo(handle);
assertTrue("Date of the next event must be greater than 0", lUntilNextEvent > 0);
assertTrue("Period until next event must be greater than 0", lTimeRemaining > 0);
assertTrue("Period until next event must be smaller than time until next even because it "
+ "it is called later", lUntilNextEvent > lTimeRemaining);
assertTrue("Info("+info+") must be 'TimerSLSBean.startTimer'",
"TimerSLSBean.startTimer".equals(info));
assertTrue("Must be able to get a handle", handle != null);
bean.stopTimer(handle);
}
/**
* Test that a session that does not implement TimedObject cannot obtain
* the TimerService from its EJBContext
*
* @throws Exception Unexpected Exception indicating an error
*/
public void testBadStatelessSessionBeanTimer()
throws Exception
{
TimerSLSBHome home = (TimerSLSBHome) getEJBHome("ejb/test/timer/NoTimedObjectBean");
TimerSLSB bean = home.create();
try
{
bean.startTimer(SHORT_PERIOD);
fail("Was able to call NoTimedObjectBean.startTimer");
}
catch(RemoteException e)
{
log.info("Saw exception as expected", e);
}
bean.remove();
}
private EJBHome getEJBHome(String pJNDIName)
throws NamingException
{
InitialContext lContext = new InitialContext();
return (EJBHome) lContext.lookup(pJNDIName);
}
private void login() throws Exception
{
lc = null;
String username = "jduke";
char[] password = "theduke".toCharArray();
AppCallbackHandler handler = new AppCallbackHandler(username, password);
log.debug("Creating LoginContext(ejb-timers)");
lc = new LoginContext("ejb-timers", handler);
lc.login();
log.debug("Created LoginContext, subject="+lc.getSubject());
}
private void logout() throws Exception
{
if( lc != null )
{
lc.logout();
lc = null;
}
}
}