Quantcast
Viewing all articles
Browse latest Browse all 15

Java Property Management Across Build Environments

Using Java properties to specify “boundary conditions” or user preferences of any non-trivial application is standard practice in many development shops. However, even though using properties is a good abstraction, if the property files are packaged with the application, they are not exactly a 100% configurable at runtime.

On the other hand, if they are not packaged with the application and interjected on the application’s classpath, they can be configured at runtime. But that is not a really palatable option from source code management point of view. In this post, I will talk about the pros and cons of these two approaches, and then describe a hybrid approach that works well and keeps everyone happy!

There are two technologies that I will rely on for the proposed solution:

Let us assume that the following build environments exist:

  • DEV – local developer’s environment.
  • NIGHTLY – build server’s continuous build
  • TEST – internal testing
  • UAT – User Acceptance Testing
  • STAGE – pre-production
  • PROD – production

Approach 1: Build-time Injection of Properties

This approach uses Maven’s Resource filtering and Profiles.

First create the following property files and place them in src/main/filters of your mavenized project:

  • acme-common.properties – Holds properties that are not environment specific, like customer.telephone.number, company.tag.line etc
  • acme-${env}.properties – There should be as many files as there are build environments. For example, acme-dev.properties, acme-nightly.properties etc. These files contain properties that are environment specific. For example, datasource urls including userId and passwords, JMS endpoints, email flags etc.

Optionally create one more file as below and place it in ${user.home}

  • my-acme.properties – This holds all properties that are specific to the developer. Examples could be email address of the developer, local db url etc.

Since this property file is developer specific, notice that it is not in the source tree (and therefore not in source control).

Now configure the filter section of the pom of your project like so:

    <build>
        ...
	    <filters>
	      <filter> src/main/filters/acme-common.properties </filter>
	      <filter> src/main/filters/acme-${env}.properties </filter>
	    </filters>
		<resources>
			<resource>
				<directory>src/main/resources</directory>
				<filtering>true</filtering>
			</resource>
		</resources>
          ...
	</build>

Note that files specified later in the filters section override the earlier ones for the same property name.

In addition, set up a profile section like so:

    <profiles>
        <profile>
            <id>DEV</id>
             <properties><env>dev</env></properties>
        </profile>
        <profile>
            <id>NIGHTLY</id>
            <properties><env>nightly</env></properties>
        </profile>
		...
        <profile>
            <id>PROD</id>
            <properties><env>prod</env></properties>
        </profile>
    </profiles>

By doing the above and issuing a maven package -PUAT will ensure that values specified in acme-common.properties, acme-uat.properties and my-acme.properties are injected appropriately in the (xml files) that reside in the resources directory. Therefore the war or jar or ear (whatever the final artifact is) will have the correct values in them.

Note,that in this approach, there should not be any property file in the final classpath of the artifact, because properties are already injected into appropriate files at build time.

Pros:

  • Since property files are part of the code base, and the code base is built for a specific environment, there is a lower chance of the wrong file being used in the wrong environment.
  • There is no deploy-time  modification of the build environment. There is only one process that modifies the run-time environment, and that is the deploy process.

Cons:

  • Properties cannot be changed without re-building and re-deploying the application. Changing property file values in a packaged war can lead to all sort of problems from cached properties issues to re-loading of the webapp automatically when the timestamp on the property file changes (depends on the servlet container).
  • Secure passwords have to be checked into source control.
  • The application has to be re-built for every environment.

Approach 2: Run-time substitution of Properties

In this approach, we will use Spring’s PropertyPlaceHolderConfigurer and a property file that is placed on the application’s classpath.

First create one property file (or several, for that matter) that hold all the properties used by the application and place it in a location outside source control.

The add the following bean definition in Spring’s application context file:

<bean id="propertyPlaceholderConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
  <property name="locations">
	    <list>
	      <value>classpath:acme-runtime.properties</value>
  </property>
  <property name="ignoreResourceNotFound" value="true"/>
</bean>

Or, if you prefer, the more recent:

<context:property-placeholder system-properties-mode="OVERRIDE" location="acme-runtime.properties"/>

By doing he above, we will have ensured that at run-time, all properties are substituted with the correct values.

Pros:

  • Properties can be changed on the fly, without having to build and re-deploy the application.
  • The property file(s) are not under source control, so secure passwords are not in source control
  • Password management can happen for all environments by a trusted source instead of the development group.

