An expert of Object Oriented development and architecture, with 10 years experience in variety of platforms ranging from C++ low level communication systems, media streaming services and Java related frameworks and technologies, tools and development environments, web services in variety of implementation (Axis2, gSoap, JAX-WS and more). Serving today as a Member of the Comverse Chief Programmer Office. Also teaches in Shenkar College. Ronen has posted 1 posts at DZone. View Full User Profile

Maven Dependecy JAR Configuration

10.12.2010
| 7552 views |
  • submit to reddit

When moving to Maven from other frameworks there are many questions arising, such as how to handle the configuration of a dependency artifact. 

What will happen, for example, when your dependency has some configuration files? The standard Maven way is to put all the configuration files under src/main/resources. That way, they will be located in the resulting jar in the right place, enabling your application to function properly.

But, what if you need those configuration files to be subject to change? In the case that they are located inside the jar, you are stuck with the default configuration without the ability to edit and change the module behavior.

I will offer here a way to enable such jar configuration usage.

Let us say we have a module, packed into Jar called jar-with-conf. This module contains a single class called ConfReader.
This class contains a single static function that retrieves a value from a configuration file, given a key:

public class ConfReader
{
public static String getValue(String key)
{
String val = null;
Configuration config = null;
try
{
config = new PropertiesConfiguration("src/main/config/conf.properties");
}
catch (ConfigurationException e)
{
Logger.error(...)
}

if (config != null)
{
val = config.getString(key);
}

return val;
}
}

 Apache commons-configuration is used in this example.

This module has also, a configuration file called conf.properties and it is located at src/main/config:

key1=value1
key2=value2

Now, let us say there is an application that is dependent on jar-with-conf called app-using-jar-with-conf (forgive the names I named - one of the toughest task in computer science, as someone once said). This is the simplest application:

public class UsingDepWithConf
{
public static void main( String[] args )
{
System.out.println("value of key1 is " + ConfReader.getValue("key1"));
System.out.println("value of key2 is " + ConfReader.getValue("key2"));
}
}

 Of course, while running this simple main method, getValue() will fail as the configuration file is absent from the jar (we didn't put it under the resources directory).

In order to achieve the above requirements we will create a zip, containing the config directory and deploy it to a Maven repository. The client application will retrieve the zip and extract it to its own directory structure.

  1. Creating The Zip
    We will use maven-assembly-plugin for the job. First let's create an assembly descriptor and call it zip.xml:
    <assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd">
    <id>bin</id>
    <baseDirectory>/</baseDirectory>
    <formats>
    <format>zip</format>
    </formats>
    <fileSets>
    <fileSet>
    <directory>${basedir}/src/main/config</directory>
    </fileSet>
    </fileSets>
    </assembly>
    In plain English we say here we want to create a zip file from the config directory. Next let's configure the pom.xml of jar-with-conf to use this descriptor:
    <build>
    <plugins>
    <plugin>
    <artifactId>maven-assembly-plugin</artifactId>
    <version>2.2-beta-5</version>
    <configuration>
    <descriptors>
    <descriptor>src/main/assembly/zip.xml</descriptor>
    </descriptors>
    </configuration>
    <executions>
    <execution>
    <id>make-assembly</id>
    <phase>package</phase>
    <goals>
    <goal>single</goal>
    </goals>
    </execution>
    </executions>
    </plugin>
    </plugins>
    </build>
    Which means, we want to create a single zip file during the package phase according to the zip.xml assembly file.
  2. Deploy The Zip
    Of course, packing the config directory into a zip file will not suffice. There is no way client application can access it. That is exactly the reason we have Maven repositories. We will deploy the zip file alongside the module jar artifact. For that, I'll use maven-deploy-plugin. This plugin's deploy goal is bound when creating a jar artifact to the deploy phase in the default Maven lifecycle. Here, I am going to use a different goal - deploy-file which enables us the deployments of artifacts other than the default ones. Here is a snippet of the deployment task in the pom.xml:
    <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-deploy-plugin</artifactId>
    <version>2.4</version>
    <executions>
    <execution>
    <id>deploy-conf-zip</id>
    <phase>deploy</phase>
    <goals>
    <goal>deploy-file</goal>
    </goals>
    </execution>
    </executions>
    <configuration>
    <repositoryId>snapshots</repositoryId>
    <file>${project.build.directory}/${project.artifactId}-${project.version}-bin.zip</file>
    <url>http://nexus:8081/nexus/content/repositories/snapshots</url>
    <groupId>com.my-company.example</groupId>
    <artifactId>jar-with-conf-config</artifactId>
    <version>${project.version}</version>
    <packaging>zip</packaging>
    </configuration>
    </plugin>
    In the above snippet, the goal deploy-file is bound to the deploy phase, and in the configuration section, all the relevant information is supplied for successful deployment, like repositoryId (make sure it is configured in your settings.xml file and that you have permissions to deploy to the repository), repository URL and, of course, the new artifact identifier - the trinity of groupId, artifactId and version.
  3. Using The Jar with the Zip
    Now, let's configure an application to be dependent on the jar-with-conf artifact and also retrieve the configuration folder and put it in the right place. The following are snippets from the pom.xml of the application app-using-jar-with-conf: The easy part is the artifact dependency:
    <dependency>
    <groupId>com.my-company.example</groupId>
    <artifactId>jar-with-conf</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <type>jar</type>
    <scope>compile</scope>
    </dependency>
    Next, let's retrieve the second artifact that contains the configuration:
    <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-dependency-plugin</artifactId>
    <executions>
    <execution>
    <id>unpack</id>
    <phase>package</phase>
    <goals>
    <goal>unpack</goal>
    </goals>
    <configuration>
    <artifactItems>
    <artifactItem>
    <groupId>com.my-company.example</groupId>
    <artifactId>jar-with-conf-config</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <type>zip</type>
    <overWrite>false</overWrite>
    <outputDirectory>${basedir}</outputDirectory>
    </artifactItem>
    </artifactItems>
    <outputDirectory>${basedir}</outputDirectory>
    </configuration>
    </execution>
    </executions>
    </plugin>
    The details of this plugin can be found here. The result of this will be an unpacked config folder under src/main in the app-using-jar-with-conf project. The jar of jar-with-conf is in classpath and its configuration is available for change in the client application.


This kind of project setup encourages modularity in big systems. System architects should break the design into self-contained modules with simple and clear functionality and API. Those modules can serve whichever piece of software that is in need of such functionality.

Taken from http://ronenp.wordpress.com/2010/08/12/maven-dependecy-jar-configuration/.

Published at DZone with permission of its author, Ronen Perez.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

John J. Franey replied on Tue, 2010/10/12 - 6:34am

This appears to be a contrived problem and makes maven look bad.

I've come across many java libraries requiring configuration and have not had to extract the configuration out of their distribution into my projects that use them. Lets see: JPA libraries read 'persistence.xml', log4j projects read 'log4j.xml', .... I didn't have to pull these out of their distributions in a build file.

I bet configuration file extraction from a distribution archive will look ugly in any build tool. It looks ugly in maven. I favor maven and I would not want to see this code. It is too much noise.

Why not create a configuration file (with the correct format) in the dependent project's src/main/resources? You would be able to spend your time on more interesting tasks. If 'jar-with-conf' does not allow configuration like this....well, its not really 'configuration' in the sense we expect.

Ronen Perez replied on Tue, 2010/10/12 - 7:30am in response to: John J. Franey

This solution is intended for a situation we encounter many times. The situation is that an application is dependent on a large number of existing modules, some developed years ago. Those modules are in most cases “read only” and are built in a way that they read the configuration from XML or properties file and you must be able to change the configuration during deployment.
In log4j you rewrite the configuration each time, giving only the parameters you need. This in not always possible. I am referring to a situation that the module needs the configuration to exist in its entirety. in that case you can either manually copy it from somewhere and manage every time the module (and thus the configuration) is changing or use this approach.

The reason I prefer that is the same reason we prefer artifacts to be downloaded to our classpath automatically via the Maven dependency management rather than to manually put them in a lib folder in previous days.

Pieter van der Meer replied on Tue, 2010/10/12 - 10:00am

Ronen,
I must agree with Franey that this makes maven and any other tool look bad. The problem is not with the build tool but the library. If its important that some properties are/must/can be loaded with other values than a certain default the library (jar) must be able to handle it.
What you are trying to do is solve a problem that was introduced by your self, pack the config (staticly) in the artifact.
A current project i'm working does this multiple times (spring based). Care is taken to make the properties that need to be influenced outside the library (jar) can be set outside it. i.e. the propertyOverride mechanism from spring.
(BTW, log4j can be solved with multiple classloaders, make sure your toplevel CL has the log4j config on its path.

Mladen Girazovski replied on Tue, 2010/10/12 - 10:13am in response to: John J. Franey

I agree with John, this does look very weird, i have personally never come accross a situation where i needed something like this.

Another odd thing is the hardcoded path

config = new PropertiesConfiguration("src/main/config/conf.properties"); 

This would work only during build time (and even then it is bad imho, at least use the target instead of src), since at runtime there is no path src/main/... i guess this is due to the illustrating example, not a real world example.

Anyways, seems to me that  those were old non-Maven Projects that were not properly mavenized.

Ronen Perez replied on Tue, 2010/10/12 - 1:32pm in response to: Mladen Girazovski

spot on. this is exactly the situation I am referring to.

Thomas Kern replied on Thu, 2012/09/06 - 10:58am

Although I’m sure this is a valid solution to some problems, more generally it goes against the grain of dependency injection etc.

That is, in most cases, wouldn’t it be better for the component to allow itself to be configured by its client, say as a JavaBean, or via a Builder, to allow flexible configuration with Spring or Guice.

Failing that, couldn’t it just accept a Properties instance to its constructor, and let its container worry about how the properties will be loaded?

http://www.java-tips.org 

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.