Deploy an app to Heroku using Wildfly and Postgresql driver

Since a Postgres Database is the most simple persistent datastore at Heroku, I was trying to setup a jee application example using this SQL database. If you have already read some of my previous blogposts, I am using Buildpack API to deploy the application to a Dyno.

Since I already got an example working with Wildfly and Mysql, it was an easy step to conduct a buildpack to install the Postgresql driver into the Wildfly container. It’s now available at https://github.com/mwiede/heroku-buildpack-wildfly-postgresql.

I also provide an example of the application, which uses maven properties to extract the database url and credentials from system properties and use them in persistence.xml.

Here are the build and deploy steps to make it work (compare to https://github.com/mwiede/greeter#usage)

$ git clone https://github.com/mwiede/greeter.git
$ cd greeter
$ heroku create
$ heroku addons:create heroku-postgresql:hobby-dev
$ heroku buildpacks:clear
$ heroku buildpacks:add heroku/java
$ heroku buildpacks:add https://github.com/mwiede/heroku-buildpack-wildfly
$ heroku buildpacks:add https://github.com/mwiede/heroku-buildpack-wildfly-postgresql
$ git push heroku master

encode special characters using maven-antrun-plugin

Recently I was deploying an java web application on heroku, but one of the fields, which were picked up from system properties to be used during the maven build to set the credentials of the datasource, contained a non escaped character, which brought the deployment to fail:

21:37:17,355 ERROR [org.jboss.msc.service.fail] (MSC service thread 1-7) MSC000001: Failed to start service jboss.deployment.unit."ROOT.war".PARSE: org.jboss.msc.service.StartException in service jboss.deployment.unit."ROOT.war".PARSE: WFLYSRV0153: Failed to process phase PARSE of deployment "ROOT.war"

        at org.jboss.as.server.deployment.DeploymentUnitPhaseService.start(DeploymentUnitPhaseService.java:154)

        at org.jboss.msc.service.ServiceControllerImpl$StartTask.startService(ServiceControllerImpl.java:1948)

        at org.jboss.msc.service.ServiceControllerImpl$StartTask.run(ServiceControllerImpl.java:1881)

        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)

        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)

        at java.lang.Thread.run(Thread.java:748)

Caused by: org.jboss.as.server.deployment.DeploymentUnitProcessingException: Unexpected character '<' (code 60) (expected a name start character)

 at [row,col {unknown-source}]: [25,26]

        at org.jboss.as.connector.deployers.ds.processors.DsXmlDeploymentParsingProcessor.deploy(DsXmlDeploymentParsingProcessor.java:105)

        at org.jboss.as.server.deployment.DeploymentUnitPhaseService.start(DeploymentUnitPhaseService.java:147)

        ... 5 more

Caused by: com.ctc.wstx.exc.WstxUnexpectedCharException: Unexpected character '<' (code 60) (expected a name start character)

 at [row,col {unknown-source}]: [25,26]

        at com.ctc.wstx.sr.StreamScanner.throwUnexpectedChar(StreamScanner.java:647)

        at com.ctc.wstx.sr.StreamScanner.parseFullName(StreamScanner.java:1933)

        at com.ctc.wstx.sr.StreamScanner.parseEntityName(StreamScanner.java:2057)

        at com.ctc.wstx.sr.StreamScanner.fullyResolveEntity(StreamScanner.java:1525)

        at com.ctc.wstx.sr.BasicStreamReader.nextFromTree(BasicStreamReader.java:2748)

        at com.ctc.wstx.sr.BasicStreamReader.next(BasicStreamReader.java:1073)

        at com.ctc.wstx.sr.BasicStreamReader.getElementText(BasicStreamReader.java:670)

        at org.jboss.jca.common.metadata.common.AbstractParser.rawElementText(AbstractParser.java:166)

        at org.jboss.jca.common.metadata.common.AbstractParser.elementAsString(AbstractParser.java:153)

        at org.jboss.jca.common.metadata.ds.DsParser.parseDataSource(DsParser.java:1149)

        at org.jboss.jca.common.metadata.ds.DsParser.parseDataSources(DsParser.java:177)

        at org.jboss.jca.common.metadata.ds.DsParser.parse(DsParser.java:120)

        at org.jboss.jca.common.metadata.ds.DsParser.parse(DsParser.java:79)

        at org.jboss.as.connector.deployers.ds.processors.DsXmlDeploymentParsingProcessor.deploy(DsXmlDeploymentParsingProcessor.java:90)

        ... 6 more

To come around this problem, we need to encode those characters as proper xml entities. So here is how I did this:

First, introduce the system property as maven property inside of the property section:

 <properties>
<db.url>${JDBC_DATABASE_URL}</db.url>
<db.user>${JDBC_DATABASE_USERNAME}</db.user>
<db.password>${JDBC_DATABASE_PASSWORD}</db.password>
</properties>

