How to provide a context configuration for a web application in Tomcat?
Asked Answered
L

6

50

I have a web application that relies on some resources and parameters to be configured after it is installed, like a JDBC connection.

What I have come up with is providing a META-INF/context.xml which is copied into [engine-name]/[server-name]/[app-name].xml by Tomcat when I deploy the application. This way all I am providing is a war file that can be copied into the appBase folder (webapps). Tomcat's documentation says if there is such a file it won't be overwritten which is really great, since the changes made after deployment won't be lost.

But there is a subtle issue here: Since we deploy the application by copying into webapps directory, Tomcat will first uninstall the existing application as well as the configuration file. This way the configuration file will be lost / overwritten which is not desirable. Tomcat won't modify this behaviour as far as I know.

The question is: Is there a way to work around this issue by installing the application in a way that Tomcat won't remove the existing configuration file. Or, is there a better way of packaging the application?

Please note that we don't want to set autoDeploy to false and we cannot use human intervention for the installation (which rules out using Tomcat Manager web application).

If I get the configuration file out of .war file and copy it separately as [engine-name]/[server-name]/[app-name].xml, Tomcat will still associate it with my application and remove it once I copy a new .war file.

Another assumption is: We don't know in advance the values to the configuration. We will only provide a sample configuration (a placeholder, if you wish) while actual configuration will be performed at some time later (not necessarily in the installation time).

Thanks

Leech answered 22/8, 2011 at 1:30 Comment(4)
can't you remove your context.xml file from war and put it directly where tomcat needs it ? It would not be uninstalled and reinstalled anymore. On jetty, that's the way hot deploy worksSlather
I tried that, if the file matches the [engine]/[host]/[app].xml pattern it is considered to be as part of the application, even though it wasn't in the .war file (o_O).Leech
Can't you provide build scripts for all the different environments so that you don't have to modify anything after deployment?Occultism
@home: How is it going to help? Can you describe it some more? Modification after deployment is not an issue, deployment after modification is ;-)Leech
L
11

I managed to resolve this issue somehow.

1- Install an exploded WAR directory somewhere outside Tomcat's appBase, let's assume it is in /usr/local/MyApp. [You can use a WAR file for this step instead of WAR directory, if your application runs from an unexploded war.]

2- Copy the context configuration file into [tomcat.conf]/[engine]/[hostname] directory, let's call it MyApp.xml. This file will point to the location of the application:

<?xml version="1.0" encoding="UTF-8"?>
<!-- Context configuration file for my web application -->
<Context docBase="/usr/local/MyApp" privileged="true" antiResourceLocking="false" antiJARLocking="false">
        <Resource name="jdbc/myapp-ds" auth="Container" type="javax.sql.DataSource"
                maxActive="100" maxIdle="30" maxWait="10000" username="XXX" password="XXX"
                driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/mydb" />
</Context>

3- You are now free to go and modify the configuration file.

4- Update the application by copying new version of your application in /usr/local/MyApp

Notes:

a) This solution applies to an unexpanded .war file as well, but since we use Spring's Log4JConfigListener it wouldn't run from an unexploded .war file. Tomcat doesn't explode .war files put outside appBase (webapps) folder.

b) This approach doesn't prevent you from having context.xml in /usr/local/MyApp/META-INF/context.xml since it will not be used by Tomcat in this configuration. You can use it in your dev environment, where you dump your .war file into the appBase (webapps) folder.

This is what I've got so far, still looking out for better solutions.

Leech answered 24/8, 2011 at 5:36 Comment(2)
You can also look into Overlay Deployer which is supported by both Tomcat and Jetty: eclipse.org/jetty/documentation/current/overlay-deployer.htmlLeech
overlay-deployer link above does not work anymore, but here it seems to be: github.com/eclipse/jetty.project/tree/jetty-9.4.x/…Friend
Z
46

The solution is simple: don't put configuration in your context.xml.

Here is a solution that we use (which works well for a number of diverse external customers):

We have a single war which will be used in multiple environments, webapp.war. We have three environments, development, integration and production. Integration and production are at the customer site. We don't know passwords and file paths for the client integration and production sites.

We use a combination of two things: JNDI lookup for database stuff and external properties files.

In the context.xml that is delivered in the war, we have a ResourceLink

<ResourceLink name="jdbc/webapp"
     global="uk.co.farwell.webapp.datasource.MySqlDataSource" />

This gives a reference to a globally defined data source, which is defined in the server.xml for Tomcat.

<Resource auth="Container" 
          driverClassName="com.mysql.jdbc.Driver" 
          name="uk.co.farwell.webapp.datasource.MySqlDataSource" 
          password="xxx" url="xxx" username="fff" />

So the database details can be changed by editing the server.xml without changing the webapp.war. Crucially, this only needs to be done once for each server, not at redeploy.

In our spring configuration, to define the dataSource we have:

<jee:jndi-lookup id="dataSource" jndi-name="jdbc/webapp" />

For other properties, we have a global application.properties file which is delivered along with the webapp.war, but is not part of the war. This is referenced by a -D on the command line to start Tomcat. -Duk.co.farwell.webapp.applicationDir="/usr/xxx/fff". We pick up the definition and read the properties file. The database stuff could be done this way as well, but we'd lose the pooling done by Tomcat.

Another thing: we don't have to rebuild if servers are moved, or if machines are changed for some reason. This is a matter for the customer and their infrastructure people.

