Stand alone business model with Activiti, Eclipselink and unit tests on PostgreSQL

Somewhere around 2007 – 2008 I was setting up a new project which had a business model (BM) as the central component. The business model I envisioned had a three layered structure: an AbstractBean handling all the Java bean plumbing, a from-the-database-reverse-engineered class with the properties (setters and getters), and finally a class containing the custom code on top. I wanted to settle on a standard (JPA) and tried using Hibernate at first, but it could not support this layered structure back then, so I ended up using Eclipselink (and with great satisfaction ever since, I must add).

I also have never been a fan of pure application servers (EJBs), so this BM is compiled into a single jar (already postprocessed by Eclipselink), and simply included in all kinds of applications; a client-server Swing application, a REST interface to support an Android app, several im- and exporters for EDIFACT, emails, XML, and of course a nice in-house webapp. Not having an additional single point of failure (besides the database) is very pleasing and keeps the overall architecture much more simple.

Furthermore, the BM is smart. Unlike EJB solutions, in which the entities basically are java-database records, my entities actually know how to do things. For example: you can ask a purchase order to check if a delivery is correct. In the BM the entities have logic, but to prevent bloat, larger pieces of code are extracted into service classes. Important is that all, entities and services, are distributed as one jar and thus available to all applications using the BM.

This tried concept is reused in many of my other projects. But recently I came to a project which had one major difference; the business processes were always in a state of flux. So I decided to extend this concept by allowing the processes to be described in a workflow. But I still want to be able to simply build a jar and distribute that to the applications.

There are many workflow solutions, but one based on BPEL seemed like the best choice, since BPEL is not targeted specifically at a single usage. It just tries to describe a business process, unlike for example workflows that solely focus on orchestrating web services. Initially I started to use Apache’s jBMP as the workflow engine, but I ran into some maturity problems especially in the designer department, and people suggested I should switch to Alfresco’s Activiti (which, turns out, also suffers from the maturity problems in the designer, but I stuck with it anyhow).

In the project architecture that a workflow engine envisions, the workflow usually is the master of all. It decides what happens next, whether that is a user having to do something, or an email being send, or a subworkflow that needs to start. So you get a structure like this:

Activiti has special support for business models using JPA. And this is a great setup, but not quite what I had in mind. The workflow-centric setup makes all the applications subordinates of the workflow. And if you happen to run an JEE EJB server, than that also is a great place to have the workflow execute. But my idea was more along the lines of:

The BM is still the primary, the workflow only replaces the normally hard coded order in which certain things are done. The appropriate methods in the BM can be called from the application, or by the workflow, and the BM checks and forwards the associated workflow. For example; you can only call a purchase order’s “buy” method, if the associated workflow is in the “approved” state.

In the end, no matter which architectural concept is used, there still is this problem that there are two arrows pointing to two databases. And those need to be kept in sync; if either fails, the other cannot commit it changes. And even if you put both sets of tables in the same database, they still be will accessed through separate connections, because Activiti and Eclipselink have their own connection management. This is where JTA-XA (distributed transaction management) comes in.

Because the BM is distributed as a single jar, there is no guarantee that a container like JBoss will provide a JTA-XA implementation. The BM can also run in Tomcat, or stand alone in Swing, or in a simple batch process. This means that JTA-XA in certain usages is added as a stand alone library. As with workflows, there are a number of free alternatives to choose from, two are mentioned most frequently; Atomikos and Bitronix. Bitronix has the reputation to be easy to setup, so I went with that one.

Naturally my BM has a bunch of unit tests to make sure it does, and keeps doing, what it is supposed to. So the first step in this project was to get the BM setup for the unit tests. Below is a straight through piece of code that does the whole enchilada:


public class BitronixTrial
{
	public static void main(String[] args) 
	throws SecurityException, IllegalStateException, RollbackException, HeuristicMixedException, HeuristicRollbackException, SystemException, NamingException, NotSupportedException
	{
		// setup JTA-XA (Bitronix) with two datasource that it needs to keep in sync
		// first the datasource for the business model
		PoolingDataSource lPoolingDataSourceFrozn = new PoolingDataSource();
        lPoolingDataSourceFrozn.setUniqueName("frozn");
        lPoolingDataSourceFrozn.setMaxPoolSize(3);
        lPoolingDataSourceFrozn.setShareTransactionConnections(true);
        lPoolingDataSourceFrozn.setClassName(org.postgresql.xa.PGXADataSource.class.getName());
        lPoolingDataSourceFrozn.getDriverProperties().setProperty("serverName", "127.0.0.1:5432"); 
        lPoolingDataSourceFrozn.getDriverProperties().setProperty("databaseName", "frozn"); 
        lPoolingDataSourceFrozn.getDriverProperties().setProperty("user", "postgres"); 
        lPoolingDataSourceFrozn.getDriverProperties().setProperty("password", "postgres");
        lPoolingDataSourceFrozn.init();
        // and then the data source for Activiti
		PoolingDataSource lPoolingDataSourceActiviti = new PoolingDataSource();
        lPoolingDataSourceActiviti.setUniqueName("activiti");
        lPoolingDataSourceActiviti.setMaxPoolSize(3);
        lPoolingDataSourceActiviti.setShareTransactionConnections(true);  // this is very important when using Activiti listeners
        lPoolingDataSourceActiviti.setClassName(org.postgresql.xa.PGXADataSource.class.getName());
        lPoolingDataSourceActiviti.getDriverProperties().setProperty("serverName", "127.0.0.1:5432"); 
        lPoolingDataSourceActiviti.getDriverProperties().setProperty("databaseName", "activiti"); 
        lPoolingDataSourceActiviti.getDriverProperties().setProperty("user", "postgres"); 
        lPoolingDataSourceActiviti.getDriverProperties().setProperty("password", "postgres");
        lPoolingDataSourceActiviti.init();

        // emulate a JNDI enviroment (using Glassfish's)
        System.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.naming.java.javaURLContextFactory");
        System.setProperty(Context.URL_PKG_PREFIXES,  "org.apache.naming");
        InitialContext ic = new InitialContext();
        ic.createSubcontext("java:");
        ic.createSubcontext("java:comp");
        ic.bind("java:comp/frozn", lPoolingDataSourceFrozn);
        ic.bind("java:comp/activiti", lPoolingDataSourceActiviti);
        
		// setup the entity manager factory
		Map lOptions = new HashMap();
        lOptions.put(PersistenceUnitProperties.TRANSACTION_TYPE, "JTA");
        lOptions.put(PersistenceUnitProperties.JTA_DATASOURCE, "java:comp/frozn");
        lOptions.put(PersistenceUnitProperties.TARGET_SERVER, EclipselinkBitronixServer.class.getName()); 
        lOptions.put(PersistenceUnitProperties.TARGET_DATABASE, PostgreSQLPlatform.class.getName());	        
        lOptions.put(PersistenceUnitProperties.NATIVE_SQL, "true"); // allow native SQL
        lOptions.put(PersistenceUnitProperties.CACHE_STATEMENTS, "true"); // allow caching of statements
        lOptions.put(PersistenceUnitProperties.UPPERCASE_COLUMN_NAMES, "true"); // uppercase all column names
        lOptions.put(PersistenceUnitProperties.JOIN_EXISTING_TRANSACTION, "true"); // reads and write should go through the same connection, otherwise batchtransfer will have connection conflicts
        lOptions.put(PersistenceUnitProperties.JDBC_SEQUENCE_CONNECTION_POOL , "true"); // this is deprecated, but the replacement CONNECTION_POOL_SEQUENCE does not work on 3.1.2-M1  // force sequences to use a separate pool, so rollback do not undo counter increments
        lOptions.put(PersistenceUnitProperties.CACHE_SHARED_DEFAULT, "false"); // do not use the shared cache (otherwise refresh will not update from db)
        lOptions.put(PersistenceUnitProperties.ID_VALIDATION, null); // allow zero to be used as an @Id
        lOptions.put(PersistenceUnitProperties.LOGGING_LEVEL, "finest");
		EntityManagerFactory lEntityManagerFactory = Persistence.createEntityManagerFactory("frozn", lOptions);
		
		// start a transaction
        BitronixTransactionManager lBitronixTransactionManager = TransactionManagerServices.getTransactionManager();        	        
        lBitronixTransactionManager.begin();

		// since Activiti binds stuff to the current thread, we also bind an EM to the thread. 
        EntityManager lEntityManager = lEntityManagerFactory.createEntityManager();
        EntityManagerFinderThread.register();
        EntityManagerFinderThread.associateWithCurrentThread(lEntityManager);
        
        // we some how need to way to tell Activiti what the active user is for claiming tasks, so we associate a user name with the entity manager
        BM.setEMAssociationWorkflowUser(lEntityManager, "kermit");

        // setup Activiti 
        ProcessEngineConfiguration lProcessEngineConfiguration = JtaProcessEngineConfiguration.createProcessEngineConfigurationFromInputStream(ActivitiUtil.getActivitiCfgXmlAsInputStream());
        //lProcessEngineConfiguration.setDataSourceJndiName("java:comp/activiti");
        //lProcessEngineConfiguration.setMailServerHost("localhost");
        //lProcessEngineConfiguration.setMailServerPort(25);
        //lProcessEngineConfiguration.setTransactionsExternallyManaged(true);
        //lProcessEngineConfiguration.setDatabaseSchemaUpdate("true");
        //lProcessEngineConfiguration.setJobExecutorActivate(false);
        ProcessEngine lProcessEngine = lProcessEngineConfiguration.buildProcessEngine();
	
        // create a new entiity
        Application lApplication = new Application();
		lApplication.setMortgageAmount(250_000);
		lApplication.setMortgageDuration(7*12);
		lApplication.startWorkflow();
		lApplication.calculate();
		lEntityManager.persist(lApplication);

		// commit
		lBitronixTransactionManager.commit();
		lBitronixTransactionManager.shutdown();
	}
}

