0

I am playing around with EJB timers but I've run into trouble when trying to run both timers and persistent entities in the same project. In my initial setup I only had timers and these fired as expected:

@Stateless
public class TimerHandler {

    @Resource
    protected TimerService mTimerService;

    @PostConstruct
    public void init() {
        // could do cool stuff but choose not to
    }

    public Timer start(long aDuration) {
        TimerConfig conf = new TimerConfig();
        conf.setPersistent(false); // don't want the timer to be saved
        return mTimerService.createSingleActionTimer(aDuration, conf);
    }

    @Timeout
    public void timeOutAction(Timer aTimer) {
        // does fancy stuff
        System.out.println("So fancy :)");
    }

}

I had a bit of trouble getting the timers to run but I went the bruteforce way and reinstalled Payara (Glassfish). After this using the Timer was fine. I could start and cancel it as so:

@Stateful
public class MyClass {

    @EJB
    private TimerHandler mTimerHandler;

    private Timer mTimer;

    public void startTimer(int aDuration) {
        mTimer = mTimerHandler.start(aDuration);
    }

    public void stopTimer() {
        try {
            mTimer.cancel();
        } catch (NoSuchObjectLocalException | NullPointerException ex) {
            System.out.println("There is no timer running.");
        }
    }
}

However, the problem arose after I tried to add Entities to my project. My entity looks like this:

@Entity
public class TestEntity implements Serializable {

    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String testValue;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getTestValue() {
        return testValue;
    }

    public void setTestValue(String value) {
        testValue = value;
    }

    // removed standard code for @Override of equals(), 
    // hashCode() & toString()
}

Which I manipulate through my controller bean:

@Stateless
public class TestDBController {
    @PersistenceContext(unitName = "TimerTestWithDBPU")
    private EntityManager em;

    public long saveValue(String value) {
        TestEntity entity = new TestEntity();
        entity.setTestValue(value);
        em.persist(entity);
        em.flush();
        return entity.getId();
    }

    public String getValue(long aId) {
        TestEntity entity = em.find(TestEntity.class, aId);
        return entity.getTestValue();
    }
}

and I have my Persistence Unit (persistence.xml) set up in the following way:

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence" 
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
            xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence 
            http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
  <persistence-unit name="TimerTestWithDBPU" transaction-type="JTA">
    <jta-data-source>jdbc/timer_test_pool</jta-data-source>
    <exclude-unlisted-classes>false</exclude-unlisted-classes>
    <properties>
      <property name="javax.persistence.schema-generation.database.action" 
                  value="create"/>
    </properties>
  </persistence-unit>
</persistence>

After adding this Entity and Persistence Unit I get the following error:

EJB Timer Service is not available. 
            Timers for application with id [XYZ] will not be deleted

Why is this? Can't you run an application with both ejb timers and persistent entities?

Christian Eriksson
  • 2,038
  • 2
  • 22
  • 28

1 Answers1

0

It turns out, You Can! Who would have thunk it...

This phrase from the Glassfish App Dev Guide pointed me in the right direction.

Using the EJB Timer Service is equivalent to interacting with a single JDBC resource manager. If an EJB component or application accesses a database either directly through JDBC or indirectly (for example, through an entity bean’s persistence mechanism), and also interacts with the EJB Timer Service, its data source must be configured with an XA JDBC driver.

I'll try to describe what I did (I'm new in the Java EE world so some concepts and names of features might not be correct)

Turns out that you need to configure a resource in your application server (Payara) in my case so that it uses the XA JDBC drivers rather than the normal JDBC drivers. (I do not fully understand why so if someone cares to elaborate I'd love to hear it)

To do this:

  1. Create a database, give it a Name and specify the User name and Password
  2. Enter Payaras admin page -> Resources -> JDBC -> JDBC Connection Pools -> New
  3. Specify:
    • A Name, in my case "TestPool"
    • Choose Resource Type to be: "javax.sql.XADataSource"
    • Choose your vendor: I use JavaDB
  4. Click next and let Datasource Classname be the default value
  5. Specify at minimum (I believe) the following properties:
    • ServerName (eg. localhost)
    • PortNumber (eg. 1527)
    • Password (eg. test)
    • User (eg. test)
    • URL (eg. jdbc:derby://localhost:1527/Test)
    • DatabaseName (eg. Test)
  6. Click Finish

The ConnectionPool needs to be connected to a JDBC resource and get a JNDI name for the server to be able to use it. In the admin pages of Payara:

  1. Go to -> Resources -> JDBC -> JDBC Resources - New
  2. Give it a name (eg. jdbc/TestPool) and choose the Pool you created earlier.
  3. Add a description if you like (descriptions are cool)
  4. Click ok!

Now add the new connection pool as the DataSource of your project in your Persistence Unit (persistance.xml).

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1" xmlns="htt...>
  <persistence-unit name="TimerTestPU" transaction-type="JTA">
    <jta-data-source>jdbc/TestPool</jta-data-source> <!-- Magic Line -->
    .
    .
    .
  </persistence-unit>
</persistence>

At this point I got it to work. However there still seemed to be a problem, because if I ran other applications which used a timer while still using a non XA JBDC Driver for the database it actually broke that which I got to work. This manifested in not being able to Ping the default __TimerPool connection pool, with the following error (If someone could shed light on this I'd be all ears):

java.lang.NoClassDefFoundError: Could not initialize class
        org.apache.derby.jdbc.EmbeddedDriver Could not initialize class 
        org.apache.derby.jdbc.EmbeddedDriver

I ended up deleting the default connection pool for ejb drivers (__TimerPool) and created a new, using the same procedure as above (1-10) but with a name like "TimerEJB". To get that to work properly you need to "install" the database which means create a standard database that fits the Application server. Payara and Glassfish provides SQL for this in:

%install-path-of-payara%\glassfish\lib\install\databases\

choose the file ejbtimer_[VENDOR_NAME].sql and run this on the database you created. After this you should make this the default connection tool for ejb timers. At Payaras admin page:

  1. Go to -> Configurations -> server-config -> EJB Container -> EJB Timer Service
  2. Enter the JNDI name (resource name) that you created for the TimerEJB connection pool (step 8.) as the "Timer Datasource"
  3. Save

Now restart everything, or at least Payara and the database server, and you should be good to go.

The last part was inspired by @ejohansson's answer to a related question.

Hope this helps someone :)

Community
  • 1
  • 1
Christian Eriksson
  • 2,038
  • 2
  • 22
  • 28
  • AS you are using non-persistent timer, this might be a bug in Payara/Glassfish. Non-persisten timers should not require a datasource. If you can, try with fresh Payara Web Profile distribution and see if the timer works out of the box (Web Profile doesn't support persistent timers at all, so should not require a datasource for timers). – OndroMih Jan 23 '17 at 08:32