Cons:

  • Since the files are not under source control, there is a chance of the wrong file getting into the wrong environment. This is especially cumbersome in development environments where a property file will have to be passed around to each developer separately to get her up and running.
  • A separate process will have to be implemented (apart from the deploy process) to mange these property files.

Approach 3: Combine the two approaches

Here we will use Maven filtering and Spring’s PropertyPlaceHolderConfigurer together.
See figure below:

Image may be NSFW.
Clik here to view.

However, the key is the balance between how much is achieved via Build time Injection vs Run time substitution. Depending on what is done using each of these technologies, the above diagonal line can be made to move to the right (for more build-time behavior) or to the left (for more run-time behavior).

Here are the steps for one possible configuration:

  1. Set Maven filtering on on the resources directory like so. Note that there are no filter files.
  2. 	<build>
    	    ...
    		<resources>
    			<resource>
    				<directory>src/main/resources</directory>
    				<filtering>true</filtering>
    			</resource>
    		</resources>
    		...
    	</build>
  3. Create the following property files in your resources directory. All the property files will be in the packaged artifact.
  4.       src/main/resources/
                             |
                             +-- properties/
                                         |
                                         +-- acme-common.properties
                                         +-- acme-dev.properties
                                         +-- acme-qas.properties
                                         +-- acme-prd.properties
  5. Specify the following profiles and property values in the pom.xml
  6.     <profiles>
            <profile>
                <id>DEV</id>
                 <properties>
                    <env>dev</env>
                    <log-file-location>${user.home}/logs/acme-dev.log</log-file-location>
                    <acme-runtime-properties-location>${user.home}/acme-runtime.properties</acme-runtime-properties-location>
                </properties>
            </profile>
            <profile>
                <id>QAS</id>
                 <properties>
                    <env>qas</env>
                    <log-file-location>/var/tmp/logs/acme-qas.log</log-file-location>
                    <acme-runtime-properties-location>/var/tmp/acme-runtime.properties</acme-runtime-properties-location>
                </properties>
            </profile>
            <profile>
                <id>PRD</id>
                 <properties>
                    <env>prd</env>
                    <log-file-location>/opt/app/logs/acme-prd.log</log-file-location>
                    <acme-runtime-properties-location>/opt/app/acme-runtime.properties</acme-runtime-properties-location>
                </properties>
            </profile>
        </profiles>
  7. Lastly, place the spring contexts in src/main/resources and introduce the PropertyPlaceHolderConfigurer in the bootstrap context like so:
  8.     	<context:property-placeholder system-properties-mode="OVERRIDE"
    			location="classpath:properties/acme-common.properties,
    			classpath:properties/acme-${env}.properties,
    			file://${acme-runtime-properties-location}"
    			ignore-resource-not-found="true"
    			/>
Tying it all together

By specifying the configuration like above, we will have achieved:

  1. The ability to place environment specific properties in the environment specific property file(s) and have the correct file passed on to the PropertyPlaceHolderConfigurer at build time. Note that even though, all the property files will exist in the final artifact, only the correct file will be specified to the PropertyPlaceHolderConfigrer to enable run time substitution.
  2. The PropertyPlaceHolderConfigurer will substitute the correct values in the rest of the spring contexts at run time from the passed in common and environment specific files.
  3. Those properties that are not specified in the common or environment specific property files are picked up from the runtime-properties file. (Even if the same property is specified in the common or environment specific files AND the runtime property file, the runtime property will trump the similarly named property in the other files). The runtime property file, therefore becomes a good place to specify passwords and other secure information that will not be checked into source control.
  4. Note that the run-time property file has been specified using the file protocol. This implies that that file can be placed anywhere in the file system, outside the application or system classpath. And certainly out of source control.
  5. Since log4j.xml file (that has the log file location specified via a property) is NOT managed by spring (in this configuration), it’s log-file-location property is specified outside the property files and directly in the pom profiles.
  6. Note the use of ${user.home} indirection for log file location. This is to prevent unnecessary changes in the pom which would occur if each developer were to specify her own log-file-location differently.

Now that we know the order the files above are accessed in (at run time) we can decide for which environment we would like to place secure information in source control by placing them in environment specific property files, and for which, not, by placing them in the runtime property file.


Viewing all articles
Browse latest Browse all 15

Trending Articles