Externalizing Tomcat webapp config from .war file
Asked Answered
B

4

43

I am having trouble with configuring a webapp in Tomcat 7. In my WAR file, there is a properties file myApp/WEB-INF/classes/myProps.props, and it contains environment-specific properites. I am trying to override that configuration file on the server, so that the same WAR file will deploy to multiple environments.

I heard there was a way to do this using replacement config files in tomcat/conf/Catalina/myApp. This is the method I am having trouble figuring out.

Also, myApp.war is one of many running on the same Tomcat server, and it does not run as localhost. I want to be able to solve this problem for several of the webapps.

Server version: Apache Tomcat/7.0.23
Server built:   Nov 20 2011 07:36:25
Server number:  7.0.23.0
OS Name:        Linux
Background answered 19/12, 2012 at 16:21 Comment(1)
I tried to change the order of the tags in order to put "tomcat configuration linux", instead of "linux tomcat configuration", in order to fix the page title that now says "linux" instead of "tomcat". It seems it's not easy to do it, or I can't do it.Fluorene
D
70

Your tomcat/conf/Catalina/<host> can contain context descriptors that let you configure lots of things including defining "environment entries", which are accessible from Java via JNDI. There are lots of ways to go about using it. Personally, I set an environment entry which is the file system path to my properties file. My app is built to check for this entry, and if it doesn't exist, look for the file on the classpath instead. That way, in dev, we have the dev properties right there on the classpath, but when we build and deploy, we point it to an external file.

There's good documentation for configuring a context on the Tomcat website. See the Defining a Context section on details of how to create the file and where to put it.

As an example, if your host is named myHost and your app is a war file named myApp.war in the webapps directory, then you could create tomcat/conf/Catalina/myHost/myApp.xml with this content:

<Context>
    <Environment name="configurationPath" value="/home/tomcat/myApp.properties" type="java.lang.String"/>
</Context>

Then from your code, you'd do a JNDI lookup on java:comp/env/configurationPath (95% certainty here) to get that string value.

Darsey answered 21/12, 2012 at 23:43 Comment(2)
@Ryan Stewart Can you provide an example of how the above configuration be used in java code?Charleton
@Ryan Stewart Can you help me to fix similar problem here #36447468Priester
S
49

I like .properties files instead of

  • JNDI - why build complex object during program configuration instead of initialization time?
  • system properties - you can't separately configure several instances of same WAR in single Tomcat
  • context parameters - they accessible only in javax.servlet.Filter, javax.servlet.ServletContextListener which my be inconvenient

Tomcat 7 Context hold Loader element. According to docs deployment descriptor (what in <Context> tag) can be placed in:

  • $CATALINA_BASE/conf/server.xml - bad - require server restarts in order to reread config
  • $CATALINA_BASE/conf/context.xml - bad - shared across all applications
  • $CATALINA_BASE/work/$APP.war:/META-INF/context.xml - bad - require repackaging in order to change config
  • $CATALINA_BASE/work/[enginename]/[hostname]/$APP/META-INF/context.xml - nice, but see last option!!
  • $CATALINA_BASE/webapps/$APP/META-INF/context.xml - nice, but see last option!!
  • $CATALINA_BASE/conf/[enginename]/[hostname]/$APP.xml - best - completely out of application and automatically scanned for changes!!!

Context can hold custom Loader org.apache.catalina.loader.VirtualWebappLoader (available in modern Tomcat 7, you can add own separate classpath to your .properties), and Parameter (accessed via FilterConfig.getServletContext().getInitParameter(name)) and Environment (accessed via new InitialContext().lookup("java:comp/env").lookup("name")):

<Context docBase="${basedir}/src/main/webapp"
         reloadable="true">
    <!-- http://tomcat.apache.org/tomcat-7.0-doc/config/context.html -->
    <Resources className="org.apache.naming.resources.VirtualDirContext"
               extraResourcePaths="/WEB-INF/classes=${basedir}/target/classes,/WEB-INF/lib=${basedir}/target/${project.build.finalName}/WEB-INF/lib"/>
    <Loader className="org.apache.catalina.loader.VirtualWebappLoader"
            virtualClasspath="${basedir}/target/classes;${basedir}/target/${project.build.finalName}/WEB-INF/lib"/>
    <JarScanner scanAllDirectories="true"/>

    <Parameter name="min" value="dev"/>
    <Environment name="app.devel.ldap" value="USER" type="java.lang.String" override="true"/>
    <Environment name="app.devel.permitAll" value="true" type="java.lang.String" override="true"/>
</Context>

If you use Spring and it's XML config:

<context:property-placeholder location="classpath:app.properties"/>

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="oracle.jdbc.OracleDriver"/>
    <property name="url" value="jdbc:oracle:thin:@${db.host}:${db.port}:${db.user}"/>
    <property name="username" value="${db.user}"/>
    <property name="password" value="${db.pass}"/>
</bean>

With Spring injecting above properties into bean fields are easy:

@Value("${db.user}") String defaultSchema;

instead of JNDI:

@Inject ApplicationContext context;
Enviroment env = context.getEnvironment();
String defaultSchema = env.getProperty("db.user");

Note also that EL allow this (default values and deep recursive substitution):

@Value('${db.user:testdb}') private String dbUserName;

<property name='username' value='${db.user.${env}}'/>

See also:

NOTE With extending classpath to live directory you also allowed to externilize any other configs, like logging, auth, atc. I externilize logback.xmlin such way.

UPDATE Tomcat 8 change syntax for <Resources> and <Loader> elements, corresponding part now look like:

<Resources>
    <PostResources className="org.apache.catalina.webresources.DirResourceSet"
                   webAppMount="/WEB-INF/classes" base="${basedir}/target/classes" />
    <PostResources className="org.apache.catalina.webresources.DirResourceSet"
                   webAppMount="/WEB-INF/lib" base="${basedir}/target/${project.build.finalName}/WEB-INF/lib" />
</Resources>
Slink answered 1/10, 2014 at 17:9 Comment(3)
Curious: if you've got logback.xml in src/main/resources and also externally, what would you use in tomcat 8 to use the external config - would it be PostResources, PreResources or something else?Dora
EE spec mandates behavior with container Classloaders. Some framewords break standard but most follow. So classpath would be as it specified in Context and usually first hit is used.Slink
Tried that @Slink and it works with Tomcat8, however, noticed the following things: * If the app is undeployed and tomcat is restarted, it results in errors as $App.xml exists but tomcat tries to load the Resources to the path which no longer exists * Unable to deploy the app at / now. Wanted to deploy the spring boot generated war file on Tomcat 8, and made the change to conf/server.xml to have the context path as empty, but this no longer works with any other context path except the App name.Dora
W
1

You can try to place your configuration (properties file) in Apache Tomcat\lib in JAR file and remove it from the web application. When the Tomcat's class loader won't find your config in webapp it will try to find in "lib" directory. So you can externalize your configuration just moving the config to global lib dir (it's shared among other webapps).

Windblown answered 22/12, 2012 at 13:54 Comment(2)
Thank you, but this doesn't quite do what I need it to. The war file needs to run on developer's machines which have no config of their own.Background
If you are using tomcat maven plugin, you can probably provide custom config to tomcat, so the developers would have it.Windblown
G
0

I just added a setenv.bat or setenv.sh script in the bin folder of tomcat. Set the classpath variable like

set CLASSPATH=my-propery-folder
Grandiloquence answered 6/7, 2017 at 11:49 Comment(2)
Not really useful if you're deploying to a shared server that someone else manages. Not terrible, but somewhat limited,Expressivity
Probably there is a typo and it's "property" and not "propery"Fluorene

© 2022 - 2024 — McMap. All rights reserved.