Saturday, March 20, 2010

Dyanmic ResourceBundles in CDI

So I consider this blog post more of a"Part 2" from my last blog post.

If you're anything like me, you like using ResourceBundles but find them to be an annoyance when it comes to using the same bundle in both your view and actions. I'm attempting to, with this blog post, make that a lot simpler for you.

What are resource bundles? java.util.ResourceBundle is an abstract class in Java SE that is Locale aware. It's an easy way to add i18n support in your application. You can use it to load "bundles" either programatically or from a properties file. In this example, I will provide you with some code that loads the bundle from a properties file, have an optional annotation that can specify what bundle and what locale, as well as code that dynamically looks up Locales.

Just like last time, we'll need to have a Producer method. In this example, the annotation is optional (as we assume that this code can handle the injection of any instance of java.util.ResourceBundle) and there will be an optional secondary producer for the Locale itself.

So first, the first draft of the Producer method:



@Produces
public ResourceBundle produceResourceBundle(InjectionPoint ip) {
Class container = ip.getMember().getDeclaringClass();
String baseName = container.getCanonicalName().replace(".","/");
Locale locale = Locale.getDefault();
return ResourceBundle.getBundle(baseName, locale);
}


Now, clearly this producer doesn't work that well, but it's a start. Using this, you can use @Inject ResourceBundle bundle; which if it is placed inside of a class com.tad.cdi.comps.Bundler, will look for a bundle with the same name; com/tad/cdi/comps/Bundler. Good so far, right?

Now I'm going to introduce the optional qualifier, @Bundle. Using @Bundle, you can specify a runtime dependency on either a different ResourceBundle (then the one being injected into) or a different locale.

Bundle.java:

@Retention(RUNTIME)
@Target({METHOD, FIELD, PARAMETER, TYPE})
public @interface Bundle {
public String baseName() default "";
public String locale() default "";
}


The Producer method to support this grows a bit, as we now support @Bundle, @Bundle(baseName="") @Bundle(locale="") and @Bundle(baseName="",locale="") so we have to add this logic to the producer.

A note for those not familiar with them: Locales, in string format, take the shape of language_Country_qualifier, so you can write something as simple as "en", "sp_US", or even "fr_CA_with.napoleon.dialect" as your Bundle.

An appropriate producer method would look something like this:


@Produces
public ResourceBundle produceResourceBundle(InjectionPoint ip) {
Bundle b = null;
if (ip.getAnnotated().isAnnotationPresent(Bundle.class)) {
b = ip.getAnnotated().getAnnotation(Bundle.class);
}
Class container = ip.getMember().getDeclaringClass();
String baseName = (b == null || b.baseName().equals("")) ? container.getCanonicalName() : b.baseName();
Locale locale = null;
if (b == null) {
locale = Locale.getDefault();
} else if (b.locale().equals("") || b.locale().equals("default")) {
locale = Locale.getDefault();
} else {
String[] lpieces = b.locale().split("_", 3);
switch (lpieces.length) {
case 0:
locale = Locale.getDefault();
break;
case 1:
locale = new Locale(lpieces[0]);
break;
case 2:
locale = new Locale(lpieces[0], lpieces[1]);
break;
case 3:
locale = new Locale(lpieces[0], lpieces[1], lpieces[2]);
break;
default:
locale = Locale.getDefault();
break;
}
}
baseName = baseName.replace(".", "/");
return ResourceBundle.getBundle(baseName, locale);
}


What we're doing with this code, we allow a a baseName to be specified in the @Bundle, this can take the form of "some.dotted.expression" or even "some/path/expression," meaning the bundle can be anywhere - doesn't need to be one dedicated to this class. Note that the behavior of @Bundle if no baseName is found is to use the enclosing class. Locale works similarly as well, you can specify locale="some_locale_expression" to load the locale, or leave it blank to load the default locale.

Still, this may not do everything that you need. If you're like me, you have some object that contains the locale of the current HTTP Session/User. And it may look something like this:


@SessionScoped
public class User {
...
public Locale getUsersLocale() { return myLocale; }
...
}


