25 June 2018

In this post I would like to outline how to build a self-contained Java EE application (WAR), including JPA via a custom JDBC-driver, but with zero application-server configuration/customizing. The goal is to drop the Java EE application into a vanilla application-server. Zero configuration outside the WAR-archive. I will be using the latest Java EE 8-compliant application-servers but that does not mean you cannot use a Java EE 7-compliant server.

To achieve our goal, I will be leveraging a feature of Java EE 7 that I always found interesting but did not use very often due to it’s limitations: @DatasourceDefinition. It is a way of declaring a datasource and connection-pool within your application via annotation; instead of having to configure it outside the application via non-portable configuration-scripts for the application-server of your choice. E.g. on JBoss you would usually configure your datasource in the standalone*.xml; either directly or via a JBoss .cli-script. Below you find an example how to define a datasource via annotation in a portable way:

@DataSourceDefinition(
        name = "java:app/jdbc/primary",
        className = "org.postgresql.xa.PGXADataSource",
        user = "postgres",
        password = "postgres",
        serverName = "localhost",
        portNumber = 5432,
        databaseName = "postgres")

To me, this was seldom useful because you hard-code your database-credentials. There has been a proposal for Java EE 7 to support password-aliasing, but it never made it into the spec. In the past, I only used it for small applications and proof-of-concepts.

Until now! A twitter-discussion lead me to realize that at least Wildfly and Payara come with vendor-specific features to do variable-replaments in the annotation-values. But lets start from the beginning.

Datasource-definition and JPA

Below you find a useful pattern to define and produce a datasource within your application:

@Singleton
@DataSourceDefinition(
        name = "java:app/jdbc/primary",
        className = "org.postgresql.xa.PGXADataSource",
        user = "postgres",
        password = "postgres",
        serverName = "postgres",
        portNumber = 5432,
        databaseName = "postgres",
        minPoolSize = 10,
        maxPoolSize = 50)
public class DatasourceProducer {

	@Resource(lookup="java:app/jdbc/primary")
	DataSource ds;

	@Produces
	public DataSource getDatasource() {
		return ds;
	}
}

The @DatasourceDefinition annotation is sufficient here to bind the datasource for PostgreSQL under the global JNDI-name java:app/jdbc/primary.

The usage of @Resource and @Produces is just additional code that exposes the datasource and makes it injectable in other managed beans via @Inject Datasource ds. But for JPA, this is not needed. What we need is a persistence.xml that uses the same JNDI-name:

<?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="DefaultPU" transaction-type="JTA">
        <jta-data-source>java:app/jdbc/primary</jta-data-source>
        <exclude-unlisted-classes>false</exclude-unlisted-classes>
        <properties>
            <property name="javax.persistence.schema-generation.database.action" value="drop-and-create" />
            <property name="javax.persistence.schema-generation.scripts.action" value="drop-and-create" />
            <property name="javax.persistence.schema-generation.scripts.create-target" value="schemaCreate.ddl" />
            <property name="javax.persistence.schema-generation.scripts.drop-target" value="schemaDrop.ddl" />

            <property name="eclipselink.logging.level.sql" value="FINE" />
            <property name="eclipselink.logging.level" value="FINE" />

            <property name="hibernate.show_sql" value="true" />
            <property name="hibernate.format_sql" value="true" />
        </properties>
    </persistence-unit>
</persistence>

From here on, it is plain JPA: Define some entity and inject the EntityManager via @PersistenceContext EntityManager em; to interact with JPA.

Packaging of the JDBC-driver

You might have noticed that the @DataSourceDefinition references the JDBC-driver-class org.postgresql.xa.PGXADataSource. Obviously, it has to be available for the application so it can connect to the database. This can be achieved by placing the JDBC-driver in the application-server. E.g. under Wildfly, you register the JDBC-driver as a module. But what we want is a self-contained application where the JDBC-driver is coming within the application’s web-archive (WAR). This is very simple to achieve by adding a runtime-dependency to to the JDBC-driver. You favorite build-tool should support it. In Gradle, it is done like this:

dependencies {
    providedCompile 'javax:javaee-api:8.0'
    runtime 'org.postgresql:postgresql:9.4.1212'
}

Dynamic Configuration

What we have now is a self-contained Java EE application-archive (WAR) but the connection to the database and the credentials are hard-coded in the annotation-properties. To make this really useful, we have to be able to overwrite this values for each stage and deployment. I.e. the database-credentials to the QA-environment’s database will be different than for production. Unfortunately, there is no portable/standard way. But if you are willing to commit to a specific application-server, it is possible. A Twitter-discussion lead me to the documentation for Payara and Wildfly both supporting this feature in some way.

Payara

So, for Payara we find the documentation here. Note that we will have to modify the annotation-values like this to read from environment variables:

@DataSourceDefinition(
        name = "java:app/jdbc/primary",
        className = "org.postgresql.xa.PGXADataSource",
        user = "${ENV=DB_USER}",
        password = "${ENV=DB_PASSWORD}",
        serverName = "${ENV=DB_SERVERNAME}",
        portNumber = 5432,
        databaseName = "${ENV=DB_DATABASENAME}",
        minPoolSize = 10,
        maxPoolSize = 50)

You can find this as a working Gradle-project plus Docker-Compose environment on Github. The steps are very simple:

git clone https://github.com/38leinaD/jee-samples.git
cd jee-samples/datasource-definition/cars
./gradlew build
docker-compose -f docker-compose.payara.yml up

When the server is started, you can send below request to create a new row in a database-table:

curl -i -X POST -d '{"model": "tesla"}' -H "Content-Type: application/json" http://localhost:8080/cars/resources/cars

If you are wondering where the values like ${ENV=DB_USER} are set, check the docker-compose.payara.yml.

Widlfly

So, how about Wildfly?

For Wildfly, you can find it under "Annotation Property Replacement" in the admin-guide.

First, we have to enable the variable-replacement feature in the standalone*.xml; which is not the case by default.

<subsystem xmlns="urn:jboss:domain:ee:4.0">
    <annotation-property-replacement>true</annotation-property-replacement>
    <!-- ... -->
</subsystem>

So, technically, we still hava to modify the application-server in the standalone*.xml in this case.

But then, you can use annotation-properties in the format ${<environment-variable>:<default-value>}:

@DataSourceDefinition(
    name = "java:app/jdbc/primary",
    className = "org.postgresql.xa.PGXADataSource",
    user = "${DB_USER:postgres}",
    password = "${DB_PASSWORD:postgres}",
    serverName = "${DB_SERVERNAME:postgres}",
    portNumber = 5432,
    databaseName = "${DB_DATABASENAME:postgres}",
    minPoolSize = 10,
    maxPoolSize = 50)

If you try this, you might notice the following exception:

Caused by: org.postgresql.util.PSQLException: FATAL: role "${DB_USER:postgres}" does not exist
	at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2455)
	at org.postgresql.core.v3.QueryExecutorImpl.readStartupMessages(QueryExecutorImpl.java:2586)
	at org.postgresql.core.v3.QueryExecutorImpl.<init>(QueryExecutorImpl.java:113)
	at org.postgresql.core.v3.ConnectionFactoryImpl.openConnectionImpl(ConnectionFactoryImpl.java:222)
	at org.postgresql.core.ConnectionFactory.openConnection(ConnectionFactory.java:52)
	at org.postgresql.jdbc.PgConnection.<init>(PgConnection.java:216)
	at org.postgresql.Driver.makeConnection(Driver.java:404)
	at org.postgresql.Driver.connect(Driver.java:272)
	at java.sql.DriverManager.getConnection(DriverManager.java:664)
	at java.sql.DriverManager.getConnection(DriverManager.java:247)
	at org.postgresql.ds.common.BaseDataSource.getConnection(BaseDataSource.java:86)
	at org.postgresql.xa.PGXADataSource.getXAConnection(PGXADataSource.java:48)
	at org.jboss.jca.adapters.jdbc.xa.XAManagedConnectionFactory.getXAManagedConnection(XAManagedConnectionFactory.java:515)
	... 133 more

It seems there is a bug in the latest Wildfly that does not allow to use variables for the user/password properties. For now, we will continue with user and password beeing hardcoded and only the serverName and databaseName as dyanmic values:

@DataSourceDefinition(
    name = "java:app/jdbc/primary",
    className = "org.postgresql.xa.PGXADataSource",
    user = "postgres",
    password = "postgres",
    serverName = "${DB_SERVERNAME:postgres}",
    portNumber = 5432,
    databaseName = "${DB_DATABASENAME:postgres}",
    minPoolSize = 10,
    maxPoolSize = 50)

This works without any issues if the defaults match your environment. Explicitly overwriting these values can be achived via Java’s system-properties. E.g -DDB_SERVERNAME=postgres1 on the commandline. See docker-compose.wildfly.yml for a complete example. Before you can run this Wildfly-setup in the demo-application, you need to comment in the right annotation in DatasourceProducer.java. The default setup is for Payara.

Liberty

Liberty does not have support for variables yet, but there is interest and an issue has been filed:

Conclusion

If you make a choice for either Payara or Wildfly, you are able to build a truely self-contained Java EE application. We have seen how to achive this for a WAR-archive leveraging JPA or plain JDBC. The JDBC-driver is contained within the WAR-archive and configuration for the datasources can be inject from the outside via environment variables or Java system-properties.

Payara and Wildfly offer slightly different mechanisms and syntax. Payara shines because it does not require any additional application-server config. But we cannot specify defaults in the annotation-values and always need to provide environment-variables from the outside.

Wildfly allows to set default-values on the annotation-properties. This makes it possible to deploy e.g. in a development-environment without the need to set any environment-variables. A minor disadvantage is that the default configuration does not have the annotation-property-replacement enabled. So, the only vendor-specific config that is required is the enabling of this feature. Also, currently this mechanism is riddled by a bug. Overwriting the user/password is not working at the time of writing.

With this, both application-servers offer a useful feature for cloud-native applications. Unfortunately, you have to decide for a specific application-server to leverage it. But standardization-efforts are already on their way. The above discussion on Twitter has already been brought over to the Jakarta EE mailing-list. Feel free to join the discussion if you think this is a useful feature that should be standardized.

Post Mortem

Some time after writing this article, I notices that the OmniFaces library comes with a nice workaround via a wrapper datasource that reads all the wrapped datasource’s configuration from a config-file.

Arjan Tijms, who is one of the creators of the library, has described the implementation in detail on his blog.