And the activiti.cfg.xml:


<beans xmlns="http://www.springframework.org/schema/beans" 
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans   http://www.springframework.org/schema/beans/spring-beans.xsd">

  <bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
  
	<property name="dataSourceJndiName" value="java:comp/activiti" />
	<property name="transactionsExternallyManaged" value="true" />	
    		
    <property name="databaseSchemaUpdate" value="true" />
    <property name="jobExecutorActivate" value="false" />
    <property name="mailServerHost" value="localhost" /> 
    <property name="mailServerPort" value="25" />    
  </bean>

</beans>

Here are some of the highlights:

  • Line 06+: When setting up the DataSource you cannot use “url” as you would when setting up a PostgresSQL JDBC connection. The driver properties in the configuration are “forwarded” to an instance of PGXADataSource via its setters, and there is no “setURL”.
  • Line 22: setShareTransactionConnections is very important for Activiti. Otherwise everything will initially seem to work, but inside a task- or execution-listener a different JDBC connection will be used, resulting in locking conflicts or other strange behavior.
  • Line 30: Because this code does not run in a container, there is no JNDI implementation and one must be setup manually. Dedicated stand alone JNDI implementations like Spring’s SimpleJndiBeanFactory are not complex enough and result in a.o. not-supported exceptions. That is why the full fledged JNDI from Glassfish is used.
  • Line 40+ and 67+: You can see in the Eclipselink setup code that I prefer using Java code above configuration file. There are a few reasons: 1) I can put all configuration in one file instead of one for Activiti, one for Eclipselink, etc. 2) The compiler helps with checking if the config is ok. And 3) I can validate the configuration values. Activiti also allows for programmatic configuration, but that does not work and I had to comment out the lines and use the config file. In programmatic config Activiti keeps using the H2 database instead of PostgreSQL.
  • Line 43: Eclipselink comes bundled with a number of supported servers, but Bitronix is not one of them. So manual classes need to be written to integrate Bitronix (see below).
  • Line 55: A JTA-XA transaction is started.
  • Line 59: An EntityManager is created. This must be done inside a JTA-XA transaction, otherwise Eclipselink won’t register to it.
  • Line 60: I want my business model to be able to get to an EntityManager at any time. Because there is no container to do injection, an EntityManagerFinder is used that checks to see if one is associated with the current thread. (This is how Activiti does things as well.)
  • Line 65: When claiming a task in Activiti, you need to specify the user claiming it. You cannot use the “authenticated user”, and there also is no getter for getting the authenticated user. So we associate a user with the entity manager.
  • Line 67: The Activiti engine must be setup within a JTA-XA transaction, so it can get a connection.
  • Line 77: An entity is created and persisted through Eclipselink, the entity also starts an associated workflow. This results in insert statements in both databases and should only be committed as a whole.
  • Line 85: the JTA-XA is committed.

