Categories
Java

ThreadLocal

Bei der Migration einer größeren Anwendung (> 120.000 LOC) von einem SAP NetWeaver 7.3 mit Java 1.6 auf einen Tomcat 8.5 mit Java 1.8 hatten wir ein “interessantes” Problem:

Nach der erfolgreichen Umstellung einer Schnittstelle (SAP PI/PO) auf unseren neuen Tomcat Server wurden wir am nächsten Tag damit konfrontiert, dass die PI keinen Zugang mehr zu unserem Server hätte. Die Verbindung würde zwar aufgebaut, aber die Annahme der Daten dann mit HTTP Code 401 abgelehnt.

Das Kuriose dabei: Im Prinzip findet gar keine Authentifizierung in der Anwendung statt, denn diese Schnittstelle ist lediglich für die PI freigegeben, über einen IP Filter.

Ein Neutstart unseres Tomcat-Servers konnte das Problem kurzfristig beheben, bis es dann wieder auftrat.

Der Code des empfangenden Servlets sieht stark vereinfacht so aus:

public class IdocImportServlet extends HttpServlet implements Servlet {
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        if (UserInSession.get() == null) {
            UserInSession.put("DiesIstDiePI");
        } else {
            response.setStatus(401);
            response.getWriter().println("Illegal attempt to submit data");
            return;
        }
   		doStuff(request, response);
    }
}

Da diese Kommunikation an den üblichen Authentifizierungsmechanismen vorbei läuft, muss der UserInSession auch immer NULL sein. Aber schauen wir uns diese Klasse mal genauer an, denn der Name “InSession” ist irreführend:

public final class UserInSession {
	private static ThreadLocal sessionUser = new ThreadLocal() {
        protected synchronized Object initialValue() {
            return null;
        }
    };
	public static void put(String userId) {
        sessionUser.set(user);
    }
	public static String get() {
        return ((String) sessionUser.get());
    }
}

Unabhängig von der Session wird der User zu Begin der Verarbeitung im Servlet gesetzt. Es ist also eher ein “UserInRequest”, der da gesetzt wird.
Der User wird in einer ThreadLocal gespeichert und kann so später an anderer Stelle wieder aus dieser ThreadLocale ausgelesen werden.
Das alles sollte auch kein Problem machen und so funktionieren. Und beim Debugging hat es dann auch erstmal funktioniert.

Um mir das Debugging-Leben etwas einfacher zu gestalten, habe ich die Payload einer Übertragung aus den Logfiles herausgesucht und in eine Datei (data.xml) gespeichert und dann per Terminal Befehl an die Schnittstelle zu schicken:

curl -X POST -d @data.xml http://localhost:8080/myApp/PISchnittstelle

Beim ersten Mal hat alles funktioniert und auch dann noch ein paar Mal, bis es dann nicht mehr ging.
Das Problem ließ sich beim Debuggen erkennen: der UserInSession war auf einmal schon mit dem User “DiesIstDiePI” gesetzt und daher wurde der Zugang verweigert.

Wie sich bei der Analyse herausstelle, läuft ein Request in einem Thread. So weit so gut.
Der Tomcat stellt eine bestimmte Anzahl an Threads bereit. Und nach <Anzahl der Threads +1> Requests wird der erste Thread wiederverwertet. Dummerweise ist dabei aber noch die ThreadLocale aus dem ersten Request gefüllt.

In der alten SAP NetWeaver Umgebung scheint immer ein “frischer” Thread zu kommen, so dass der Code in der alten Umgebung funktioniert hat. Wodurch genau dieses unterschiedliche Verhalten ausgelöst wird, konnten wir leider nicht in vertretbarer Zeit herausfinden.

Lösung

Die Lösung (oder vielleicht doch eher ein Workaround) besteht darin, bei jedem Request die ThreadLocal zu löschen. Gerne hätten wir nach einer Lösung gesucht, die ThreadLocal möglicherweise ganz los zu werden, aber der Aufwand wäre in Summe zu groß gewesen (was ich hier auf dieser Seite darstelle ist eine sehr stark vereinfachte und verkürzte Version des Codes und des Problems).

