The gateway in this system is responsible for routing messages to a payment network and processing asynchronous responses which are then communicated to the originating source system. In this case the source system interface to the gateway is quite simple, using TextMessage objects placed on a JMS queue hosted on JBoss. The response messages from the gateway are simple ACK/NACK messages containing an identifier and are placed on a separate JMS response queue processed by the source system. Unfortunately the test payment network is not always available and is only able to accept payment messages to specific test entities, this limits the variety of test cases and introduces reliability issues in the regression testing. Given the simple interface mechanism it was logical to create a gateway simulator that merely processed the request queue and sent corresponding responses to the response queue. The original implementation code as as follows:
@MessageDriven(activationConfig = { @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"), @ActivationConfigProperty(propertyName = "destination", propertyValue = "queue/InQueue") //... }) @TransactionManagement(value = TransactionManagementType.CONTAINER) @TransactionAttribute(TransactionAttributeType.REQUIRED) public class SimulateGatewayMDB implements MessageListener { static Logger log = Logger.getLogger(SimulateGatewayMDB.class); @Resource(mappedName = "ConnectionFactory") private QueueConnectionFactory connectionFactory; @Resource(mappedName = "queue/OutQueue") private Queue responseQueue; public void onMessage(Message message) { try { String reference = message.getStringProperty("Reference"); log.info("Simulator received message: " + reference); queueReply(reference); } catch (Exception e) { log.error("Message processing failed.", e); } } protected void queueReply(String sourceReference) throws Exception { final String SEPARATOR = ";"; StringBuffer message = new StringBuffer("GWRESPONSE"); message.append(SEPARATOR); message.append(sourceReference).append(SEPARATOR); message.append("ACK").append(SEPARATOR); message.append("ACK by SimulateGW").append(SEPARATOR); message.append("Response").append(SEPARATOR); publishMessage(message.toString()); } private void publishMessage(String message) throws Exception { QueueConnection queueConnection = connectionFactory.createQueueConnection(); QueueSession queueSession = queueConnection.createQueueSession(false, Session.AUTO_ACKNOWLEDGE); TextMessage textMessage = queueSession.createTextMessage(message); QueueSender sender = queueSession.createSender(responseQueue); sender.send(textMessage); queueConnection.close(); } }
This implementation works exactly as expected, placing a response message on the response queue for each request message. Unfortunately it works a bit too well... In the production environment the message is sent to an external network and has a delay between the request being sent and the response being received (hence the asynchronous processing). This delay allows the source system to perform additional processing, updating the message status and moving it along a workflow. The almost instantaneous response from the simulator prevents this processing from occurring which causes some of the test cases to fail.
The obvious solution to this timing issue is to introduce an artificial delay between the request being received and the response being sent. In a standalone application this delay would typically be accomplished by calling Thread.sleep() to delay further processing. The EJB specification is quite clear about not interfering with the container thread management:
"The enterprise bean must not attempt to manage threads. The enterprise bean must not attempt to start, stop, suspend, or resume a thread, or to change a thread’s priority or name. The enterprise bean must not attempt to manage thread groups.
These functions are reserved for the EJB container. Allowing the enterprise bean to manage threads would decrease the container’s ability to properly manage the runtime environment."
It is unclear whether or not Thread.sleep() should be considered a thread management attempt but it is preferable to use the container provided timer service. Fortunately this service is remarkably simple to use:
- Add a TimerService resource
- Add a @Timeout annotated method as a callback on timer expiry
@Resource javax.ejb.TimerService timerService; public void onMessage(Message message) { try { String reference = message.getStringProperty("Reference"); log.info("Simulator received message: " + reference); timerService.createTimer(10000, reference); } catch (Exception e) { log.error("Message processing failed.", e); } } @Timeout public void sendResponse(Timer timer) { try { log.info("Sending response: " + timer.getInfo()); queueReply((String) timer.getInfo()); } catch (Exception e) { log.error("Message processing failed.", e); } }
The changes above introduce a ten second delay between the request being received and the response being sent. A few notes about the implementation:
- The createTimer call creates a single action timer which is only triggered once when the specified period (in milliseconds) has passed.
- The reference object passed into the createTimer method is a string but can be replaced with any class implementing the Serializable interface
- There are various other methods serving different purposes which are provided by the TimerService interface
Andrew Lee Rubinger and Bill Burke's book Enterprise JavaBeans 3.1 is an excellent resource for EJB3 implementation. Additional credit to O'Reilly for providing quality book downloads in a number of DRM-free formats. Thanks to Alex Gorbatchev's SyntaxHighlighter for providing the code formatting script embedded in this page.