Well, if you've already gotten that part, then you're almost done. Using the BeanManager interface in CDI, you can actually dynamically load this Locale using BeanManager.getReference. First thing you need to do is add a Producer equivalent to the above method. Preferably, since User is already a SessionScoped CDI ManagedBean, the producer will go into the same method. Now, you do need to remember to handle the rules of CDI Producers - can't return null. So here's how to add the necessary logic to produce the Locale:


@SessionScoped
public class User {
...
public Locale getUsersLocale() { return myLocale; }
@Produces public Locale produceSessionUsersLocale() { return myLocale; }
...
}


What this now does is produce a Dependent Locale into your context. You can then modify your ResourceBundle producer in the following way; note that I have added logic that works even if this producer is absent, based on the previous steps.


@Inject
BeanManager beanManager;

@Produces
public ResourceBundle produceResourceBundle(InjectionPoint ip) {
Bundle b = null;
if (ip.getAnnotated().isAnnotationPresent(Bundle.class)) {
b = ip.getAnnotated().getAnnotation(Bundle.class);
}
Class container = ip.getMember().getDeclaringClass();
String baseName = (b == null || b.baseName().equals("")) ? container.getCanonicalName() : b.baseName();
Locale locale = null;
try {
Bean localeBean = (Bean) beanManager.getBeans(Locale.class).iterator().next();
CreationalContext cc = beanManager.createCreationalContext(localeBean);
Locale producedLocale = (Locale) beanManager.getReference(localeBean, Locale.class, cc);
locale = producedLocale;
} catch (Exception e) {
//not sure what to do here yet.
System.out.println("Caught an exception trying to load a ResourceBundle");
if (b == null) {
locale = Locale.getDefault();
} else if (b.locale().equals("") || b.locale().equals("default")) {
locale = Locale.getDefault();
} else {
String[] lpieces = b.locale().split("_", 3);
switch (lpieces.length) {
case 0:
locale = Locale.getDefault();
break;
case 1:
locale = new Locale(lpieces[0]);
break;
case 2:
locale = new Locale(lpieces[0], lpieces[1]);
break;
case 3:
locale = new Locale(lpieces[0], lpieces[1], lpieces[2]);
break;
default:
locale = Locale.getDefault();
break;
}
}
}
baseName = baseName.replace(".", "/");
return ResourceBundle.getBundle(baseName, locale);
}


So as you can see, we are attempting to lookup the Locale in the current context. If we find one, we always use that Locale; otherwise we use the original logic.

Now obviously, this guide wouldn't be too useful unless we had some Arquillian test cases.

The code is located in the same project as the previous post, but here are the essentials.

A test producer object, this can produce Locale as needed:


public class LocaleProducer {
@Produces
public Locale produceSomeLocale() {
System.out.println("Have a request for Locale English...");
return Locale.ENGLISH;
}
}


I also wrote two tests, one that uses the Locales with and without @Bundle, another that uses the producer style.

To verify the logic without Producing Locales:


@Deployment
public static JavaArchive createDeployment() {
return Archives.create("test.jar", JavaArchive.class)
.addClass(BundleProducer.class)
.addClass(Bundle.class)
.addResource("com/tad/cdi/mods/properties/ResourceBundleInjectTest.properties")
.addResource("com/tad/cdi/mods/properties/Special_sp.properties")
.addManifestResource("META-INF/beans.xml",
ArchivePaths.create("beans.xml"));
}

@Inject ResourceBundle bundle;

@Inject @Bundle(baseName="com/tad/cdi/mods/properties/Special",locale="sp")
ResourceBundle bundleSpecialSP;

@Test
public void testBundleContents() {
assertEquals("world",bundle.getString("hello"));
}

@Test
public void testBundleSpecified() {
assertEquals("bottom",bundleSpecialSP.getString("bob"));
}


And to test including the ability to produce locales:


@Deployment
public static JavaArchive createDeployment() {
return Archives.create("test.jar", JavaArchive.class)
.addClass(BundleProducer.class)
.addClass(Bundle.class)
.addClass(LocaleProducer.class)
.addResource("com/tad/cdi/mods/properties/ProducedLocaleTest_en.properties")
.addManifestResource("META-INF/beans.xml",
ArchivePaths.create("beans.xml"));
}

@Inject ResourceBundle bundle;

@Test
public void testBundleContents() {
assertEquals("world",bundle.getString("hello"));
}


So there you have it, enjoy your ResourceBundles!

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!