The EJB Timer Service is a container-managed service that provides a way to allow methods to be invoked at specific times or time intervals. The EJB Timer may be set to invoke certain EJB methods at a specific time, after a specific duration, or at specific recurring intervals. The Timer Service is implemented by the EJB container and injected into the EJB through the EJBContext interface, or the EJB can access it through a JNDI lookup. In the example presented below, the EJB timer is set when the client invokes the startTimer method. The checkStatus() method is used to get the time left for the next timeout. The example was implemented on Glassfish server, with Eclipse 3.2 used for development.
Follow these steps to implement the example.
The EJB
- The EJB interface: The interface for the EJB is shown below.
package ejb;
public interface ITimedBean {
public String checkStatus();
public void startTimer();
}ITimedBean.java - The Bean: The following is the code for the EJB followed by a brief explanation
package ejb;
import java.util.Collection;
import java.util.Iterator;
import javax.annotation.Resource;
import javax.ejb.Remote;
import javax.ejb.SessionContext;
import javax.ejb.Stateless;
import javax.ejb.Timeout;
import javax.ejb.Timer;
@Stateless(mappedName="ejb/timedBean")
@Remote
public class TimedBean implements ITimedBean {
@Resource
private SessionContext ctx;
public void startTimer() {
ctx.getTimerService().createTimer(1000, 1000, null);
System.out.println("Timers set");
}
public String checkStatus() {
Timer timer = null;
Collection timers = ctx.getTimerService().getTimers();
Iterator iter = timers.iterator();
while (iter.hasNext()) {
timer = (Timer) iter.next();
return ("Timer will expire after " + timer.getTimeRemaining() + " milliseconds.");
}
return ("No timer found");
}
@Timeout
public void handleTimeout(Timer timer) {
System.out.println("HandleTimeout called.");
}
}TimedBean.java - The @Stateless(mappedName="ejb/timedBean") annotation declares the bean as a stateless EJB. The mappedName attribute defines the global JNDI name to which this EJB will be bound.
- In the startTimer method, the ctx.getTimerService().createTimer(1000, 1000, null); call is used to create the timer, this call will create a timer that invokes the methods at specific intervals. Another way to create a timer would be to use the call ctx.getTimerService().createTimer(1000, null);, in which case, the timer will invoke the EJB method just once.
- The EJB 3 Timer Service also allows you to send client-specific information at timer creation, throught the public Timer createTimer(long initialDuration, long intervalDuration, java.io.Serializable info); and some overloaded methods as shown below in the TimerService interface.
public interface javax.ejb.TimerService {
public Timer createTimer(long duration,java.io.Serializable info);
public Timer createTimer(long initialDuration,long intervalDuration, java.io.Serializable info);
public Timer createTimer(java.util.Date expiration,java.io.Serializable info);
public Timer createTimer(java.util.Date initialExpiration,long intervalDuration, java.io.Serializable info);
public Collection getTimers();
} - A timer may be cancelled at any time, by using the cancel() method in the Timer interface.
- The @Timeout annotation declares the handleTimeout() method to be a callback method for the timer.
- Deploying: When deploying on Glassfish using the Admin console, check the "Generate RMI Stubs in a Jar File" option. The Jar file will be created in the GLASSFISH_HOME/domains/DOMAIN_NAME/generated/xml/j2ee-modules/APPLICATION_NAME directory.
The Timer Client
For this example, I used a web client. The client has a context listener which loads the timer when the application is deployed. A single servlet is used to invoke the checkStatus() method on the client.
- Start with a Dynamic Web Application in Eclipse. Include the generated client Jar file as a dependency.
- The Context Listener: The code for the context listener is shown below.
package servlets;
import javax.ejb.EJB;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import ejb.ITimedBean;
public class LoadTimer implements ServletContextListener {
@EJB(name="timerBean", mappedName="corbaname:iiop:localhost:3700#ejb/timedBean")
private ITimedBean timerBean;
public void contextInitialized(ServletContextEvent servletcontextevent) {
System.out.println("Starting timer");
timerBean.startTimer();
}
public void contextDestroyed(ServletContextEvent servletcontextevent) {
}
public ITimedBean getTimerBean() {
return timerBean;
}
public void setTimerBean(ITimedBean timerBean) {
this.timerBean = timerBean;
}
}LoadTimer.java
You will notice that the Context listener has a timerBean field with @EJB annotation. The mapped name for the EJB is defined to be the full JNDI name.@EJB(name="timerBean", mappedName="corbaname:iiop:localhost:3700#ejb/timedBean")
This is only needed in case of remote deployments on different clusters. In the same cluster you could simply use "ejb/timedBean". - The Servlet: The servlet is has a similar @EJB annotation.
package servlets;
import java.io.IOException;
import javax.ejb.EJB;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import ejb.ITimedBean;
public class TimerClient extends javax.servlet.http.HttpServlet implements javax.servlet.Servlet {
@EJB(name="timerBean", mappedName="corbaname:iiop:localhost:3700#ejb/timedBean")
private ITimedBean timerBean;
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.getWriter().println(timerBean.checkStatus());
response.getWriter().println(timerBean.checkStatus());
response.getWriter().println(timerBean.checkStatus());
}
public ITimedBean getTimerBean() {
return timerBean;
}
public void setTimerBean(ITimedBean timerBean) {
this.timerBean = timerBean;
}
}TimerClient.java - The Deployment descriptor: The deployment descriptor is listed below.
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<display-name>TimerClientWeb</display-name>
<listener>
<listener-class>servlets.LoadTimer</listener-class>
</listener>
<servlet>
<description></description>
<display-name>TimerClient</display-name>
<servlet-name>TimerClient</servlet-name>
<servlet-class>servlets.TimerClient</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>TimerClient</servlet-name>
<url-pattern>/timerClient</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
</web-app>web.xml
Timer Service Additional Info (From the EJB 3.0 Specification)
- Invocations of the methods to create and cancel timers and of the timeout callback method are typically made within a transaction.
- If the transaction is rolled back, the timer creation is rolled back and so is the case with timer cancellation.
- If container-managed transaction demarcation is used and the REQUIRED or REQUIRES_NEW transaction attribute is specified or defaulted (Required or RequiresNew if the deployment descriptor is used), the container must begin a new transaction prior to invoking the timeout callback method.
- If the transaction fails or is rolled back, the container must retry the timeout at least once.
- Timers survive container crashes, server shutdown, and the activation/passivation and load/store cycles of the enterprise beans that are registered with them.
- Since the timeout callback method is an internal method of the bean class, it has no client security context. When getCallerPrincipal is called from within the timeout callback method, it returns the container's representation of the unauthenticated identity.
No comments:
Post a Comment