Friday, October 28, 2011

Eclipse shortcuts for Sublime Text 2

I am a Linux guy. I used to pimp gedit with all plugins under the sun in an effort to transform it into TextMate, but no matter what I try it keeps being an awkward coding experience. Still on the search for alternatives, last month I discovered Sublime Text 2 and its entire new way of understanding text editing.

Sublime Text is just perfect for lightweight coding. I am not going to get into the details, since there are better places documenting this (see for example this list of tips and tricks). It is the perfect tool for times where a full-blown IDE is overkill: after all, it makes little sense to open Eclipse if you are going to spend the next eight hours debugging JavaScript or HTML (Agile and all that).

Now, this is the thing: I work with multiple major projects in the same week, performing different tasks and roles. I switch environments A LOT. Having to think "where am I editing?" before duplicating lines or saving files was messing into my flow.

So, without further ado, behold the

Ultimate eclipse shortcuts for Sublime Text 2

Not exactly rocket science, but still: go to Preferences -> Key bindings - User and paste this:

[
 { "keys": ["shift+enter"], "command": "run_macro_file", "args": {"file": "Packages/Default/Add Line.sublime-macro"} },
 { "keys": ["alt+up"], "command": "swap_line_up" },
 { "keys": ["alt+down"], "command": "swap_line_down" },
 { "keys": ["ctrl+alt+j"], "command": "join_lines" },
 { "keys": ["ctrl+alt+down"], "command": "duplicate_line" },
 { "keys": ["shift+ctrl+r"], "command": "show_overlay", "args": {"overlay": "goto", "show_files": true} },
 { "keys": ["ctrl+shift+s"], "command": "save_all" },
 { "keys": ["ctrl+l"], "command": "show_overlay", "args": {"overlay": "goto", "text": ":"} },
 { "keys": ["shift+ctrl+f4"], "command": "close_all" },
 { "keys": ["shift+ctrl+y"], "command": "lower_case" },
 { "keys": ["shift+ctrl+x"], "command": "upper_case" },
 { "keys": ["ctrl+d"], "command": "run_macro_file", "args": {"file": "Packages/Default/Delete Line.sublime-macro"} }
]

Save, and you will have a selection of finest eclipse shortcuts configured right into your editor. It is as far as I could get without getting into macros. You can help yourself and find the complete list of commands under Preferences -> Key bindings - Global, and complement this with our own discoveries. Feel free to share your findings at the comment section bel-o-o-o-ow!

Thursday, October 13, 2011

Apache Barcamp Spain: a wrap-up

After ten years of IT events, one would think that I have already seen everything under the sun: Spain, Germany, Switzerland, Flanders, Java-related or not, we all end up doing similar things. Come, learn something, attend networking, maybe get some book signed by some mega-crack author with a funny name. Exchange business cards or LinkedIn ids or Twitter names or QR codes. That's it.

Then something like Apache Barcamp Spain gets together and it's like - WOW.

If I had to coin some term, it would be "beer-oriented event". This being my first barcamp, I must say that the format is a fresh and new perspective much more interesting that the typical "get together and talk" mumble-jumble.

The talks

First off, you don't know what you are getting into. Literally. You make the trip to Seville, which in our case means getting three people into the A-Team van to co-star a 500+ km of roadtrip movie. You do all that, yet you don't know the talks that will happen the day after. Everyone proposes a talk and a voting process happens, and for all you know you could end-up learning new, surprising flavors of freaky.

Fortunately this was not the case: from Play! framework to GIS, Agile, Maven, Groovy, Web or Mobile, the landscape was full of well-seasoned experts. I loved the talks, but also the pace: instead of the typical 45-minute sessions, these were shortened up to 30 which leaves just the time to get to the point. No fluff, no fillers, just beans.

With three parallel tracks, you know you will miss 66% of the talks. I know I did, and some great talks are not included here. Sorry for that.

The following is my contribution about CSS. Bear in mind that these are introductory talks, but I tried to make them fun and throw something in for the most experienced.

The people

CELEBRITIES. All of them. Hey, we got a T-shirt to prove it.