Um den UserInSession vor jedem Aufruf des Servlets löschen zu können, haben wir einen zusätzlichen Filter eingebaut:

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;


public class ThreadLocalFilter implements Filter {

    public void destroy() {
        // nothing to do
    }

    public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2)
            throws IOException, ServletException {
        // Wipe all ThreadLocal
        UserInSession.put(null);
        arg2.doFilter(arg0, arg1);
    }

    public void init(FilterConfig arg0) throws ServletException {
        // nothing to do
    }
}

Und diesen Filter entsprechend in der web.xml eingebaut:

    <filter>
        <filter-name>ThreadLocalFilter</filter-name>
        <filter-class>deringo.filter.ThreadLocalFilter</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>ThreadLocalFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

Categories
Java

Remote Debugging

I need to directly debug on the application Tomcat server, not only on my local Tomcat instance.

Compile Java code

For debugging we need to keep the line numbers while compiling.
To build the war file we use an ANT script and we have to add the debug and debuglevel attributes in the javac tag:

<javac srcdir="${src}" destdir="${build}" 
       includeantruntime="false" 
       encoding="UTF-8" source="1.8" 
       fork="true" 
       debug="on" debuglevel="lines,vars,source" 
       verbose="true">
  [...]
</javac>

Configure Tomcat server

To enable remote debugging on the Tomcat server add some arguments to CATALINA_OPTS in setenv.sh file:

ATALINA_HOME=/app/myApp/tomcat
CATALINA_BASE=/app/myApp/tomcat
CATALINA_PID=/app/myApp/tomcat/tomcat.pid
JAVA_HOME=/app/java/jdk8u265-b01-jre
CATALINA_OPTS="$CATALINA_OPTS -Djava.library.path=/app/library -Xdebug -Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=n"
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/app/library
export LD_LIBRARY_PATH
JAVA_OPTS="${JAVA_OPTS} -Djavax.net.ssl.trustStore=/app/certs/corporate_truststore.jks -Djavax.net.ssl.trustStorePassword=secret -Xms256M -Xmx1024M -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/app/myApp/dump"

Configure Eclipse

Create a new debug configuration:


Categories
Java

Deploy to Tomcat with Maven

In my last post I decribed the way to build and deploy a war file to Tomcat application server with Ant.

In this post I show how to deploy the build war file to Tomcat application server with Maven.

Create inside the “ant” folder this Maven file:

<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>deringo</groupId>
    <artifactId>myApp</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>
    <name>myApp</name>
    <description>myApp Maven deployment</description>

    <properties>
        <java.version>1.8</java.version>
        <tomcat.version>8.5.33</tomcat.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-catalina</artifactId>
            <version>${tomcat.version}</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.0</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>3.2.2</version>
                <configuration>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                    <warName>myApp</warName>
                    <wtpContextName>myApp</wtpContextName>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.tomcat.maven</groupId>
                <artifactId>tomcat7-maven-plugin</artifactId>
                <version>2.2</version>
                <configuration>
                    <url>http://myAppServer:7011/manager/text</url>
                    <username>tomcat</username>
                    <password>tomcat</password>
                    <warFile>dist/myApp.war</warFile>
                    <path>/myApp</path>
                    <update>true</update>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

Execute with goal: tomcat7:deploy

Use Properties file

In my ANT file I have used a properties file for tomcat username/password etc. In my Maven script I want also to use this properties file.

Unfortunatly Maven can not handle property files out of the box. But there is a Plugin we can use:

        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>properties-maven-plugin</artifactId>
            <version>1.0-alpha-1</version>
            <executions>
                <execution>
                    <phase>initialize</phase>
                    <goals>
                        <goal>read-project-properties</goal>
                    </goals>
                    <configuration>
                        <files>
                            <file>tomcat.properties</file>
                        </files>
                    </configuration>
                </execution>
            </executions>
        </plugin>

This is the same, as writing this directly into pom.xml:

<properties>
    <tomcat.username>tomcat</tomcat.username>
    <tomcat.password>tomcat</tomcat.password>
</properties>

The complete pom.xml with properties:

<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>deringo</groupId>
  <artifactId>myApp</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>war</packaging>
  <name>myApp</name>
  <description>myApp Maven deployment</description>

  <properties>
    <java.version>1.8</java.version>
    <tomcat.version>8.5.33</tomcat.version>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.apache.tomcat</groupId>
      <artifactId>tomcat-catalina</artifactId>
      <version>${tomcat.version}</version>
      <scope>provided</scope>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>properties-maven-plugin</artifactId>
        <version>1.0-alpha-1</version>
        <executions>
          <execution>
            <phase>initialize</phase>
            <goals>
              <goal>read-project-properties</goal>
            </goals>
            <configuration>
              <files>
                <file>tomcat.properties</file>
              </files>
            </configuration>
          </execution>
        </executions>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.8.0</version>
        <configuration>
          <source>${java.version}</source>
          <target>${java.version}</target>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-war-plugin</artifactId>
        <version>3.2.2</version>
        <configuration>
          <failOnMissingWebXml>false</failOnMissingWebXml>
          <warName>myApp</warName>
          <wtpContextName>myApp</wtpContextName>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.tomcat.maven</groupId>
        <artifactId>tomcat7-maven-plugin</artifactId>
        <version>2.2</version>
        <configuration>
          <url>${tomcat.manager.url}</url>
          <username>${tomcat.username}</username>
          <password>${tomcat.password}</password>
          <warFile>dist/myApp.war</warFile>
          <path>${webapp.name}</path>
          <update>true</update>
        </configuration>
      </plugin>
    </plugins>
  </build>

</project>

Update 2021-03-24

I tried the configuration in a project but got this error:

Plugin execution not covered by lifecycle configuration: org.codehaus.mojo:properties-maven-plugin:1.0-alpha-1:read-project-properties (execution: default, phase: initialize)

I solved the error adding a pluginManagement tag:

<build>
    <pluginManagement>
        <plugins>
            <plugin> ... </plugin>
            <plugin> ... </plugin>
                  ....
        </plugins>
    </pluginManagement>
</build>

But this solution seems more like a workaround when reading the discussion on stackoverflow.
…need to search further…

Use in Jenkins

I have not tried it, but it should be possible to set an environment variable in Jenkins build step to execute with a configurable filename.

For example TOMCAT_PROPERTIES=jenkins-home/secret/tomcat-dev.properties.
And we can use it in Maven script this way:

            <configuration>
              <files>
                <file>${env.TOMCAT_PROPERTIES}</file>
              </files>
            </configuration>
Categories
Java

Deploy to Tomcat

Deploy myApp.war file to Tomcat application server, using Tomcat manager app and an ant script.

Make a folder “ant” in your project and this it the folder for all other actions below.

Make a folder “dist” and generate your myApp.war file into this folder with the “build.xml” script.

Make a folder “tomcat-libs” and copy following files from your tomcat installation:

  • catalina-ant.jar
  • catalina.jar
  • servlet-api.jar
  • jsp-api.jar

Create file “tomcat.properties” with properties for tomcat server and tomcat manager app.

Create “deploy.xml” file for deployment.

Execute deploy.xml script with ant, it must show the info sections with properties of your tomcat.properties file.

Execute deploy-webapp target from deploy.xml script to deploy your myApp.war file to tomcat server through the tomcat manager app.

