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

  • For Bloomreach Experience Manager 14: Oracle or OpenJDK Java 8 JRE (or JDK) already installed under: /usr/java/jre1.8.0 *
  • For Bloomreach Experience Manager 15: Oracle or OpenJDK Java 11 JDK already installed under: /usr/java/jdk-11.0.15 *
  • 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)

  • Download the latest release of Apache Tomcat 8.5 or 9.0 and unpack it.
cd /usr/local
tar -xzvf apache-tomcat-8.<LATEST>.tar.gz
  • Create a symlink to it:
ln -s apache-tomcat-8.<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 directory for the common classloader and place the required jars there:
mkdir -p /usr/local/share/tomcat-common/lib
cd /usr/local/share/tomcat-common/lib
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
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
wget http://search.maven.org/remotecontent?filepath=javax/jcr/jcr/2.0/jcr-2.0.jar -O jcr-2.0.jar
wget http://search.maven.org/remotecontent?filepath=com/mysql/mysql-connector-j/8.0.32/mysql-connector-ja-8.0.32.jar -O mysql-connector-j-8.0.32.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.

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

  • 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="javax.mail.Session" mail.smtp.host="localhost"/>
    
        <!-- 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-11.0.15
    
    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"
    RMI_OPTS="-Djava.rmi.server.hostname=127.0.0.1"
    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"
    
    CATALINA_OPTS="${JVM_OPTS} ${VGC_OPTS} ${REP_OPTS} ${DMP_OPTS} ${RMI_OPTS} ${L4J_OPTS} ${JRC_OPTS}"
    
    export JAVA_HOME CATALINA_HOME CATALINA_BASE
    
  • Make the file bin/setenv.sh executable:
chmod +x bin/setenv.sh
  • Add a conf/log4j2.xml 

    Download Example log4j2-dist.xml file.

    If you use the log4j2.xml example above, make sure to add to the end of the web.xml of the cms webapp the following part:

    <env-entry>
      <env-entry-name>logging/contextName</env-entry-name>
      <env-entry-type>java.lang.String</env-entry-type>
      <env-entry-value>cms</env-entry-value>
    </env-entry>

    and add to the end of the web.xml of the site webapp the following part: 

    <env-entry>
      <env-entry-name>logging/contextName</env-entry-name>
      <env-entry-type>java.lang.String</env-entry-type>
      <env-entry-value>site</env-entry-value>
    </env-entry>
  • (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

#
# Copyright BloomReach 2017
#

# 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?