Configure the Application Server (Apache Tomcat on Linux)

Introduction

Goal

Configure an application server using the Bloomreach Experience Manager standard stack, running Apache Tomcat on a Linux operating system.

Background

This page describes how to set up a Bloomreach Experience Manager application server according to our best practices. The end result is the most commonly used setup based on the Developer Edition Stack and the brXM Standard Stack.

Assumptions

  • Java already installed:
    • For Bloomreach Experience Manager 16:
      Oracle or OpenJDK Java 17 JDK installed, e.g. /usr/java/jdk-17.0.10 *
    • For Bloomreach Experience Manager 15:
      Oracle or OpenJDK Java 11 JDK installed, e.g. /usr/java/jdk-11.0.22 *
    • For Bloomreach Experience Manager 14:
      Oracle or OpenJDK Java 8 JDK installed, e.g. /usr/java/jdk-1.8 *
  • Relational database server (MySQL or other supported database server) already installed

Intended Results

The instructions on this page aim for the following results:

  • Bloomreach Experience Manager will run under its own dedicated user called cms, with home-directory: /opt/cms *
  • Base Apache Tomcat installation under: /usr/local *
  • Tomcat common libs under: /usr/local/share/tomcat-common/lib *

* These are the locations we recommend. It is possible to use different locations depending on your environment setup.

Preparation

  • Create a user account so you can run Bloomreach Experience Manager under it's own user.

    useradd -m -d /opt/cms cms

Install and Configure Apache Tomcat

To ease upgrades, we recommend to split up Catalina Base from Catalina Home. This way, all custom configuration can be done in Catalina Base and Catalina Home can be kept completely stock. Upgrading Apache Tomcat should be no more than changing a symlink and restarting.

Install Apache Tomcat (Catalina Home)

  • Dowload the latest applicable release from Apache Tomcat: version 10.1 for brXM 16 or 9.0 (or 8.5) for brXM 15/14, then unpack it.
cd /usr/local
tar -xzvf apache-tomcat-<LATEST>.tar.gz
  • Create a symlink to it:
ln -s apache-tomcat-<LATEST> tomcat
  • Correct some permissions:
find /usr/local/tomcat/ -type d -print0 | xargs -0 chmod 755
find /usr/local/tomcat/ -type f -print0 | xargs -0 chmod 644
find /usr/local/tomcat/ -type f -name \*\.sh -print0 | xargs -0 chmod 755

Install Common Libraries

  • Create the tomcat-common/lib directory for the common classloader and place the required database connector jar there:
mkdir -p /usr/local/share/tomcat-common/lib
cd /usr/local/share/tomcat-common/lib
# e.g. mysql connector
wget http://search.maven.org/remotecontent?filepath=com/mysql/mysql-connector-j/8.4.0/mysql-connector-j-8.4.0.jar -O mysql-connector-j-8.4.0.jar
Note: If not using MySQL, please replace the mysql-connector-j with the appropriate JDBC Driver for your database server. See System Requirements for a list of supported databases servers.
  • Legacy, before brXM 16: if you do not have the following jars in the distribution's common/lib directory, also place these in tomcat-common/lib
# geronimo jta spec
wget http://search.maven.org/remotecontent?filepath=org/apache/geronimo/specs/geronimo-jta_1.1_spec/1.1.1/geronimo-jta_1.1_spec-1.1.1.jar -O geronimo-jta_1.1_spec-1.1.1.jar 
# jakarta mail
wget http://search.maven.org/remotecontent?filepath=com/sun/mail/jakarta.mail/1.6.7/jakarta.mail-1.6.7.jar -O jakarta.mail-1.6.7.jar 
# jcr
wget http://search.maven.org/remotecontent?filepath=javax/jcr/jcr/2.0/jcr-2.0.jar -O jcr-2.0.jar

Configure Apache Tomcat (Catalina Base)

This is where Tomcat will run from, and where we'll place all configuration that's different from or in addition to the stock Tomcat configuration files.

Prepare Catalina Base

  • Create the necessary directories for the Catalina Base and copy the configuration files that will be modified later:

    su - cms
    mkdir -p tomcat/bin tomcat/conf tomcat/logs tomcat/shared/lib heapdumps tomcat/temp tomcat/webapps tomcat/work
    ln -sf /usr/local/tomcat/bin/startup.sh tomcat/bin/startup.sh
    ln -sf /usr/local/tomcat/bin/shutdown.sh tomcat/bin/shutdown.sh
    cd /usr/local/tomcat/conf
    cp catalina.policy catalina.properties server.xml web.xml tomcat-users.xml ~/tomcat/conf
    cd ~/tomcat
By default the configuration file tomcat-users.xml will not be used. It is however configured in server.xml, so Tomcat will complain about it missing at level SEVERE. The alternative is to comment out the UserDatabase resource and realm in conf/server.xml.