If you are using for example Jenkins, you can set the “secprops.location” from outside the script, so you can use the same script for different tomcat installations.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE project>
<project name="myApp" default="info" basedir=".">

  <description>
    myApp ant deploy file.
  </description>

  <property name="secprops.location" value="tomcat.properties" />
  <property file="${secprops.location}" />

  <property name="war.file" value="dist/myApp.war" />

  <target name="info">
    <echo message="-------------------------------------------------------------------" />
    <echo message="Commands:" />
    <echo message="  start-webapp" />
    <echo message="  stop-webapp" />
    <echo message="  undeploy-webapp" />
    <echo message="  deploy-webapp" />
    <echo message="  sessions-webapp" />
    <echo message="-------------------------------------------------------------------" />
    <echo message="Properties: "/>
    <echo message="tomcat.manager.url = ${tomcat.manager.url}"/>
    <echo message="tomcat.username    = ${tomcat.username}"/>
    <echo message="tomcat.password    = ${tomcat.password}"/>
    <echo message="webapp.name        = ${webapp.name}"/>
    <echo message="war.file           = ${war.file}"/>
    <echo message="-------------------------------------------------------------------" />
  </target>

  <path id="catalina-ant-classpath">
    <fileset dir="tomcat-libs">
      <include name="catalina-ant.jar" />
      <include name="catalina.jar" />
    </fileset>
  </path>

  <taskdef name="catalina-start" classname="org.apache.catalina.ant.StartTask" classpathref="catalina-ant-classpath" />
  <taskdef name="catalina-stop" classname="org.apache.catalina.ant.StopTask" classpathref="catalina-ant-classpath" />
  <taskdef name="catalina-deploy" classname="org.apache.catalina.ant.DeployTask" classpathref="catalina-ant-classpath" />
  <taskdef name="catalina-undeploy" classname="org.apache.catalina.ant.UndeployTask" classpathref="catalina-ant-classpath" />
  <taskdef name="catalina-sessions" classname="org.apache.catalina.ant.SessionsTask" classpathref="catalina-ant-classpath" />
  
  <target name="sessions-webapp">
    <catalina-sessions url="${tomcat.manager.url}" username="${tomcat.username}" password="${tomcat.password}" path="/${webapp.name}" failonerror="false" />
  </target>
  
  
  <target name="stop-webapp">
    <catalina-stop url="${tomcat.manager.url}" username="${tomcat.username}" password="${tomcat.password}" path="/${webapp.name}" failonerror="false" />
  </target>

  <target name="start-webapp">
    <catalina-start url="${tomcat.manager.url}" username="${tomcat.username}" password="${tomcat.password}" path="/${webapp.name}" />
  </target>

  <target name="undeploy-webapp">
    <catalina-undeploy url="${tomcat.manager.url}" username="${tomcat.username}" password="${tomcat.password}" path="/${webapp.name}" failonerror="false" />
  </target>

  <target name="deploy-webapp">
    <echo message="START to deploy file ${war.file} to Tomcat Server: ${tomcat.manager.url}" />
    <catalina-deploy url="${tomcat.manager.url}" username="${tomcat.username}" password="${tomcat.password}" path="/${webapp.name}" war="file:${war.file}" update="true"/>
    <echo message="END of deployment" />
  </target>

</project>
tomcat.manager.url=http://myAppServer:7011/manager/text
tomcat.username=tomcat
tomcat.password=tomcat
webapp.name=myApp
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE project>
<project name="myApp" default="clean" basedir=".">
  <description>
    myApp ant build file.
  </description>
  <!-- Set global properties for this build -->
  <property name="src" location="../source"/>
  <property name="webcontent" location="../WebContent"/>
  <property name="build" location="build"/>

  <tstamp prefix="build-info">
    <format property="current-date" pattern="d-MMMM-yyyy" locale="en" />
    <format property="current-time" pattern="hh:mm:ss a z" locale="en" />
  </tstamp>

  <target name="init">
    <!-- Create the build directory structure used by compile -->
    <mkdir dir="${build}"/>
    <!-- Create the distribution directory -->
    <mkdir dir="${dist}/lib"/>
  </target>

  <target name="compile" depends="init" description="compile the source">
    <!-- Compile the Java code from ${src} into ${build} -->
    <javac srcdir="${src}" destdir="${build}" includeantruntime="false" encoding="UTF-8">
      <classpath>
        <!-- Taken from Apache Tomcat 8.5 lib folder -->
        <pathelement path="tomcat-libs/servlet-api.jar"/>
        <pathelement path="tomcat-libs/jsp-api.jar"/>
      </classpath>
    </javac>
    <!-- Copy all non-java ressoures from source folder -->
    <copy todir="${build}">
      <fileset dir="${src}" excludes="**/*.java" />
    </copy>

  </target>

  <target name="war" depends="compile">
    <war destfile="dist/myApp.war" webxml="${webcontent}/WEB-INF/web.xml">
      <fileset dir="${webcontent}"/>
      <classes dir="${build}"/>
      <manifest>
        <attribute name="Manifest-Version" value="1.0"/>
        <attribute name="Built-On" value="${build-info.current-date}"/>
        <attribute name="Built-At" value="${build-info.current-time}"/>
      </manifest>
    </war>
  </target>

  <target name="clean" description="clean up">
    <!-- Delete the ${build} directory trees -->
    <delete dir="${build}"/>
  </target>
