воскресенье, 26 июня 2011 г.

Taking part in OpenMRS development sprint and everyday routine

Hi there,

Well, the first month of summer almost gone, there were a lot-lot of interesting things during first three weeks of this month, and last week wasn't exception of that. So, after I finished integration of localization tool into project and provided mechanism of handling error messages, I decided to integrate myself with current community activities. I thought to myself, that taking part in a development sprint would be the best in this case. 

Currently, there is a development sprint that is devoted to order entry. Order entry is a process of electronic entry of medical practitioner instructions for the treatment of patients (particularly hospitalized patients) under his or her care. The main goal of sprint is to create enterprise-quality support for order-entry within OpenMRS. 

So, I joined to that development sprint, choosed the first ticket and began to work. And it began to be very interesting for me, although even if that was introductory ticket of sorts, it was very plesantly to work with whole community, to saw how another tickets were clacked like nuts and how whole sprint work flow is organized. Also was plesantly to saw how well managed work. And, as soon as I closed the first my ticket I immediately took the next one, and then next one. And I was watching, how well was any of code reviews. After any review I was receiving feedbacks about quality of my code, was making follow up changes within code if it was necessary. 

And, you know, watching on all this process, I wanted to be better and more better to help community to make things even better. So, I began to work with twice inspiration. I got the ticket where was needed a lot of work, and done it. In fact, sprint leader praised me and offered to do a really big and interesting. I was very honored, and took that ticket without any doubts. Sprint leader has made some clarifications for me and I began to work on that ticket. And you know, the devil is not so black as he is painted, and I did that ticket. By that time I already have had partial acces to branch, on which is being all work of sprint. So, I was commiting more and more code, was receiving feedbacks, was making follow up changes and to the end of this week I'm about to completely close this ticket. Really, I'm satisfied of work shoulder to shoulder with whole community, it's really great, and I am grateful to them that they gave me such opportunity.

As for my project, this week I also made a lot of routine work. But, in general, probably, you can guess, that usually a lion's share of work consists of routine. Unfortunately, we have no way to getting away of that. So, according to my schedule, I had to perform some pages translation and to merge active trunk into my working branch. And I have done all this things. I can say that they also were useful for me because I improved my knowledges of foreign languages even more. As for merging active trunk into my branch, I did it without any hustle (inter alia, I had only one merging conflict and sucessfully resolved it). This is due to the way how distributed work on whole project and mostly due to that fact, that the component of whole system, on which is targeted my project, isn't frequently changing.

So, I guess that it's all for today, if you have any questions leave them in comments below.
 

With best regards, Taras Chorny!

воскресенье, 19 июня 2011 г.

Providing mechanism for localization of error messages

Hi there,


After succesfull integration of custom localization tool, I began testing how it works. And really, it worked like a charmed, and showed good performance, and I was really happy. But as soon as I missed field for new db name, when filled in form at 2nd step of advanced installation, I was unpleasantly surprised. Regardless, wizard was running in Italian, instead of error message "Base dati di nuova nome è richiesta" I saw "New database name is required" still in English. Cause, I simply forgot about localization of error messages. So, I decided to fix it as soon as possible.
First, that you should know about, that the nature of error messages quite differs from normal messages. The first difference, is that any error message, in comparison with normal GUI message,  won't be shown with 100% of probability. It can be shown, or it can't be shown, it depends. We can say that they are forming dynamicaly. But our localization tool can handle only static messages which are kept inside of messages properties files.So, as conclusion of that, we should have to teach our localization tool to handle such messages. 