Seriously, the organization made a big effort to make it clear that this show was about the people. I mean, of course all events do, but here you could really feel the love. Beer-time was the required great excuse to meet great people, and I found this to be one of the best formulas to get in touch. Relaxed and natural, like friends at a party.

Pity that we were just 100 people. I know quite a crowd that was left out because of the limited capacity.

The party

This event was co-sponsored by several companies, Extrema between them, but one company really stood out: Atlassian.

Atlassian sponsored the closing party, and a huge one at that. Flamenco, beers, jamón serrano, mojitos when the time was right. It was exactly the kind of party I would ask of Seville.

Funny fact of the night: After one hour of animated chat, I ended up recognizing Carlos Sanchez because of the avatar in his business card. Avatares reales ya :)

Thursday, September 22, 2011

Style your forms using JavaScript or CSS

These days we are hacking like crazy in CSS land, trying to introduce in our project some nice memories from our Java background like Not Reinventing The Wheel or avoiding the NIH syndrome. Which brings us to this little nice thought:

I don't want to style buttons EVER AGAIN.


I simply have some better things to do. Call it "button", "a.button", "input[type=submit]", I don't care. I just want them to look gorgeous, be as lightweight as possible, and look the same in all browsers. Since it's 2011, I will get all cocky and also want them to be free.

Since we are at it, let's take a look at how to style form controls, period.

Style your widgets using JavaScript


Aristo is a great theme for jQuery UI that includes some great styles for everything under the jQuery sun, which includes buttons of several kinds. It's really great if your needs are not that simple (complex layouts with something clever like sliders or datepickers).
The only problem is that it relies on jQuery to initialize the correct Look & Feel artifacts, which means that during the first access to your web page there are a couple of seconds of my patented YourButtonsLookLikeCrap (tm) effect. There are plenty of workarounds for this, like creating the whole button markup using javascript.

If you need something less heavy there is a project to get Aristo buttons using just CSS, but it's still too green. Better go with skeleton.

Style your controls using just CSS


Skeleton is an extension over the 960 grid system. They extend the grid concept by adding extra typography and media queries oriented to mobile, and it absolutely excels at providing lightweight form controls. It's likely that it will not fit perfectly into your own design, but the stylesheet is really easy to understand and you can tune it to your needs. It's also extremely lightweight, in case that's a concern.

Style your select controls


You know those huge combo boxes that happen every now and then, when reality hits your application? With Chosen you can replace them transparently:
They have plenty of examples of controls, including ways to provide for your Ajax needs.

I know there must be other exciting tools out there that I am missing. How do you make your forms rock? Please share!

Aside: These be Crazy Months


I just came back from spending my whole summer in Beijing. It was an incredible experience that I am absolutely planning to repeat in February, this time with Shanghai. So far I now just the survival basics like "hello", "thanks", "chicken" and "tea". With that (and smiling a lot) you can survive a whole month and get a grip of the culture, which by the way is quite impressive. I also managed to save some time to learn chinese chess, and I can proudly say that I'm absolutely lame at it.

 Now I just got back to Spain! Next month I'll be at Apache BarCamp Spain with a CSS for non-designers and CSS3 101 talks, hope to see you there to share some beers and plenty of chinese stories.

Tuesday, May 24, 2011

Firefox 4 and 3.6 at the same time

With the arrival of HTML5 web development has turned into the sunny side of the hill again. These days the world would be a perfect place if I could launch two browser instances at the same time, one with support for the latest standards (almost) and another without.

This is something that has been documented online, but from fragmented sources: it is possible to have Firefox 3.X and 4 installed and running at the same time. Actually, it's quite simple as the whole installation process takes less than ten minutes.


Step one: Installing the thing


This one is a no-brainer: you should install both Firefox 3.6 and 4 in separate folders. For Windows, this means using different folders and creating separate shortcut icons.




For Linux, it means installing Firefox 4 from your favorite package manager (.rpm or .deb) and downloading Firefox 3.6 as .tar.gz and uncompressing it somewhere like /usr/local/firefox3.6 (depending on your Linux version, you may prefer to do it the other way around).

