Showing posts with label EJB3. Show all posts
Showing posts with label EJB3. Show all posts

Sunday, July 10, 2011

EJB 3 example - Exposing a stateless session beans as a web service (JBoss AS6)

In this post, we'll see how to expose an EJB3 stateless session bean as a web service using annotations (= Top-down method given that the starting point is code implementation), within JBoss AS 6. For those who are already familiar with JAX-WS annotations, the implementation part will be straightforward. But the tricky part could reside in how to retrieve its WSDL in order to invoke it : this part depends on the server in use and may even be vendor-specific. If you deploy a web service on a Tomcat server, for instance, it is possible to specify in the server.xml file that you want to publish a list of all the available web services from a given application context...

You'll see below that retrieving the WSDL from a web service deployed on JBoss AS 6 is even simpler.

1° Implementing the web service

One important thing you shouldn't forget is that, as defined in the EJB 3 specifications, only stateless session beans can be exposed as web services. Below, you'll see the 2 ways JAX-WS specifications allow you to follow in order to implement a stateless session bean as a web service : with or without defining an interface.

A. Without defining a service interface

package beans;

import java.math.BigDecimal;

import javax.ejb.Stateless;
import javax.jws.WebService;

@Stateless
@WebService(serviceName = "CalcService", portName = "CalcPort")
public class CalculatorBean {
 
 public Double add(Double operand1, Double operand2){
  BigDecimal op1 = new BigDecimal(operand1.toString());
  BigDecimal op2 = new BigDecimal(operand2.toString());
  
  return op1.add(op2).doubleValue();
 }
}

B With a service interface
package beans;

import java.util.Date;

import javax.jws.WebService;

@WebService
public interface Clock {
 
 public Date getTime();
}


package beans;

import java.util.Date;

import javax.ejb.Stateless;
import javax.jws.WebService;

@Stateless
@WebService(serviceName="G-Shock", portName="G-Shock-Port", endpointInterface="beans.Clock")
public class ClockBean implements Clock{

 @Override
 public Date getTime() {
  return new Date();
 }

}
 

