Tuesday, 21 June 2011

Madura Rules Part 5

I've been working on, and posting on, other topics but I did say I would mention I18n issues and how Madura Rules handles them. In fact the heavy lifting is done in Madura Objects, when it isn't done by standard Java features. If you don't know about I18n at all then go here.

The specific problems we need to solve with this are:
  • Labels for fields should be in the selected language
  • Drop down lists should be populated in the correct language
The first thing to notice is that in a multi-user application, like any web app, we have a problem. Web requests arrive with a locale specified, which is great, but we need it 15 call levels into the application, which is not. Either we pass the locale in a lot of methods or we do something smarter. We could use Locale.setDefault() but that is not specific to this request. Other requests will be forced to use our setting, or we will be forced to use theirs. The setting is JVM wide. Not what we need.

We have a similar issue with the .properties file. That is a little different because we can use Spring to inject the file where we want it, this will automatically pick the right version of the file according to our locale. But it is too limiting to have everything in .properties files and there are lots of dynamically instantiated classes that I need to inject it into, so I can't use Spring there.

We can solve all these issues with a small factory class called MessageTranslator. While I almost always use Spring injection this time I'm using a factory because it gives me a simple way to get a class that is local to the current thread and hence to the local request. I know Spring provides various scopes for beans but this is simpler.

So, MessageTranslator allows me to construct an instance of the class and store it on a ThreadLocal. The class holds the Locale and the MessageResource. The MessageResource is Spring's nice way to handle those .properties files. But we will come back to that.

Early in the processing of the request, ie when I still have easy access to the locale, I create a MessageTranslator for this thread with the locale and the MessageResource. Thereafter I can get a locale based message like this:
MessageTranslator.getMessageTranslator().getMessage(String code)
The getMessage() method is overridden to allow me to pass arguments and a default message if I need it. But the point is I do not need to pass the locale or event the MessageResource. Nor do I need to inject the MessageResource to use it.

The code is simple enough and you can find it in Madura Objects at
nz.co.senanque.localemanagement.MessageTranslator

So what do I use this for exactly?

First, whenever Madura Objects fetches a field label it calls the MessageTranslator assuming the name being used is actually a key to the resource. So if you have a field labelled 'amount' then it will look up your properties file for that key and return what it finds. For the English properties file you would have amount=amount but for the French file you would perhaps use amount=quantite. Also, if you failed to put a label on a field then the field name will be used.

To make this work you create a bean in the Spring file like this:
<bean id="messageSource"
 class="org.springframework.context.support.ResourceBundleMessageSource">
 <property name="basenames">
 <list>
  <value>Messages</value>
 </list>
 </property>
</bean>
and then inject that somewhere convenient for you to create the MessageTranslator for this request. This variant shows that you can have a list of properties files, though we only used one here. This is Messages.properties. The French variant would be Messages_fr_FR.properties but you don't need to specify it in your Spring file, it will be found automatically.

So far so good. We can deliver the right labels for the language. But what about the values in the drop down?

Here it gets a little tricky because, while we can just create a properties file for everything, we may be loading values from a database or other external source. Those values might change and we won't want to rebuild our application with a new properties file every time they do. We also want to be able to maintain the alternate language data in a similar way to the primary data. That means if it the primary data is stored in a database we want the translated names stored there too, not in a properties file we forget to update.

Fortunately Spring helps us out here. We can just adjust our messageSource bean so that it looks like this:
<bean id="messageSource" class="nz.co.senanque.i18n.XMLMessageSource">
 <property name="resource" value="classpath:/Messages.xml"/>
 <property name="parentMessageSource">
 <bean class="org.springframework.context.support.ResourceBundleMessageSource">
  <property name="basenames">
  <list>
   <value>Messages</value>
  </list>
  </property>
 </bean>
 </property>
</bean>
I'm using a custom class (XMLMessageSource) that extends org.springframework.context.support.AbstractMessageSource. AbstractMessageSource allows you to inject a parent source and in this case I am using my Messages.properties file. If XMLMessageSource fails to find an entry it will delegate to the parent and look there.
My XMLMessageSource is fairly simple, it looks up an XML document for the right entry and it handles multiple locales which may be specified in the document. I use this for testing and illustration rather than production. You can replace this with a class that looks up a database or whatever. The net effect is that any lookups go to the first class and then to the second (and possibly to more places if you can use that).

When an application is building a drop down list it calls nz.co.senanque.validationengine.FieldMetadata.getChoiceList() which gives a list of ChoiceBase objects. Each of these has a key and a description. The key is the real value, the value we want to store in a database etc and the value we would compare with in a rule. The description is the display value. If we call ChoiceBase.toString() we get the translated description. So the description becomes the lookup code and you can store the codes and the translation for each language in the same place.

It depends on your UI architecture how well this actually works, but it works very nicely with Vaadin. Vaadin supports POJOs as updateable UI objects and Madura Objects are exactly that, though they have extra metadata as well which we want to expose to the UI. But more about UI next time.

There is one other I18n issue to cover and that is the messages generated from rules when there is an error. Recall that we can have a rule that look like this:
constraint: Customer "check the count: {0}" [invoiceCount]
{
 invoiceCount < 10L; }

This says we only allow up to 9 invoices on a customer (just an example, not something you would do in the real world). If this constraint is violated the message 'check the count...'
will be generated and the argument(s) listed in brackets will be applied. Now, what if you want this message in French?

When the rules are generated a properties file is generated along with the Java called messages.properties and all of these messages are placed in it with appropriate labels. The MessageTranslator is used to resolve the message code so this messages.properties file must be
included in your messageResource bean somewhere for it to work.

