Sunday, March 14, 2010

Writing a Property Loader in Java EE 6 and Testing it using Arquillian

So, like most people, I'm interested in Java EE 6, and the new CDI framework that comes with it. I see it as a huge time saver and seems to fit in well with the whole ecosystem of Java EE (not to mention, it also knocks Spring down a little, a plus in my book).

We're beginning to adopt Java EE 6 at work. A big application has been running for a few weeks already and now we have a couple of smaller applications that are likely to run with our live site (one being a search engine). I'm not aiming this article to be an intro to Weld, CDI or Java EE 6; I would expect anyone reading this to be at least familiar with the concepts.

Something that's big for us is testing, and allowing the application to be configurable. We definitely push the separation between development and administration and I have been ensuring that all applications match that mindset. As a result, loading property files is a typical use case for us. I was looking at how we've been doing it, and decided that it should be much simpler if we had a producer method to produce property files. Some of our files reside in the deployment archives, most on the file system; and we usually like to make it a system property to configure where to read the files from. Below is some sample code explaining how to implement a Property Loader in CDI

First, the qualifier; it only takes one argument the value.

@Qualifier
@Retention(RUNTIME)
@Target({METHOD, FIELD, PARAMETER, TYPE})
public @interface ConfiguredBy {
@Nonbinding public String value();
}


We use Nonbinding on the value so that there's only one producer necessary; since the value is essentially an argument to what we want to produce, and what it gets attached to is the Produced object. For the sake of my code, I only support producing instances of java.util.Properties.

Here's what the Producer method looks like (and yell if you don't like my code; note that the real version would be cleaner assuming you had an injectable SLF4J Logger).

   @Produces @ConfiguredBy("")
/**
* Produces a java.util.Properties object based on the qualifier ConfiguredBy
* and the injection point.
*
* Behavior is as follows:
* - the value can be a System property or a path.
* - If it's a system property, we convert it to the value of that property first
*
* - Then we check to see if the value now is an absolute path or a classpath entry
* - We try both.
*/
public Properties produceProperties(InjectionPoint ip) {
Properties p = new Properties();
String value = ip.getAnnotated().getAnnotation(ConfiguredBy.class).value();
System.out.println("Producing properties with value.... "+value);
if(value == null || value.equals("")) {
//if the given file is empty, we can't load it too well.
return p;
}
String propValue = System.getProperty(value);
if(propValue != null) {
value = propValue;
}

File f = new File(value);
if(f.exists() && !f.isDirectory()) {
//so it's on the file system, let's load it.
try{
FileInputStream fis = new FileInputStream(f);
p.load(fis);
} catch (IOException e) {
System.out.println("Problem reading the file, ignoring.");
}
}
//now we try to get it from the classpath, as a resource.
try{
InputStream is = this.getClass().getClassLoader().getResourceAsStream(value);
p.load(is);
} catch (Exception e) {
System.out.println("Problem reading the file, ignoring.");
}
return p;
}
So what this code allows us to do is attempt to load a file based on a System property, and it can exist on the file system or as a resource in the class loader.

Now here's the fun part, how to test this using Arquillian. For those that don't know, Arquillian is a new tool from JBoss designed to help testing software in a container. As of this post, they support Glassfish V3, Weld SE, JBoss AS 5.1 and JBoss AS 6.0. It looks like they also support OpenEJB; but I'm not sure what version. I was able to run my tests on this class using the Weld SE container using JUnit. They also support TestNG; but I've historically always leaned towards JUnit.

Here's an example of a testing class, in this case I use a System property to find the location of a file in the classpath.

First, the maven dependencies.


<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.7</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.jboss.weld</groupId>
<artifactId>weld-se</artifactId>
<version>1.0.1-Final</version>
</dependency>

<dependency>
<groupId>org.jboss.arquillian.container</groupId>
<artifactId>arquillian-weld-embedded</artifactId>
<version>1.0.0-SNAPSHOT</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.jboss.arquillian</groupId>
<artifactId>arquillian-junit</artifactId>
<version>1.0.0-SNAPSHOT</version>
<scope>test</scope>
</dependency>


In order to test a system property, I needed to add it explicitly in the surefire plugin in maven:


<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.5</version>
<configuration>
<systemPropertyVariables>
<propertyProducerTestSystemProperty.file>META-INF/test1.properties</propertyProducerTestSystemProperty.file>
</systemPropertyVariables>
</configuration>
</plugin>


My test1.properties file, located in src/test/resources/META-INF/, was simply:


key1=value2
key2=value2
key3=something
key4=key4

My test case class was then very simple to write. This is all I had to do to test my producer method, create an injection point!


@RunWith(Arquillian.class)
public class PropertyProducerTestSystemProperty {

@Deployment
public static JavaArchive createDeployment() {
return Archives.create("test.jar", JavaArchive.class)
.addClass(PropertyProducer.class)
.addClass(ConfiguredBy.class)
.addManifestResource("META-INF/test1.properties",
ArchivePaths.create("test1.properties"))
.addManifestResource(
"META-INF/beans.xml",
ArchivePaths.create("beans.xml"));
}

@Inject @ConfiguredBy("propertyProducerTestSystemProperty.file")
Properties properties;

@Test
public void testPropertySize() {
Assert.assertEquals(4,properties.size());
}
@Test
public void testPropertyContains() {
Assert.assertFalse(properties.containsKey("search.url.base"));

}
@Test
public void testPropertyValue() {
Assert.assertEquals("something", properties.getProperty("key3"));
}

}

The createDeployment method, since it's annotated @Deployment signifies the files that will be included in the test - what the container will need to deploy. Note that there's no code in my code that has a direct dependency on how it's tested - switching from Weld SE to Glassfish is simply done in maven. Also note that they have more robush ways of testing in the examples, but it's beyond the scope of this topic.

For each test case, the injection point will get processed and injected; and you can verify that the tests pass.

If you want to give this a try, you can clone the source from my google code site, tadcdicomps and then click on the Source tab. this is the properties project under hg. To run the tests, I have a Test Suite that has multiple test classes in it, the command I ran after building was

mvn test -Dtest=PropertiesTestSuite

Enjoy!

1 comment: