2

I am developing a Web application in Java using Eclipse and Tomcat 6.

I need to pass a value to the servlet that is not hard-coded into the .war file.
For instance, let's say my servlet needs a filename, and that filename is different on each server.
How do I pass it to the servlet, without hardcoding it in the .war file?

Please help me with the instructions on how to do this:

  • from within Eclipse (for instance: add xxxx to file yyy and then run)
  • on the server on which I will deploy the application

I'm thinking in the direction of getInitParameter, but I haven't found a way to pass init parameters except for the web.xml inside the .war file, which is not an option since it needs to be configurable. So if you could show me how I can set init parameters in an external file, the problem is solved.

Note: I'm looking for really exact advice, as in “edit that file on that location”. I have found many related questions and answers, but none of them was specific enough to make it work.

Ruben Verborgh
  • 3,545
  • 2
  • 31
  • 43

4 Answers4

3

There are a number of ways to go about this. It really depends on at what point you know the location of the file you are trying to load. If it is something you will configure per tomcat instance/server then an environment variable or JVM argument would probably be easiest. You could then look up this variable via System.getProperty in your code.

You can pass a JVM argument on a per-tomcat basis via CATALINA_HOME/bin/setenv.sh.

#!/bin/sh
CATALINA_OPTS="$CATALINA_OPTS
    -Dmy.file.path=/path/on/this/host/file.txt"

And in your code:

String path = System.getProperty("my.file.path")

Dhanush's suggestion is also a good one that might be more sustainable in the long run. It has the advantage of keeping all of your per environment configuration in a single location that you can keep in source control. With his approach however you'd still need a way to select the appropriate key from the properties file (because this properties file would be bundled in your war), which you could again use an environment variable or JVM argument to control.

If you wanted to get slightly fancier you could look into using JNDI

Adam B
  • 1,724
  • 3
  • 14
  • 29
1

If the location of the config is known, you can pass it as a servlet parameter. In your web.xml where you declare your servlet (I refer to configFile as the file you wish to get a reference to):

<servlet>
    <servlet-name>ConfigParser</servlet-name>
    <servlet-class>foo.baar.ConfigParser</servlet-class>
    <init-param>
        <param-name>configFilePath</param-name>
        <param-value>/path/to/the/config/file</param-value>
    </init-param>
</servlet>

I guess you know where the web.xml file is because you are already using servlet.

Then in your servlet, you can you ServletConfig.getInitParameter("configFilePath") to get the location of the config file. e.g. in your servlet:

public void init(ServletConfig config) throws ServletException {
    super.init(config);
    String path_to_config_file=config.getInitParameter("configFilePath");
}

Container will call this method with the ServletConfig where you get your reference to the config file. That means, you are not required to have that file in eclipse at all. With this approach, you don't have to do anything special on your server, the only thing to take care of is that the file is copied over and the path you declare on your web.xml is correct.

If the location of the file can be constructed dynamically, you can use ServletContext.getRealPath("/") that returns the absolute path of the webapp.

---UPDATE---

Answer to the updated question. I don't know what is the best practice to do it, but there is a workaround. You create a file (conf_location.txt) in the tomcat home directory which contains one line with the location of the file you want to pass to the servlet. In your servlet, you can get access to the file with this hack (given your war is in $TOMCAT_HOME/webapps/mywar.war):

  public void init() throws ServletException{
      String contextPath=getServletContext().getRealPath("/");
      File tomcatHome=new File(contextPath).getParentFile().getParentFile();
      File configFile=new File(tomcatHome,"conf_location.txt");
      try {
          String config_location = new Scanner(configFile).useDelimiter("\\Z").next();
      } catch (Exception e) {}
  }
  • But isn't `web.xml` part of the `.war` file? Because that is where I declare my servlets. I do like the `init-param` way, but I want to be able to set this outside of the `.war`. Is that possible and where do I put it? – Ruben Verborgh Feb 11 '14 at 19:02
  • But your servlet needs to know what to use/parse. You just pass the reference to the file, not the file itself. If you want to include the file as well, you put it in "webapp/WEB-INF/classes" and from your servlet you can parse it as "Dhanush Gopinath" is describing below. I might have misunderstood the problem though. –  Feb 11 '14 at 19:53
  • Passing the reference is fine, but how do I do that (without hard-coding that reference inside my .war file)? – Ruben Verborgh Feb 11 '14 at 21:02
  • This is the reference in the web.xml that will get passed to the servlet: /path/to/the/config/file. This is the path to the file you are interested to parse. I updated the answer with the ``init`` method where you can get the reference to this file. –  Feb 11 '14 at 21:05
  • Thanks, but is that `web.xml` in the .war file? Because my problem is that the path can be different on each server. So I can't hard-code it inside a file that goes into the .war. – Ruben Verborgh Feb 11 '14 at 21:09
  • But how do you know which file to use? Do you have a relative path to the file? I mean do you know the path to the file that is relative to the context path? E.g. http://localhost/mywebapp/xxx.html, the context here is ``mywebapp``. The problem is that servlets are managed by the container. So you can't create an instance or invoke a method on them directly. The only way to interact with them is through a container. –  Feb 11 '14 at 21:13
  • 1
    I've clarified this in the question. Basically, I know nothing in advance. On server A, I want to be able to pass "/data/myfile.txt", on server B, I want to pass "/home/user/files/otherfile.txt". So I assume I can somehow edit Tomcat's `context.xml` or similar to pass a value. I like the use of `getInitParameter`, but I want to set that value somewhere else. I've read it should be possible with `context.xml`, but nowhere I found exact instructions. – Ruben Verborgh Feb 11 '14 at 21:18
  • Might be a workaround, but the only way that works so far. Great, thanks! – Ruben Verborgh Feb 11 '14 at 22:15
  • 1
    Not particularly elegant solution, but it might work. –  Feb 11 '14 at 22:23
  • I agree, but I couldn't get any of the alternatives working. For some reason, Tomcat didn't pick up the file location if I added it to the classpath (at least from within Eclipse, but that's what I need for development). – Ruben Verborgh Feb 12 '14 at 10:58
1
  1. For a real project, you shouldn't be using servlets directly. Use a framework that helps guide you in a direction. Spring is very common.
  2. If you use Spring, in your application context you'll have a bean called a Property Placeholder Configurer. See for example: spring PropertyPlaceholderConfigurer and context:property-placeholder. The best practice is to hard code that file location (either file: or classpath:), but have that live outside the WAR file.

application-context.xml:

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations">
        <list>
            <value>classpath:config/properties/database.properties</value>
        </list>
    </property>
    <property name="ignoreResourceNotFound" value="true"/>
</bean>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
    <property name="driverClassName" value="${jdbc.driverClassName}" />
    <property name="url" value="${jdbc.url}" />
    <property name="username" value="${jdbc.username}" />
    <property name="password" value="${jdbc.password}" />
</bean>
  1. There are several design problems with loading configuration files in the code that needs it. It makes testing hard, it makes the general design less flexible, and if tomorrow you think that a property file is insufficient and you want to store configuration in something like ZooKeeper or JNDI, you have to rewrite a lot of code. Write your code as objects that get injected with settings instead, and instantiate them following the dataSource pattern from above.
Community
  • 1
  • 1
James Kingsbery
  • 7,298
  • 2
  • 38
  • 67
  • For larger projects, I totally agree and I have used Spring in the past. However, I'm working on a single servlet that needs to be reused in different applications. This is why I don't want to use any particular framework. – Ruben Verborgh Feb 12 '14 at 11:00
0

In your servlet code you can load it from a Properties file, which is residing in your classpath, like this

    Properties props = new Properties();
    try {
        props.load(this.getClass().getClassLoader().getResourceAsStream("config.properties"));
    } catch (IOException e1) {
        log.error(e1.getMessage(), e1);
    }
    String prop1 = props.getProperty("prop1");
Dhanush Gopinath
  • 5,652
  • 6
  • 37
  • 68