In both cases, you need the link to download an older version of Firefox, which is somehow hidden in their website (search for "All systems and languages" and "Other Firefox Downloads", or just click here).

Step two: add -no-remote


Firefox will check at launch time if there is a running instance and attach to it instead of starting a new one, which is definitely not what we want since it will not check for versions in the process. Launching 3.6 will result in your existing Firefox 4.0 window getting the focus, but nothing else. No new window. No "ta-daaaa!"

This behavior can be overriden by adding -no-remote to your program shortcuts (Windows or Linux). That will skip the check, and your new window will open.

Step three: use different profiles


You may be able at this point to launch separate windows for Firefox 3.6 and 4 at the same time, but this is no good if they start stepping on the plugins of each other. The plugin release that may be valid for 3.6 is not for 4, and viceversa (fact is, anything other than Firebug has big chances of not having been ported to Firefox 4 yet). You need separate plugin folders.

The good news is that your plugins are stored in a folder relative to your Firefox profile. Most users (99.9% or so) have a single "default" profile that is selected automatically and are not aware about profiles (now you are; lucky you!). Just launch:

firefox -ProfileManager



Create two separate profiles, which will allow for two separate sets of plugins. I like to name them "Firefox 3.6" and "Firefox 4", in an attempt to be original and unexpected. They will be stored inside your user home folder. Accept and close the window.

To avoid the profile selection window over and over again, the profile can be set at your Firefox launcher (or shortcut icon, if you are using Windows). I will assume some locations and profile names here:

Linux
/usr/bin/firefox -no-remote -P "Firefox 4"
/usr/local/firefox3.6/firefox -no-remote -P "Firefox 3.6"

Windows:
"C:\Program Files\Firefox 4.0\firefox" -no-remote -P "Firefox 4"
"C:\Program Files\Firefox 3.6\firefox" -no-remote -P "Firefox 3.6"

That's it. Now you can develop using both versions with separate Firebug installations.

More. More. More.


This is an excerpt of the first lab in our HTML5 and CSS3 course that is starting tomorrow. This 4-day course includes 20+ labs filled with polyfills, mobile and print CSS, Internet Explorer workarounds, transitions, form controls and mostly everything under the CSS3 / HTML5 sun.

Thursday, February 10, 2011

Applications dealing with multiple timezones

This is a requirement that can hardly be considered common. Most applications are not forced to deal with more than one timezone: intranet applications, nationwide deployments (most European Countries enjoy this great invention that is Central European Time), even air navigation systems place everything in UTC.

We recently deployed Koliseo on AppEngine. This is still a work in progress, where some features have raised interesting questions: what happens when a friend tells you to meet on Friday at 17:15? How should you store this in the database?

Multiple painzones


I love the way computers measure time: milliseconds since 1970. Holy cow. No leap years, no arbitrary seconds-in-a-minute conversion, no Daylight Saving Time and it doesn't matter where you are. Give me a number that I can sort and compare. Computers do not have problems with dates. Humans do.

Then, the only problem is parsing and formatting these dates.

Parse date


Ye olde way of providing time information says that you should put a test field, label it "when" and let the user do his thing. Suppose that he introduces "25 Oct 2010 at 00:00", sitting comfortably in his nice office in Madrid (GMT + 1, where DST applies).

  • What the user means: "25 Oct 2010 at 00:00" in NiceAndWarmOffice@Madrid
  • What the server understood: "25 Oct 2010 at 00:00" in local server time. For AppEngine this is UTC, which means one or two hours less, depending on the season.

The user wanted to express Oct 25 00:00 (Madrid time), but the server understood Oct 24 23:00 (Madrid time). This is the value that will get stored in the database.

You need to ask for the timezone of the user. It can be automatically deduced using javascript (it comes in small and jumbo sizes), but it is nice practice to let the user modify the timezone later, just in case.

This timezone must be stored associated to something, which in our case is a venue. From there on, any date introduced in Koliseo must be associated to this entity. Following this example, any Performance must specify where before telling when if we want to be able to parse the introduced timestamp.

The code to parse is quite straightforward thanks to Saint JodaTime:

