Setting up Tomcat for Bloomreach development

October 7, 2020

Setting up Tomcat for Bloomreach development

In this blog post I’ll write down my experiences in getting a BrXM (Bloomreach Experience Manager) project up and running in my development environment, and in particular how to I deal with multiple Hippo versions (libraries) of my projects during an upgrade.

Why run Tomcat from your IDE instead of using cargo.run?

The Bloomreach documentation mentions mvn -P cargo.run as the sure-fire way of running your Bloomreach web application locally. While this is a robust way of getting it to run, which almost always works unless you have messed up your pom.xml too much, it is a bit cumbersome for two reasons:

  • If you need need to specify arguments to the Java VM, you must type them in every time you start your web app.
  • Debugging is a bit complicated, as your webserver is running as an external application and you must connect a remote debugging session to it from your IDE.

Both these drawbacks are taken care of by using the integrated Tomcat webserver in your IDE. This plugin allows you to control stopping and starting the application server from within your IDE, it automates the process of attaching the debugger to the JVM, and it also lets you specify various run / debug configurations where any Java VM argument you set is stored as part of the launcher configuration.

Note that if you are using the community version of IntelliJ, you have no integration with Tomcat and debugging your web application is indeed done by running it with cargo.run and attaching the debugger to it from the IDE.

Each minor BrXM version has its own version of the libraries

Starting with the release of BrXM version 13, the version of the release is reflected in the names of the Bloomreach specific libraries needed at runtime. Since these libraries must be installed somewhere in your Tomcat application server instance, this makes it messy to use one and the same Tomcat server installation for different versions of the Bloomreach project.

This doesn’t work

You could be tempted (as was I) to just add all libraries for all versions to your Tomcat server but this does not work because then at startup the server tries to start multiple instances of singleton entities which results in fatal errors.

Another approach I tried was to separate the diffent versions of the Bloomreach libraries in different directories and then adding the relevant directory to the class loader path. I could however not get this to work either. The path to the library directories is set in Tomcat’s catalina.properties configuration file. I tried making different versions of this file pointing to the different subdirectories with the right versions of the libraries. You can override Tomcat’s default name for the catalina.properties file by specifying a -Dcatalina.conf=xxx in the VM arguments of your launcher.

Although this change gets picked up by Tomcat, the problem here is that my IDE (IntelliJ) keeps its own partial copy of Tomcat and somehow specifiying a different name for the catalina.properties file causes confusion about the root directory for the relative paths in this setting.

A third approach was to keep the different versions of the libraries in separate subdirectories and link to them as required with a symbolic link whose name is in the catalina.properties file. Switching versions would then require swapping around the targets of the links. For unclear reasons, this did not work either.

Here is how it does work

In the end I created separate copies of the whole Tomcat installation, one each with its own version of the Bloomreach libraries, and configured all in my IDE so I can choose the correct version of the application server to match the version of my project.

While doing this I found that contrary to what other sources recommend, your Tomcat setup can be quite simple. As usual, you obtain the correct versions of the library files by running your project with the desired BrXM version as parent from the command line using mvn clean package && mvn -P cargo.run which leaves all required jar files in your target/tomcat9x directory. Don’t worry if it won’t start properly, we are only interested in the libraries that were collected during the build. Just copy all these jar files