You can, then, supply alternate messages.properties files of your own, eg one in French. As long as the locale MessageTranslator is initialised properly the right things will happen.

There is another properties file that is used by the validation engine in Madura Objects. This is ValidationMessages.properties and it holds all of the error messages generated by the validation. As usual it must be part of your messageResource bean. You can supply your own translations of these as required. So your messageResource bean will look something like this:
<bean id="messageSource" class="nz.co.senanque.i18n.XMLMessageSource">
 <property name="resource" value="classpath:/Messages.xml"/>
 <property name="parentMessageSource">
 <bean class="org.springframework.context.support.ResourceBundleMessageSource">
  <property name="basenames">
  <list>
   <value>Messages</value>
   <value>ValidationMessages</value>
   <value>com.mydomain.rules.messages</value>
  </list>
  </property>
 </bean>
 </property>
</bean>

Next time I will post about using all this in a Vaadin application. There is, it turns out, very little to do.

Saturday, 11 June 2011

Ubuntu 11.04

I've just upgraded from Ubuntu 10.04 to 11.04. It went well. Here are my upgrade notes:

The machine is a (now aging) Dell Inspiron 9400. I've had this machine a while. When I was thinking of upgrading it a while ago I new the replacement would come with Vista and I'd heard nothing good about that, so I switched to Linux, Ubuntu 8.04 to be precise. It ran 7x faster, really.

Just a little of that might have been that I used a new disk drive that might be faster. A decent chunk was that there is no need to virus check every file I save to disk. And everything pretty much worked. Some small tussles with the second monitor I use but nothing major. Harder things like Bluetooth connections etc work out of the box with no messing about.

So this is the second upgrade I've done to a Linux box, and the third Linux system I've had running on it. I need to note that the way I do it is start with a clean disk and do a complete install, not an in-place upgrade. I use the upgrade exercise as an excuse to clear out the rubbish that builds up. You can do an in place upgrade if you want.

There is just one thing I'm not sure I like. They've revised the Gnome UI and made it quite a bit different. I can probably get used to it, but I can also set the user option to Classic (it's on the login screen) and it looks like the old one. This might not be available in future versions though, so I'll probably have to get used to the new one eventually.

The biggest thing I have to do, then, is reinstall the old software. Most of this comes from the distros making it trivial, but I do have to walk my old data over too.

Oracle: Oracle has a deb file that I can download and install. But after the install I'm not done. I need to run this: sudo /etc/init.d/oracle-xe configure
That asks me for a new port number (default 8080 I use for other stuff). Once that is done it runs fine.

FireFox: I need my bookmarks. I can export them to an HTML file on the old system and import into the new.

Thunderbird: Just copy the files from ~/.thunderbird in the old system to the new (actually that applies to a few other things, but I'm selective, these things are sometimes used as caches and I'm trying to clean up here.

Eclipse: Just copied the whole directory over to the same place on the new drive. I keep mine in /opt/eclipse36 (ie 3.6 ie Helios). BTW do not do this from a Windows system. There are o/s executable files in the Eclipse directory tree. A lot of stuff will work but some of it won't.

Java: I need to have both Sun Java 6 and Sun Java 5. 6 comes out of the distros just fine. For 5 I just copied the old directory over (to /usr/lib/jvm, ie next to java6)

I had a little trouble getting my local svn permissions right but that is all described here.

I notice there's one improvement over 10.04. Previously if I shut the lid it either wouldn't suspend or it would suspend but not want to come back up. Under 11.04 the suspend seems to work.
I haven't missed suspend much because the system boots so fast shutting down and restarting is no bother. But it's nice to see it there.

The one thing left that doesn't quite work in 10.04 is scanning with OCR. There are OCR programs for Linux but after I read the docs I decided they weren't ready, too hard to use. So I went to Windows for OCR. It doesn't come up much, and someone will tidy the OCR up eventually. Possibly they already have, ie haven't tried that out. It won't be o/s sensitive, though, so I don't expect 11.04 to make any difference there.

There is one other thing. IE doesn't work on Linux, of course. I have three internal work sites that don't like FF. I've not had problems on public sites though, so you probably don't care. My work around is to use Chrome which works okay. The screens look a bit funny but they do work.

But overall, this is working very well indeed. Installs in Linux have got almost boring these days, but since most people still run Windows (for some reason) I get geek points when my colleagues notice my Linux system.

Tuesday, 7 June 2011

Is this Science Fiction?

Does anyone else feel like they are living in a science fiction movie?
No, not the Matrix, that one (well the way it starts, anyway) is too easy.

I grew up on a farm near a small town. The most high tech thing we had was probably the automatic washing machine, and that broke down a lot. My father got a second hand one so he had replacement parts for the first and he seemed to tinker with it often. I used to read stories about spaceships and watch shows like Star Trek.

Today I was at Bangkok airport. It is huge and new and feels very 21st century. Security guards ride about on segways, everyone (including me) has a headset of some kind. I got on a plane, which isn't a spaceship, though from the inside the difference is a bit subtle. It flies and it gets high enough for the atmosphere to be unbreathable. It goes faster than I can comprehend and, oh yes, I can watch TV on board! Not just what someone is broadcasting, I can pick what I want to see when I see it.

When I was a kid I thought TV was pretty flash, but I could only watch what someone else broadcast and there was only one channel. That was the choice: watch or not. Actually, if I want I can watch videos loaded on my phone (as long as we aren't taking off or landing).

Now I'm at Singapore airport, another huge place and so big they have trains on overhead rails to carry me between terminals. That is so like space ports I used to read about long ago.

We don't have flying cars or a moon base yet, but it feels like we're most of the way there.