</project>
Categories
Java Linux

Setup Tomcat manager app

In my last post I set up a Tomcat application server in general, now I enable Tomcat manager app for deployment.

# Tomcat Users
mv /app/myApp/tomcat/conf/tomcat-users.xml /app/myApp/tomcat/conf/tomcat-users.xml_original
vim /app/myApp/tomcat/conf/tomcat-users.xml

vim/app/myApp/tomcat/conf/server.xml

# By default the Manager is only accessible from a browser running on the same machine as Tomcat. If you wish to modify this restriction, you'll need to edit the Manager's context.xml file.
vim /app/myApp/tomcat/webapps/manager/META-INF/context.xml
<?xml version="1.0" encoding="UTF-8"?>
<tomcat-users xmlns="http://tomcat.apache.org/xml"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://tomcat.apache.org/xml tomcat-users.xsd"
              version="1.0">
 
  <role rolename="manager-gui"/>
  <role rolename="manager-script"/>
  <user username="tomcat" password="tomcat" roles="manager-gui,manager-script"/>
 
</tomcat-users>
<Server port="7010" shutdown="SHUTDOWN">
  <GlobalNamingResources>
    <Resource name="UserDatabase" auth="Container" type="org.apache.catalina.UserDatabase" description="User database that can be updated and saved" factory="org.apache.catalina.users.MemoryUserDatabaseFactory" pathname="conf/tomcat-users.xml" />
  </GlobalNamingResources>
 
  <Service name="Catalina">
    <Connector port="7011" />
   
    <!-- Define an AJP 1.3 Connector on port 7012 -->
    <Connector port="7012" protocol="AJP/1.3" secretRequired="false" />
    <Engine name="Catalina" defaultHost="localhost" jvmRoute="myApp-dev">
      <Host name="localhost" appBase="webapps" />
      <Realm className="org.apache.catalina.realm.LockOutRealm">
        <!-- This Realm uses the UserDatabase configured in the global JNDI
             resources under the key "UserDatabase".  Any edits
             that are performed against this UserDatabase are immediately
             available for use by the Realm.  -->
        <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
               resourceName="UserDatabase"/>
      </Realm>
    </Engine>
  </Service>
</Server>
<Context antiResourceLocking="false" privileged="true" >
    <!-- ## uncomment this Block ##
    <Valve className="org.apache.catalina.valves.RemoteAddrValve"
         allow="127\.\d+\.\d+\.\d+|::1|0:0:0:0:0:0:0:1" />
    -->
</Context>

Test Tomcat manager app

Open http://myAppServer:7011/manager and login with username: tomcat and password: tomcat.

Categories
Java Linux

Setup Tomcat application server

Setup files

su myUser
 
# Java
cd /app/java
tar -xzf /app/files/OpenJDK8U-jre_x64_linux_hotspot_8u265b01.tar.gz
tar -xzf /app/files/OpenJDK11U-jre_x64_linux_hotspot_11.0.8_10.tar.gz
tar -xzf /app/files/OpenJDK15U-jre_x64_linux_hotspot_15_36.tar.gz
 
# myApp Tomcat
cd /app/myApp
tar -xzf /app/files/apache-tomcat-8.5.59.tar.gz
tar -xzf /app/files/apache-tomcat-9.0.39.tar.gz
 