./target/tomcat9x/common/lib/*.jar

./target/tomcat9x/shared/lib/*.jar   # except for your projects repository data jars

to your Tomcat servers lib/ directory and make sure that this Tomcat lib directory is in the common.loader paths set in conf/catalina.properties (it is there by default):

common.loader="${catalina.home}/lib/*.jar","<your entries here>"

Setting up a Tomcat server in IntelliJ UI

After having prepared a Tomcat installation by adding the required libraries specific to the version of Hippo you are using, and setting the common.loader path if necessary, you can very easily configure your IntelliJ IDE to use it:

   File -> Settings -> Build, Excecution, Deployment -> Application Servers

takes you to the dialog where you can add your Tomcat server. Make sure to not select the TomEE server as this is a different beast. After pressing the + for addition, select the root directory of your Tomcat installation and confirm. The server is added with a default name that is the version number of the Tomcat server. I suggest you change this name to something that reflects the version of the Hippo libraries that you installed in this particular copy of Tomcat, so that you can easily tell the various versions apart.

Here is a screenshot:
setup-tomcat-in-intellij

Some details about Tomcat configuration

Tomcat uses two environment variables in its default settings for various paths: CATALINA_BASE and CATALINA_HOME. The reason they both exist and may have different values is to allow for mutliple instances of Tomcat (each running its own set of web applications). In that case CATALINA_BASE points to an instance specific location (that holds the web applications), while CATALINA_HOME points to the shared location (that holds Tomcat).

You can see which values these environment variables have by inspecting the server logging, the values are logged at startup by Tomcat. In this way I discovered that IntelliJ keeps a copy of some (but not all) of the Tomcat configuration somewhere in its directory structure, and makes CATALINA_BASE point to that copy, while CATALINA_HOME points to the instance of Tomcat that you set up as the Tomcat server in the IDE. In addition to making a copy of some of the Tomcat files, my IDE edits these copies for its own purposes.

Since it will rarely be the case that you need to run multiple instances from the same Tomcat directory, my advice is to only use the CATALINA_HOME environment variable when setting configuration for use of the Hippo libraries. That way you can be certain that your intended configuration settings are not interfered with by whatever your IDE is doing.

For similar reasons, there used to be three different properties for setting a classpath in the Tomcat configuration: common.loader, server.loader and shared.loader. The intention was to optimize class loading by making a difference between classes needed only in the server, classes needed only in your web application, and classes needed by both, the idea being that by configuring it just right you could reduce memory requirements by preventing classes from being loaded redundantly by both the server and the application.

This has lead to much confusion, and since memory is not really a restriction anymore, the scheme has been abandoned and Tomcat 8 uses only the common.loader property. The other two properties (shared.loader and server.loader) still exist in the configuration, presumably for compatibility reasons, but they should no longer be used. In fact, in a default Tomcat installation only the common.loader setting has a value.

Some useful settings for the JVM

Here are some of the more common VM properties and settings for running your web application:

  • -Xms1024m and -Xmx2048m: These VM arguments control how much memory your web application + web server combination is allowed to request from the operating system.
  • -Dlog4j.configurationFile=/home/dimario/projects/acme/conf/log4j2-dev.xml: This property points to the log4j configuration for the project.
  • -Dproject.basedir=/home/dimario/projects/acme: This property points to the root directory of the project. It is used by Spring to resolve various configured properties that have a ${project.basedir} placeholder in them.
  • -Drepo.path=/data/hippo/acme: This BrXM specific property points to the directory where you want your H2 database for the JCR to be. The default is to place it in the Maven target directory. But this means that every time you run mvn clean it will destroy your database, and the next time you start up the whole JCR must be bootstrapped into a new database, which takes several ages.
  • -Ddynamic.bean.generation=false: Since version 13.2.0, BrXM has the ability to generate bean classes at runtime based on the properties that describe a bean in the JCR configuration (dynamic content beans). Historically, our project has used hand-coded Java beans. It turns out that when you mix hand-coded and generated beans, this leads to strange errors when casting beans up and down the inheritance tree. So our project turns auto generated beans explicitly off (default is on).
  • -Drepo.autoexport.allowed=false: This BrXM specific property controls whether changes made with CMS and the console may be written back to the yaml files from which the database was bootstrapped. Sometimes this is what I want and I change this value to true. But most times it is an annoyance because even looking at a document in CMS may result in a whole horde of JCR .meta tags being added to all its sibling nodes. If these changes are written back to the code base, I suddenly have a whole bunch of unwanted edits in the yaml files which make it difficult to confirm legitimate changes that I do want to add to my version control.
  • -Drepo.bootstrap=true: This BrXM specific property controls if bootstrapping of the yaml configuration is allowed when a repository database already exists. In a development environment, you want a true for this behaviour, in all other environments a false.

Here is a screenshot of setting up these VM arguments in the launcher:
launcher-vm-in-intellij

Troubleshooting

The logging created by Tomcat is generally not very helpful for tracing the reason your application won’t run in the server. Here are some example errors from my application log and what causes them:


01.10.2020 14:41:35 ERROR RMI TCP Connection(2)-127.0.0.1 [RepositoryServlet.init:246] Error while setting up JCR repository: 
javax.jcr.RepositoryException: unchecked exception: java.lang.NoClassDefFoundError: com/onehippo/cms7/services/hst/WorkspaceHasher
	at org.hippoecm.repository.HippoRepositoryFactory.getHippoRepository(HippoRepositoryFactory.java:161) ~[hippo-repository-connector-13.4.1.jar:13.4.1]

This happens when I try to start an BrXM application version 13.4.1 (as seen in the log message) in a Tomcat server instance that has libraries for BrXM version 13.4.3


java.lang.ClassNotFoundException: org.apache.catalina.startup.Catalina

When Tomcat can’t even find itself, this usually means you have made a typo in the lib path of the common.loader property.


01.10.2020 14:50:10 ERROR RMI TCP Connection(2)-127.0.0.1 [ContextLoader.initWebApplicationContext:312] Context initialization failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.context.support.PropertySourcesPlaceholderConfigurer#0': Cannot resolve reference to bean 'acmeProps' while setting bean property 'properties'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'acmeProps': Invocation of init method failed; nested exception is java.io.FileNotFoundException: class path resource [acme.properties] cannot be opened because it does not exist

This is an error specific for the application I work with. Amongst others, it needs a file acme.properties somewhere along the classpath from which it wants to read application specific configuration. I generally solve this by adding a path to the directory for this file to my catalina.properties#common.loader path:

common.loader="${catalina.home}/lib/*.jar","/home/dimario/projects/acme/conf/"

with the acme.properties file residing in ~/projects/acme/conf on my development machine.

The important part of the log message here is class path resource [acme.properties] cannot be opened because it does not exist In fact, the file does exist but not in one of the directories on the classpath. Again, this error usually is caused by typoes in the list of directories for common.loader


01.10.2020 15:06:35 WARN  RMI TCP Connection(2)-127.0.0.1 [DbDataStore.convert:761] Retrieving database resource 
java.io.IOException: Permission denied
	at java.io.UnixFileSystem.createFileExclusively(Native Method) ~[?:1.8.0_265]
	at java.io.File.createTempFile(File.java:2026) ~[?:1.8.0_265]

This error happens when the temp dir in your Tomcat installation cannot be written by the user attempting to start the server. On startup, Tomcat wants to write some temporay files and this error tells you it is unable to do so.

Generally speaking it is good practice in linux to install stuff as root and run it as an ordinary user. However, in this case that makes root the owner of said temp directory and ordinary users have no write permission on it, which leads to this error.

Mario Pinkster

Mario Pinkster

Java Developer at Sentia Consultancy