views:

699

answers:

6

I have inherited a Java application (servlets) that runs under Tomcat. For historical reasons, the code has different "look and feel" options based on where the application will be deployed (essentially a matter of branding).

There are several constants that control this branding process, which have different functions and should not be compacted into a single constant (i.e. BRAND, MULTI-LANGUAGE, plus the locations of icons and css style sheets, etc.).

Currently the development team must manually change the constants (they are at least localized in one data class and well documented), then recompile the application using ANT.

What is the best way to automate this process assuming at least Ant 1.8 and Java 6.x?

I know that there have not been any good solutions using compiler arguments (like one could do in C or C++), and am leaning to some "best way" to edit either the source file containing the constants, or putting them into another file and swapping them out using the ant build process. I'd like to have a result that would work using something like "ant build brand-x" where changing the brand would change the resulting build.

Thanks,

-Richard

+2  A: 

Use the replace task in ant to change the values.

Adam Peck
This is brute-force, but it works. It does overwrite the file, however - so you must create a saved (or template) copy of the file and copy it fresh each time. -R
Huntrods
A: 

Use Ant property files, and build with "-Dbrand=X".

Limbic System
+1  A: 

There's also a "spring" way, which is to use a properties file, and a bean that pulls the value from the properties and injects them into the classes that need them, e.g.:

<bean id="propertyPlaceholder"  class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
 <property name="location" value="classpath:configuration.properties" />
</bean>

and then, you can inject properties with an "ant-like" syntax:

<bean id="connectionPool"  class="com.mysql.jdbc.jdbc2.optional.MysqlConnectionPoolDataSource">
 <property name="databaseName" value="mydb" />
 <property name="url" value="${db.url}" />
 ...

which probably would entail more rewriting than you'd like. If you're going to be changing constants on each compile, I'd watch out for this gotcha (if you're using static finals, that is).

public class Foo {
 public static final int SOME_CONSTANT=1;
..
}

public class Bar {
  ...
   int x=5+Foo.SOME_CONSTANT;
  ...
}

If you then change SOME_CONSTANT in Foo to 2 but don't recompile Bar, Bar will retain the value of 1 for SOME_CONSTANT, as the static finals are compiled in (since the compiler sees that it shouldn't need to ever figure them out again).

Steve B.
A: 

I prefer using ant's expandproperties filter instead of the replace task. With the replace task, the build file tends to grow to be mostly tokenization. expandproperties lets you embed ant properties directly in your text.

<copy file="from" tofile="to">
  <filterchain>
    <expandproperties />
  </filterchain>
</copy>
Craig P. Motlin
+7  A: 

Put your values into a properties file, say "myapp.properties" and then load them into your constants from the classpath at startup (see below for how this fits into the build process):

public class Constants
{
    private static final Properties props = new Properties();
    public static final String MY_CONSTANT;

    static
    {
        InputStream input = Constants.class.getResourceAsStream("/myapp.properties");
        if(input != null)
        {
           try
           {
              properties.load(input);
           }
           catch(IOException e)
           {
              // TODO log error
           }
        }

        // Initialize constants (dont' forget defaults)
        MY_CONSTANT = properties.getProperty("constant", "default");
        // .. other constants ...
    }
}

Now, have a separate properties file for each branding. Pass its name to ANT via -D or build.properties and copy the file to your build directory right before you jar (or war) it up.

Obviously, the code above will work, but there are many ways you can clean it up and make it bullet proof.

Dave Ray
This is a good solution, but does not work for non-string constants (I tried - several ways). That's why I ended up going with the less elegant ant replace method. -Richard
Huntrods
You can't just add a "private static int getInt(String name, int def)" helper method? I don't see any reason it wouldn't work for non-Strings, at least with a little more code.
Dave Ray
A: 

I have a solution that works as needed for this particular situation. I've used the ant replace task in conjunction with a "saved" version of the constant class:

    <target name="one" description="constant substitution #1">
 <delete file="./tme3/MyConst.java" />
 <copy file="./save/MyConst.java" tofile="./tme3/MyConst.java" />
 <replace file="./tme3/MyConst.java" token="@BRANDING@" value="ONE_BRAND"/>
 <replace file="./tme3/MyConst.java" token="@STYLESHEET@" value="../stylesheet/onebrand.css"/>
 <replace file="./tme3/MyConst.java" token="@FAVICON@" value="../images/onebrand.ico"/>
 <replace file="./tme3/MyConst.java" token="@SHOW_LANGUAGES@" value="false"/>
</target>

I just make copies of this block and change the substitutions for the cases I need - in my particular case there are 3 sets now, but more expected. Thanks one and all for the great replies.

-Richard

Huntrods