DateTimeZone dtz = DateTimeZone.forID(timezoneID);
DateTimeFormatter formatter = DateTimeFormat
 .forPattern("yyyy-MM-dd HH:mm") // replace with your favorite format string
 .withZone(dtz)
 ;
Date date = formatter.parseDateTime(dateAsString).toDate();

Format date


How do you want to display dates to the user?

  • Relative units: This is the best way to display unambiguous dates ("ten minutes ago", "one week ago", etc). This is done in Loom by using l:formatDate, but the first case I can remember of this practice was found in Ruby
  • User time: This is usually not interesting, since a date is associated to something happening (in Koliseo, a performance) and is usually meaningful inside their own context and timezone.
  • Original timezone: This is the most common case. Example: tell me that the show is scheduled at 17:30 (local time of the show, in this case Madrid), not browser time (Helsinki or whatever).

To format dates in the original timezone you can use the same snippet of code just replacing formatter.parse() with format().

Javascript code


Whenever you send or receive a Date from the server, the typical JSON serialization uses "milliseconds from 1970", which can be a problem. This value cannot be used as is because the timezone information is missing. 17:35 in Madrid will be deserialized as 16:35 if I am browsing from London, which is wrong.

Twitter solves this by serializing dates as text, then you can parse and separate the parts to display (day, month, year, weekday). To test your javascript code, just add "-Duser.timezone=UTC" to your server launch script and play a little with your application. If you do not see any date mismatch, everything is good.

Monday, November 01, 2010

Loom 2.2 has been released

We have just released Loom 2.2 with a lot of stuff to make web development easier:

Better javascript/CSS resource bundles


Loom has been supporting javascript and CSS dependencies for some time, and now they just got easier (think sprockets):

JSP file:
<l:css resource="style.css"/>
<l:script resource="main.js"/>

style.css:
/* 
// @include "myfile.css" 
// @include 'classpath:/mypackage/file2.css' 
*/ 

main.js:
// @include "myfile.js" 
// @include 'classpath:/mypackage/file2.js' 

These dependencies can now be redefined without restarting the server. In production environments they will be concatenated, minified and gzipped - which brings us to the next point:

More and better features

Added support for Google Closure Compiler


According to multiple sources Google Closure Compiler produces a better compression rate than YUI Compressor. This is nice, but what really got us is the awesome error reporting which even warns about IE gotchas (trailing commas, anyone?).

Google Closure compiler is now the preferred javascript compressor and will be used if it is present in the classpath. For CSS, YUI compressor is still the (only?) solution.

New Ajax Paged Table


Loom includes some great server-side paged components. With 2.1 we introduced a new Ajax paged list where the whole pagination process was performed by javascript code, and with 2.2 we are extending this to paged tables:

var paged = new loom.ui.PagedTable($('container2'), { 
        url: '<l:url mapping="Action.event" decorator="link-only"/>', 
        columnNames: [ 'timestamp', 'contents', 'user.name', 'user.avatar' ], 
        // optional fields: specify CSS class names and cell renderer
        columnClassNames: ['date'], 
        columnRenderers: [ function(o) { return o.timestamp == null? '' : new Date(o.timestamp); } ] 
  }); 

This is an alternative to server-side paging. Just use the solution that suits you.

Better fluent interfaces


Some interfaces got better at handling HTTP headers and cookies:

// the bad: 
Resolution res = forward() 
   .set("foo", "bar"); 
res.addCookie("baz", "bar"); 
return res; 

// the ugly: 
getResponse().addHeader("baz", "bar"); 
return forward(); 