Second, define the maven-antrun-plugin as plugin to be executed during validate phase, the phase, before even resources are copied or filtered by maven. Important is that the flag exportAntProperties is set to true. I was not able to override an existing property, therefore I created new ones.

<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<phase>validate</phase>
<configuration>
<exportAntProperties>true</exportAntProperties>
<target>
<script language="javascript">
<![CDATA[ 

if (!String.prototype.encodeHTML) {
String.prototype.encodeHTML = function () {
return this.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&apos;');
};
}

project.setProperty("enc.db.url",project.getProperty("db.url").encodeHTML());
project.setProperty("enc.db.user",project.getProperty("db.user").encodeHTML());
project.setProperty("enc.db.password",project.getProperty("db.password").encodeHTML());

]]>
</script>
</target>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>

Third, use the properties in any resouce file while filtering:

<datasources xmlns="http://www.jboss.org/ironjacamar/schema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.jboss.org/ironjacamar/schema http://docs.jboss.org/ironjacamar/schema/datasources_1_0.xsd">
<!-- The datasource is bound into JNDI at this location. We reference
this in META-INF/persistence.xml -->
<datasource jndi-name="java:jboss/datasources/GreeterQuickstartDS"
pool-name="greeter-quickstart" enabled="true" use-java-context="true">
<connection-url>${enc.db.url}</connection-url>
<driver>postgresql</driver> 
<security>
<user-name>${enc.db.user}</user-name>
<password>${enc.db.password}</password>
</security>

</datasource>
</datasources>

See the complete code example at my github repo https://github.com/mwiede/greeter.

Connecting from jconsole or VisualVM to wildfly swarm instances

Recently I was trying out to monitor a wildfly swarm application and I wondered why the standard java approach to connect via JMX RMI did not work. I was using parameters

-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.rmi.port=9010 -Dcom.sun.management.jmxremote.port=9010 -Dcom.sun.management.jmxremote.local.only=false -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false

and I was not able to connect via rmi.

But some articles (http://www.mastertheboss.com/jboss-server/wildfly-8/monitoring-wildfly-using-visualvm and https://dzone.com/articles/remote-jmx-access-wildfly-or) gave hints about the same issue with Jboss or Wildfly, so basically it works the same way. On the other hand, it would have been possible using jolokia, which reports via http endpoint, but it was driving me nuts, why I could’nt get it to work with jdk tools. Even in the wildfly swarm documentation, it is not really described.

Here are the steps, how to connect.

  • Check whether you have fraction jmx and or management inside your pom. jmx is mandatory, and management is important to know, because if you do not have it, you will be able to connect on the applications listening port (standard is 8080), otherwise, you can only connect via administration port (standard is 9990).
  • Start your application. I realized, that although you are trying to connect from your local to an application running on your local, the connection does not succeed. In any other scenario, for example, if you application is running in your docker host, then you have to configure the swarm to allow connections from remote anyway. When service is not configured to allow remote connections, the following appears in the log:
[org.wildfly.swarm.jmx] (main) JMX not configured for remote access

To allow remote connections, you have to start your app with parameter

-Dswarm.jmx.remote=true

or you have to provide this setting in the yaml.

  • If you do not have management as fraction, then you will find the following line in the logs
[org.wildfly.swarm.jmx] (main) JMX configured for remote connector: implicitly using standard interface
  • When using jconsole or jvisualvm you will need a jboss-client.jar which contains the implementation of the vendors protocol. As mentioned on the other sites, you can easily start jconsole with a script provided in any of the downloads of wildfly application server, which already included the required library.
$JBOSS_HOME/bin/jconsole.sh

To start jvisualvm, you need to do it like this:

jvisualvm.exe -cp:a $JBOSS_HOME\bin\client\jboss-client.jar

If you do not want to download the complete server package, you can also use the library itself. Maven coordinates:

<dependency>
 <groupId>org.wildfly</groupId>
 <artifactId>wildfly-client-all</artifactId>
 <version>12.0.0.Final</version>
</dependency>

or download link https://repo.maven.apache.org/maven2/org/wildfly/wildfly-client-all/12.0.0.Final/wildfly-client-all-12.0.0.Final.jar

  • If you do not have management fraction connect with string
service:jmx:remote+http://localhost:8080

or if you have management fraction included, connect with

service:jmx:remote+http://localhost:9990
  • If you are using service:jmx:http-remoting-jmx://localhost:9990 to connect, you will find a warning in the standard out
WARN: The protocol 'http-remoting-jmx' is deprecated, instead you should use 'remote+http'.

So please keep in mind, that the deprecated connection strings mentioned everywhere in the internet, will not work any more in the future.