So, how did I accomplish that? First, I decided that all error messages should be inside properties files, otherwise, the problem seems to be unsolveable. And as next step, I defined entire set of error messages, which ever can be displayed to user. Then, I completely localized them (it took most of my time). Jumping ahead, I should say that some error messages can be formed from dynamical parts, and it makes additional inconveniences. For representation of such messages I used compound messages. More about them you can read here.
So, after all messages were prepared, I should have find the way, how to point out localization tool, which translation of error message to use. Decision was really simple and graceful at the same time - in all places in code, where forming errors, I used error messages keys. For example, when user didn't fill new db name field, instead of "New database name is required" I put "install.error.dbNewName". Thereby, localization tool will accept such key and load corresponding translation of message fo this key. For handling compound error messages I'm also putting all dynamical part of message with their code together. For this I had to use map for keeping error messages. For this map I used the key of messages as entry's key, and an array of objects as entry's value. As result, in templates handling of error messages proceeds as follows:

        #foreach ($error in $errors.entrySet())
            #if (!$error.getValue())
                <li>$l10n.get($error.getKey())</li>
            #else
                <li>$l10n.get($error.getKey()).insert($error.getValue())</li>
            #end
        #end
where $errors - the map, mentioned above, $l10n - our custom localization tool.

So, described above feature really works, and results of its work is shown below:


As you can see, now for italian locale we can see correct translation of message about occured error.

This as all for today, you can leave your comment if you have some questions or suggestions.


With best wishes, Taras Chorny!

воскресенье, 12 июня 2011 г.

Finished integration of custom localization tool

Hi there,

The summer is already begun, and in both meanings of this word "it is very hot". Moreover, that the sun terribly bakes, so more and the work on a project, also so boils. The week, which is about to end, was also heavily rich within different events
First of all I'd like to tell you that I already passed the last exam in my student life. I got an excellent estimation, and I'm really happy of that fact that now I'm computer science engineer. And you know, that it's such ambiguous feeling - to come to the finish of your student life. From one hand, you are so happy that you've finally got the degree, but from other hand, you are just little sad, that the days of fun, days of careless life have been finished. But we should not be distressed, because GSOC is gaining speed, and more and get more interesting.
As for my project, this week I finished integration of already developed the custom localization tool. And it's all thanks to this hard work, which we've done with my mentor so far.
So, now I can tell you how it work in general. As you should have known, OpenMRS has two wizard. One for installation and another for database update.
First of them is running when application is starting first time after deploy. As soon as the wizard starts up the choise language page will be shown to user. Since we running wizard for first time and we do not have any db installed at this time, the localization tool should make a decision, which language to use for this page. It chooses the language as follow:

  1. it looks into http request object and gets client's system locale. If this locale is supported by OpenMRS it shows first page of initial setup wizard translated for this locale.
  2. if this locale isn't supported by application or, just not specified, localization tool makes a decision to use english translation for first page by default.
At first page user can select preferred language to use it further (within wizard) or leave everything as is. So, selected by user language will be used to show translation for pages during wizard running. User can also  specify that it's need to OpenMRS to remember his choise for further using (within application in general -e.g., for using it when db update wizard will be runned). In this case, after sucessfull finishing of installation wizard, localization tool will check if user turn on this feature, and if it's true, localization tool will store the user selected language into db:
  1.   first, as user property,
  2.   and next, as OpenMRS system default locale.
As for database upgrade wizard, there is one special thing. This wizard can only be runned by user, who has admin privileges. In fact, update wizard has extra page for user to log in. So, before user logged in, custom localization tool also makes a decision which translation to use for first page of update wizard. It makes this decision as follows:
  1. it looks into http request header and gets client's system locale. If this locale is supported by OpenMRS it shows first page of database update wizard translated for this locale.
  2. if this locale isn't supported by application or, just not specified, localization tool makes a decision to use OpenMRS system default locale (as you know, it could be specified  when initial setup wizard is running).
  3. and if system default locale is also not specified it uses english translation by default.