// the new API:
return forward() 
   .set("foo", bar") 
   .addCookie("baz", "bar") 
   .addHeader("Content-Type", "application/javascript") 
;

CacheControl has also been improved:

// Before: 
CacheControl c = new CacheControl(); 
c.setCacheYears(10); 
c.setLastModified(new Date()); 
return forward().withCacheControl(c); 

// After: 
CacheControl c = new CacheControl()
   .withYears(10)
   .withLastModified(new Date());
return forward().withCacheControl(c);

Others


There is a lot of smaller improvements such as:
Work on the next release (2.3) has already started, including the redesign of some internals to address Google AppEngine development with Guice. It's exciting!

Monday, October 04, 2010

Loom 2.2 is almost out: things we learned so far

I feel guilty talking about a male customer through this post. Actually, in our case he's a she.
We just got out the new e-banking portal for BBVA Suiza. BBVA Suiza is an investment bank, one of the most visible projects we had originally developed with Loom 0.8 - now with 2.2 - and a lot has happened since then.

This post is about things we learned during these last years, and how they were combined to make our experience extremely enjoyable.

It's all about the UI


The most interesting discussions during this project were not about technology or banking products, but about the interface. We spent literally hours discussing one single page, trying to fit things in the most natural way, getting all the non-essential out.

Anyone can design an interface that takes two weeks to learn, but it gets much harder to design something intuitive. It takes a lot of work to arrive to your hamburger interface. On the way we have challenged the breadcrumbs location, the tabs metaphor, the nesting of contents, everything.

It's not about stateless or stateful


It's not 1999 anymore. The caveats of stateless and stateful interfaces are well known, and unless you are a zealot of some concrete Church of Thought, your sweet spot will most probably be somewhere in the middle. Free your server resources by moving more to the browser. Remember that sometimes light pages will do better than a super XXL javascript golem. Get faster responses by avoiding a full page refresh. Do not put too much stress on javascript unless you want to spend your life debugging. Hate IE over everything else. Remember your SEO.

The list is long, and you should make your own choices. Just remember not to waste too much time arguing about which way is better.

Speed!


Any idiot can carve lots of features into your application and make it clunky, take years to download, and lose the train of thought of your users in the eternity that happens between one page and the next. If you aim to make your users rock (as you should) your application should feel natural, which means working towards the 1 second goal.

There are lots of tricks to get there. We have posted some here and there, but nothing beats simplicity: there is nothing faster than not including a feature.

Of course, your customer may not always agree. This is were the next point gets relevant.

Get your customer on your same train


We all listen to the same song: "I want the UI of Word and the performance of Google and it has to read my brain, because my users are extra dumb. Dilbert is real and I'm living his life".

Don't patronize your users. He's the same ordinary guy that understands "=$C$8+E5" in Excel, or plans his trips using Google Maps. Maybe you think that your case is special, but it's not: 90% of your users are just like everybody else. There are users that cannot differentiate between a laptop and Alaska, but these will get back to you no matter what.

You need to get your customers on your same train. That will happen if you deliver good software, so don't try to agree to all crazy features or deliver the supah-dupah-canvas-based forms with embedded music. It's important to make a distinction between what your users wants and what your user needs, and it helps if he understands when you refuse to add something.

So?


We are not there yet. There are things that we would do different and legacy features we would love to drop, but all in all, everyone is happy. Their customers are calling just to send congratulations (how often do you see that), and suddenly we found ourselves talking to another three banks that want to build something similar.

The technology helped a lot. But what really allowed us to deliver was all the weight that was ditched by the way.

Monday, September 13, 2010

SimpleDS 1.0 is released

Gotta love 1.0 releases
This week we are releasing version 1.0 of SimpleDS. SimpleDS is a simple persistence framework for Google AppEngine that provides an alternative to JDO or JPA.

This release brings a lot of new features:

Cached queries


This is the star feature of this release. Starting with 1.0, all query results can be cached. To maximize cache performance only the returned primary keys will be cached, so you still have to use @Cacheable to cache the entity itself.

There are cases were this feature is killer, e.g. to retrieve a User given the email:

User user = entityManager.createQuery(User.class)
  .equal("email", googleUser.getEmail())
  .withCacheSeconds(3600)
  .asSingleResult()
  ;

DEBUG CacheManagerImpl - Level 2 cache hit: qdata{kind=User,pred=[email = test@example.com]}
DEBUG CacheManagerImpl - Level 2 cache hit: User(5)
DEBUG Level1Cache - Level 1 cache hit: User(5)

Cached queries work with FetchOptions (cursors, limit and offset) and support only count(), asSingleEntity(), asList() and asPagedList(). Any invocation to asIterable() / asIterator() will ignore the cache. To clear cached data, just prepare the same query and invoke clearCache().

More about cache here.

Better fluent interfaces


The syntax has been simplified from this (older version):

SimpleQuery query = entityManager.createQuery(User.class).equal("name", "foo");
return entityManager.find(query);

To this:

return entityManager.createQuery(User.class)
  .equal("email", email)
  .equal("enabled", true)
  .sortAsc("email")
  .asList()
  ;

Now we rarely use more than one line for most queries. In this example, if email is null it will just be ignored.

Several methods have been added: asList(), asSingleResult(), asIterator(), asIterable() and PagedQuery.asPagedList(). The old EntityManager methods have been deprecated.

More about queries here.

New Functions


We are also adding some new Functions for transforming persistent entities. Some examples of use:

// save space for your relationships by storing Set instead of Set
Set userIds = user.getFriends();
Collection userKeys = Collections2.transform(userIds, new IdToKeyFunction(User.class));
Collection users = entityManager.get(userKeys);

// transform back
CompositeFunction func = new CompositeFunction(
 new EntityToKeyFunction(User.class), 
 new KeyToIdFunction()
);
user.setFriends(Collections2.transform(users, func));

// just return a collection of email addresses
return Collections2.transform(users, new EntityToPropertyFunction(User.class, "email"));

More about functions and transformations here.

Added JRebel support


I personally use JRebel for development, which means that I rarely restart my development server. This was a problem with SimpleDS, which was unable to detect changes such as new persistent attributes, etc. SimpleDS can now be used with JRebel just by adding this to your startup code:

if (SystemProperty.environment.get().equals("Development"))
   ClassMetadataReloader.register();

Then in eclipse ("Go to your launcher config -> Arguments -> VM Arguments")

-javaagent:/usr/local/java/appengine-java-sdk/lib/agent/appengine-agent.jar -noverify
"-javaagent:${env_var:REBEL_HOME}/jrebel.jar"

Minor changes


  • Added @Property.converter to override the default converter for one persistent property. You can, for example, store a String attribute as Text.
  • Added new methods SimpleQuery.withReadPolicy(ReadPolicy) and SimpleQuery.withDeadline(double)
  • Added new methods SimpleQuery.withStartCursor(Cursor) and SimpleQuery.withEndCursor(Cursor)
  • Cache settings will be ignored when invoked within a transaction.
  • We have moved from commons-logging to slf4j. This may break binary compatibility, but is a huge boost in performance.

Let me get off-topic for a while


Last week this very same blog reached the 400-subscribers mark! It's awesome to find so many people interested, and I find extremely rewarding the contacts I receive every now and then, sometimes just to say hi. Last week it came from Australia! (hey Ángel).

So this is me, taking a small detour to say thanks. And yes, I will try to post more often :))