Zamindar answered 26/8, 2011 at 11:55 Comment(4)
This is a good approach. Maybe we shouldn't care about how the JNDI datasources are configured in production.Leech
I just broke down and added everything into the global context.xml file. Tomcat makes my life painful sometimes.Launcher
This works great for most cases. But if you need to deploy the same app to multiple contexts, each with a different database, it won't work.Navada
@Lachlan: but this is half true ... multiple contexts with different databases must be handled in a way that the global jndi names (assuming we want global ones) naturally must include some context id in a way, say mydb-prod, mydb-test, mydb-demo etc. So you have to modify at least the descriptors in the webapp.jar in this way. We do this via Maven or ANT which works fine. The URL context itself and some other things are effected as well (e.g. .../prod/..., .../test/..., ...). This is quite robust and separates conn definition from usage again.Friend
L
11

I managed to resolve this issue somehow.

1- Install an exploded WAR directory somewhere outside Tomcat's appBase, let's assume it is in /usr/local/MyApp. [You can use a WAR file for this step instead of WAR directory, if your application runs from an unexploded war.]

2- Copy the context configuration file into [tomcat.conf]/[engine]/[hostname] directory, let's call it MyApp.xml. This file will point to the location of the application:

<?xml version="1.0" encoding="UTF-8"?>
<!-- Context configuration file for my web application -->
<Context docBase="/usr/local/MyApp" privileged="true" antiResourceLocking="false" antiJARLocking="false">
        <Resource name="jdbc/myapp-ds" auth="Container" type="javax.sql.DataSource"
                maxActive="100" maxIdle="30" maxWait="10000" username="XXX" password="XXX"
                driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/mydb" />
</Context>

3- You are now free to go and modify the configuration file.

4- Update the application by copying new version of your application in /usr/local/MyApp

Notes:

a) This solution applies to an unexpanded .war file as well, but since we use Spring's Log4JConfigListener it wouldn't run from an unexploded .war file. Tomcat doesn't explode .war files put outside appBase (webapps) folder.

b) This approach doesn't prevent you from having context.xml in /usr/local/MyApp/META-INF/context.xml since it will not be used by Tomcat in this configuration. You can use it in your dev environment, where you dump your .war file into the appBase (webapps) folder.

This is what I've got so far, still looking out for better solutions.

Leech answered 24/8, 2011 at 5:36 Comment(2)
You can also look into Overlay Deployer which is supported by both Tomcat and Jetty: eclipse.org/jetty/documentation/current/overlay-deployer.htmlLeech
overlay-deployer link above does not work anymore, but here it seems to be: github.com/eclipse/jetty.project/tree/jetty-9.4.x/…Friend
D
5

This is how we can manage to externalize webapp context from .WAR File

  1. Place your .WAR file somewhere outside tomcat
  2. Create a $APP_NAME.xml file into $TOMCAT_HOME/conf/[Engine]/[Host]/ directory.
  3. Now file "$APP_NAME.xml" we just created need to have context definition and parameters + Any EnvironmentVariable you want specific to that context.

For e.g. I have an webapp called VirtualWebApp.

I will create file like VirtualWebApp.xml with below context definition :

<Context docBase="/home/appBase/VirtualWebApp" path="/VirtualWebApp" reloadable="true">
    <Environment name="webservice.host" type="java.lang.String" value="1.2.3.4" />
    <Environment name="webservice.port" type="java.lang.String" value="4040" />
</Context>

To access these environment variables you have to write below code(Just lookup) :

InitialContext initialContext = new javax.naming.InitialContext();

host = (String)initialContext.lookup("java:comp/env/webservice.host");
port = (String)initialContext.lookup("java:comp/env/webservice.port");
Demarcate answered 7/11, 2015 at 9:11 Comment(3)
Please make sure you add initial namespace "java:comp/env/" for looking up entries.Demarcate
How will tomcat know where your .war file is?Lutestring
docBase attribute represents the path of war file exploded. e.g. docBase="/home/appBase/VirtualWebApp"Demarcate
A
3

By referring to Apache Tomcat 5.5 Documentation:

In the $CATALINA_HOME/conf/context.xml file: the Context element information will be loaded by all webapps

You could easily try this approach, it might work, but I'm not sure if this is a good solution especially if you are running multiple webapps on Tomcat.

Asaasabi answered 28/8, 2011 at 12:40 Comment(0)
O
1

I don't know how to modify Tomcat's behaviour but I could think of 2 different solutions:

  1. different (parameterized) build scripts for each environment, so that you define a parameter called env to your build scripts and depending on the value it places the environment specific context.xml in your WAR during build.
  2. Create an install script for each environment that first redeploys the WAR file (places it in webapps directory) and then makes modifications to the Tomcat installation depending on environment, e.g. different hostname for JDBC DataSource in context.xml.

I make heavy use of the latter approach as it works in enterprise environments. Separation of duties policies often prohibit the dev team from knowing e.g. production database passwords. Option #2 solves this problem because only IT operations have access to the environment specific install scripts after they have been created.

Occultism answered 22/8, 2011 at 6:59 Comment(2)
This is the scenario I want to avoid: 1- We install the application and configure it properly. 2- Our infrastructure team moves VMs around and modifies configuration properly. Everyone happy. 3- We update the application and deploy a new .war file. 4- BANG! Changes in step 2 are lost.Leech
The second approach works if we can force the application to be configured again every time it is being updated. We might do that if I don't find a better way to release the application.Leech
M
1

@n0rm1e: not sure if tomcat provides any sort of solution for you problem. But one possible solution can be:- create an ant script with following steps:

i) Check existence of .xml file in [engine-name]/[server-name] directory. If it exists, take a back up of this/rename it.

ii) copy your war file to tomcat webapps. Restart tomcat server.

iii) copy backup-ed configuration file back to [engine-name]/[server-name] directory

Munguia answered 23/8, 2011 at 10:33 Comment(1)
Although I don't like this approach but we ended up doing this (in RPM pre-install and post-install scripts), because people couldn't turn their head around applications going somewhere other than webapps folder :DLeech

© 2022 - 2024 — McMap. All rights reserved.