The classes to integrate Bitronix in Eclipselink:


public class EclipselinkBitronixServer extends ServerPlatformBase
{
	public EclipselinkBitronixServer(DatabaseSession newDatabaseSession)
	{
		super(newDatabaseSession);
	}

	@SuppressWarnings("rawtypes")
	@Override
	public Class getExternalTransactionControllerClass()
	{
		if (externalTransactionControllerClass == null)
		{
			externalTransactionControllerClass = BitronixTransactionController.class;
		}
		return externalTransactionControllerClass;
	}

	@Override
	protected void initializeServerNameAndVersion()
	{
		this.serverNameAndVersion = EclipselinkBitronixServer.class.getSimpleName();
	}
}

public class BitronixTransactionController extends JTATransactionController
{
	public BitronixTransactionController()
	{
		super();
	}

	@Override
	protected TransactionManager acquireTransactionManager() throws Exception
	{
		TransactionManager lTransactionManager = bitronix.tm.TransactionManagerServices.getTransactionManager();
		return lTransactionManager;
	}
}

The dependencies that are needed:


	<!-- activiti -->
	<dependency>
		<groupId>org.activiti</groupId>
		<artifactId>activiti-engine</artifactId>
		<version>5.10</version>
	</dependency>

	<!-- Eclipselink -->
	<dependency>	
		<groupId>org.eclipse.persistence</groupId> 		
		<artifactId>javax.persistence</artifactId>		
		<version>2.0.0</version>		
	</dependency>
	<dependency>	
		<groupId>org.eclipse.persistence</groupId> 		
		<artifactId>eclipselink</artifactId>			
		<version>2.4.0</version>	
	</dependency>

	<!-- postgres JDBC driver -->
	<dependency>
		<groupId>postgresql</groupId>
		<artifactId>postgresql</artifactId>
		<version>9.1-901.jdbc4</version>
	</dependency>

	<!-- Bitronix JTA Transaction Manager -->		
	<dependency>
		<groupId>org.codehaus.btm</groupId>
		<artifactId>btm</artifactId>
		<version>2.1.3</version>
	</dependency>

	<!-- for testing; to emulate a JNDI environment for unittesting -->
	<dependency>
		<groupId>org.glassfish.main.web</groupId>
		<artifactId>web-naming</artifactId>  		
		<version>3.1.2.2</version>
		<scope>test</scope>
	</dependency>

The approach above works great. A few points that are indirectly related:

  • Activiti’s user guide says When the variable is requested the next time, it will be loaded from the EntityManager based on the class and Id stored. This is not true; the entity is serialized in a workflow variable and then later merged to the EntityManager. It is not what I wanted, because other code may have modified the entity. But it is simple to write two methods that converts an entity to a string, for example “Application:123″, and use EntityManager.find to load it again.
  • There is a configuration setting called max_prepared_transactions in postgresql.conf. This is the maximum amount of XA transactions that can exist at any single time in the database. In PostgreSQL 9+ this is default set to 0 and will throw an exception. It is recommended to set it at least to twice the maximum amount of connections in your pool.
  • If you do not create the EntityManager inside a JTA-XA transaction, then you need to manually join it to the transaction using “joinTransaction”.
About these ads
This entry was posted in Activiti, Java, JTA-XA, unittest. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s