Monday, September 06, 2010

The current state of logging frameworks

Logging is a commodity, which is argot to mean that I simply don't care as long as it gets out of the way. Recently I had to revisit my own logging decisions based on a single tip: Google Closure Compiler (the library we are using to minify our javascript files) was ignoring my logging setup.

It's like opening the closet you buried ten years ago when log4j was the one and only available choice.

First, the basics

Logging reliably since 1999

One thing is the logging framework (the thingie that writes stuff out):

  • log4j: Started by Ceki Gülcü back in the day, it was the framework used by the Java community until Sun decided to include a different logging implementation in the JRE. After Ceki split from the project, it has been mostly dead in the water.
  • Java Util Logging (JUL): First included with JRE 1.4.2, it is a simple (some would say naïve) implementation that includes just a handful of possible logging setups. You can extend it, but you should code it yourself.
  • Logback: It seems that Ceki Gülcü didn't stop after leaving the Apache Software Foundation. Logback is an implementation of the SLF4J API that can replace log4j or JUL.

A separate concept is the logging isolation library, because when you are implementing a library you cannot require (or expect) a specific logging framework:

  • Commons Logging: Supported by the Apache Software Foundation, this framework has been sadly well-known because of their memory leaks. At the time of this writing their last release dates back to 2007.
  • SLF4J: A logging API layer by Ceki Gülcü that includes adapters for Log4J, JUL, and almost anything you can think of. The adapter plugin mechanism is quite well-thought, just drop your jar in the classpath and you are ready to go.