Configure the Tomcat Instance

  • To allow the CMS application to open the RMI port, edit conf/catalina.policy and add the following to the end:

    grant codeBase "jar:file:${catalina.home}/webapps/" {
      permission java.net.SocketPermission "*:1099", "connect, accept, listen";
    };
  • Edit conf/catalina.properties and add the shared/lib path to the shared classloader:

    shared.loader="${catalina.base}/shared/lib","${catalina.base}/shared/lib/*.jar"
  • Edit conf/catalina.properties and add the tomcat-common/lib path to the common classloader:

    common.loader=<original common.loader path>,"/usr/local/share/tomcat-common/lib","/usr/local/share/tomcat-common/lib/*.jar"

Add Environment-Specific Configuration for Bloomreach Experience Manager

  • If you do not want your distribution's context.xml to be leading, remove it from there and add a conf/context.xml with at least the following (and adjust it to match your environment where necessary):

    <?xml version='1.0' encoding='utf-8'?>
    <Context>
        <!-- Disable session persistence across Tomcat restarts -->
        <Manager pathname="" />
    
        <Parameter name="repository-address" value="rmi://127.0.0.1:1099/hipporepository" override="false"/>
        <Parameter name="repository-directory" value="${catalina.base}/../repository" override="false"/>
        <Parameter name="start-remote-server" value="false" override="false"/>
    
        <Parameter name="check-username" value="liveuser" override="false"/>
    
        <Resource name="mail/Session" auth="Container"
            type="jakarta.mail.Session" mail.smtp.host="localhost"/>
        <!--- use type="javax.mail.Session" for brXM 15 and lower -->
     
        <!-- JNDI resource exposing database connection goes here -->
    
    </Context>

    Session serialization and session replication for the CMS platform web application is neither required nor supported by BloomReach and for the site web application by default not needed.

  • At this point you also need to add a JNDI resource to conf/context.xml, and add a conf/repository.xml configuration file adjusted for your database. If using MySQL you should be able to use the examples provided at Configure Bloomreach Experience Manager for MySQL as-is. See Databases for other database servers.

  • Add a file bin/setenv.sh with the following contents (adjust where necessary, e.g. JAVA_HOME):

    JAVA_HOME=/usr/java/jdk-17.0.10  # or jdk-11.0.15, jdk-1.8
    
    CATALINA_HOME="/usr/local/tomcat"
    CATALINA_BASE="/opt/cms/tomcat"
    CATALINA_PID="${CATALINA_BASE}/work/catalina.pid"
    
    CLUSTER_ID="$(whoami)-$(hostname -f)"
    
    MAX_HEAP=1024
    MIN_HEAP=1024
    
    REP_OPTS="-Drepo.bootstrap=false -Drepo.config=file:${CATALINA_BASE}/conf/repository.xml"
    JVM_OPTS="-server -Xmx${MAX_HEAP}m -Xms${MIN_HEAP}m -XX:+UseG1GC -Djava.util.Arrays.useLegacyMergeSort=true"
    DMP_OPTS="-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/opt/cms/heapdumps"
    JRC_OPTS="-Dorg.apache.jackrabbit.core.cluster.node_id=${CLUSTER_ID}"
    L4J_OPTS="-Dlog4j.configurationFile=file://${CATALINA_BASE}/conf/log4j2.xml -DLog4jContextSelector=org.apache.logging.log4j.core.selector.BasicContextSelector"
    VGC_OPTS="-verbosegc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:${CATALINA_BASE}/logs/gc.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=2048k"
    
    # following --add-opens for brXM 16 on Tomcat 10.1.20 to .24 only. 
    # It's a workaround needed in combination with Freemarker back-end templating and may also be omitted in a SPA set-up
    ADD_OPENS_OPTS="--add-opens java.xml/com.sun.org.apache.xml.internal.utils=ALL-UNNAMED"
    
    CATALINA_OPTS="${JVM_OPTS} ${VGC_OPTS} ${REP_OPTS} ${DMP_OPTS} ${RMI_OPTS} ${L4J_OPTS} ${JRC_OPTS} ${ADD_OPENS_OPTS}"
    
    export JAVA_HOME CATALINA_HOME CATALINA_BASE
    
  • Make the file bin/setenv.sh executable:
chmod +x bin/setenv.sh
  • Legacy: if you don't have conf/log4j2.xml in your packaged distribution file, add it manually: download example log4j2.xml file.

    If you use the log4j2.xml example above, make sure that the web.xml of the cms and site webapps contain an <env-entry> element of type java.lang.String, with name logging/contextName and value "cms" and "site", respectively.

  • (Optional) Modify the JSP servlet configuration in conf/web.xml by adding some extra parameters.
    <servlet>
        <servlet-name>jsp</servlet-name>
        <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
        <init-param>
            <param-name>fork</param-name>
            <param-value>false</param-value>
        </init-param>
        <init-param>
            <param-name>xpoweredBy</param-name>
            <param-value>false</param-value>
        </init-param>

        <!-- BEGIN extra init-params -->

        <init-param>
            <param-name>trimSpaces</param-name>
            <param-value>true</param-value>
        </init-param>
        <init-param>
            <param-name>development</param-name>
            <param-value>false</param-value>
        </init-param>
        <init-param>
            <param-name>checkInterval</param-name>
            <param-value>7200</param-value>
        </init-param>
        <init-param>
            <param-name>modificationTestInterval</param-name>
            <param-value>7200</param-value>
        </init-param>
        <init-param>
            <param-name>genStrAsCharArray</param-name>
            <param-value>true</param-value>
        </init-param>
        <init-param>
            <param-name>enablePooling</param-name>
            <param-value>false</param-value>
        </init-param>

        <!-- END extra init-params -->

        <load-on-startup>3</load-on-startup>
    </servlet>

