Showing posts with label JBoss. Show all posts
Showing posts with label JBoss. Show all posts

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">

Wednesday, May 4, 2011

Configuring JBoss mail service to use Gmail as SMTP server

In this post, I'll show how to configure the JBoss mail service to use Gmail as an SMTP server, so that you'll be able to send emails using a valid Gmail account, from within an application that has been deployed in JBoss. 

1° Environment / Prerequisites

  • As in the other posts, I am running JBoss AS 6
  • Any valid Gmail account
2° Mail service configuration


In the deploy directory of your JBoss server, you'll find a file name mail-service.xml (note that this file only exists in the deploy directory of the following server profiles : ALL, DEFAULT and standard). Simply edit this file as shown below :



 
 
 

 
  java:/GMail

  
  someuser@gmail.com

  
  myPassword

  
   
    
    
    
    
   
  
  jboss:service=Naming
 

3° Sending mail : example

On the personal project I am currently working at, mails are sent from a business method within a stateless session bean. Here's the code excerpt that does send the mail : 


Context initCtx;
  try {
   initCtx = new InitialContext();
   Session session = (Session) initCtx.lookup("java:/GMail");
   Message message = new MimeMessage(session);
   
   InternetAddress to[] = new InternetAddress[1];
   to[0] = new InternetAddress("destination@gmail.com");  
   message.setRecipients(Message.RecipientType.TO, to);
   
   message.setSubject("How to send a mail with GMAIL from JBoss AS");
   message.setContent("Hello world", "text/plain");
   Transport.send(message);
  } catch (NamingException e) {
   e.printStackTrace();
  } catch (AddressException e) {
   e.printStackTrace();
  } catch (MessagingException e) {
   e.printStackTrace();
  }
Now, let's take a look at the recipient mailbox : 


Sunday, January 2, 2011

JBOSS AS 6 : Adding and testing a new Oracle 11g datasource

1 Prerequisites
  • Oracle 11g (R1 or R2 doesn't matter but anyway, I installed the R1)
  • a defined database
  • JBoss AS 6
 2 Adding the new datasource


As described in the JBoss AS documentation, we just need to create and configure a new RDBS-ds.xml file and put it in the jboss-6.0.0.Final\server\your-server-configuration\deploy directory.

In my case, in order to get to the point as quickly as possible (and because I only need a basic datasource configuration at the moment), I created my own oracle-ds.xml file by copying the sample one which is located at : jboss-6.0.0.Final\docs\examples\jca

Here's my own oracle-ds.xml file :

<?xml version="1.0" encoding="UTF-8"?>
<datasources>
    <local-tx-datasource>
        <jndi-name>OracleDS</jndi-name>
        <connection-url>jdbc:oracle:thin:@localhost:1521:CFDB</connection-url>
        <driver-class>oracle.jdbc.driver.OracleDriver</driver-class>
        <user-name>CF_USER</user-name>
        <password>mypassword</password>
        <valid-connection-checker-class-name>org.jboss.resource.adapter.jdbc.vendor.OracleValidConnectionChecker</valid-connection-checker-class-name>
        <metadata>
            <type-mapping>Oracle11g</type-mapping>
        </metadata>
    </local-tx-datasource>
</datasources>

At this point there's 2 things you should pay attention to :
  • Make sure there's no other xxx-ds.xml file with an identical jndi-name, otherwise, the datasource cannot be deployed correctly.
  • Don't forget to drop the appropriate JDBC driver library in the same directory as the datasource configuration file, that is : jboss-6.0.0.Final\server\your-server-configuration\deploy
3 Testing the connection
1° start the application server
2° log into the server administration console (http://localhost:xxxx/admin-console/login.seam)
3° go to the datasource section as shown below


4° Select the newly created datasource and open the CONTROL tab


5° Click on the Test connection button to run a test
6° As as result, make sure the test ran successfully and that a connection has been obtained


    To check whether a connection has been obtained, especially if you ran several tests, click on the Show 
    details below... button and make sure the result value is Yes


    The result value is critical to test the connection to your datasource because as long as your datasource 
    configuration file is correct, the test could run successfully while your database is not even running, for 
    instance ...