The Google case


Google makes a point of not including external dependencies with their libraries, and when they do the package name is jarjar'ed to avoid version conflicts. This would not be needed in an ideal world, but as long as framework designers keep making backwards-incompatible changes I think it's a sane decision. Anyone that has used ASM or even Google Collections / Guava knows what I'm talking about (fun trivia fact: the earliest occurrence of this practice I can recall was inside Weblogic 8.1 in 2003, but my memory is not what I would call a trustable source :)

With this strategy logging becomes a problem because at some point you cannot keep renaming classes. This makes the JDK logging the only remaining alternative - for Google, that is.

The choice


A bit of history here: when log4j was out, it could write anywhere: JMS, Database, Email, the OS event system, Grandma's Cheesecacke Recipes Book... But that was soooo 1999. Today we have aspects, servlet filters and all kind of interceptor mechanisms that make these options obsolete. Of the handful times I have configured a log that goes anywhere other than the standard output, I have always ended up replacing it with a more elaborate solution.

"Zillions of appenders" is thus not exactly an important feature in my book. Performance, stability and support are. A quick search using Google pretty much confirms that the competition is over. SLF4J is being widely used by other frameworks, includes an awesome support (with releases every couple of months, the latest at the time of this writing was July 2010), no major known glitches, and fits perfectly with all the logging frameworks mentioned before.

About the logging framework... well, I will keep playing with JUL and Logback, but log4j is left behind as far as I am concerned.

UPDATE: You can also see a longer explanation by the SpringSource team here.

Thursday, August 05, 2010

URL-based locale

We should assume that users will not always be browsing from a comfortable location (pronounce: home / work). Even if they do, every now and then I have guests at home that do not speak Spanish at all. Try opening the Google home page in a cybercafé in Germany or Finland to see what I mean. It may be convenient, but the browser language is often not good enough for the real world.



If your application supports multiple languages you should give the user an option to change the locale without messing with the browser settings. Specifically, don't force the user to find the settings in a foreign language and foreign browser, and don't assume that he/she is allowed to change them.

There are a couple of possible implementations for this:

  • Store the locale in the user session: this solution does not persist the locale during browser restarts and implies an existing session for each anonymous user, which is not always an option.
  • Store the locale in the user settings (database): this is possible only if you do not have anonymous users.
  • Store the locale in a browser cookie.
  • Store the locale in the URL

Of these options only the last three have reasonable quality, only the last two work for public websites, and only the last one works for web crawlers. Google recommends to use locale-aware DNS names, which is lingo speak to put your locale somewhere in your DNS name. If you are reading this sitting comfortably on your big heap of money, you may start registering "myhost.es" and "myhost.de", but the rest of us will go with "es.myhost.com" and "de.myhost.com".

Implementing this in your own application is easy: Loom has PrefixLocaleResolver, but with other frameworks you may just use your own web Filter to resolve the user locale as well. Naïve version follows:

public class LocaleResolverFilter implements Filter {

  @Override
  public void doFilter(ServletRequest req, ServletResponse response, FilterChain chain)
      throws IOException, ServletException {

     HttpServletRequest request = (HttpServletRequest) req;
     String serverName = request.getServerName();
     String lang = StringUtils.substringBefore(serverName, ".");
     if (lang.length() != 2) {
         lang = "en";
     }
     
     final Locale locale = new Locale(lang);
     chain.doFilter(new HttpServletRequestWrapper(request) {
      
          @Override
          public Locale getLocale() {
              return locale;
          }
      
     }, response);
  }
  
  @Override
  public void destroy() {
      // empty
  }

  @Override
  public void init(FilterConfig filterConfig) throws ServletException {
      // empty
  }
}

In order to test this, you should add this entry to your hosts file:

127.0.0.1 localhost es.localhost en.localhost www.localhost

Me, I find it funny to think about my "English localhost" :) If you have any interesting ways of detecting your user language, I would love to hear it!