Configure the Service

  • Create an init script: /etc/init.d/cms, adjust the variables at the start if a different user or home directory is used.

The name of the init script is used by the script, so adjust the name to match the user if not using cms
#!/bin/bash
### BEGIN INIT INFO
# Provides:          tomcat
# Required-Start:    $remote_fs $syslog
# Required-Stop:     $remote_fs $syslog
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Start Tomcat at boot time
# Description:       Start Tomcat instance located at the user with the same name
#                    as the script. Tomcat is started with the privileges of the user.
### END INIT INFO

# Get basename and clean up start/stop symlink cruft (aka S20cms)
appname=$(basename $0)
appname=${appname##[KS][0-9][0-9]}
appuser=${appname}
apphome=/opt/${appuser}/tomcat

config=${apphome}/bin/setenv.sh
start_tomcat=${apphome}/bin/startup.sh
stop_tomcat=${apphome}/bin/shutdown.sh

CATALINA_PID="${apphome}/work/catalina.pid"

if [[ -r ${config} ]]; then
   . ${config}
else
   echo "Environment config missing: ${config}"
   exit 1
fi

if [[ -n "${JAVA_HOME+x}" && -z ${JAVA_HOME} ]]; then
  echo "Please point JAVA_HOME in $(basename) to your SUN JRE of JDK"
  exit 1
fi

export JAVA_HOME CATALINA_OPTS CATALINA_PID CATALINA_HOME CATALINA_BASE

if [[ $(id -u) == 0 ]]; then
  SU="su - ${appuser} -c"
elif [[ ${appuser} != $(/usr/bin/id -un) ]]; then
  echo "Access denied: You are neither a superuser nor the ${appuser} user"
  exit 1
fi

test -r ${CATALINA_PID} && PID=$(cat ${CATALINA_PID})

cleanup() {
  /usr/bin/find ${apphome}/work/ ${apphome}/temp/ -maxdepth 1 -mindepth 1 -print0 | xargs -0 rm -rf
}

start() {
  echo -n "Starting ${appname}: "
  cd ${apphome}
  if [[ -n ${PID} ]]; then
    if ps -eo pid | grep -wq ${PID}; then
      echo "${appname} (${PID}) still running.."
      exit 1
    else
      echo "(removed stale pid file ${CATALINA_PID}) "
      rm -f ${CATALINA_PID}
    fi
  fi
  cleanup
  ${SU} ${start_tomcat} > /dev/null
  if [[ $? ]]; then
    echo "${appname} started."
  else
    echo "${appname} failed to start."
  fi
}

stop() {
  echo -n "Shutting down ${appname}: "
  cd ${apphome}
  ${SU} ${stop_tomcat} > /dev/null
  if [[ -n ${PID} ]]; then
    echo "waiting for ${appname} to stop"
    for ((i=0;i<25;i++)); do
      RUNNING=$(ps -eo pid | grep -w ${PID})
      if [[ ${i} == 24 ]]; then
        kill ${PID} > /dev/null 2>&1 && sleep 5s && \
         kill -3 ${PID} > /dev/null 2>&1 && \
         kill -9 ${PID} > /dev/null 2>&1
      elif [[ ${RUNNING// /} == ${PID} ]]; then
        echo -n "."
        sleep 1s
      else
        break
      fi
    done
  fi
  test -e ${CATALINA_PID} && rm -f ${CATALINA_PID}
  echo "${appname} stopped."
}

case "${1}" in
  start)
    start
    ;;
  stop)
    stop
    ;;
  restart)
    if [[ -n ${PID} ]] && ps -eo pid | grep -wq ${PID}; then
      kill -3 ${PID}
    fi
    stop
    sleep 2s
    start
    ;;
  *)
    echo "Usage: ${0} {start|stop|restart}"
    ;;
esac

exit 0
  • Install the service

# Debian/Ubuntu
update-rc.d cms defaults

# Redhat
chkconfig --add cms

Please note that while Red Hat Enterprise Linux 7.2 uses systemd, it is still possible to use the above init script as stated in the RHEL 7.2 /etc/init.d/README:

Note that traditional init scripts continue to function on a systemd
system. An init script /etc/rc.d/init.d/foobar is implicitly mapped
into a service unit foobar.service during system initialization.

Next Steps

Did you find this page helpful?
How could this documentation serve you better?
On this page
    Did you find this page helpful?
    How could this documentation serve you better?