To test these classes, simply create an EJB project with Eclipse (FYI, I'm using Eclipse Helios). Create a source folder named "src" within this project. Then create a package named "beans". Paste the 2 classes and the interface in the "beans package" and deploy the whole project on the JBoss AS server.

2° Accessing the published WSDL

Once both the services are deployed, go to the administration page of your JBoss AS server (since I'm working locally using the 8080 port, mine is located at http://localhost:8080) :


Then, click on "JBoss Web Services Console". This link will bring you into the JBossWS page. Then, click on  "View a list of deployed services" This will bring you to a page where you'll see a list of available web services : 


From this page, you can click on the "Endpoint Address" of each service, which will bring you to the corresponding WSDL.

VoilĂ , you're go to go with your first EJB 3 compliant web service.

Sunday, June 5, 2011

EJB 3 example - How to use Timer service (JBoss AS 6)

Through this post, I wanna show you how easy it is to use the timer service provided by EJB 3 compliant application server. The timer service is a very convenient feature that helps you schedule tasks that must be ran once or repeatedly. Besides, timers created with the timer service are persisted so that they're kept running when the server is restarted (useful for crash-recovery). As stated in the title, I use JBoss AS 6 to deploy my example but you could deploy yours on any other EJB 3 compliant application server.

Description of my example

In my example, I've implemented a stateless session bean that manage user subscriptions to a website. At each subscription, I'll start a timer that will check, every once in a while, whether the subscribed user is still active (for that purpose, every user record in my database will contain a column that contains the last connection date).

Java EE timer service : annotation or deployment descriptor?

As almost every service available in Java EE, you could configure timer service either with annotations or with the deployment descriptor. But you'll typically configure it with annotation because configuring it via the description descriptor will make you loose some flexibility : using annotation, you'll be able to start, cancel, define timer delay, and so on, from within your code.

Where to use timer service?

As for EJB 3 specifications, timer can be created only for stateless session bean and for message-driven bean.

How to use timer service ?

1° In order to create a timer, you'll need to get hold of an instance of TimerService. To do so, there are 3 possibilities : 
  • through dependency injection :  @resource private TimerService timerService;
  • through JNDI lookup
  • through EJBContext : @resource private EJBContext context; context.getTimerService( );
2° Specify the method within your bean, that will be executed by the timer. To do so, there are 2 possibilities
  • Implement a method with the following signature : public/protected void methodName (Timer timer) and annotate it with @Timeout.
  • Have your bean implement the TimedObject interface. You'll  then have to implement the public void ejbTimeout(Timer timer) method
3° Finally, you'll have to create a timer from within the bean. To do so, you can call methods such as createTimer(...) and createCalendarTimer(...) on the TimerServiceInstance.

Code example
package beans.userManagement;

import java.io.Serializable;
import java.math.BigInteger;
import java.security.SecureRandom;
import java.util.Date;

import javax.annotation.Resource;
import javax.ejb.EJB;
import javax.ejb.Stateless;
import javax.ejb.Timeout;
import javax.ejb.Timer;
import javax.ejb.TimerService;
import javax.interceptor.AroundInvoke;
import javax.interceptor.InvocationContext;

import tools.StringManager;
import beans.dao.UserDao;
import beans.emailManagement.EmailManagement;
import dto.User;
import exceptions.EmailException;
import exceptions.UserManagementException;

/**
 * Session Bean implementation class UserSubscriptionManagementBean
 */
@Stateless
public class UserSubscriptionManagementBean implements UserSubscriptionManagementLocal, UserSubscriptionManagementRemote{
    private static final long NB_DAYS_BEFORE_REMOVAL = 30L;
    private static final long NB_DAYS_BEFORE_WARNING = 15L;
    private static final long MAXIMUM_INACTIVE_PERIOD_BEFORE_REMOVAL = 1000L * 3600L * 24L * NB_DAYS_BEFORE_REMOVAL; // maximum inactivity period = 30 days 
    private static final long MAXIMUM_INACTIVE_PERIOD_BEFORE_WARNING = 1000L * 3600L * 24L * NB_DAYS_BEFORE_WARNING;

    @EJB
    private UserDao userDao;
    @EJB
    private EmailManagement emailManager;
    @Resource
    private TimerService timerService;

    private User user;

    /**
     * Default constructor. 
     */
    public UserSubscriptionManagementBean() {}

    @Override
    public void subscribe(String email, String nickname, String password, 
            String firstName, String lastName, Date birthdate, Date subscriptionDate) throws UserManagementException {

        // insert new User in database
        ...
        
        userDao.insert(user);
        // starting a timer to cleanup inactive user / send warning
        createTimers();
    }

    private void createTimers(){
        timerService.createTimer(MAXIMUM_INACTIVE_PERIOD_BEFORE_WARNING, user.getEmail());
        timerService.createTimer(MAXIMUM_INACTIVE_PERIOD_BEFORE_REMOVAL, user);
    }

    @Timeout
    public void cleanupInactiveUsers(Timer timer){
        Serializable info = timer.getInfo();

        if(info != null){
            if(info instanceof User){ // deleting inactive user
                User userToCheck = userDao.find(((User)info).getEmail());

                if(userToCheck == null){
                    return;
                }
                
                Long inactivityPeriod = System.currentTimeMillis() - userToCheck.getLastConnectionDate().getTime();

                if(inactivityPeriod >= MAXIMUM_INACTIVE_PERIOD_BEFORE_REMOVAL){
                    userDao.delete(userToCheck);
                    StringBuilder object = new StringBuilder("Automatic unsubscription");
                    StringBuilder message = new StringBuilder();
                    message.append("Your Crowd Freighting account has been deleted due to " + NB_DAYS_BEFORE_REMOVAL + " days inactivity");
                    message.append("\n\nKind regards, \nThe Crowd Freighting Crew.");

                    try {
                        emailManager.sendEmail(new String[]{userToCheck.getEmail()}, 
                                               null, 
                                               null, 
                                               object.toString(), 
                                               message.toString());                    
                    } catch (EmailException e) {
                        e.printStackTrace();
                    }
                }else{
                    timerService.createTimer(MAXIMUM_INACTIVE_PERIOD_BEFORE_REMOVAL - inactivityPeriod, userToCheck);
                }
            }else if(info instanceof String){ // send warning to user
                User userToCheck = userDao.find((String) info);
                Long inactivityPeriod = System.currentTimeMillis() - userToCheck.getLastConnectionDate().getTime();

                if(inactivityPeriod >= MAXIMUM_INACTIVE_PERIOD_BEFORE_WARNING){
                    StringBuilder object = new StringBuilder("Subscription warning");
                    StringBuilder message = new StringBuilder();
                    message.append("Your Crowd Freighting account has been inactive for " + NB_DAYS_BEFORE_WARNING + " days.");
                    message.append("\nIt will be removed from our database when it reached " + NB_DAYS_BEFORE_REMOVAL + " days of inactvity");
                    message.append("\n\nKind regards, \nThe Crowd Freighting Crew.");
                    
                    try {
                        emailManager.sendEmail(new String[]{userToCheck.getEmail()}, 
                                               null, 
                                               null, 
                                               object.toString(), 
                                               message.toString());                    
                    } catch (EmailException e) {
                        e.printStackTrace();
                    }
                }else{
                    timerService.createTimer(MAXIMUM_INACTIVE_PERIOD_BEFORE_REMOVAL - inactivityPeriod, userToCheck);
                }
            }
        }
    }
} 

Troubeshooting for JBoss AS 6


When using timer service with JBoss AS 6, you may run into the following exception : 

21:40:29,665 WARN  [com.arjuna.ats.arjuna] ARJUNA-12140 Adding multiple last resources is disallowed.
Current resource is com.arjuna.ats.internal.arjuna.abstractrecords.LastResourceRecord@1bc46c
21:40:29,665 WARN  [org.hibernate.util.JDBCExceptionReporter] SQL Error: 0, SQLState: null
21:40:29,665 ERROR [org.hibernate.util.JDBCExceptionReporter] Could not enlist in transaction
on entering meta-aware object!; - nested throwable:
(javax.transaction.SystemException: java.lang.Throwable: Unabled to enlist resource,
see the previous warnings. 
....

The explanation is that since JBoss AS 6, the server default behavior seems to allow only one datasource at a time. The thing is timer service already use a datasource to persist timer ... To solve this issue, you'll need to edit the %JBOSS_HOME%\server\%YOUR_SERVER_PROFILE%\deploy\transaction-jboss-beans.xml file and add the following property : 

<property name="allowMultipleLastResources">true</property>

in the following bean : 

<bean name="CoreEnvironmentBean" class="com.arjuna.ats.arjuna.common.CoreEnvironmentBean">