At the labos we are currently looking at opensource and openstandards for the tourism business. Which obviously involves implementing internationalization for messages, interface and for data collected. As we are currently using Spring with Hibernate I wanted to document our simple way for doing i18n for hibernate models using aspects as it is really a cross-cutting concern.
Here is the way we have done it (I am sure it can be improved and suggestions would be welcome)
Given a hibernate model that contains 2 fields, Name and Title that are both Strings.
public class SomeModel extends LocalizedModel {
protected String title;
protected String name;
}
and a ModelManager class that manages the hibernate layer (using DAOS internally) for the CRUD operations.
public class SomeModelManager {
public Model getModel(int id);
public createOrUpdateModel(Model model);
public void destroyModel(Model model);
}
What I would like is that these two fields hold multiple values for different languages while keeping my models as they are.
As I18n is a cross-cutting concern the most obvious choice is to use Aspects and Spring implements aspects very nicely.
So for this implementation I used three aspects, one for reading, one for removing and one for adding or updating.
Now each interceptor needs to know which of a models fields are to be localized. For this I just create a method for each field of the form so that Java Reflection can see which fields need to be localized. I also extend a LocalizedModel which is just an abstract class that can be empty and is not necessary.
public class SomeModel extends LocalizedModel{
protected String title;
protected String name;
public void LocalizedTitle(){}
public void LocalizedName(){}
}
plus the standard getters and setters.
I am using Spring localeResolver for capturing the users locale for displaying the labels in the interface and also to know which version of the fields I need to get from the database.
<bean id=”localeResolver” class=”org.springframework.web.servlet.i18n.SessionLocaleResolver” />
I then create a new hibernate model to store my text for the different languages.
This model (say Label) contains the following fields (taken from Gavin Kings i18n for Hibernate article)
id, code (a unique string for the field for a given model), locale (String for the locale),owner_id (id of owning object), text (the text to store)
so an example would be
(1, “SomeModel.Title”,”it”,3,”This is some text”)
the combination of code, locale and owner_id is unique.
The interceptors work by intercepting the crud methods and finding the localizedfields from the Localized methods and then CRUDing a Label for the given field for the given language. I have put the source code below.
The Read interceptor method looks something like this:
public void afterReturning(Object returnValue, Method method, Object[] arguments,
Object targetObject) throws Throwable {
LocalizedModel m =new Model();
if (returnValue.getClass().getGenericSuperclass().equals(LocalizedModel.class))
{
setLocaleForLocalizedObject(returnValue);
return;
}
Collection c = new ArrayList();
if (returnValue.getClass().isInstance(c))
{
for (Object obj : (Collection)returnValue){
if (obj.getClass().getGenericSuperclass().equals(LocalizedModel.class)){
setLocaleForLocalizedObject(obj);
}
}
}
}
private void setLocaleForLocalizedObject(Object returnValue) throws Exception {
LocalizedModel model = (LocalizedModel) returnValue;
List<String> localisedFieldNames = MultiLingualInterceptor.getLocalisedFields(model);
for (String field: localisedFieldNames){
String descriptor = MultiLingualInterceptor.createDescriptorCode(model, field);
int id = model.getId();
String locale = localeContainer.getLocale();
Label label = labelManager.getLabel(id,descriptor,locale);
Method SetterForField = model.getClass().getMethod((”set” + field), new Class[] {String.class});
if (label == null)
{
label = new Label();
label.setText(new String());
}
SetterForField.invoke(model, label.getText());
}
}
The Write interceptor method looks something like this:
public Object invoke(MethodInvocation invocation) throws Throwable {
String Locale = localeContainer.getLocale();
LocalizedModel model = (LocalizedModel) invocation.getArguments()[0];
List<String> localisedFieldNames = MultiLingualInterceptor.getLocalisedFields(model);
HashMap<String, String> fieldValues = new HashMap<String,String>();
for (String field: localisedFieldNames){
String methodGetter = “get” + field;
Method getLocalisedFieldText = model.getClass().getMethod(methodGetter);
String text = (String) getLocalisedFieldText.invoke(model);
fieldValues.put(field, text);
String descriptor = MultiLingualInterceptor.createDescriptorCode(model, field);
Method SetterForField = model.getClass().getMethod((”set” + field), new Class[] {String.class});
SetterForField.invoke(model, descriptor);
}
Object rval = null;
try {
rval = invocation.proceed();
for (String field: localisedFieldNames){
String text = fieldValues.get(field);
String descriptor = MultiLingualInterceptor.createDescriptorCode(model, field);
Label label = createLabel(descriptor, model);
label.setText(text);
labelManager.add(label);
}
} catch (Throwable t) {
}
return rval;
}
and the delete looks something like this:
public Object invoke(MethodInvocation invocation) throws Throwable {
// TODO Auto-generated method stub
Object rval = null;
Object[] obj = invocation.getArguments();
Object targetManager = invocation.getThis();
LocalizedModel m = new Model();
LocalizedModel model = null;
Integer i = new Integer(1);
if (obj[0].getClass().isInstance(i)){
Integer modelId = (Integer) obj[0];
IhManager manager = (IhManager) targetManager;
model = (LocalizedModel) manager.get(modelId);
}
else if(obj[0].getClass().isInstance(m)){
model = (LocalizedModel) obj[0];
}
try {
rval = invocation.proceed();
if (model != null){
List<String> localisedFieldNames = MultiLingualInterceptor.getLocalisedFields(model);
for (String field: localisedFieldNames){
String modelName = model.getClass().getSimpleName();
String descriptor = modelName + “.” + field;
int id = model.getId();
String locale = localeContainer.getLocale();
HashMap<String,Label> list = labelManager.getLabels(id, descriptor);
for (Label label : list.values()){
labelManager.remove(label);
}
}
}
} catch (Throwable t) {
}
return null;
}
So in your code when you want to get the Model in a specific language just call
SetLocale on the localeContainer before calling the manager. Remembering to set it back if you want the model and the interface to be different.
Downsides
This approach is quick to implement for new models and works great for us, but it did mean that we had to make sure all our models had “lazy=true” so that we could call the the managers to ensure that the aspects fired. But to be honest I prefer this anway.
In Italian
Its coming…
Postato in: OpenSource | Messo il tag: Aspects, Hibernate, i18N, Spring