Externalize Tomcat configuration
Asked Answered
T

4

18

I have a DataSource configuration in context.xml. Is it possible not to hard-code database parameters in that file? For example, use an external properties file, and load the parameters from it?

Something, like this:

context.xml:

  <Resource
  name="jdbc/myDS" auth="Container"
  type="javax.sql.DataSource"
  driverClassName="oracle.jdbc.OracleDriver"
  url="${db.url}"
  username="${db.user}"
  password="${db.pwd}"
  maxActive="2"
  maxIdle="2"
  maxWait="-1"/>

db.properties:

db.url=jdbc:oracle:thin:@server:1521:sid
db.user=test
db.pwd=test
Tonicity answered 13/7, 2012 at 10:40 Comment(1)
The context.xml file is already an external file. Why do you think you need another one?Ceraceous
S
14

As stated here, you could do this in the following way.

1.Download tomcat library to get the interface definition, for instance by defining maven dependency:

    <dependency>
        <groupId>org.apache.tomcat</groupId>
        <artifactId>tomcat-coyote</artifactId>
        <version>7.0.47</version>
    </dependency>

2.Next step is to create a com.mycompany.MyPropertyDecoder in the following way:

import org.apache.tomcat.util.IntrospectionUtils;
public class MyPropertyDecoder implements IntrospectionUtils.PropertySource  {
    @Override
    public String getProperty(String arg0) {
        //TODO read properties here
        return null;
    }
}

3.Put MyPropertyDecoder.class into tomcat7/lib folder
4.Define org.apache.tomcat.util.digester. PROPERTY_SOURCE property at tomcat7/conf/catalina.properties as following:

org.apache.tomcat.util.digester.PROPERTY_SOURCE=com.mycompany.MyPropertyDecoder

5.Update your context.xml with properties vars

<Resource name="jdbc/TestDB"
           auth="Container"
           type="javax.sql.DataSource"
           username="root"
           password="${db.password}"
           driverClassName="com.mysql.jdbc.Driver"
           url="jdbc:mysql://localhost:3306/mysql?autoReconnect=true"
           ...  

6.Put application.properties file somewhere in your project/container
7.Make sure MyPropertyDecoder correctly reads application.properties
8.Enjoy!

PS Also there is a similar approach described for tc Server.

Savoy answered 14/11, 2013 at 8:45 Comment(6)
Great, this works for me. Do you know if the same approach can be used to enable/disable a code snippet, that is by using an entry value of the property file like a flag?Promptitude
Objnewbie, using properties files for enabling/disabling some functionality of your app is ok. However you shouldn't implement Tomcat's IntrospectionUtils.PropertySource for this. Because this is just for Tomcat container to understand your properties files. In your scenario I would suggest using properties files and some handy ways to reading it, for instance @Value annotation from Spring framework.Savoy
Thank you very much. Unfortunately our team don't use Spring, but Struts 1.3.10. I posted a questionPromptitude
Can the application.properties file be put in another location different from Tomcat lib folder? From some tests, it seems to me that it's not possible (for example in WEB-INF project folder)Promptitude
Sure it can. Generally, the location should be present in classpath, and that would work.Savoy
if I put it in WEB-INF/classes or WEB-INF/lib, changing also the web.xml in different ways, I obtain an Unexpected exception resolving reference java.lang.NumberFormatException (this not happens if the file is in tomcat lib)Promptitude
G
5

It is easy with context deploy descriptors, which look like:

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

There are several places where you can put this config, for my opinion best option is $CATALINA_BASE/conf/[enginename]/[hostname]/$APP.xml

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

See discussion at:

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>
Glyco answered 1/10, 2014 at 17:31 Comment(0)
B
2

Sure, this is possible. You have to register ServletContextListener to your web.xml like this:

<!-- at the beginning of web.xml -->

<listener>
    <listener-class>com.mycompany.servlets.ApplicationListener</listener-class>
</listener>

Source of com.mycompany.servlets.ApplicationListener:

package com.mycompany.servlets;

public class ApplicationListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        // this method is invoked once when web-application is deployed (started)

        // reading properties file
        FileInputStream fis = null;
        Properties properties = new Properties();
        try {
            fis = new FileInputStream("path/to/db.properties")    
            properties.load(fis);
        } catch(IOException ex) {
            throw new RuntimeException(ex);
        } finally {
            try {
                if(fis != null) {
                    fis.close();
                }
            } catch(IOException e) {
                throw new RuntimeException(e);
            }
        }

        // creating data source instance
        SomeDataSourceImpl dataSource = new SomeDataSourceImpl();
        dataSource.setJdbcUrl(properties.getProperty("db.url"));
        dataSource.setUser(properties.getProperty("db.user"));
        dataSource.setPassword(properties.getProperty("db.pwd"));

        // storing reference to dataSource in ServletContext attributes map
        // there is only one instance of ServletContext per web-application, which can be accessed from almost anywhere in web application(servlets, filters, listeners etc)
        final ServletContext servletContext = servletContextEvent.getServletContext();
        servletContext.setAttribute("some-data-source-alias", dataSource);
    }

    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {
        // this method is invoked once when web-application is undeployed (stopped) - here one can (should) implement resource cleanup etc
    }

}

And then, somewhere in web-application code to access dataSource:

ServletContext servletContext = ...; // as mentioned above, it should be accessible from almost anywhere
DataSource dataSource = (DataSource) servletContext.getAttribute("some-data-source-alias");
// use dataSource

SomeDataSourceImpl is some concrete implementation of javax.sql.DataSource. Please advise if you doesn't use specific DataSources (like ComboPooledDataSource for connection pooling) and don't know how to obtain it - I will post how to bypass this.

some-data-source-alias - is just String alias(key) for your DataSource instance in ServletContext attribute map. Good practice is to give aliases prepended with package name like com.mycompany.mywebapp.dataSource.

Hope this helps...

Bonanza answered 13/7, 2012 at 11:13 Comment(5)
Thank You for Your answer. I am sure it works perfectly. I wonder if exists some out-of-the-box solution.Tonicity
Do you mean solution which uses JNDI with Tomcat's DataSource?Bonanza
Basically yes. I wonder if Tomcat accepts some kind of variables in context.xml, so Administrator can hold these variables in an external properties-file.Tonicity
As per docs.oracle.com/javase/jndi/tutorial/beyond/env/source.html: one can specify JNDI resources in jndi.properties file, but it should reside either in application classpath or in JAVA_HOME/lib/jndi.properties which is quite limited. And it looks like Tomcat uses only JNDI functionality in context.xml, so most probably it's not possible to have something like url="${db.url}" there referencing value from some external file. I've seen situation where context.xml is preprocessed by Ant before packing into .war file.Bonanza
Thanks You. So it seems that there is no out-of-the box solution.Tonicity
C
0

If this is Tomcat 7, you can write your own org.apache.tomcat.util.IntrospectionUtils.PropertySource implementation read variables written like "${...}" in context.xml. You'll need to set the system property org.apache.tomcat.util.digester.PROPERTY_SOURCE to point to your PropertySource implementation.

Connolly answered 16/7, 2012 at 1:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.