# Certificate
cp /app/files/corporate_truststore.jks /app/certs/
 
# SAP JCO
cp /app/files/_sapjco3-64/3.0.19/linuxx86/libsapjco3.so /app/library/
cp /app/files/_sapjco3-64/3.0.19/linuxx86/sapjco3.jar /app/library/

Setup Tomcat

su myUser
 
# Symlink to actual Tomcat version
ln -s /app/ccp/apache-tomcat-8.5.59 /app/myApp/tomcat
 
# remove sample application
# but keep the Tomcat Manager app for deployment
rm -rf /app/myApp/tomcat/webapps/docs
rm -rf /app/myApp/tomcat/webapps/examples
rm -rf /app/myApp/tomcat/webapps/ROOT
 
# configure Tomcat
vim /app/myApp/tomcat/bin/setenv.sh
mv /app/myApp/tomcat/conf/server.xml /app/myApp/tomcat/conf/server.xml_original
vim /app/myApp/tomcat/conf/server.xml
 
# expand Classpath
vim /app/myApp/tomcat/conf/catalina.properties
# tomcat/bin/setenv.sh
CATALINA_HOME=/app/myApp/tomcat
CATALINA_BASE=/app/myApp/tomcat
CATALINA_PID=/app/myApp/tomcat/tomcat.pid
JAVA_HOME=/app/java/jdk8u265-b01-jre
CATALINA_OPTS="$CATALINA_OPTS -Djava.library.path=/app/library"
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/app/library
export LD_LIBRARY_PATH
JAVA_OPTS="${JAVA_OPTS} -Djavax.net.ssl.trustStore=/app/certs/corporate_truststore.jks -Djavax.net.ssl.trustStorePassword=notchangeit -Xms512M -Xmx2048M -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/app/myApp/dump"
# tomcat/conf/catalina.properties
common.loader= [...] ,"/app/library/sapjco3.jar"
# server.xml
<Server port="7010" shutdown="SHUTDOWN">
  <Service name="Catalina">
    <Connector port="7011" />
   
    <!-- Define an AJP 1.3 Connector on port 7012 -->
    <Connector port="7012" protocol="AJP/1.3" secretRequired="false" />
    <Engine name="Catalina" defaultHost="localhost" jvmRoute="myApp-dev">
      <Host name="localhost" appBase="webapps" />
    </Engine>
  </Service>
</Server>

Tomcat as a Service

# As root
vim /etc/systemd/system/tomcat.service
# enable script:
systemctl enable tomcat.service
# tomcat.service

# Systemd unit file for myApp tomcat
#
# To create clones of this service:
   
# Systemd unit file for tomcat
 [Unit]
 Description=myApp Tomcat Web Application Container
 After=syslog.target network.target
   
   
 [Service]
 Type=forking
   
 ExecStart=/app/myApp/tomcat/bin/startup.sh
 ExecStop=/app/myApp/tomcat/bin/shutdown.sh
   
 User=myUser
 Group=myUser
   
 [Install]
 WantedBy=multi-user.target

Service control

Enable user myUser to control Tomcat services:

visudo -f /etc/sudoers
##################################################
## Allow user myUser to control (apache & tomcat) services
%myUser ALL=(root) NOPASSWD: /bin/systemctl
%myUser ALL=(root) NOPASSWD: /usr/sbin/service

Test Tomcat

For Tomcat testing I use my ShowHeaders app (GitHub).
ShowHeaders is a minimalistic webapp that is not much more than a “Hello World”, but it shows the HTTP headers, what is quite useful when testing reverse proxy integration.

# copy ShowHeaders App for Testing (also for Reverse Proxy Configuration Testing)
[myUser@DEV ~]$ cp /app/files/ShowHeaders/ROOT.war /app/myApp/tomcat/webapps/
# start tomcat:
[myUser@DEV ~]$ sudo systemctl start tomcat

#
curl localhost:7011

Test connection from outside the server itself: http://myAppServer:7011/