After user logged in into database update wizard, system already knows, who is this user (we know his username), localization tool looks into db for user locale property. And if it exists, it uses corresponding translation of update wizard pages. If not. it uses the language, which was choosen before log in. 
So, this week I was solving of some problem , which were as obstaclefor providing descripted above behaviour.
By and large, this week I was solving some problems, which were as obstacle for providing behaviour, mentioned above.  And now, we have first realy working version of custom localization tool. And yes, it should be strongly tested. But it's an issue to be solved on next  two weeks.

I guess, that this is all for this time, if you have any questions or suggestions, please, leave them within comments below.

With best regards, Taras Chorny!

суббота, 4 июня 2011 г.

Providing of some "yum-yum" features for project

Hi, everybody!
Without any doubts, I already began to believe strongly, that someone, who said that time passes very fast, was devilishly right. And today, I'm going to tell you, my dear reader, about what interesting I've done this week. Admittedly, I had some unsolved tasks at beginning of week. They, preferably, were related to inner components of my project.
The first of those tasks was creation of some kind of "cache" for loaded messages properties files. In a nutshell, I've created a java map with languages, mapped to the loaded properties files. First of all, it is very convinient to have such map, because it works like a cache and provides an opportunity to access translations without reloading resources, when current language is changing. I've assigned the task of creation such map to custom resources loader component. In fact, this map creates when creates an instance of resource loader component. After creation (we have a single instance of custom resource loader), that component will contain the map therein and other components will have a read-only access to him. And since an access don't mean any modification with the resources within cache, we should not do him (access) synchronized.. Basically, in case of using a single instance of resource loader we also won't use a lot of memory for such cache.
And now, I'm about to show you, how I'm loading the resources into map. Generally, I'm doing it within a single private method of custom resource loader class. Notably, that this method is called once at constructor of resource loader class. Also it is important to note that constructor of that class receives as parameter an absolute path to directory where resources are located. Next, location of that directory is passed in as parameter to method for creation map for resources. This method is shown below:
   
    private void loadResources(String basedir) {
        File propertiesDir = new File(basedir);
        for (File possibleFile : propertiesDir.listFiles()) {
            log.warn("filename : " + possibleFile.getAbsolutePath());
            if (possibleFile.getName().startsWith(FilterUtil.PREFIX) && possibleFile.getName().endsWith(FilterUtil.SUFFIX)) {
                Locale locale = parseLocaleFrom(possibleFile.getName(), FilterUtil.PREFIX);
                log.warn("locale : " + locale.toString());
                getResource().put(locale, getFileSystemResource(possibleFile.getAbsolutePath(), FilterUtil.PREFIX, locale));
                getAvailablelocales().add(locale);
            }
        }
        if (log.isWarnEnabled() && (getResource().size() == 0)) {
            log.warn("No properties files found.");
        }
    }



As you can see, at first I'm creating a file object for directory with resources to be able to iterate over the list of nested files and directories. And then, I'm iterating over all these nested files and checking if current file is messages properties file. If it's true, I'm trying to derive the locale from the file name (e.g. if file is named messages_it.properties the locale "it" will be derived). Next, I'm loading resource bundle for that locale and putting it resource bundle into caching map with derived locale as key.
The second task for this week was creation of the feature, which allows us to have one velocity toolbox per one http client's session. Before this week we had single velocity toolbox for all client's sessions. And, as you can guess,  it is awful, because we have locales overlaping when multiple users are running wizards at the same time. This problem causes because, from one hand, we configure one instance of velocity toolbox per one filter and, from the other hand, web container instantiates only one filter instance for <filter ..> tag per JVM for application. 
Desicion was to have map, that contains http session's id as key and configured toolbox context as value. And since toolbox context object can be merged with velocity context, we will get appropriate context object for current client's preferred locale and use it within velocity context when will rendering tempaltes. 
Also, during this week I done tasks related to navigation components appearence, made changes within stroing/retrieving locale parameters methods and continued integration of existing localization tool into database update wizard.
So, I guess it is enought for today. If you have any quesions or suggestions leave them in comments below,


With best regards, Taras Chorny.