<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/atom10full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:openSearch="http://a9.com/-/spec/opensearch/1.1/" xmlns:blogger="http://schemas.google.com/blogger/2008" xmlns:georss="http://www.georss.org/georss" xmlns:gd="http://schemas.google.com/g/2005" xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" gd:etag="W/&quot;A04DRX08fyp7ImA9WhBbEEw.&quot;"><id>tag:blogger.com,1999:blog-10814242</id><updated>2013-05-08T15:32:54.377+02:00</updated><category term="hibernate" /><category term="tools" /><category term="jsf" /><category term="jdbc" /><category term="web-services" /><category term="javascript" /><category term="java" /><category term="cache" /><category term="html5" /><category term="web" /><category term="books" /><category term="loom" /><category term="jsp" /><category term="simpleds" /><category term="antipattern" /><category term="stripes" /><category term="trends" /><category term="tests" /><category term="accessibility" /><category term="appengine" /><category term="agile" /><category term="jpa" /><category term="spring" /><category term="html" /><category term="log" /><category term="design" /><category term="xss" /><category term="performance" /><category term="fun" /><category term="eclipse" /><category term="training" /><category term="talks" /><title>The 90th percentile</title><subtitle type="html">Ignacio Coloma on tech and the remaining 10%</subtitle><link rel="http://schemas.google.com/g/2005#feed" type="application/atom+xml" href="http://icoloma.blogspot.com/feeds/posts/default" /><link rel="alternate" type="text/html" href="http://icoloma.blogspot.com/" /><link rel="next" type="application/atom+xml" href="http://www.blogger.com/feeds/10814242/posts/default?start-index=26&amp;max-results=25&amp;redirect=false&amp;v=2" /><author><name>Nacho Coloma</name><uri>https://plus.google.com/112843640399200525430</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh6.googleusercontent.com/-XL2pDJUO-0U/AAAAAAAAAAI/AAAAAAAAAxw/Wr1DmPB3TOA/s512-c/photo.jpg" /></author><generator version="7.00" uri="http://www.blogger.com">Blogger</generator><openSearch:totalResults>85</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><feedburner:info uri="the90thpercentile" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/atom+xml" href="http://icoloma.blogspot.com/feeds/posts/default" /><feedburner:feedFlare href="http://www.newsgator.com/ngs/subscriber/subext.aspx?url=http%3A%2F%2Ficoloma.blogspot.com%2Ffeeds%2Fposts%2Fdefault" src="http://www.newsgator.com/images/ngsub1.gif">Subscribe with NewsGator</feedburner:feedFlare><feedburner:feedFlare href="http://feeds.my.aol.com/add.jsp?url=http%3A%2F%2Ficoloma.blogspot.com%2Ffeeds%2Fposts%2Fdefault" src="http://o.aolcdn.com/favorites.my.aol.com/webmaster/ffclient/webroot/locale/en-US/images/myAOLButtonSmall.gif">Subscribe with My AOL</feedburner:feedFlare><feedburner:feedFlare href="http://www.bloglines.com/sub/http://icoloma.blogspot.com/feeds/posts/default" src="http://www.bloglines.com/images/sub_modern11.gif">Subscribe with Bloglines</feedburner:feedFlare><feedburner:feedFlare href="http://www.netvibes.com/subscribe.php?url=http%3A%2F%2Ficoloma.blogspot.com%2Ffeeds%2Fposts%2Fdefault" src="http://www.netvibes.com/img/add2netvibes.gif">Subscribe with Netvibes</feedburner:feedFlare><feedburner:feedFlare href="http://fusion.google.com/add?feedurl=http%3A%2F%2Ficoloma.blogspot.com%2Ffeeds%2Fposts%2Fdefault" src="http://buttons.googlesyndication.com/fusion/add.gif">Subscribe with Google</feedburner:feedFlare><entry gd:etag="W/&quot;CUMMQHY6eip7ImA9WhJbFEQ.&quot;"><id>tag:blogger.com,1999:blog-10814242.post-7588524014888037306</id><published>2012-09-24T15:28:00.001+02:00</published><updated>2012-09-24T15:31:21.812+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-09-24T15:31:21.812+02:00</app:edited><title>Sublime Text Editor tricks for web developers</title><content type="html">&lt;div dir="ltr" style="text-align: left;" trbidi="on"&gt;
&lt;br /&gt;
Last weekend we were at&amp;nbsp;&lt;a href="http://barcampspain.com/"&gt;Apache Barcamp Spain&lt;/a&gt;&amp;nbsp;with a talk about cool tools for web developers.&lt;br /&gt;
&lt;br /&gt;
Aside: if you are coming to a single event in Spain on a given year, make it this one. Barcamp Spain has developed its own soul, where the beer+networking experience is so much fun, and the food by itself is totally worth the trip.&lt;br /&gt;
&lt;br /&gt;
Some people were asking for a transcript of the third part, our own set of tips and tricks to boost Sublime Text Editor for web development. So here it comes:&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Feel comfortable&lt;/h2&gt;
Chances are Sublime Text Editor is not your first editor. You probably had a previous life, in which you were already proficient with some editor (Eclipse, Bean, TextMate, whatever). This is about getting things done, so I would recommend to configure Sublime with the typical shortcuts that are hard-coded into your brain. Me, I am used to&amp;nbsp;&lt;a href="http://icoloma.blogspot.com.es/2011/10/eclipse-shortcuts-for-sublime-text-2.html"&gt;Eclipse shortcuts&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
The idea is to feel comfortable. Get whatever you need to make Sublime as good as your previous editor of choice. Now, awesomeness can start.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Maximize space&lt;/h2&gt;
I am not quite a fan of the distraction-free mode in Sublime, but I do want to maximize the used space on screen (the data-ink ratio, for Tufte fans). So here it goes:&lt;br /&gt;
&lt;ul style="text-align: left;"&gt;
&lt;li&gt;Toggle sidebar on/off: &lt;b&gt;Ctrl + K, Ctrl + B&lt;/b&gt;. This is useful if you rarely use it. In my case, it's Shift+Ctrl+R to open any file, which makes the sidebar mostly ignored.&lt;/li&gt;
&lt;li&gt;See several files at once: &lt;b&gt;Shift + Alt + [1234589]&lt;/b&gt; (you have to choose one). You can compare them side by side, top vs bottom, etc. Once you are done, you can switch back with &lt;b&gt;Shift+Alt+1&lt;/b&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://2.bp.blogspot.com/-3VoHC2Lwgfk/UGBYXKvTu-I/AAAAAAAAAjo/VTCAhhd3tgM/s1600/two-columns.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://2.bp.blogspot.com/-3VoHC2Lwgfk/UGBYXKvTu-I/AAAAAAAAAjo/VTCAhhd3tgM/s1600/two-columns.png" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;h2 style="text-align: left;"&gt;
Surround with tag:&amp;nbsp;Shift+Alt+W&lt;/h2&gt;
This is a useful shortcut for inline tags (bold, italics, links), but not so much for block tags (div, p, etc). Try it:&lt;br /&gt;
&lt;ul style="text-align: left;"&gt;
&lt;li&gt;Write any text.&lt;/li&gt;
&lt;li&gt;Select a word, press &lt;b&gt;Shift+Alt+W&lt;/b&gt;, then type &lt;b&gt;b&lt;/b&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://1.bp.blogspot.com/-sNld2XLekVU/UGBe8cispXI/AAAAAAAAAj8/JMufT1bbdH0/s1600/foo.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://1.bp.blogspot.com/-sNld2XLekVU/UGBe8cispXI/AAAAAAAAAj8/JMufT1bbdH0/s1600/foo.png" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;/div&gt;
&lt;br /&gt;
&lt;h2 style="text-align: left;"&gt;
Block select: Shift + Right-click drag&lt;/h2&gt;
This is great when you need to insert tags at the start of each line (p, li).&lt;br /&gt;
&lt;ul style="text-align: left;"&gt;
&lt;li&gt;Prepare your list of data&lt;/li&gt;
&lt;li&gt;Select the code and hit Tab to indent it a bit.&lt;/li&gt;
&lt;li&gt;While holding the &lt;b&gt;Shift key, right click and drag&lt;/b&gt; to select a rectangular selection of code.&lt;/li&gt;
&lt;li&gt;Now type the start tag to get it inserted at the beginning of each line.&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://4.bp.blogspot.com/-uG9FJ_t1oR4/UGA7ASCeelI/AAAAAAAAAh8/j-mFPoPY60c/s1600/block-select.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/-uG9FJ_t1oR4/UGA7ASCeelI/AAAAAAAAAh8/j-mFPoPY60c/s1600/block-select.png" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://2.bp.blogspot.com/-S8PPoMjEMX0/UGA6_VnHhJI/AAAAAAAAAh0/3qg4LAjecls/s1600/block-select-after.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://2.bp.blogspot.com/-S8PPoMjEMX0/UGA6_VnHhJI/AAAAAAAAAh0/3qg4LAjecls/s1600/block-select-after.png" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;h2 style="text-align: left;"&gt;
Multiple cursors&lt;/h2&gt;
The previous are just concrete examples of using multiple cursors. For the purpose of understanding, let's say you can have multiple instances of a SublimeCursor class (I do not have any insight of the source code, but I found this approach easier to understand). This is a compiled list of things you can do, extracted by trial and error:&lt;br /&gt;
&lt;ul style="text-align: left;"&gt;
&lt;li&gt;&lt;b&gt;Ctrl + click&lt;/b&gt;: create new cursor.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Ctrl + double click&lt;/b&gt;: create a new cursor selecting the word that was double-clicked.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Shift + click&lt;/b&gt;: select using the latest cursor (so: ctrl + click, then shift + click to add a selection to the set of cursors)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Any keyboard action&lt;/b&gt;: Operate on all cursors at once. Keyboard operations (like Ctrl+V) will work, and so will normal typing (like hitting &amp;lt;end&amp;gt; to write the ending tag at the end of each line).&lt;/li&gt;
&lt;/ul&gt;
This is best understood with a set of examples:&lt;br /&gt;
&lt;ul style="text-align: left;"&gt;
&lt;li&gt;Write any text.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Ctrl+click&lt;/b&gt; at the beginning of each line.&lt;/li&gt;
&lt;li&gt;Write &amp;lt;p&amp;gt; or &amp;lt;li&amp;gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;/div&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://1.bp.blogspot.com/-yk9PinKMYfM/UGBA_VDaRrI/AAAAAAAAAjI/LIzPbr7YN04/s1600/before+ctrl+click.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="166" src="http://1.bp.blogspot.com/-yk9PinKMYfM/UGBA_VDaRrI/AAAAAAAAAjI/LIzPbr7YN04/s320/before+ctrl+click.png" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://4.bp.blogspot.com/-OBXoRygbuNw/UGBA70rsiSI/AAAAAAAAAis/wQgdClqpIss/s1600/after+ctrl+click.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="164" src="http://4.bp.blogspot.com/-OBXoRygbuNw/UGBA70rsiSI/AAAAAAAAAis/wQgdClqpIss/s320/after+ctrl+click.png" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;/div&gt;
&lt;br /&gt;
Forgot to enclose tag attributes with quotes?&lt;br /&gt;
&lt;ul style="text-align: left;"&gt;
&lt;li&gt;&lt;b&gt;Ctrl + double click&lt;/b&gt; on every attribute&lt;/li&gt;
&lt;li&gt;Type &lt;b&gt;"&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://3.bp.blogspot.com/-Pq8PMdN9aNU/UGBA8krRcbI/AAAAAAAAAiw/ELofCqvBOy8/s1600/after+ctrl+dblclick.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="152" src="http://3.bp.blogspot.com/-Pq8PMdN9aNU/UGBA8krRcbI/AAAAAAAAAiw/ELofCqvBOy8/s320/after+ctrl+dblclick.png" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://3.bp.blogspot.com/-KYthV3iheUU/UGBA9BSeFRI/AAAAAAAAAi4/LdAygUirHnc/s1600/after+quotes.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="152" src="http://3.bp.blogspot.com/-KYthV3iheUU/UGBA9BSeFRI/AAAAAAAAAi4/LdAygUirHnc/s320/after+quotes.png" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;ul style="text-align: left;"&gt;
&lt;li&gt;&lt;b&gt;Ctrl + double click&lt;/b&gt; on words that you want to make bold.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;If you want to select more than one, &lt;b&gt;Ctrl+Click at the beginning, then Shift+Click at the end&lt;/b&gt;.&lt;/li&gt;
&lt;li&gt;Hit &lt;b&gt;Shift + Alt + W&lt;/b&gt;, then type &lt;b&gt;b&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://3.bp.blogspot.com/-1VW1l50qEHs/UGBBBQAb40I/AAAAAAAAAjU/CyqRPlyG_CU/s1600/before+shift+alt+W.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="150" src="http://3.bp.blogspot.com/-1VW1l50qEHs/UGBBBQAb40I/AAAAAAAAAjU/CyqRPlyG_CU/s320/before+shift+alt+W.png" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://3.bp.blogspot.com/-kPXV5_cPAaM/UGBA-DCydcI/AAAAAAAAAjE/LDT0TkjFSQA/s1600/after+shoft+alt+W.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="172" src="http://3.bp.blogspot.com/-kPXV5_cPAaM/UGBA-DCydcI/AAAAAAAAAjE/LDT0TkjFSQA/s320/after+shoft+alt+W.png" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;br /&gt;
There are tons of possible applications, and we found ourselves doing some really funky stuff.&amp;nbsp;What is your personal trick? Use the comments section or tweet me &lt;a href="http://twitter.com/nachocoloma"&gt;@nachocoloma&lt;/a&gt;.&lt;/div&gt;
&lt;img src="http://feeds.feedburner.com/~r/The90thPercentile/~4/bdADtyCziEc" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://icoloma.blogspot.com/feeds/7588524014888037306/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://icoloma.blogspot.com/2012/09/sublime-text-editor-tips-and-tricks-for.html#comment-form" title="5 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/10814242/posts/default/7588524014888037306?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/10814242/posts/default/7588524014888037306?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/The90thPercentile/~3/bdADtyCziEc/sublime-text-editor-tips-and-tricks-for.html" title="Sublime Text Editor tricks for web developers" /><author><name>Nacho Coloma</name><uri>https://plus.google.com/112843640399200525430</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh6.googleusercontent.com/-XL2pDJUO-0U/AAAAAAAAAAI/AAAAAAAAAxw/Wr1DmPB3TOA/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/-3VoHC2Lwgfk/UGBYXKvTu-I/AAAAAAAAAjo/VTCAhhd3tgM/s72-c/two-columns.png" height="72" width="72" /><thr:total>5</thr:total><feedburner:origLink>http://icoloma.blogspot.com/2012/09/sublime-text-editor-tips-and-tricks-for.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C08FRXg7fSp7ImA9WhVaFU0.&quot;"><id>tag:blogger.com,1999:blog-10814242.post-1340191830382303351</id><published>2012-06-11T10:00:00.000+02:00</published><updated>2012-06-12T13:50:14.605+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-06-12T13:50:14.605+02:00</app:edited><title>Using LiveReload on Linux</title><content type="html">&lt;p&gt;This is a guest entry from &lt;a href="https://diasp.eu/u/jrvidal"&gt;José Roberto Vidal&lt;/a&gt;, our newest acquisition at Extrema Sistemas. It's a perfect combination between me, not having enough time to write something coherent, and Roberto having something interesting to say and nowhere to stick it on. You can also find a Spanish version &lt;a href="http://blog.extrema-sistemas.com/2012/05/31/utilizar-livereload-con-linux/"&gt;here&lt;/a&gt;.
&lt;p&gt;
LiveReload is an application that allows you to refresh automatically your browser whenever any file gets modified, even compiling any Sass / Less / CoffeeScript files. In a nutshell, it avoids you the tedious-to-death &lt;code&gt;Alt+Tab F5&lt;/code&gt;.
&lt;p&gt;
Its installation in Mac OS or Windows is quite straightforward, but things get a bit trickier with Linux (surprise, surprise). This is what this post is about.

&lt;h2&gt;Installation and configuration&lt;/h2&gt;

&lt;img title="Leave the usual sacrifice of blood and caffeine by the side table. Thank you" src="http://extremasistemas.files.wordpress.com/2012/05/boromir6.jpg" alt="Leave the usual sacrifice of blood and caffeine by the side table. Thank you" &gt;

&lt;p&gt;Where Ruby gems get involved:

&lt;pre&gt;sudo apt-get install ruby1.9.1 ruby1.9.1-dev&lt;/pre&gt;

&lt;p&gt;Now you can use RubyGems to get the application. Inside your project folder, do:

&lt;pre&gt;sudo gem install bundle
sudo gem install guard
sudo gem install guard-livereload
bundle init&lt;/pre&gt;

&lt;p&gt;Edit the recently created &lt;code&gt;Gemfile&lt;/code&gt; and add these two lines:

&lt;pre&gt;gem 'guard'
gem 'guard-livereload'&lt;/pre&gt;

&lt;p&gt;And execute 

&lt;pre&gt;bundle exec guard init livereload&lt;/pre&gt;

&lt;h2&gt;Monitored files and browser extension&lt;/h2&gt;

&lt;p&gt;Your generated &lt;code&gt;Guardfile&lt;/code&gt; includes some predefined monitoring folders that you may have to modify if yours is not a Rails project. After that, just start the Guard server:

&lt;pre&gt;$ bundle exec guard
Guard uses NotifySend to send notifications.
Guard is now watching at '/home/me/workspace/project'
LiveReload 1.6 is waiting for a browser to connect.
&gt; 
&lt;/pre&gt;

&lt;p&gt;At this point you probably can't wait to crack your knuckles and give it a try... Download the LiveReload extension for your browser (&lt;a href="https://chrome.google.com/webstore/detail/jnihajbhpnppcggbcgedagnkighmdlei"&gt;Chrome&lt;/a&gt;,  &lt;a href="https://addons.mozilla.org/en-US/firefox/addon/livereload/"&gt;Firefox&lt;/a&gt;) and configure it (right click -&gt; options) to specify the hostname and port used by the Guard server (by default &lt;code&gt;localhost:35729&lt;/code&gt;). 

&lt;div&gt;
&lt;a href="http://1.bp.blogspot.com/-hQNg6FNol3Y/T9C0SXzdufI/AAAAAAAAAgk/BD22HMWKRSk/s1600/livereload-options.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" src="http://1.bp.blogspot.com/-hQNg6FNol3Y/T9C0SXzdufI/AAAAAAAAAgk/BD22HMWKRSk/s400/livereload-options.png" style="height: 200px"/&gt;&lt;/a&gt;
&lt;/div&gt;

&lt;p&gt;Once enabled, the &lt;code&gt;guard-livereload&lt;/code&gt; server will notice it:

&lt;pre&gt;&gt; Browser connected.
Browser URL: http://mymachine:myport/&lt;/pre&gt;

&lt;h2&gt;Disco!&lt;/h2&gt;

&lt;p&gt;If you get here, any modification to monitored files (CSS stylesheets, images, even JavaScript files) is noticed and applied immediately at the browser without refreshing.

&lt;p&gt;Guard comes whith a lot of &lt;a href="https://rubygems.org/search?utf8=%E2%9C%93&amp;query=guard-"&gt;different flavors&lt;/a&gt; to automatize tasks in different environments. Get your own, or follow &lt;a href="http://railscasts.com/episodes/264-guard"&gt;their nice screencast&lt;/a&gt;.

&lt;h2&gt;More&lt;/h2&gt;

&lt;p&gt;This is one of the labs that you can find in our &lt;a href="http://extrema-sistemas.com/training/html5-and-css3"&gt;HTML5+CSS3 course&lt;/a&gt;, together with Bootstrap, Sass and another 25 labs. It's just three days that you may choose to come to one of our public scheduled dates or make one specific for your own company.&lt;img src="http://feeds.feedburner.com/~r/The90thPercentile/~4/cJnn8jQntpU" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://icoloma.blogspot.com/feeds/1340191830382303351/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://icoloma.blogspot.com/2012/06/using-livereload-on-linux.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/10814242/posts/default/1340191830382303351?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/10814242/posts/default/1340191830382303351?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/The90thPercentile/~3/cJnn8jQntpU/using-livereload-on-linux.html" title="Using LiveReload on Linux" /><author><name>Nacho Coloma</name><uri>https://plus.google.com/112843640399200525430</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh6.googleusercontent.com/-XL2pDJUO-0U/AAAAAAAAAAI/AAAAAAAAAxw/Wr1DmPB3TOA/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://1.bp.blogspot.com/-hQNg6FNol3Y/T9C0SXzdufI/AAAAAAAAAgk/BD22HMWKRSk/s72-c/livereload-options.png" height="72" width="72" /><thr:total>0</thr:total><feedburner:origLink>http://icoloma.blogspot.com/2012/06/using-livereload-on-linux.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CU4MR3c5eyp7ImA9WhJbFEQ.&quot;"><id>tag:blogger.com,1999:blog-10814242.post-8281695213370872642</id><published>2011-10-28T23:10:00.000+02:00</published><updated>2012-09-24T15:39:46.923+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-09-24T15:39:46.923+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="tools" /><title>Eclipse shortcuts for Sublime Text 2</title><content type="html">&lt;div dir="ltr" style="text-align: left;" trbidi="on"&gt;
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.
&lt;br /&gt;
&lt;img border="0" height="279" src="http://1.bp.blogspot.com/-nXV9aoBplLA/TqsVu91w0jI/AAAAAAAAAeg/E6gA671j9y0/s400/sample-screenshot.png" style="display: block;" title="Notice the scrollbar on the right. Seriously, look again." width="400" /&gt;

&lt;br /&gt;
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 &lt;a href="http://72lions.com/2011/08/26/sublime-text-2-tips/"&gt;list of tips and tricks&lt;/a&gt;). 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).
&lt;br /&gt;
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. 
&lt;br /&gt;
So, without further ado, behold the
&lt;br /&gt;
&lt;h2&gt;
Ultimate eclipse shortcuts for Sublime Text 2&lt;/h2&gt;
Not exactly rocket science, but still: go to &lt;i&gt;Preferences -&amp;gt; Key bindings - User&lt;/i&gt; and paste this:
&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;[
 { "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"} }
]
&lt;/pre&gt;
&lt;br /&gt;
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 &lt;i&gt;Preferences -&amp;gt; Key bindings - Global&lt;/i&gt;, and complement this with our own discoveries. 

&lt;br/&gt;
If you like this, do not miss our new post about &lt;a href="http://icoloma.blogspot.com.es/2012/09/sublime-text-editor-tips-and-tricks-for.html"&gt;Sublime Text Editor tips for Web Developers&lt;/a&gt;.
&lt;/div&gt;
&lt;img src="http://feeds.feedburner.com/~r/The90thPercentile/~4/jg6uLPip-jU" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://icoloma.blogspot.com/feeds/8281695213370872642/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://icoloma.blogspot.com/2011/10/eclipse-shortcuts-for-sublime-text-2.html#comment-form" title="10 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/10814242/posts/default/8281695213370872642?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/10814242/posts/default/8281695213370872642?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/The90thPercentile/~3/jg6uLPip-jU/eclipse-shortcuts-for-sublime-text-2.html" title="Eclipse shortcuts for Sublime Text 2" /><author><name>Nacho Coloma</name><uri>https://plus.google.com/112843640399200525430</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh6.googleusercontent.com/-XL2pDJUO-0U/AAAAAAAAAAI/AAAAAAAAAxw/Wr1DmPB3TOA/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://1.bp.blogspot.com/-nXV9aoBplLA/TqsVu91w0jI/AAAAAAAAAeg/E6gA671j9y0/s72-c/sample-screenshot.png" height="72" width="72" /><thr:total>10</thr:total><feedburner:origLink>http://icoloma.blogspot.com/2011/10/eclipse-shortcuts-for-sublime-text-2.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D08HQn04eip7ImA9WhdbFEQ.&quot;"><id>tag:blogger.com,1999:blog-10814242.post-1777319408612504796</id><published>2011-10-13T10:47:00.000+02:00</published><updated>2011-10-13T10:50:33.332+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-10-13T10:50:33.332+02:00</app:edited><title>Apache Barcamp Spain: a wrap-up</title><content type="html">&lt;div&gt;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.

&lt;p&gt;
Then something like Apache Barcamp Spain gets together and it's like - WOW.
&lt;/p&gt;
&lt;p&gt;
&lt;img border="0" src="http://4.bp.blogspot.com/-8u69wwhGEtE/Tpai1Cr72tI/AAAAAAAAAeM/8tq26OpRJZs/s800/beer-beer-beer-BEER-BEER-BEER%2521%2521%2521.jpg" title="for (i = 0; i &lt; undefined; ) do echo 'cerveza!!!!!!'"/&gt;
&lt;/p&gt;

&lt;p&gt;
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.  
&lt;/p&gt;

&lt;h2&gt;The talks&lt;/h2&gt;
&lt;p&gt;
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 &lt;a href="http://techcrunch.com/2009/10/09/google-wave-is-easier-to-understand-than/" title="Obviously you want to know what this is..."&gt;new, surprising flavors of freaky&lt;/a&gt;.
&lt;/p&gt;&lt;p&gt;
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.
&lt;/p&gt;&lt;p&gt;
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.
&lt;/p&gt;&lt;p&gt;
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.
&lt;/p&gt;

&lt;div style="width:425px" id="__ss_9645504"&gt; &lt;strong style="display:block;margin:12px 0 4px"&gt;&lt;a href="http://www.slideshare.net/icoloma/css-for-nondesigners" title="Css for nondesigners" target="_blank"&gt;Css for nondesigners&lt;/a&gt;&lt;/strong&gt; &lt;iframe src="http://www.slideshare.net/slideshow/embed_code/9645504" width="425" height="355" frameborder="0" marginwidth="0" marginheight="0" scrolling="no"&gt;&lt;/iframe&gt; &lt;div style="padding:5px 0 12px"&gt; View more &lt;a href="http://www.slideshare.net/" target="_blank"&gt;presentations&lt;/a&gt; from &lt;a href="http://www.slideshare.net/icoloma" target="_blank"&gt;Ignacio Coloma&lt;/a&gt; &lt;/div&gt; &lt;/div&gt;

&lt;div style="width:425px" id="__ss_9645478"&gt; &lt;strong style="display:block;margin:12px 0 4px"&gt;&lt;a href="http://www.slideshare.net/icoloma/css3-101" title="Css3 101" target="_blank"&gt;Css3 101&lt;/a&gt;&lt;/strong&gt; &lt;iframe src="http://www.slideshare.net/slideshow/embed_code/9645478" width="425" height="355" frameborder="0" marginwidth="0" marginheight="0" scrolling="no"&gt;&lt;/iframe&gt; &lt;div style="padding:5px 0 12px"&gt; View more &lt;a href="http://www.slideshare.net/" target="_blank"&gt;presentations&lt;/a&gt; from &lt;a href="http://www.slideshare.net/icoloma" target="_blank"&gt;Ignacio Coloma&lt;/a&gt; &lt;/div&gt; &lt;/div&gt;

&lt;h2&gt;The people&lt;/h2&gt;

&lt;p&gt;
CELEBRITIES. All of them. Hey, we got a T-shirt to prove it.
&lt;/p&gt;&lt;p&gt;
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.
&lt;/p&gt;&lt;p&gt;
Pity that we were just 100 people. I know quite a crowd that was left out because of the limited capacity.
&lt;/p&gt;
&lt;h2&gt;The party&lt;/h2&gt;

&lt;p&gt;
This event was co-sponsored by several companies, Extrema between them, but one company really stood out: Atlassian.
&lt;/p&gt;&lt;p&gt;
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.
&lt;/p&gt;&lt;p&gt;
Funny fact of the night: After one hour of animated chat, I ended up recognizing &lt;a href="http://blog.carlossanchez.eu/"&gt;Carlos Sanchez&lt;/a&gt; because of the avatar in his business card. Avatares reales ya :)
&lt;/p&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/The90thPercentile/~4/W5CDa10YsVo" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://icoloma.blogspot.com/feeds/1777319408612504796/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://icoloma.blogspot.com/2011/10/apache-barcamp-spain-wrap-up.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/10814242/posts/default/1777319408612504796?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/10814242/posts/default/1777319408612504796?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/The90thPercentile/~3/W5CDa10YsVo/apache-barcamp-spain-wrap-up.html" title="Apache Barcamp Spain: a wrap-up" /><author><name>Nacho Coloma</name><uri>https://plus.google.com/112843640399200525430</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh6.googleusercontent.com/-XL2pDJUO-0U/AAAAAAAAAAI/AAAAAAAAAxw/Wr1DmPB3TOA/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://4.bp.blogspot.com/-8u69wwhGEtE/Tpai1Cr72tI/AAAAAAAAAeM/8tq26OpRJZs/s72-c/beer-beer-beer-BEER-BEER-BEER%2521%2521%2521.jpg" height="72" width="72" /><thr:total>0</thr:total><feedburner:origLink>http://icoloma.blogspot.com/2011/10/apache-barcamp-spain-wrap-up.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DE8EQX85fyp7ImA9WhdVF0w.&quot;"><id>tag:blogger.com,1999:blog-10814242.post-3982651885999577338</id><published>2011-09-22T20:40:00.000+02:00</published><updated>2011-09-22T20:40:00.127+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-09-22T20:40:00.127+02:00</app:edited><title>Style your forms using JavaScript or CSS</title><content type="html">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:&lt;br /&gt;
&lt;br /&gt;
&lt;p style="text-align: center"&gt;&lt;b&gt;I don't want to style buttons EVER AGAIN.&lt;/b&gt;&lt;/p&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
Since we are at it, let's take a look at how to style form controls, period.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Style your widgets using JavaScript&lt;/h2&gt;
&lt;br /&gt;
&lt;a href="http://www.warfuric.com/taitems/demo.html"&gt;Aristo&lt;/a&gt; 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).

&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;img border="0" src="http://2.bp.blogspot.com/-HVnEpr2l2kY/Tnt0pJegn5I/AAAAAAAAAd0/CRi8MJp0g-c/s800/aristo.png" title="Aristo includes styles for all kind of jQuery UI widgets" /&gt;&lt;/div&gt;
The only problem is that it relies on jQuery to initialize the correct Look &amp;amp; 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.&lt;br /&gt;
&lt;br /&gt;
If you need something less heavy there is a project to get Aristo buttons &lt;a href="http://aristocss.com/"&gt;using just CSS&lt;/a&gt;, but it's still too green. Better go with skeleton.

&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Style your controls using just CSS&lt;/h2&gt;
&lt;br /&gt;
&lt;a href="http://www.getskeleton.com/"&gt;Skeleton&lt;/a&gt; is an extension over the 960 grid system. They extend the grid concept by adding extra&amp;nbsp;typography&amp;nbsp;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.

&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;img border="0" src="http://3.bp.blogspot.com/-uvl7unhTZA4/Tnt0paGAOUI/AAAAAAAAAeE/pUbvccY5KrA/s800/skeleton.png" title="Skeleton is a nice and lightweight starting point for your CSS" /&gt;&lt;/div&gt;
&lt;h2&gt;
Style your select controls&lt;/h2&gt;
&lt;br /&gt;
You know those huge combo boxes that happen every now and then, when reality hits your application? With &lt;a href="http://harvesthq.github.com/chosen/"&gt;Chosen&lt;/a&gt; you can replace them transparently: 

&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;img border="0" src="http://4.bp.blogspot.com/-nQme-KJG22o/Tnt0pMxChiI/AAAAAAAAAd8/GJwKL6kxZko/s800/chosen.png" title="Use chosen for a really different experience with select fields" /&gt;&lt;/div&gt;
They have plenty of examples of controls, including ways to provide for your Ajax needs.&lt;br /&gt;
&lt;br /&gt;
I know there must be other exciting tools out there that I am missing. How do you make your forms rock? Please share!

&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Aside: These be Crazy Months&lt;/h2&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
&amp;nbsp;Now I just got back to Spain! Next month I'll be at &lt;a href="http://barcampspain.com/en/"&gt;Apache BarCamp Spain&lt;/a&gt; with a &lt;i&gt;CSS for non-designers&lt;/i&gt; and &lt;i&gt;CSS3 101&lt;/i&gt; talks, hope to see you there to share some beers and plenty of chinese stories.&lt;img src="http://feeds.feedburner.com/~r/The90thPercentile/~4/3jJPOPCt80I" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://icoloma.blogspot.com/feeds/3982651885999577338/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://icoloma.blogspot.com/2011/09/style-your-forms-using-javascript-or.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/10814242/posts/default/3982651885999577338?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/10814242/posts/default/3982651885999577338?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/The90thPercentile/~3/3jJPOPCt80I/style-your-forms-using-javascript-or.html" title="Style your forms using JavaScript or CSS" /><author><name>Nacho Coloma</name><uri>https://plus.google.com/112843640399200525430</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh6.googleusercontent.com/-XL2pDJUO-0U/AAAAAAAAAAI/AAAAAAAAAxw/Wr1DmPB3TOA/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/-HVnEpr2l2kY/Tnt0pJegn5I/AAAAAAAAAd0/CRi8MJp0g-c/s72-c/aristo.png" height="72" width="72" /><thr:total>0</thr:total><feedburner:origLink>http://icoloma.blogspot.com/2011/09/style-your-forms-using-javascript-or.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CkMESX8_fCp7ImA9WhZVEk8.&quot;"><id>tag:blogger.com,1999:blog-10814242.post-6373814736731473548</id><published>2011-05-24T09:00:00.005+02:00</published><updated>2011-05-24T09:00:08.144+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-05-24T09:00:08.144+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="training" /><category scheme="http://www.blogger.com/atom/ns#" term="html5" /><category scheme="http://www.blogger.com/atom/ns#" term="web" /><category scheme="http://www.blogger.com/atom/ns#" term="html" /><title>Firefox 4 and 3.6 at the same time</title><content type="html">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.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;img border="0" src="http://1.bp.blogspot.com/-La6Cy7DI1QI/TdPnbQD6KoI/AAAAAAAAAcY/aEy-YF3BUvM/s1600/screenshot.jpg" title="More than one way to skin a fox" /&gt;&lt;/div&gt;&lt;br /&gt;
&lt;h2&gt;Step one: Installing the thing&lt;/h2&gt;&lt;br /&gt;
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. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center; overflow: hidden;"&gt;&lt;a href="http://2.bp.blogspot.com/-9GDWhFcOjIw/TdP7EjtimBI/AAAAAAAAAcg/v3vORieSQ9k/s1600/install1.jpg" target="_blank"&gt;&lt;img border="0" src="http://2.bp.blogspot.com/-9GDWhFcOjIw/TdP7EjtimBI/AAAAAAAAAcg/v3vORieSQ9k/s1600/install1.jpg" style="width: 45%; float: left"/&gt;&lt;/a&gt;&lt;a href="http://4.bp.blogspot.com/-AB-lHRtYz_E/TdP7E3775rI/AAAAAAAAAco/dmVJejFxZ5s/s1600/install2.jpg" target="_blank"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/-AB-lHRtYz_E/TdP7E3775rI/AAAAAAAAAco/dmVJejFxZ5s/s1600/install2.jpg" style="width: 45%; float: left; margin-right: 10px;"/&gt;&lt;/a&gt;&lt;br /&gt;
&lt;/div&gt;&lt;br /&gt;
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).&lt;br /&gt;
&lt;br /&gt;
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 &lt;a href="http://www.mozilla.com/en-US/firefox/all-older.html"&gt;click here&lt;/a&gt;).&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;Step two: add -no-remote&lt;/h2&gt;&lt;br /&gt;
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!"&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;Step three: use different profiles&lt;/h2&gt;&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
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:&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;firefox -ProfileManager
&lt;/pre&gt;&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center; "&gt;&lt;img border="0" height="271" src="http://3.bp.blogspot.com/-na9TMLdVaQc/TdP7E3_iIRI/AAAAAAAAAcw/_B0WLUJIY3g/s400/install3.jpg" width="377" /&gt;&lt;br /&gt;
&lt;/div&gt;&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
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:&lt;br /&gt;
&lt;br /&gt;
Linux&lt;br /&gt;
&lt;pre&gt;/usr/bin/firefox -no-remote -P "Firefox 4"
/usr/local/firefox3.6/firefox -no-remote -P "Firefox 3.6"
&lt;/pre&gt;&lt;br /&gt;
Windows:&lt;br /&gt;
&lt;pre&gt;"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"
&lt;/pre&gt;&lt;br /&gt;
That's it. Now you can develop using both versions with separate Firebug installations.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;More. More. More.&lt;/h2&gt;&lt;br /&gt;
This is an excerpt of the first lab in our &lt;a href="http://extrema-sistemas.com/training/html5-and-css3"&gt;HTML5 and CSS3 course&lt;/a&gt; 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.&lt;img src="http://feeds.feedburner.com/~r/The90thPercentile/~4/KeztbuEbdCM" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://icoloma.blogspot.com/feeds/6373814736731473548/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://icoloma.blogspot.com/2011/05/firefox-4-and-36-at-same-time.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/10814242/posts/default/6373814736731473548?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/10814242/posts/default/6373814736731473548?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/The90thPercentile/~3/KeztbuEbdCM/firefox-4-and-36-at-same-time.html" title="Firefox 4 and 3.6 at the same time" /><author><name>Nacho Coloma</name><uri>https://plus.google.com/112843640399200525430</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh6.googleusercontent.com/-XL2pDJUO-0U/AAAAAAAAAAI/AAAAAAAAAxw/Wr1DmPB3TOA/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://1.bp.blogspot.com/-La6Cy7DI1QI/TdPnbQD6KoI/AAAAAAAAAcY/aEy-YF3BUvM/s72-c/screenshot.jpg" height="72" width="72" /><thr:total>0</thr:total><feedburner:origLink>http://icoloma.blogspot.com/2011/05/firefox-4-and-36-at-same-time.html</feedburner:origLink></entry><entry gd:etag="W/&quot;AkAERXozcSp7ImA9Wx9UE08.&quot;"><id>tag:blogger.com,1999:blog-10814242.post-1998012563390794601</id><published>2011-02-10T10:00:00.006+01:00</published><updated>2011-02-10T10:05:04.489+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-02-10T10:05:04.489+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="appengine" /><title>Applications dealing with multiple timezones</title><content type="html">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.&lt;br /&gt;
&lt;br /&gt;
We recently deployed &lt;a href="http://koliseo.com/"&gt;Koliseo&lt;/a&gt; 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?&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/_5fcKwF7jItc/TVLi1MTD68I/AAAAAAAAAbo/4zQcU4BCUdU/s1600/multiple-pain.jpg" title="Multiple painzones" alt="Multiple painzones"/&gt;&lt;/div&gt;&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
Then, the only problem is &lt;strong&gt;parsing&lt;/strong&gt; and &lt;b&gt;formatting&lt;/b&gt; these dates.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;Parse date&lt;/h2&gt;&lt;br /&gt;
&lt;i&gt;Ye olde way of providing time information&lt;/i&gt; 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). &lt;br /&gt;
&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;&lt;b&gt;What the user means&lt;/b&gt;: "25 Oct 2010 at 00:00" in NiceAndWarmOffice@Madrid&lt;/li&gt;
&lt;li&gt;&lt;b&gt;What the server understood&lt;/b&gt;: "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.&lt;/li&gt;
&lt;/ul&gt;&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
You need to ask for the timezone of the user. It can be automatically deduced using javascript (it comes in &lt;a href="http://blog.redfin.com/devblog/2007/08/getting_the_time_zone_from_a_web_browser.html"&gt;small&lt;/a&gt; and &lt;a href="https://bitbucket.org/pellepim/jstimezonedetect/src/918d1f4fe44f/detect_timezone.js"&gt;jumbo&lt;/a&gt; sizes), but it is nice practice to let the user modify the timezone later, just in case. &lt;br /&gt;
&lt;br /&gt;
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 &lt;b&gt;where&lt;b&gt;&lt;/b&gt;&lt;/b&gt; before telling &lt;b&gt;when&lt;/b&gt; if we want to be able to parse the introduced timestamp.&lt;br /&gt;
&lt;br /&gt;
The code to parse is quite straightforward thanks to Saint JodaTime:&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="prettyprint"&gt;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();
&lt;/pre&gt;&lt;br /&gt;
&lt;h2&gt;Format date&lt;/h2&gt;&lt;br /&gt;
How do you want to display dates to the user?&lt;br /&gt;
&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;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 &lt;a href="http://loom.sourceforge.net/docs/loom-core/tlddoc/formatDate.html"&gt;l:formatDate&lt;/a&gt;, but the first case I can remember of this practice was found in &lt;a href="http://api.rubyonrails.org/classes/ActionView/Helpers/DateHelper.html#method-i-time_ago_in_words"&gt;Ruby&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;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.&lt;/li&gt;
&lt;li&gt;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).&lt;/li&gt;
&lt;/ul&gt;&lt;br /&gt;
To format dates in the original timezone you can use the same snippet of code just replacing formatter.parse() with format().&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;Javascript code&lt;/h2&gt;&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
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.&lt;img src="http://feeds.feedburner.com/~r/The90thPercentile/~4/a6F8wOGahRA" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://icoloma.blogspot.com/feeds/1998012563390794601/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://icoloma.blogspot.com/2011/02/applications-dealing-with-multiple.html#comment-form" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/10814242/posts/default/1998012563390794601?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/10814242/posts/default/1998012563390794601?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/The90thPercentile/~3/a6F8wOGahRA/applications-dealing-with-multiple.html" title="Applications dealing with multiple timezones" /><author><name>Nacho Coloma</name><uri>https://plus.google.com/112843640399200525430</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh6.googleusercontent.com/-XL2pDJUO-0U/AAAAAAAAAAI/AAAAAAAAAxw/Wr1DmPB3TOA/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://4.bp.blogspot.com/_5fcKwF7jItc/TVLi1MTD68I/AAAAAAAAAbo/4zQcU4BCUdU/s72-c/multiple-pain.jpg" height="72" width="72" /><thr:total>1</thr:total><feedburner:origLink>http://icoloma.blogspot.com/2011/02/applications-dealing-with-multiple.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C0EAQXoyeSp7ImA9Wx5bFk4.&quot;"><id>tag:blogger.com,1999:blog-10814242.post-7444280768483569158</id><published>2010-11-01T18:14:00.001+01:00</published><updated>2010-11-01T18:14:00.491+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-11-01T18:14:00.491+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="loom" /><title>Loom 2.2 has been released</title><content type="html">We have just released Loom 2.2 with a lot of stuff to make web development easier:&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;Better javascript/CSS resource bundles&lt;/h2&gt;&lt;br /&gt;
Loom has been supporting &lt;a href="http://loom.extrema-sistemas.org/doc/1.x/ref/javascript-css-resource-bundles"&gt;javascript and CSS dependencies&lt;/a&gt; for some time, and now they just got easier (think sprockets):&lt;br /&gt;
&lt;br /&gt;
JSP file:&lt;br /&gt;
&lt;pre class="prettyprint"&gt;&amp;lt;l:css resource="style.css"/&amp;gt;
&amp;lt;l:script resource="main.js"/&amp;gt;
&lt;/pre&gt;&lt;br /&gt;
style.css: &lt;br /&gt;
&lt;pre class="prettyprint"&gt;/* 
// @include "myfile.css" 
// @include 'classpath:/mypackage/file2.css' 
*/ 
&lt;/pre&gt;&lt;br /&gt;
main.js:&lt;br /&gt;
&lt;pre class="prettyprint"&gt;// @include "myfile.js" 
// @include 'classpath:/mypackage/file2.js' 
&lt;/pre&gt;&lt;br /&gt;
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:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em; text-align: center;"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/_5fcKwF7jItc/TM7vCHENwVI/AAAAAAAAAbc/4tPbGyWXA2Q/s1600/more-and-better-features.jpg" alt="More and better features" title="More and better features"/&gt;&lt;/div&gt;&lt;h2&gt;Added support for Google Closure Compiler&lt;/h2&gt;&lt;br /&gt;
According to multiple sources &lt;a href="http://www.bloggingdeveloper.com/post/Closure-Compiler-vs-YUI-Compressor-Comparing-the-Javascript-Compression-Tools.aspx"&gt;Google Closure Compiler produces a better compression rate than YUI Compressor&lt;/a&gt;. This is nice, but what really got us is the awesome error reporting which even warns about IE gotchas (trailing commas, anyone?).&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;New Ajax Paged Table&lt;/h2&gt;&lt;br /&gt;
Loom includes some great &lt;a href="http://loom.extrema-sistemas.org/doc/1.x/ref/pagedtable-and-pagedlist"&gt;server-side paged components&lt;/a&gt;. 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: &lt;br /&gt;
&lt;br /&gt;
&lt;pre class="prettyprint"&gt;var paged = new loom.ui.PagedTable($('container2'), { 
        url: '&amp;lt;l:url mapping="Action.event" decorator="link-only"/&amp;gt;', 
        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); } ] 
  }); 
&lt;/pre&gt;&lt;br /&gt;
This is an alternative to server-side paging. Just use the solution that suits you.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;Better fluent interfaces&lt;/h2&gt;&lt;br /&gt;
Some interfaces got better at handling HTTP headers and cookies:&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="prettyprint"&gt;// 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") 
;
&lt;/pre&gt;&lt;br /&gt;
CacheControl has also been improved:&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="prettyprint"&gt;// 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);
&lt;/pre&gt;&lt;br /&gt;
&lt;h2&gt;Others&lt;/h2&gt;&lt;br /&gt;
There is a lot of smaller improvements such as:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;Customizable &lt;a href="http://loom.extrema-sistemas.org/doc/1.x/ref/recaptcha"&gt;reCaptcha L&amp;amp;F&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;loom-messages.properties has been translated to italian (thanks to Marco Bazzani)&lt;/li&gt;
&lt;li&gt;We have switched from commons-logging to slf4j because &lt;a href="http://icoloma.blogspot.com/2010/09/current-state-of-logging-frameworks.html"&gt;performance is just better&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;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!&lt;img src="http://feeds.feedburner.com/~r/The90thPercentile/~4/Jx7QG2UrCts" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://icoloma.blogspot.com/feeds/7444280768483569158/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://icoloma.blogspot.com/2010/11/loom-22-has-been-released.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/10814242/posts/default/7444280768483569158?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/10814242/posts/default/7444280768483569158?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/The90thPercentile/~3/Jx7QG2UrCts/loom-22-has-been-released.html" title="Loom 2.2 has been released" /><author><name>Nacho Coloma</name><uri>https://plus.google.com/112843640399200525430</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh6.googleusercontent.com/-XL2pDJUO-0U/AAAAAAAAAAI/AAAAAAAAAxw/Wr1DmPB3TOA/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://4.bp.blogspot.com/_5fcKwF7jItc/TM7vCHENwVI/AAAAAAAAAbc/4tPbGyWXA2Q/s72-c/more-and-better-features.jpg" height="72" width="72" /><thr:total>0</thr:total><feedburner:origLink>http://icoloma.blogspot.com/2010/11/loom-22-has-been-released.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CkMCRHk6cCp7ImA9WhVbGUo.&quot;"><id>tag:blogger.com,1999:blog-10814242.post-2531404439126410037</id><published>2010-10-04T11:00:00.005+02:00</published><updated>2012-06-06T10:14:25.718+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-06-06T10:14:25.718+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="loom" /><category scheme="http://www.blogger.com/atom/ns#" term="web" /><category scheme="http://www.blogger.com/atom/ns#" term="html" /><title>Loom 2.2 is almost out: things we learned so far</title><content type="html">&lt;img src="http://3.bp.blogspot.com/_5fcKwF7jItc/TKWmneV228I/AAAAAAAAAbE/kUX_ltkxDCY/s640/our-customer-2.jpg" style="float: left; margin-right: 10px;" title="I feel guilty talking about a male customer through this post. Actually, in our case he's a she." alt="I feel guilty talking about a male customer through this post. Actually, in our case he is a she."/&gt;&lt;br /&gt;
We just got out the &lt;a href="https://www.bbva.ch/v3/market/index/spain/ibex35"&gt;new e-banking portal&lt;/a&gt; for BBVA Suiza. BBVA Suiza is the BBVA Group's International Private Banking unit, 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.&lt;br /&gt;
&lt;br /&gt;
This post is about things we learned during these last years, and how they were combined to make our experience extremely enjoyable.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;It's all about the UI&lt;/h2&gt;&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
&lt;a href="http://stuffthathappens.com/blog/2008/03/05/simplicity/" title="Not exactly your typical UI (original by Eric Burke)"&gt;&lt;img src="http://stuffthathappens.com/blog/wp-content/uploads/2008/03/simplicity.png" style="clear: left; float: left; margin: 10px 10px 10px 0; width: 280px;" /&gt;&lt;/a&gt; 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 &lt;a href="http://informationarchitects.jp/the-interface-of-a-cheeseburger/"&gt;hamburger interface&lt;/a&gt;. On the way we have challenged the breadcrumbs location, the tabs metaphor, the nesting of contents, everything.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;It's not about stateless or stateful&lt;br /&gt;
&lt;/h2&gt;&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;Speed!&lt;/h2&gt;&lt;br /&gt;
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 &lt;a href="http://blog.punchbarrel.com/2010/05/11/another-inspiring-talk-from-kathy-sierra/"&gt;you should&lt;/a&gt;) your application should feel natural, which means working towards the &lt;a href="http://www.useit.com/papers/responsetime.html"&gt;1 second goal&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
There are lots of tricks to get there. We have posted some &lt;a href="http://www.slideshare.net/icoloma/caching-web-contents-in-the-browser-999134"&gt;here&lt;/a&gt; and &lt;a href="http://icoloma.blogspot.com/2009/09/caching-user-supplied-images-in-browser.html"&gt;there&lt;/a&gt;, but nothing beats simplicity: there is nothing faster than not including a feature. &lt;br /&gt;
&lt;br /&gt;
Of course, your customer may not always agree. This is were the next point gets relevant.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;Get your customer on your same train&lt;/h2&gt;&lt;br /&gt;
You can align your customer with your own goals just by delivering Software That Works, 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 the user asks and what he needs, and it helps if the rationale behind a refusal is clear and shared by everybody.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;So?&lt;/h2&gt;&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
The technology helped a lot. But what really allowed us to deliver was all the weight that was ditched by the way.&lt;img src="http://feeds.feedburner.com/~r/The90thPercentile/~4/zUrTfmgGJi0" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://icoloma.blogspot.com/feeds/2531404439126410037/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://icoloma.blogspot.com/2010/10/loom-22-is-almost-out-things-we-learned.html#comment-form" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/10814242/posts/default/2531404439126410037?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/10814242/posts/default/2531404439126410037?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/The90thPercentile/~3/zUrTfmgGJi0/loom-22-is-almost-out-things-we-learned.html" title="Loom 2.2 is almost out: things we learned so far" /><author><name>Nacho Coloma</name><uri>https://plus.google.com/112843640399200525430</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh6.googleusercontent.com/-XL2pDJUO-0U/AAAAAAAAAAI/AAAAAAAAAxw/Wr1DmPB3TOA/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://3.bp.blogspot.com/_5fcKwF7jItc/TKWmneV228I/AAAAAAAAAbE/kUX_ltkxDCY/s72-c/our-customer-2.jpg" height="72" width="72" /><thr:total>1</thr:total><feedburner:origLink>http://icoloma.blogspot.com/2010/10/loom-22-is-almost-out-things-we-learned.html</feedburner:origLink></entry><entry gd:etag="W/&quot;Dk4EQXY-fCp7ImA9Wx5XE0U.&quot;"><id>tag:blogger.com,1999:blog-10814242.post-8218894199706158007</id><published>2010-09-13T15:35:00.001+02:00</published><updated>2010-09-13T15:35:00.854+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-09-13T15:35:00.854+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="simpleds" /><category scheme="http://www.blogger.com/atom/ns#" term="appengine" /><title>SimpleDS 1.0 is released</title><content type="html">&lt;img alt="Gotta love 1.0 releases" border="0" src="http://2.bp.blogspot.com/_5fcKwF7jItc/TI4YXnayLHI/AAAAAAAAAa4/vciaBtenXd8/s1600/gotta-love-1.0-releases.jpg" style="float: left;" title="Gotta love 1.0 releases" /&gt;&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
This release brings a lot of new features:&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;Cached queries&lt;/h2&gt;&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
There are cases were this feature is killer, e.g. to retrieve a User given the email:&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="prettyprint"&gt;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)
&lt;/pre&gt;&lt;br /&gt;
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().&lt;br /&gt;
&lt;br /&gt;
More about cache &lt;a href="http://code.google.com/p/simpleds/wiki/Cache"&gt;here&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;Better fluent interfaces&lt;/h2&gt;&lt;br /&gt;
The syntax has been simplified from this (older version):&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="prettyprint"&gt;SimpleQuery query = entityManager.createQuery(User.class).equal("name", "foo");
return entityManager.find(query);&lt;/pre&gt;&lt;br /&gt;
To this:&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="prettyprint"&gt;return entityManager.createQuery(User.class)
  .equal("email", email)
  .equal("enabled", true)
  .sortAsc("email")
  .asList()
  ;&lt;/pre&gt;&lt;br /&gt;
Now we rarely use more than one line for most queries. In this example, if email is null it will just be ignored.&lt;br /&gt;
&lt;br /&gt;
Several methods have been added: asList(), asSingleResult(), asIterator(), asIterable() and PagedQuery.asPagedList(). The old EntityManager methods have been deprecated.&lt;br /&gt;
&lt;br /&gt;
More about queries &lt;a href="http://code.google.com/p/simpleds/wiki/SimpleQuery"&gt;here&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;New Functions&lt;/h2&gt;&lt;br /&gt;
We are also adding some new Functions for transforming persistent entities. Some examples of use:&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="prettyprint"&gt;// save space for your relationships by storing Set&lt;long&gt; instead of Set&lt;key&gt;
Set&lt;long&gt; userIds = user.getFriends();
Collection&lt;key&gt; userKeys = Collections2.transform(userIds, new IdToKeyFunction(User.class));
Collection&lt;user&gt; users = entityManager.get(userKeys);

// transform back
CompositeFunction&lt;user, long=""&gt; 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"));&lt;/user,&gt;&lt;/user&gt;&lt;/key&gt;&lt;/long&gt;&lt;/key&gt;&lt;/long&gt;&lt;/pre&gt;&lt;br /&gt;
More about functions and transformations &lt;a href="http://code.google.com/p/simpleds/wiki/Functions"&gt;here&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;Added JRebel support&lt;/h2&gt;&lt;br /&gt;
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:&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="prettyprint"&gt;if (SystemProperty.environment.get().equals("Development"))
   ClassMetadataReloader.register();&lt;/pre&gt;&lt;br /&gt;
Then in eclipse ("Go to your launcher config -&amp;gt; Arguments -&amp;gt; VM Arguments")&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="prettyprint"&gt;-javaagent:/usr/local/java/appengine-java-sdk/lib/agent/appengine-agent.jar -noverify
"-javaagent:${env_var:REBEL_HOME}/jrebel.jar"&lt;/pre&gt;&lt;br /&gt;
&lt;h2&gt;Minor changes&lt;/h2&gt;&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;Added @Property.converter to override the default converter for one persistent property. You can, for example, store a String attribute as Text.&lt;/li&gt;
&lt;li&gt;Added new methods SimpleQuery.withReadPolicy(ReadPolicy) and SimpleQuery.withDeadline(double)&lt;/li&gt;
&lt;li&gt;Added new methods SimpleQuery.withStartCursor(Cursor) and SimpleQuery.withEndCursor(Cursor)&lt;/li&gt;
&lt;li&gt;Cache settings will be ignored when invoked within a transaction.&lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;We have moved from commons-logging to slf4j. This may break binary compatibility, but is a huge boost in performance.&lt;/li&gt;
&lt;/ul&gt;&lt;br /&gt;
&lt;h2&gt;Let me get off-topic for a while&lt;/h2&gt;&lt;br /&gt;
Last week this very same blog reached the &lt;strong&gt;400-subscribers&lt;/strong&gt; 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).&lt;br /&gt;
&lt;br /&gt;
So this is me, taking a small detour to say thanks. And yes, I will try to post more often :))&lt;img src="http://feeds.feedburner.com/~r/The90thPercentile/~4/2UJDt_B9GF4" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://icoloma.blogspot.com/feeds/8218894199706158007/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://icoloma.blogspot.com/2010/09/simpleds-10-is-released.html#comment-form" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/10814242/posts/default/8218894199706158007?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/10814242/posts/default/8218894199706158007?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/The90thPercentile/~3/2UJDt_B9GF4/simpleds-10-is-released.html" title="SimpleDS 1.0 is released" /><author><name>Nacho Coloma</name><uri>https://plus.google.com/112843640399200525430</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh6.googleusercontent.com/-XL2pDJUO-0U/AAAAAAAAAAI/AAAAAAAAAxw/Wr1DmPB3TOA/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/_5fcKwF7jItc/TI4YXnayLHI/AAAAAAAAAa4/vciaBtenXd8/s72-c/gotta-love-1.0-releases.jpg" height="72" width="72" /><thr:total>1</thr:total><feedburner:origLink>http://icoloma.blogspot.com/2010/09/simpleds-10-is-released.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CUAAR3wyfyp7ImA9Wx5QF0Q.&quot;"><id>tag:blogger.com,1999:blog-10814242.post-1910039853893489116</id><published>2010-09-06T17:40:00.007+02:00</published><updated>2010-09-06T19:22:26.297+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-09-06T19:22:26.297+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="java" /><category scheme="http://www.blogger.com/atom/ns#" term="log" /><title>The current state of logging frameworks</title><content type="html">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. &lt;br /&gt;&lt;br /&gt;It's like opening the closet you buried ten years ago when log4j was the one and only available choice. &lt;br /&gt;&lt;br /&gt;&lt;h2&gt;First, the basics&lt;/h2&gt;&lt;img alt="Logging reliably since 1999" border="0" src="http://3.bp.blogspot.com/_5fcKwF7jItc/TIUEZL2ur2I/AAAAAAAAAao/x6Essua4es4/s640/logging-anywhere.jpg" style="float: right;" title="Logging reliably since 1999" /&gt;&lt;br /&gt;&lt;br /&gt;One thing is the logging framework (the thingie that writes stuff out):&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://logging.apache.org/log4j/index.html"&gt;log4j&lt;/a&gt;: 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.&lt;/li&gt;&lt;li&gt;&lt;a href="http://download.oracle.com/javase/1.4.2/docs/guide/util/logging/overview.html"&gt;Java Util Logging (JUL)&lt;/a&gt;: 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.&lt;/li&gt;&lt;li&gt;&lt;a href="http://logback.qos.ch/"&gt;Logback&lt;/a&gt;: 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.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;A separate concept is the logging isolation library, because when you are implementing a library you cannot require (or expect) a specific logging framework:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://commons.apache.org/logging/"&gt;Commons Logging&lt;/a&gt;: 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.&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.slf4j.org/"&gt;SLF4J&lt;/a&gt;: 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.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;h2&gt;The Google case&lt;/h2&gt;&lt;br /&gt;Google makes a point of not including external dependencies with their libraries, and when they do the package name is &lt;a href="http://code.google.com/p/jarjar/"&gt;jarjar'ed&lt;/a&gt; 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 :)&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;The choice&lt;/h2&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;"Zillions of appenders" is thus not exactly an important feature in my book. Performance, stability and support are. &lt;a href="http://www.google.es/search?hl=en&amp;amp;q=slf4j+vs+commons+logging"&gt;A quick search using Google&lt;/a&gt; pretty much confirms that the competition is over. SLF4J is being &lt;a href="http://www.slf4j.org/"&gt;widely used by other frameworks&lt;/a&gt;, 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.&lt;br /&gt;&lt;br /&gt;About the logging framework... well, I will keep playing with JUL and Logback, but log4j is left behind as far as I am concerned.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;UPDATE:&lt;/strong&gt; You can also see a longer explanation by the SpringSource team &lt;a href="http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/overview.html#d0e743"&gt;here&lt;/a&gt;.&lt;img src="http://feeds.feedburner.com/~r/The90thPercentile/~4/ISY7HHFnlaY" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://icoloma.blogspot.com/feeds/1910039853893489116/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://icoloma.blogspot.com/2010/09/current-state-of-logging-frameworks.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/10814242/posts/default/1910039853893489116?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/10814242/posts/default/1910039853893489116?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/The90thPercentile/~3/ISY7HHFnlaY/current-state-of-logging-frameworks.html" title="The current state of logging frameworks" /><author><name>Nacho Coloma</name><uri>https://plus.google.com/112843640399200525430</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh6.googleusercontent.com/-XL2pDJUO-0U/AAAAAAAAAAI/AAAAAAAAAxw/Wr1DmPB3TOA/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://3.bp.blogspot.com/_5fcKwF7jItc/TIUEZL2ur2I/AAAAAAAAAao/x6Essua4es4/s72-c/logging-anywhere.jpg" height="72" width="72" /><thr:total>0</thr:total><feedburner:origLink>http://icoloma.blogspot.com/2010/09/current-state-of-logging-frameworks.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DEYEQXY7cSp7ImA9Wx5SEE8.&quot;"><id>tag:blogger.com,1999:blog-10814242.post-4274458516168348019</id><published>2010-08-05T18:35:00.000+02:00</published><updated>2010-08-05T18:35:00.809+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-08-05T18:35:00.809+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="web" /><title>URL-based locale</title><content type="html">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. &lt;br /&gt;
&lt;br /&gt;
&lt;img border="0" src="http://2.bp.blogspot.com/_5fcKwF7jItc/TFrccQOF5HI/AAAAAAAAAak/4y0Vkuxsebk/s640/anybody-there.jpg" /&gt;&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
There are a couple of possible implementations for this:&lt;br /&gt;
&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;Store the locale in the &lt;strong&gt;user session&lt;/strong&gt;: 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.&lt;/li&gt;
&lt;li&gt;Store the locale in the &lt;b&gt;user settings&lt;/b&gt; (database): this is possible only if you do not have anonymous users.&lt;/li&gt;
&lt;li&gt;Store the locale in a &lt;b&gt;browser cookie&lt;/b&gt;.&lt;/li&gt;
&lt;li&gt;Store the locale &lt;b&gt;in the URL&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;br /&gt;
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 &lt;a href="http://www.google.com/support/webmasters/bin/answer.py?answer=182192"&gt;locale-aware DNS names&lt;/a&gt;, 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".&lt;br /&gt;
&lt;br /&gt;
Implementing this in your own application is easy: Loom has &lt;a href="http://loom.svn.sourceforge.net/viewvc/loom/loom-core/trunk/src/main/java/org/loom/config/PrefixLocaleResolver.java?revision=2573&amp;view=markup"&gt;PrefixLocaleResolver&lt;/a&gt;, but with other frameworks you may just use your own web Filter to resolve the user locale as well. Naïve version follows:&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="prettyprint"&gt;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
  }
}&lt;/pre&gt;&lt;br /&gt;
In order to test this, you should add this entry to your &lt;a href="http://en.wikipedia.org/wiki/Hosts_(file)#Content_and_location" title="click to see your hosts file location"&gt;hosts file&lt;/a&gt;:&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;127.0.0.1 localhost es.localhost en.localhost www.localhost&lt;/pre&gt;&lt;br /&gt;
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!&lt;img src="http://feeds.feedburner.com/~r/The90thPercentile/~4/VvMFc2kqckk" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://icoloma.blogspot.com/feeds/4274458516168348019/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://icoloma.blogspot.com/2010/08/url-based-locale.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/10814242/posts/default/4274458516168348019?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/10814242/posts/default/4274458516168348019?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/The90thPercentile/~3/VvMFc2kqckk/url-based-locale.html" title="URL-based locale" /><author><name>Nacho Coloma</name><uri>https://plus.google.com/112843640399200525430</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh6.googleusercontent.com/-XL2pDJUO-0U/AAAAAAAAAAI/AAAAAAAAAxw/Wr1DmPB3TOA/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/_5fcKwF7jItc/TFrccQOF5HI/AAAAAAAAAak/4y0Vkuxsebk/s72-c/anybody-there.jpg" height="72" width="72" /><thr:total>0</thr:total><feedburner:origLink>http://icoloma.blogspot.com/2010/08/url-based-locale.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CEMMQXc7cSp7ImA9WxFVFk0.&quot;"><id>tag:blogger.com,1999:blog-10814242.post-498212495760715637</id><published>2010-06-15T13:00:00.002+02:00</published><updated>2010-06-15T13:48:00.909+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-06-15T13:48:00.909+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="html" /><title>Markdown and your website</title><content type="html">Some ideas take a while to be shared between communities. Markdown is a concept &lt;a href="http://daringfireball.net/projects/markdown/"&gt;originally developed in Perl&lt;/a&gt; that all GitHub developers are familiar with since it is the format behind &lt;a href="http://github.com/mojombo/jekyll"&gt;GitHub pages&lt;/a&gt;, and basically it shares the fresh, lightweight smell of wiki pages:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;This is a title&lt;br /&gt;===============&lt;br /&gt;&lt;br /&gt;You can use *italics* or **bold** or ***both***.&lt;br /&gt;&lt;br /&gt;  - Lists&lt;br /&gt;  - [Links][1] &lt;br /&gt;  - etc&lt;br /&gt;&lt;br /&gt;  [1]: http://foo.net/&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;img alt="I like it simple." title="I like it simple." border="0"  src="http://4.bp.blogspot.com/_5fcKwF7jItc/TBdn-PGOCYI/AAAAAAAAAac/PIDU9y52KTM/s640/simple-is-better.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5482965390249036162" style="float: left; margin-right: 30px;"/&gt;&lt;br /&gt;&lt;br /&gt;You can edit markdown with a real-time preview using &lt;a href="http://wmd-editor.com/demo"&gt;WMD&lt;/a&gt;, which has been &lt;a href="http://github.com/derobins/wmd"&gt;open sourced&lt;/a&gt; by stackoverflow.com. For the server-side, the best java implementation I could get my hands on is &lt;a href="http://github.com/sirthias/pegdown"&gt;pegdown&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;There are some reasons that make this solution appealing:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Simple&lt;/strong&gt;: Thousands of people are contributing to Wikipedia just because this format is much easier than HTML.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;WYSIWYG editor&lt;/strong&gt;, which is a strong selling point in our case.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Secure&lt;/strong&gt;: As long as you are storing the markup instead of the HTML output, you have removed most of the security concerns associated with HTML contents introduced by the user.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Solid, coherent UI design&lt;/strong&gt;: You cannot specify your own CSS classes and styles (nobody wants another myspace :)&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;In our case we replaced a bunch of fields in a domain object (three url attributes, two phone numbers, description, etc) with a single Markdown description. That's it.&lt;br /&gt;&lt;br /&gt;Markdown is well-known in the Rails community (mostly located on GitHub), but Java developers are disseminated between java.net, google code, sourceforge and whatnot. I suppose this is the reason why these technologies are taking some time to root, which is a pity because there are lots of things in the Rails world that could make a difference also for Java.&lt;img src="http://feeds.feedburner.com/~r/The90thPercentile/~4/caHE41HBjv8" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://icoloma.blogspot.com/feeds/498212495760715637/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://icoloma.blogspot.com/2010/06/markdown-and-your-website.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/10814242/posts/default/498212495760715637?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/10814242/posts/default/498212495760715637?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/The90thPercentile/~3/caHE41HBjv8/markdown-and-your-website.html" title="Markdown and your website" /><author><name>Nacho Coloma</name><uri>https://plus.google.com/112843640399200525430</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh6.googleusercontent.com/-XL2pDJUO-0U/AAAAAAAAAAI/AAAAAAAAAxw/Wr1DmPB3TOA/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://4.bp.blogspot.com/_5fcKwF7jItc/TBdn-PGOCYI/AAAAAAAAAac/PIDU9y52KTM/s72-c/simple-is-better.jpg" height="72" width="72" /><thr:total>0</thr:total><feedburner:origLink>http://icoloma.blogspot.com/2010/06/markdown-and-your-website.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DEQBRH0zfSp7ImA9Wx5SEE8.&quot;"><id>tag:blogger.com,1999:blog-10814242.post-194116294040439294</id><published>2010-05-10T18:00:00.008+02:00</published><updated>2010-08-05T18:39:15.385+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-08-05T18:39:15.385+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="loom" /><title>Loom 2.0 is out</title><content type="html">&lt;img alt="Running to get there" border="0" height="640" src="http://3.bp.blogspot.com/_5fcKwF7jItc/S-gvG02YRFI/AAAAAAAAAaQ/3WDRpxZiZnk/s1600/running-to-get-there.jpg" style="float: left; margin-right: 10px;" title="Running to get there" /&gt;&lt;br /&gt;A major upgrade of the Loom web framework has been released. We made it a major version change since it includes some breaking changes in places where backwards-compatible was just not worth it.&lt;br /&gt;&lt;br /&gt;Tag documentation has been improved &lt;a href="http://loom.sourceforge.net/docs/loom-core/tlddoc/"&gt;a lot&lt;/a&gt;, but there are other features also worth seeing:&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Works with AppEngine&lt;/h2&gt;&lt;br /&gt;We have been developing our own share of projects with Loom, and some are running in AppEngine. To make this possible we have forked a &lt;a href="http://loom.extrema-sistemas.org/doc/1.x/ref/converters"&gt;subproject&lt;/a&gt; with AppEngine image validation, datastore type converters (Key, GeoPt, etc), and transparent support for binding &lt;a href="http://code.google.com/p/simpleds/"&gt;SimpleDS&lt;/a&gt; paged queries into paged tags.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Cache page fragments&lt;/h2&gt;&lt;br /&gt;We have added session and application-level caching of page fragments, such as:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;&amp;lt;l:cache id="application-menu" scope="application"&amp;gt;  &lt;br /&gt;   &amp;lt;!-- application menu goes here --&amp;gt;&lt;br /&gt;&amp;lt;/cache&amp;gt;&lt;/pre&gt;&lt;br /&gt;Fragments can be cached separated by locale or not. This implementation is really simple, and things like cache timeout are not included yet.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Event parameters&lt;/h2&gt;&lt;br /&gt;Events now accept parameters:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;public class BlogEntriesAction extends AbstractAction {  &lt;br /&gt;&lt;br /&gt;  // parameter name is required&lt;br /&gt;  @Path("baz")&lt;br /&gt;  public Resolution baz(@QueryParam("id") int id) { ... }  &lt;br /&gt;&lt;br /&gt;  // parameter name is not required when embedded in the URL&lt;br /&gt;  @Path("bar/{foo}")&lt;br /&gt;  public Resolution bar(String foo) { ... }&lt;br /&gt;&lt;br /&gt;  // You can mix embedded and non-embedded parameters, but&lt;br /&gt;  // embedded are expected to be resolved left-to-right&lt;br /&gt;  @Path("bazbar/{foo}")&lt;br /&gt;  public Resolution bazbar(String foo, @QueryParam("bar") int bar) { ... }&lt;br /&gt;&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;&lt;h2&gt;New JSON support&lt;/h2&gt;&lt;br /&gt;We finally deprecated our quick-and-dirty JSON implementation to adopt &lt;a href="http://jackson.codehaus.org/"&gt;jackson&lt;/a&gt;. This makes some nice features possible:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;// return a JSON response&lt;br /&gt;public Resolution doFoo() {&lt;br /&gt;   return json(myObject);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;// receive JSON objects as parameters&lt;br /&gt;public Resolution bar(@JSON @QueryParam("baz") baz) {   /* ... */ }&lt;br /&gt;&lt;br /&gt;// render as JSON inside a page&lt;br /&gt;public Resolution doFoo() {&lt;br /&gt;   return forward("mypage.jsp").setAttribute("bar", bar);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&amp;lt;script&amp;gt;&lt;br /&gt;   alert(${l:json(bar)});&lt;br /&gt;&amp;lt;/script&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h2&gt;Uploaded files&lt;/h2&gt;&lt;br /&gt;A breaking change introduced in 2.0 is that file uploads are no longer being automatically bound to java attributes. From now on you must handle these parameters yourself, and this affects the way file validations are being performed:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;@FileValidation(&lt;br /&gt;  parameterName="uploadedDocument", &lt;br /&gt;  formats={ "txt", "rtf" },&lt;br /&gt;  maxFileSize=1024000)&lt;br /&gt;public Resolution saveDocument() { &lt;br /&gt;  fileManager.merge(getRequest().getFileParameter("uploadedDocument"));&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;@ImageValidation(&lt;br /&gt;  parameterName="uploadedImage", &lt;br /&gt;  formats={ "jpg", "gif" },&lt;br /&gt;  maxWidth=100, &lt;br /&gt;  maxHeight=100, &lt;br /&gt;  maxFileSize=1024000)&lt;br /&gt;public Resolution saveDocument() {    &lt;br /&gt;  fileManager.merge(getRequest().getFileParameter("uploadedDocument"));&lt;br /&gt;} &lt;/pre&gt;&lt;br /&gt;&lt;h2&gt;New paged features&lt;/h2&gt;&lt;br /&gt;Now you can use more than one paged container (table or list) in the same HTML page, or inject a plain Collection to be conveniently displayed as a single page.&lt;br /&gt;&lt;br /&gt;Also: JSON paged lists! PagedListData instances can now be serialized JSON and handled by javascript to render the same HTML contents generated by the server-side tag.&lt;br /&gt;&lt;br /&gt;More details about the current paged tags status can be found &lt;a href="http://loom.extrema-sistemas.org/doc/1.x/ref/pagedtable-and-pagedlist"&gt;here&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;New information methods&lt;/h2&gt;&lt;br /&gt;A new set of static methods MessageUtils.info(), warn() and error() have been added, which can be invoked anywhere in your code.&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;public class MyServiceImpl implements MyService {  &lt;br /&gt;&lt;br /&gt;   public void myService() {    &lt;br /&gt;      entityManager.put();&lt;br /&gt;      MessageUtils.info("save.success");  &lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;Messages are serialized in case of a redirect, and will be displayed on the next web request.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;SSLPolicy&lt;/h2&gt;&lt;br /&gt;A new annotation has been added to indicate the level of encryption on the annotated events:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;@SSLPolicy(REQUIRES_INSECURE)&lt;br /&gt;public class FooAction extends AbstractAction {&lt;br /&gt;&lt;br /&gt;        public Resolution event1 {&lt;br /&gt;                // this event requires http:&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        @SSLPolicy(REQUIRES_SECURE)&lt;br /&gt;        public Resolution event2 {&lt;br /&gt;                // this event requires https:&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;&lt;h2&gt;Recaptcha support&lt;/h2&gt;&lt;br /&gt;In this release we are migrating from JCaptcha into ReCaptcha, since it seems to be the only implementation designed to face &lt;a href="http://video.google.com/videoplay?docid=-8246463980976635143#"&gt;Human Computation&lt;/a&gt; attacks. It is also much easier than JCaptcha to configure and use, which is a plus.&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;@Recaptcha&lt;br /&gt;public Resolution save() {  ...}&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;&amp;lt;l:form action="Foo" event="save"&amp;gt;&lt;br /&gt; &amp;lt;a:recaptcha publicKey="[publicKey]"/&amp;gt;&lt;br /&gt;&amp;lt;/l:form&amp;gt;&lt;/pre&gt;&lt;br /&gt;The bad news is that the scaffolding tool is not yet up-to-date so we had to drop it from this release. It will be added again when time permits :)&lt;br /&gt;&lt;br /&gt;The full changelog is &lt;a href="http://jira.extrema-sistemas.com/jira/browse/LOOM?report=com.atlassian.jira.plugin.system.project:changelog-panel"&gt;here&lt;/a&gt;, the list of breaking changes is &lt;a href="http://loom.extrema-sistemas.org/doc/1.x/ref/appendix-d-breaking-changes-between-1.5-and-2.0"&gt;here&lt;/a&gt;, and comments, as usual, are welcome :)&lt;img src="http://feeds.feedburner.com/~r/The90thPercentile/~4/ticjaEpFMm0" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://icoloma.blogspot.com/feeds/194116294040439294/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://icoloma.blogspot.com/2010/05/loom-20-is-out.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/10814242/posts/default/194116294040439294?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/10814242/posts/default/194116294040439294?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/The90thPercentile/~3/ticjaEpFMm0/loom-20-is-out.html" title="Loom 2.0 is out" /><author><name>Nacho Coloma</name><uri>https://plus.google.com/112843640399200525430</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh6.googleusercontent.com/-XL2pDJUO-0U/AAAAAAAAAAI/AAAAAAAAAxw/Wr1DmPB3TOA/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://3.bp.blogspot.com/_5fcKwF7jItc/S-gvG02YRFI/AAAAAAAAAaQ/3WDRpxZiZnk/s72-c/running-to-get-there.jpg" height="72" width="72" /><thr:total>0</thr:total><feedburner:origLink>http://icoloma.blogspot.com/2010/05/loom-20-is-out.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A0MHQHc9eCp7ImA9WxFRGUs.&quot;"><id>tag:blogger.com,1999:blog-10814242.post-6724592831673633507</id><published>2010-05-04T12:32:00.001+02:00</published><updated>2010-05-04T12:37:11.960+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-05-04T12:37:11.960+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="simpleds" /><category scheme="http://www.blogger.com/atom/ns#" term="appengine" /><title>SimpleDS 1.0_RC1 for AppEngine has been released</title><content type="html">For those that know me from somewhere else, SimpleDS is our open source framework for persistence in AppEngine. It has been compared with &lt;a href="http://code.google.com/p/objectify-appengine/"&gt;Objectify&lt;/a&gt; and &lt;a href="http://code.google.com/p/twig-persist/"&gt;Twig&lt;/a&gt;, and has been mentioned in the &lt;a href="http://googleappengine.blogspot.com/2010/03/app-engine-community-update.html"&gt;Google App Engine Blog&lt;/a&gt; a couple of months ago.&lt;br /&gt;
&lt;br /&gt;
This is our first feature-complete release of SimpleDS. Lots of things have been included in so little time.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;Getting up-to-date with AppEngine&lt;/h2&gt;&lt;br /&gt;
It's hard to keep up the pace with these guys. This release includes:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;Unindexed attributes.&lt;/li&gt;
&lt;li&gt;Cursors support.&lt;/li&gt;
&lt;li&gt;IN and != clauses.&lt;/li&gt;
&lt;/ul&gt;&lt;br /&gt;
&lt;h2&gt;Cache&lt;/h2&gt;&lt;br /&gt;
We have included a great &lt;a href="http://code.google.com/p/simpleds/wiki/Cache"&gt;Level 1 and Level 2 cache&lt;/a&gt;. If you come from JDO/JPA, you may already know what this means:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;Level 1 cache: This is basically a Map bound to the current thread. Until the end of the current request, any get() invocation will check this cache first. If a match is found, no invocation will be propagated to GAE.&lt;/li&gt;
&lt;li&gt;Level 2 cache: Datastore entities are also stored in memcache, which is a second chance to get a positive match. &lt;/li&gt;
&lt;/ul&gt;&lt;br /&gt;
Cacheable entities must be marked with @Cacheable, with an optional expiration time. This feature will work with single and batch get(), and the cache entries are updated with put() and delete() invocations. &lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;// Invoke memcache or the datastore
List&lt;mydata&gt; data = entityManager.get(key1, key2, key3);

// this does not invoke anything (resolved by the Level 1 cache)
MyData d2 = entityManager.get(key1);
&lt;/pre&gt;&lt;br /&gt;
&lt;h2&gt;Functions&lt;/h2&gt;&lt;br /&gt;
We are going &lt;a href="http://code.google.com/p/simpleds/wiki/Functions"&gt;extremely functional&lt;/a&gt; these days. This release includes a package with functions to transform collections and PagedList instances, which can be combined with batch get() to get even better performance results. This is a simple example, equivalent to a situation quite common when using relations:&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;// n + 1 requests to the datastore
List&lt;mydata&gt; data = entityManager.find(query);
Collection&lt;parent&gt; parents = Lists.newArrayListwithCapacity(data.size());
for (MyData d : data) {
  parents.add(entityManager.get(d.getKey().getParent());
}

// Transformations: 2 requests 
List&lt;mydata&gt; data = entityManager.find(query);
Collection&lt;key&gt; parentKeys = Collections2.transform(data, new EntityToParentKeyFunction(MyData.class));
Collection&lt;parent&gt; parents = entityManager.get(parentKeys);
&lt;/pre&gt;&lt;br /&gt;
That's all it takes to get all entities and their parents, and store them in the Level 1 cache so any request by PK will not hit memcache or the datastore. This release includes functions to retrieve parent and foreign keys, and also works with PagedList. We have taken two real-world snapshots with just this optimization (transformation + cacheable), applied to a single loop:&lt;br /&gt;
&lt;br /&gt;
Before: &lt;a href="http://www.flickr.com/photos/koliseocom/4575062969/in/set-72157623904289518/"&gt;http://www.flickr.com/photos/koliseocom/4575062969/in/set-72157623904289518/&lt;/a&gt;&lt;br /&gt;
After: &lt;a href="http://www.flickr.com/photos/koliseocom/4575696456/in/set-72157623904289518/"&gt;http://www.flickr.com/photos/koliseocom/4575696456/in/set-72157623904289518/&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
Notice that "before" are also requests to the datastore, while most of the "after" are requests to memcache.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;Background tasks&lt;/h2&gt;&lt;br /&gt;
We are aware that &lt;a href="http://code.google.com/p/simpleds/wiki/BackgroundTasks"&gt;background tasks&lt;/a&gt; are in the roadmap for AppEngine, but we needed these today. This started as an exercise to upgrade the datastore schema (add properties, delete entities etc) and ended up as a full reusable implementation of tasks that I expect to deprecate once that AppEngine includes its own, probably better, implementation.&lt;br /&gt;
&lt;br /&gt;
Background tasks require adding a servlet to web.xml (and optionally appengine-web.xml as an admin-console entry) and configuring the tasks on application start using plain Java. Tasks can be invoked directly by cron triggers, queues or by POST requests. &lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;public class WriteBehindCacheTask extends IterableTask&lt;myclass&gt; {

 protected WriteBehindCacheTask() {
  super("my-task-id");
 }

 @Override
 protected SimpleQuery createQuery(TaskRequest request) {
  return entityManager.createQuery(MyClass.class);
 }

 @Override
 protected void process(MyClass entity, TaskRequest request) {
  // ...modify entity...
  entityManager.put(entity);
 }

}
&lt;/pre&gt;&lt;br /&gt;
Query cursors and execution deferral will be done transparently (no need to limit or handle cursors). There are some implementation superclasses depending on what you need to do, and some of them just use the raw AppEngine datastore and don't even require SimpleDS to work.&lt;br /&gt;
&lt;br /&gt;
Other features can be checked out at the &lt;a href="http://code.google.com/p/simpleds/"&gt;SimpleDS home page&lt;/a&gt; and the &lt;a href="http://code.google.com/p/simpleds/#Changelog"&gt;changelog&lt;/a&gt;. Any feedback is welcome :)&lt;img src="http://feeds.feedburner.com/~r/The90thPercentile/~4/l_Q-mg5ijs4" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://icoloma.blogspot.com/feeds/6724592831673633507/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://icoloma.blogspot.com/2010/05/simpleds-10rc1-for-appengine-has-been.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/10814242/posts/default/6724592831673633507?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/10814242/posts/default/6724592831673633507?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/The90thPercentile/~3/l_Q-mg5ijs4/simpleds-10rc1-for-appengine-has-been.html" title="SimpleDS 1.0_RC1 for AppEngine has been released" /><author><name>Nacho Coloma</name><uri>https://plus.google.com/112843640399200525430</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh6.googleusercontent.com/-XL2pDJUO-0U/AAAAAAAAAAI/AAAAAAAAAxw/Wr1DmPB3TOA/s512-c/photo.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://icoloma.blogspot.com/2010/05/simpleds-10rc1-for-appengine-has-been.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DUMDRHg6eyp7ImA9WxFSGUk.&quot;"><id>tag:blogger.com,1999:blog-10814242.post-5340460011497059031</id><published>2010-04-22T16:44:00.000+02:00</published><updated>2010-04-22T16:44:35.613+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-04-22T16:44:35.613+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="talks" /><category scheme="http://www.blogger.com/atom/ns#" term="web" /><title>Our "Web Development in 2010" slides are out</title><content type="html">We just got back from Universidad Europea de Madrid after delivering a conference about current trends in web development.&lt;br /&gt;
&lt;br /&gt;
This is an introductory talk of one hour, which implies that we had to leave a lot unsaid. Even so, the number of links to promote further reading this time is much higher than what we are used to.&lt;br /&gt;
&lt;br /&gt;
Enjoy!&lt;br /&gt;
&lt;br /&gt;
&lt;div style="width:425px" id="__ss_3808056"&gt;&lt;strong style="display:block;margin:12px 0 4px"&gt;&lt;a href="http://www.slideshare.net/icoloma/developing-web-applications-in-2010" title="Developing web applications in 2010"&gt;Developing web applications in 2010&lt;/a&gt;&lt;/strong&gt;&lt;object width="425" height="355"&gt;&lt;param name="movie" value="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=ueminternational-100421162735-phpapp02&amp;stripped_title=developing-web-applications-in-2010" /&gt;&lt;param name="allowFullScreen" value="true"/&gt;&lt;param name="allowScriptAccess" value="always"/&gt;&lt;embed src="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=ueminternational-100421162735-phpapp02&amp;stripped_title=developing-web-applications-in-2010" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="355"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;div style="padding:5px 0 12px"&gt;View more &lt;a href="http://www.slideshare.net/"&gt;presentations&lt;/a&gt; from &lt;a href="http://www.slideshare.net/icoloma"&gt;Ignacio Coloma&lt;/a&gt;.&lt;/div&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/The90thPercentile/~4/x3oVpxDHkDs" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://icoloma.blogspot.com/feeds/5340460011497059031/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://icoloma.blogspot.com/2010/04/our-web-development-in-2010-slides-are.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/10814242/posts/default/5340460011497059031?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/10814242/posts/default/5340460011497059031?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/The90thPercentile/~3/x3oVpxDHkDs/our-web-development-in-2010-slides-are.html" title="Our &quot;Web Development in 2010&quot; slides are out" /><author><name>Nacho Coloma</name><uri>https://plus.google.com/112843640399200525430</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh6.googleusercontent.com/-XL2pDJUO-0U/AAAAAAAAAAI/AAAAAAAAAxw/Wr1DmPB3TOA/s512-c/photo.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://icoloma.blogspot.com/2010/04/our-web-development-in-2010-slides-are.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D04NRXc7fCp7ImA9WxBbGEk.&quot;"><id>tag:blogger.com,1999:blog-10814242.post-6988062851981599664</id><published>2010-03-17T18:00:00.007+01:00</published><updated>2010-03-17T18:13:14.904+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-03-17T18:13:14.904+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="training" /><category scheme="http://www.blogger.com/atom/ns#" term="web" /><title>Create your own bit.ly using Base58</title><content type="html">&lt;img border="0" height="320" src="http://4.bp.blogspot.com/_5fcKwF7jItc/S6EGHzmdGBI/AAAAAAAAAZM/dmvKed1L79U/s320/click_to_zoom.jpg" width="257" style="float:right" alt="Zoomin'" title="Zoomin'"/&gt;&lt;br /&gt;Following the example of bit.ly, tinyurl, owl.ly, even &lt;a href="http://cokeurl.com/"&gt;coca-cola&lt;/a&gt; has joined the short URL bandwagon. Maybe it's because of Twitter, maybe the world is focusing on mobile devices or just saving bandwidth. In any case, it's not that hard to develop your own.&lt;br /&gt;&lt;br /&gt;The implementation is quite straightforward: store the underlying URL in a database and return your primary key. This primary key when displayed as a number (Base10) is longer than strictly necessary; since each character can hold more than numbers, you are wasting space by constraining yourself to a decimal [0-9] range.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Enter Base58&lt;/h2&gt;&lt;br /&gt;Base58 is what you get after taking Base62 [a-zA-Z0-9] and removing any character that may induce to error when introduced by hand: 0 (zero), O (uppercase 'o'), I (uppercase 'i'), and l (lowercase 'L'). This concept was introduced to the general public by Flickr, which uses the following String:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ&lt;/pre&gt;&lt;br /&gt;To me, it makes more sense to use the natural order in the ASCII chart:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz&lt;/pre&gt;&lt;br /&gt;I will post here an example of the latest. For a Flickr implementation you can go &lt;a href="http://dl.dropbox.com/u/1844215/FlickrBaseEncoder.java"&gt;here&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;public class StringUtils {&lt;br /&gt;&lt;br /&gt; private static final char[] BASE58_CHARS = &lt;br /&gt;     "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray();&lt;br /&gt;&lt;br /&gt; public static String numberToAlpha(long number) {&lt;br /&gt;  char[] buffer = new char[20];&lt;br /&gt;  int index = 0;&lt;br /&gt;  do {&lt;br /&gt;   buffer[index++] = BASE58_CHARS[(int) (number % BASE58_CHARS.length)];&lt;br /&gt;   number = number / BASE58_CHARS.length;&lt;br /&gt;  } while (number &amp;gt; 0);&lt;br /&gt;  return new String(buffer, 0, index);&lt;br /&gt; }&lt;br /&gt; &lt;br /&gt; public static long alphaToNumber(String text) {&lt;br /&gt;  char[] chars = text.toCharArray();&lt;br /&gt;  long result = 0;&lt;br /&gt;  long multiplier = 1;&lt;br /&gt;  for (int index = 0; index &amp;lt; chars.length; index++) {&lt;br /&gt;   char c = chars[index];&lt;br /&gt;   int digit;&lt;br /&gt;   if (c &amp;gt;= '1' &amp;amp;&amp;amp; c &amp;lt;= '9') {&lt;br /&gt;    digit = c - '1'; &lt;br /&gt;   } else if (c &amp;gt;= 'A' &amp;amp;&amp;amp; c &amp;lt; 'I') {&lt;br /&gt;    digit = (c - 'A') + 9;&lt;br /&gt;   } else if (c &amp;gt; 'I' &amp;amp;&amp;amp; c &amp;lt; 'O') {&lt;br /&gt;    digit = (c - 'J') + 17;&lt;br /&gt;   } else if (c &amp;gt; 'O' &amp;amp;&amp;amp; c &amp;lt;= 'Z') {&lt;br /&gt;    digit = (c - 'P') + 22;&lt;br /&gt;   } else if (c &amp;gt;= 'a' &amp;amp;&amp;amp; c &amp;lt; 'l') {&lt;br /&gt;    digit = (c - 'a') + 33;&lt;br /&gt;   } else if (c &amp;gt; 'l' &amp;amp;&amp;amp; c &amp;lt;= 'z') {&lt;br /&gt;    digit = (c - 'l') + 43;&lt;br /&gt;   } else {&lt;br /&gt;    throw new IllegalArgumentException("Illegal character found: '" + c + "'");&lt;br /&gt;   }&lt;br /&gt;    &lt;br /&gt;   result += digit * multiplier;&lt;br /&gt;   multiplier = multiplier * BASE58_CHARS.length;&lt;br /&gt;  }&lt;br /&gt;  return result;&lt;br /&gt; }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;An example of the expected output size:  &lt;br /&gt;&lt;br /&gt;&lt;pre&gt;44 = m&lt;br /&gt;1431117682956369 = abc123ABC&lt;br /&gt;// Long.MAX_VALUE&lt;br /&gt;9223372036854775807 = CFq8pKn6mQN&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;This is hardly rocket science. You can find libraries to do this for &lt;a href="http://github.com/dougal/base58"&gt;ruby&lt;/a&gt;, &lt;a href="http://snipplr.com/view/23226/flickr--generate-flickr-shortened-url/"&gt;javascript&lt;/a&gt;, &lt;a href="http://darklaunch.com/2009/08/07/base58-encode-and-decode-using-php-with-example-base58-encode-base58-decode"&gt;PHP&lt;/a&gt; or even &lt;a href="http://search.cpan.org/%7Emiyagawa/Encode-Base58-0.01/lib/Encode/Base58.pm"&gt;perl&lt;/a&gt;.  &lt;br /&gt;&lt;br /&gt;&lt;h2&gt;It's Base58 everywhere&lt;/h2&gt;&lt;br /&gt;Once you start doing this, it's nothing short of addictive. You can configure your web framework to use a Base58 converter for the key attributes in your URLs and start using '/shows/cr5W' instead of '/shows/234324323423'.  &lt;br /&gt;&lt;br /&gt;Everybody is doing this right now: &lt;a href="http://www.tmdvs.me/flic.kr"&gt;Flickr&lt;/a&gt;, &lt;a href="http://www.insidefacebook.com/2009/12/14/facebook-testing-new-url-shortener-fb-me/"&gt;Facebook&lt;/a&gt; or YouTube (which seems to be using [0-9a-zA-Z_-]).&lt;br /&gt;  &lt;br /&gt;&lt;h2&gt;News - News - News&lt;/h2&gt;&lt;br /&gt;These days life is getting interesting by the minute. Last week our &lt;a href="http://code.google.com/p/simpleds/"&gt;SimpleDS&lt;/a&gt; pet project got referenced by &lt;a href="http://googleappengine.blogspot.com/2010/03/app-engine-community-update.html"&gt;Google AppEngine Blog&lt;/a&gt;, and next April we are bringing the &lt;a href="http://www.javaspecialists.eu/courses/master.jsp"&gt;Javaspecialist Master Course&lt;/a&gt; to Madrid!  &lt;br /&gt;&lt;br /&gt;It's going to be awesome. This is the most advanced Java course I can refer to, and after some conversations with Dr. Kabutz he agreed to bring it to Madrid. It consists of four days of intense performance tuning, concurrency debugging, introspection, memory profiling and some neat, challenging Java puzzles. If you feel interested, you should consider to &lt;a href="http://extrema-sistemas.com/en/training/javaspecialists_master_course"&gt;join us in April&lt;/a&gt;!&lt;img src="http://feeds.feedburner.com/~r/The90thPercentile/~4/pfYeYs_If14" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://icoloma.blogspot.com/feeds/6988062851981599664/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://icoloma.blogspot.com/2010/03/create-your-own-bitly-using-base58.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/10814242/posts/default/6988062851981599664?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/10814242/posts/default/6988062851981599664?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/The90thPercentile/~3/pfYeYs_If14/create-your-own-bitly-using-base58.html" title="Create your own bit.ly using Base58" /><author><name>Nacho Coloma</name><uri>https://plus.google.com/112843640399200525430</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh6.googleusercontent.com/-XL2pDJUO-0U/AAAAAAAAAAI/AAAAAAAAAxw/Wr1DmPB3TOA/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://4.bp.blogspot.com/_5fcKwF7jItc/S6EGHzmdGBI/AAAAAAAAAZM/dmvKed1L79U/s72-c/click_to_zoom.jpg" height="72" width="72" /><thr:total>0</thr:total><feedburner:origLink>http://icoloma.blogspot.com/2010/03/create-your-own-bitly-using-base58.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DUcMQHs4cCp7ImA9WxFVGUo.&quot;"><id>tag:blogger.com,1999:blog-10814242.post-626963547200441496</id><published>2010-02-23T09:00:00.005+01:00</published><updated>2010-06-19T21:51:21.538+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-06-19T21:51:21.538+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="loom" /><category scheme="http://www.blogger.com/atom/ns#" term="jsp" /><category scheme="http://www.blogger.com/atom/ns#" term="web" /><title>Generate your TLD using java annotations</title><content type="html">If you are stuck with JSP and keeping your TLD files by hand (like me), you are probably ignoring the elephant in the room:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;b&gt;Tag classes must be kept up-to-date&lt;/b&gt; with the TLD by hand. This means duplicate effort, a.k.a. "XML sucks".&lt;/li&gt;&lt;li&gt;For any non-trivial tag library, &lt;b&gt;inheritance is a fact&lt;/b&gt;. In order to avoid replication in the TLD, your libraries are bound to use some dark XML magic.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;IDEs are extracting JSP coding hints from the TLD file&lt;/b&gt;, so you should be introducing your documentation in the TLD by hand.&lt;/li&gt;&lt;li&gt;You are probably not sacrificing Javadoc in the process, which implies maintaining &lt;b&gt;duplicate documentation&lt;/b&gt;: java(doc) and TLD.&lt;/li&gt;&lt;li&gt;Make it triple, if you want &lt;b&gt;online HTML documentation&lt;/b&gt;.&lt;/li&gt;&lt;li&gt;There is no way to &lt;b&gt;document deprecated behavior&lt;/b&gt;.&lt;/li&gt;&lt;/ul&gt;&lt;img alt="I love 1.0 releases" src="http://2.bp.blogspot.com/_5fcKwF7jItc/S4K6r4n49XI/AAAAAAAAAZE/8IsEIeApdqY/s320/i-love-1.0-releases.jpg" style="float: right;" title="I love 1.0 releases" height="320" width="291" border="0" /&gt;&lt;br /&gt;This March I will be 35, which sucks. Last year my brother Carlos Coloma celebrated his 25-year-old anniversary reading his MSc project with his colleague Javier Reyes Alonso: &lt;i&gt;Generating TLD and HTML documentation from java annotations&lt;/i&gt;. Today we are releasing it as an open source project, &lt;a href="http://code.google.com/p/tldgen/"&gt;TLDGen&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Instant gratification: &lt;a href="http://loom.sourceforge.net/docs/loom-core/tlddoc/form.html"&gt;click here to check out the generated HTML documentation&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;TLDGen is a standalone library that does not depend on Loom. This is an important requirement to be able to generate our own TLD files.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Download tldgen.jar and add it to your classpath&lt;/h2&gt;&lt;br /&gt;First you should extract tldgen.jar from the tldgen zip file or from maven (group org.extrema-sistemas, artifact maven). This jar file contains just the annotations to get you running:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;package com.acme.tags;&lt;br /&gt;&lt;br /&gt;import org.loom.tldgen.annotations.Attribute;&lt;br /&gt;import org.loom.tldgen.annotations.Tag;&lt;br /&gt;import org.loom.tldgen.annotations.BodyContent;&lt;br /&gt;&lt;br /&gt;/** &lt;br /&gt; * This comment will be included in the generated TLD and HTML &lt;br /&gt; */&lt;br /&gt;@Tag(  &lt;br /&gt;  dynamicAttributes=true,&lt;br /&gt;  bodyContent=BodyContent.SCRIPTLESS,&lt;br /&gt;  example="&amp;lt;l:sample name=\"foo\"/&amp;gt;"&lt;br /&gt;)&lt;br /&gt;public class SampleTag extends SimpleTagSupport {&lt;br /&gt;&lt;br /&gt;  @Attribute(required=true)&lt;br /&gt;  private String name;&lt;br /&gt;&lt;br /&gt;  /** @deprecated use name instead */&lt;br /&gt;  @Attribute  public void setFoo(String value) {&lt;br /&gt;     name = value;  &lt;br /&gt;  } &lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;package com.acme.tags;&lt;br /&gt;import org.loom.tldgen.annotations.Function;&lt;br /&gt;public class Functions { &lt;br /&gt;&lt;br /&gt;  /**&lt;br /&gt;   * bar javadoc &amp;lt;p&amp;gt;this is a paragraph&amp;lt;/p&amp;gt;&lt;br /&gt;   */&lt;br /&gt;  @Function(&lt;br /&gt;     displayName="bar display name",&lt;br /&gt;     example="Function example"&lt;br /&gt;  )&lt;br /&gt;  public static void bar() {    }&lt;br /&gt;&lt;br /&gt;  @Function&lt;br /&gt;  public static void baz() {    }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Execute tldgen&lt;/h2&gt;&lt;br /&gt;You should &lt;a href="http://code.google.com/p/tldgen/"&gt;download tldgen.zip&lt;/a&gt; from google-code, uncompress it somewhere and execute all in the same line (we are assuming a standard gradle project structure):&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;tldgen&lt;br /&gt;   -sourcepath src/main/java&lt;br /&gt;   -subpackages com.acme.tags&lt;br /&gt;   -tldFile src/main/resources/META-INF/acme-tags.tld&lt;br /&gt;   -displayName "Acme Tag Library"&lt;br /&gt;   -name acme&lt;br /&gt;   -uri "http://acme.com/acme-tags.tld"&lt;br /&gt;   -htmlFolder build/docs/tlddoc&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;This should generate the TLD docs in build/docs/tlddoc and one TLD file in src/main/resources/META-INF/acme-tags.tld. As an example, you can check out &lt;a href="http://loom.sourceforge.net/docs/loom-core/tlddoc/form.html"&gt;the Loom taglib documentation&lt;/a&gt;.&lt;img src="http://feeds.feedburner.com/~r/The90thPercentile/~4/cBfk-aS1j_c" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://icoloma.blogspot.com/feeds/626963547200441496/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://icoloma.blogspot.com/2010/02/generate-your-tld-using-java.html#comment-form" title="10 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/10814242/posts/default/626963547200441496?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/10814242/posts/default/626963547200441496?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/The90thPercentile/~3/cBfk-aS1j_c/generate-your-tld-using-java.html" title="Generate your TLD using java annotations" /><author><name>Nacho Coloma</name><uri>https://plus.google.com/112843640399200525430</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh6.googleusercontent.com/-XL2pDJUO-0U/AAAAAAAAAAI/AAAAAAAAAxw/Wr1DmPB3TOA/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/_5fcKwF7jItc/S4K6r4n49XI/AAAAAAAAAZE/8IsEIeApdqY/s72-c/i-love-1.0-releases.jpg" height="72" width="72" /><thr:total>10</thr:total><feedburner:origLink>http://icoloma.blogspot.com/2010/02/generate-your-tld-using-java.html</feedburner:origLink></entry><entry gd:etag="W/&quot;Dk8ARXg5fCp7ImA9WxNQEEQ.&quot;"><id>tag:blogger.com,1999:blog-10814242.post-4311599076489044218</id><published>2009-09-16T11:55:00.002+02:00</published><updated>2009-09-16T11:54:04.624+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-09-16T11:54:04.624+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="loom" /><category scheme="http://www.blogger.com/atom/ns#" term="cache" /><category scheme="http://www.blogger.com/atom/ns#" term="web" /><title>Caching user-supplied images in the browser</title><content type="html">We have been engaged in a big project for the last months. Despite of the nice performance marks, there was a warning in Google PageSpeed that was itching us badly: "The following resources are missing a cache expiration".&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;img alt="It's all about speed" src="http://2.bp.blogspot.com/_5fcKwF7jItc/SrCvM9DoXeI/AAAAAAAAAYI/dB92IhmgAhQ/s800/its-all-about-speed.jpg" title="It's all about speed" border="0" /&gt;&lt;/div&gt;&lt;br /&gt;PageSpeed was complaining about images uploaded by the user. Resources referenced from CSS are conveniently handled by Loom, but user-provided files were not setting their cache headers appropriately.&lt;br /&gt;&lt;br /&gt;The following is, to the best of my knowledge, the same algorithm implemented by blogger and wordpress to avoid delivering the same image file over and over again. The theory has been &lt;a href="http://www.slideshare.net/icoloma/caching-web-contents-in-the-browser-999134"&gt;already covered here&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;I will be using Loom in this example, but you can reproduce this using any web framework.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Choosing your Strong Cache Validator&lt;/h2&gt;&lt;br /&gt;Cache Validators are introduced in the &lt;a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.3.3"&gt;http 1.1 spec&lt;/a&gt;. Here we will use a Strong Cache Validator to indicate that a resource should never expire. You should include both validator and resource ID in the URL, like "/resources/{id}/{cacheValue}".&lt;br /&gt;&lt;br /&gt;The most well-known candidates are:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;b&gt;MD5&lt;/b&gt;: The file checksum can be calculated when the file is saved or when the application starts up. This is how Loom (and others) set the cache headers for CSS and javascript files.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Version numbers&lt;/b&gt;: any @Version field can be used to identify persistent JPA entities.&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;In this case we will be using &lt;a href="http://loom.svn.sourceforge.net/viewvc/loom/loom/trunk/src/java/org/loom/persistence/file/PersistentFile.java?view=markup"&gt;PersistentFile&lt;/a&gt;, which includes both fields. After trying both, we found that using @Version yields much shorter URLs ("/resources/151/2" instead of "/resources/151/3b4efe6405d4fb1ada4a081f4dbef6a9").&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;@Entity&lt;br /&gt;public class PersistentFile { &lt;br /&gt;&lt;br /&gt; /** the primary key value */ &lt;br /&gt; @Id @GeneratedValue private Integer id; &lt;br /&gt;&lt;br /&gt; /** the MD5 hash of this file */ &lt;br /&gt; @Column(length=32) private String MD5;  &lt;br /&gt;&lt;br /&gt; /** the revision number */ &lt;br /&gt; @Version private Integer version;  &lt;br /&gt;&lt;br /&gt; /** the last update timestamp */ &lt;br /&gt; private Date lastModified; &lt;br /&gt; &lt;br /&gt; /** ... etc ... */&lt;br /&gt;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This field will be used to set "Expires" and "Cache-Control" headers to expire ten years in the future. As long as the user is clicking links to browse normally these resources should never be fetched again, but it will be ignored if the user clicks the "Refresh" button, which is where Weak Cache Validation comes into place.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Weak Cache Validator&lt;/h2&gt;&lt;br /&gt;With Weak Cache Validation the browser must ask for the resource, giving the server a chance to answer with a 304 (not modified) or 200 (OK) response.&lt;br /&gt;&lt;br /&gt;For this purpose we will use the "Last-Modified" header. The first request by the browser (the empty cache experience or any "Shift + Refresh" click) will be answered by:&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;img src="http://2.bp.blogspot.com/_5fcKwF7jItc/Sq_NnF1sgaI/AAAAAAAAAX4/GP4hfrv5ROM/s800/200.gif" border="0" /&gt;&lt;/div&gt;&lt;br /&gt;On a browser refresh the server will return a 304 response based on the value of the If-Modified-Since header:&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;img src="http://1.bp.blogspot.com/_5fcKwF7jItc/Sq_NoUl6SwI/AAAAAAAAAYA/8idxuzB640s/s800/304.gif" border="0" /&gt;&lt;/div&gt;&lt;br /&gt;&lt;h2&gt;The big picture&lt;br /&gt;&lt;/h2&gt;&lt;br /&gt;During the sample navigation to the homepage, images have run down from this:&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;img alt="59 image requests = 544 KB (492 KB from cache)" src="http://2.bp.blogspot.com/_5fcKwF7jItc/SolbT5bjbRI/AAAAAAAAAXQ/217dCUL1I84/s800/images-nocache.gif" title="59 image requests = 544 KB (492 KB from cache)" border="0" /&gt;&lt;/div&gt;&lt;br /&gt;To this:&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;img alt="0 image requests = 0 KB" src="http://3.bp.blogspot.com/_5fcKwF7jItc/SolbSXFGhfI/AAAAAAAAAXI/g1LRHLHv0C0/s800/images-cache.gif" title="0 image requests = 0 KB" border="0" /&gt;&lt;/div&gt;&lt;br /&gt;Saving bandwidth is great, but keep in mind that we are doing it to reduce response time. In this case, 2-3 seconds have been reduced to 0.3 just by introducing this change.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Code&lt;/h2&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;public class FileAction extends AbstractAction {  &lt;br /&gt;&lt;br /&gt; @Autowired private FileManager fileManager;  &lt;br /&gt;&lt;br /&gt; /** the id of the file */ &lt;br /&gt; private Integer id; &lt;br /&gt;&lt;br /&gt; /** the version number of the resource */ &lt;br /&gt; private Integer version;  &lt;br /&gt;&lt;br /&gt; /** the retrieved file */ &lt;br /&gt; private PersistentFile pfile;  &lt;br /&gt;&lt;br /&gt; /**  &lt;br /&gt;  * This method will be invoked before getFile(). If the current request includes a If-Modified-Since  &lt;br /&gt;  * header that matches the lastModified attribute of the file, a 304 (NOT MODIFIED) response will be   &lt;br /&gt;  * automatically sent, and getFile() will not be invoked.  &lt;br /&gt;  */ &lt;br /&gt; @Cache(on="getFile") &lt;br /&gt; public CacheControl getCacheControl() {  &lt;br /&gt;  pfile = fileManager.find(id);  &lt;br /&gt;  CacheControl c = new CacheControl();  &lt;br /&gt;  if (pfile.getVersion().equals(version)) {   &lt;br /&gt;   c.setLastModified(pfile.getLastModified());   &lt;br /&gt;   c.setCacheForever();  &lt;br /&gt;  }  &lt;br /&gt;  return c; &lt;br /&gt; } &lt;br /&gt;&lt;br /&gt; @GET @Path("/{id}/{version}") &lt;br /&gt; public Resolution getFile() {  &lt;br /&gt;  if (pfile.getVersion().equals(version)) {   &lt;br /&gt;   return send(pfile);  &lt;br /&gt;  } else {   &lt;br /&gt;   // if the version number differs, send a 301 redirect   &lt;br /&gt;   return redirect(FileAction.class, "getFile")    &lt;br /&gt;     .addParameter("id", pfile.getId())    &lt;br /&gt;     .addParameter("version", pfile.getVersion())    &lt;br /&gt;     .permanent();  &lt;br /&gt;  } &lt;br /&gt; }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;a href="http://loom.svn.sourceforge.net/viewvc/loom/loom/trunk/src/java/org/loom/servlet/CacheControl.java?view=markup"&gt;CacheControl&lt;/a&gt; is the class that does all the thinking. It sets the http headers and decides whether to return a 304 or 200 response.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;We will be in London on Sept 23rd&lt;/h2&gt;&lt;br /&gt;We are quite excited that Loom has been selected finalist of the &lt;a href="http://www.access-it-events.org/2009_access_it_awards.php"&gt;ACCESS-IT awards&lt;/a&gt; in the web 2.0 category. We will be in London on September 22 and 23, which is my first trip there. Should be fun!&lt;img src="http://feeds.feedburner.com/~r/The90thPercentile/~4/O5bF_MThwFQ" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://icoloma.blogspot.com/feeds/4311599076489044218/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://icoloma.blogspot.com/2009/09/caching-user-supplied-images-in-browser.html#comment-form" title="3 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/10814242/posts/default/4311599076489044218?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/10814242/posts/default/4311599076489044218?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/The90thPercentile/~3/O5bF_MThwFQ/caching-user-supplied-images-in-browser.html" title="Caching user-supplied images in the browser" /><author><name>Nacho Coloma</name><uri>https://plus.google.com/112843640399200525430</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh6.googleusercontent.com/-XL2pDJUO-0U/AAAAAAAAAAI/AAAAAAAAAxw/Wr1DmPB3TOA/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/_5fcKwF7jItc/SrCvM9DoXeI/AAAAAAAAAYI/dB92IhmgAhQ/s72-c/its-all-about-speed.jpg" height="72" width="72" /><thr:total>3</thr:total><feedburner:origLink>http://icoloma.blogspot.com/2009/09/caching-user-supplied-images-in-browser.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D08ARXw6fCp7ImA9WxJWGUk.&quot;"><id>tag:blogger.com,1999:blog-10814242.post-935585089578156295</id><published>2009-06-25T18:02:00.002+02:00</published><updated>2009-06-25T18:04:04.214+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-06-25T18:04:04.214+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="javascript" /><category scheme="http://www.blogger.com/atom/ns#" term="talks" /><category scheme="http://www.blogger.com/atom/ns#" term="tests" /><title>30 slides about testing ajax components</title><content type="html">Last &lt;a href="http://sunopencommunitiesforum.es/"&gt;Sun Open Community Forum&lt;/a&gt; is over, and like past events (the name changes, but people remains the same) it was the perfect opportunity to get the spanish java community together to enjoy some beer and - incidentally - to talk about software too :)&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_5fcKwF7jItc/SkNSeuQWipI/AAAAAAAAAXA/-LksfAYVguY/s1600-h/apples-in-line.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img src="http://2.bp.blogspot.com/_5fcKwF7jItc/SkNSeuQWipI/AAAAAAAAAXA/-LksfAYVguY/s400/apples-in-line.jpg" border="0" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;div style="clear: right;"&gt;This year has been great. Due to my nemesis-agenda I could not be there for the second day, which means that I missed the groovy lab by Nacho Brito (if you are talking about groovy in Spain, &lt;a href="http://groovy.org.es/"&gt;that's Nacho&lt;/a&gt;) and Daniel "greeneyed" Lopez - seriously, I wanted to see him running &lt;a href="https://swww.uib.es/webleaf/confluence/pages/viewpage.action;jsessionid=_7qNPLNUKRdAVoAwis?pageId=23691266"&gt;five languages&lt;/a&gt; in the JVM during a 2-hour lab.&lt;/div&gt;&lt;br /&gt;The ones that I managed to attend were great: &lt;a href="http://blogs.sun.com/jorgeSanchez/"&gt;Jorge Sanchez&lt;/a&gt; introduced some alternatives for large-scale integration and presented the new spanish MySQL community, and &lt;a href="http://weblogs.javahispano.org/artesanodeprimera/"&gt;Alfredo Casado&lt;/a&gt; fired some great slides about content negotiation and all things REST.&lt;br /&gt;&lt;br /&gt;We were also there: &lt;a href="http://sergialmar.wordpress.com/"&gt;Sergi Almar&lt;/a&gt; had a really succesful lab about what's  coming in Spring 3.0 which enjoyed some massive assistance. Due to lack of space, some people had to spend the entire lab with their laptops over their kneels but they still  were not leaving. I delivered a brief (30-min) talk about how to mix progressive enhancement, java, and javascript tests.&lt;br /&gt;&lt;br /&gt;&lt;div id="__ss_1630811" style="text-align: left; width: 425px;"&gt;&lt;a href="http://www.slideshare.net/icoloma/developing-and-testing-ajax-components?type=powerpoint" style="margin: 12px 0pt 3px; display: block; font-family: Helvetica,Arial,Sans-serif; font-size-adjust: none; font-size: 14px; font-stretch: normal; font-style: normal; font-variant: normal; font-weight: normal; line-height: normal; text-decoration: underline;" title="Developing and testing ajax components"&gt;Developing and testing ajax components&lt;/a&gt;&lt;object style="margin: 0px;" height="355" width="425"&gt;&lt;param name="movie" value="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=testing-ajax-components-090624042308-phpapp02&amp;amp;stripped_title=developing-and-testing-ajax-components"&gt;&lt;param name="allowFullScreen" value="true"&gt;&lt;param name="allowScriptAccess" value="always"&gt;&lt;embed src="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=testing-ajax-components-090624042308-phpapp02&amp;amp;stripped_title=developing-and-testing-ajax-components" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" height="355" width="425"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;br /&gt;&lt;div style="font-family: tahoma,arial; font-size: 11px; height: 26px; padding-top: 2px;"&gt;View more &lt;a href="http://www.slideshare.net/" style="text-decoration: underline;"&gt;Keynote presentations&lt;/a&gt; from &lt;a href="http://www.slideshare.net/icoloma" style="text-decoration: underline;"&gt;Ignacio Coloma&lt;/a&gt;.&lt;/div&gt;&lt;/div&gt;&lt;br /&gt;Lots of other things to share, but that will be in a couple of weeks. Cheers!&lt;img src="http://feeds.feedburner.com/~r/The90thPercentile/~4/ovKMK1ERhRY" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://icoloma.blogspot.com/feeds/935585089578156295/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://icoloma.blogspot.com/2009/06/30-slides-about-testing-ajax-components.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/10814242/posts/default/935585089578156295?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/10814242/posts/default/935585089578156295?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/The90thPercentile/~3/ovKMK1ERhRY/30-slides-about-testing-ajax-components.html" title="30 slides about testing ajax components" /><author><name>Nacho Coloma</name><uri>https://plus.google.com/112843640399200525430</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh6.googleusercontent.com/-XL2pDJUO-0U/AAAAAAAAAAI/AAAAAAAAAxw/Wr1DmPB3TOA/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/_5fcKwF7jItc/SkNSeuQWipI/AAAAAAAAAXA/-LksfAYVguY/s72-c/apples-in-line.jpg" height="72" width="72" /><thr:total>0</thr:total><feedburner:origLink>http://icoloma.blogspot.com/2009/06/30-slides-about-testing-ajax-components.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D04AR3w4eip7ImA9WxJRGU0.&quot;"><id>tag:blogger.com,1999:blog-10814242.post-6546857521235608395</id><published>2009-05-21T13:36:00.003+02:00</published><updated>2009-05-21T13:39:06.232+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-05-21T13:39:06.232+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="loom" /><category scheme="http://www.blogger.com/atom/ns#" term="training" /><category scheme="http://www.blogger.com/atom/ns#" term="web" /><title>Loom 1.5 is out; are you doing something interesting today?</title><content type="html">Today we are releasing Loom 1.5. This is our first release intended for public consumption, so if you are thinking about giving it a twirl we will be more than glad to help.&lt;br /&gt;&lt;br /&gt;I am not going to talk about things included in this release, such as a YSlow! 'A' rate for the &lt;a href="http://loom.extrema-sistemas.org/loomdemo/mortgages/"&gt;demo application&lt;/a&gt; (94 out of 100, yay!), the &lt;a href="http://loom.extrema-sistemas.org/doc/1.x/ref/property-annotations#ImageValidation"&gt;ImageValidation annotation&lt;/a&gt; or the &lt;a href="http://loom.extrema-sistemas.org/doc/1.x/ref/configuration#spring"&gt;simplified spring config&lt;/a&gt;. Instead, I wanted to comment something present since our 1.0 release.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Things that your web framework should be doing for you&lt;/h2&gt;&lt;br /&gt;&lt;a href="http://icoloma.blogspot.com/2008/12/start-testing-your-javascript-server.html"&gt;Last entry&lt;/a&gt; was about how your javascript and server-side code should talk HTML at both sides, because generating javascript from the server is a bad practice. This one is about how you (and by "you" I mean your web framework) should be generating your HTML markup.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;/div&gt;What follows is what we like to call a Contributed View, meaning that each piece of the web framework can contribute aspects to the generated HTML.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Form Fields&lt;/h3&gt;&lt;br /&gt;An integer field validated between 5 and 100 can contribute this to the generated HTML:&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_5fcKwF7jItc/ShGyWg-Im_I/AAAAAAAAAWw/m-QfpIsxjPg/s1600-h/contributed+view+-+input+field.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img src="http://2.bp.blogspot.com/_5fcKwF7jItc/ShGyWg-Im_I/AAAAAAAAAWw/m-QfpIsxjPg/s400/contributed+view+-+input+field.jpg" border="0" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;b&gt;Converter&lt;/b&gt;: inject a 'number' css class to be used by javascript and CSS style sheets.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Validator&lt;/b&gt;: inject attributes (scale="0" min="5" max="100") to be used by javascript validations. Better yet, inject maxlength="3" to make the browser enforce the maximum length of this field. It can get tricky since you must take into consideration negative values and decimal point.&lt;/li&gt;&lt;/ul&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;/div&gt;Notice what the server should not be generating: javascript code. You can still use prototype, jquery or dojo to validate user input, as the server side is just providing the required metadata.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Links &lt;/h3&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_5fcKwF7jItc/ShGyXWoFmFI/AAAAAAAAAW4/xY7oZ-eDyB8/s1600-h/contributed+view+-+link.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img src="http://2.bp.blogspot.com/_5fcKwF7jItc/ShGyXWoFmFI/AAAAAAAAAW4/xY7oZ-eDyB8/s400/contributed+view+-+link.jpg" border="0" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;The easiest example of URL decoration affects links pointing to forbidden resources. If the application developer is using annotations  to specify permissions (with JSR-250 &lt;a href="http://java.sun.com/javaee/5/docs/api/javax/annotation/security/RolesAllowed.html"&gt;@RolesAllowed&lt;/a&gt;, for example) the security interceptor can contribute an "unauthorized" css class to links and forms. Again, it is your responsibility as the application developer to use CSS or javascript to hide or disable them.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;About our &lt;i&gt;next &lt;/i&gt;few weeks&lt;/h2&gt;&lt;br /&gt;I have been seriously lectured by some friends about how we are not dedicating enough time to make visible the awesome things we are into these days. I hate sounding like a salesman, but people got so serious that I thought I should make an exception just for today:&lt;br /&gt;&lt;br /&gt;We are an heterogeneous software company, which means that the PHP guy sits next to the java freak most of the time and we still somehow manage to keep the peace - most of the time. When we can choose, we have been using the Loom-Spring-Hibernate combo for almost two years and are quite happy with it. There are lots of cool details like the &lt;a href="http://loom.extrema-sistemas.org/doc/1.x/ref/method-annotations#cache"&gt;browser cache annotation&lt;/a&gt; that do not have an equivalent in other web frameworks.&lt;br /&gt;&lt;br /&gt;If you test and like it, I want to know. If you test and find things that you don't like, hey, I still want to know!&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;About our &lt;i&gt;last&lt;/i&gt; few weeks&lt;/h2&gt;&lt;br /&gt;Our training courses have been a great success! We are really proud of the feedback received, 80% of the attendees to  both courses have given us the highest qualification possible. In our first &lt;a href="http://extrema-sistemas.com/en/training/springsource"&gt;Core Spring Course&lt;/a&gt; in  Madrid, people with previous spring experience started learning something new from day 2, which is a remarkable achievement for an introductory course.&lt;br /&gt;&lt;br /&gt;On a related note, a friend from Tenerife was quite pissed at me because he had to investigate a big deal to find our &lt;a href="http://extrema-sistemas.com/en/training/agile-project-management-practice"&gt;Agile Project Management Course Contents&lt;/a&gt;. It's more or less the same thing you can see in this blog, but four hours a day. Last week lots of people confessed that they were expecting to fall asleep in the middle of the course, but instead had lots of fun - and with 'we', I mean 'me included'.&lt;br /&gt;&lt;br /&gt;On the "things to improve" comments, we have been asked to provide more practical examples for both courses. We are certainly looking forward to improve that.&lt;img src="http://feeds.feedburner.com/~r/The90thPercentile/~4/sqi-2caI7P0" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://icoloma.blogspot.com/feeds/6546857521235608395/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://icoloma.blogspot.com/2009/05/loom-15-is-out-are-you-doing-something.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/10814242/posts/default/6546857521235608395?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/10814242/posts/default/6546857521235608395?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/The90thPercentile/~3/sqi-2caI7P0/loom-15-is-out-are-you-doing-something.html" title="Loom 1.5 is out; are you doing something interesting today?" /><author><name>Nacho Coloma</name><uri>https://plus.google.com/112843640399200525430</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh6.googleusercontent.com/-XL2pDJUO-0U/AAAAAAAAAAI/AAAAAAAAAxw/Wr1DmPB3TOA/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/_5fcKwF7jItc/ShGyWg-Im_I/AAAAAAAAAWw/m-QfpIsxjPg/s72-c/contributed+view+-+input+field.jpg" height="72" width="72" /><thr:total>0</thr:total><feedburner:origLink>http://icoloma.blogspot.com/2009/05/loom-15-is-out-are-you-doing-something.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CkYGR348fSp7ImA9WxVaGEo.&quot;"><id>tag:blogger.com,1999:blog-10814242.post-3097687324131257522</id><published>2009-04-16T10:15:00.001+02:00</published><updated>2009-04-16T10:22:06.075+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-04-16T10:22:06.075+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="training" /><category scheme="http://www.blogger.com/atom/ns#" term="spring" /><title>Guess who is the first SpringSource partner in Spain</title><content type="html">Yep. We are. &lt;br /&gt;&lt;br /&gt;These last weeks have been like playing the violin with one hand while running across a Super Bowl match - with the ball -, but results are finally here: we are quite excited to &lt;a href="http://www.extrema-sistemas.com/en/news/2009/04/extrema-sistemas-first-springsource-certified-official-partner-spain"&gt;announce the partnership between Extrema Sistemas and SpringSource&lt;/a&gt;.&lt;br /&gt;&lt;img style="float:right; margin:0 0 10px 10px;width: 320px; height: 304px;" src="http://1.bp.blogspot.com/_5fcKwF7jItc/SebqeGAMa2I/AAAAAAAAAWY/PHabKDcpsb4/s400/concentrate.jpg" border="0" alt="Concentrate in your training" title="Concentrate in your training" id="BLOGGER_PHOTO_ID_5325201412140591970" /&gt;&lt;br /&gt;This agreement is the conclusion of lots of work on both sides, notably &lt;a href="http://sergialmar.wordpress.com/"&gt;Sergi Almar&lt;/a&gt; who started the whole thing in the first place. Extrema Sistemas will be delivering the SpringSource official courses in Spain, starting in Madrid on Apr 27-30 and later in Barcelona on May 25-28.&lt;br /&gt;&lt;br /&gt;On a related note, I will be in Tenerife on May 4-8 to deliver our &lt;a href="http://www.extrema-sistemas.com/en/agile-project-management-practice-0"&gt;Agile Project Management Course&lt;/a&gt; on-site for a coalition of software development companies. It will be a full house, and as an interesting extra I will give a quick talk (well, "quick" be pronounced as "like two hours") at my former University. &lt;br /&gt;&lt;br /&gt;I am really excited with the perspective of looking at my old college rooms from the other side :)&lt;img src="http://feeds.feedburner.com/~r/The90thPercentile/~4/DE1_gzs6R18" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://icoloma.blogspot.com/feeds/3097687324131257522/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://icoloma.blogspot.com/2009/04/guess-who-is-first-springsource-partner.html#comment-form" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/10814242/posts/default/3097687324131257522?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/10814242/posts/default/3097687324131257522?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/The90thPercentile/~3/DE1_gzs6R18/guess-who-is-first-springsource-partner.html" title="Guess who is the first SpringSource partner in Spain" /><author><name>Nacho Coloma</name><uri>https://plus.google.com/112843640399200525430</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh6.googleusercontent.com/-XL2pDJUO-0U/AAAAAAAAAAI/AAAAAAAAAxw/Wr1DmPB3TOA/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://1.bp.blogspot.com/_5fcKwF7jItc/SebqeGAMa2I/AAAAAAAAAWY/PHabKDcpsb4/s72-c/concentrate.jpg" height="72" width="72" /><thr:total>2</thr:total><feedburner:origLink>http://icoloma.blogspot.com/2009/04/guess-who-is-first-springsource-partner.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D08BRH4zfCp7ImA9WxJWGUk.&quot;"><id>tag:blogger.com,1999:blog-10814242.post-8250072958802885714</id><published>2009-03-23T16:00:00.004+01:00</published><updated>2009-06-25T18:04:15.084+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-06-25T18:04:15.084+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="javascript" /><category scheme="http://www.blogger.com/atom/ns#" term="tests" /><title>Start testing your Javascript server-less</title><content type="html">Are you still launching tomcat to test your javascript code? You shouldn't.&lt;br /&gt;&lt;br /&gt;When applying the MVC pattern, HTML as always been considered to be part of the View, which is only correct from a server point of view. In browser-land HTML is the Model, the Controller is our javascript code, and the View is - well, it can be your chrome, your CSS, whatever. The important thing is, there are two MVCs involved:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_5fcKwF7jItc/ScJvAFPXXNI/AAAAAAAAAWA/c8UIS5uYsFM/s1600-h/server-and-browser.gif"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 213px;" src="http://4.bp.blogspot.com/_5fcKwF7jItc/ScJvAFPXXNI/AAAAAAAAAWA/c8UIS5uYsFM/s400/server-and-browser.gif" alt="" id="BLOGGER_PHOTO_ID_5314932557447912658" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Two years ago we started applying some simple rules for javascript development:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;No javascript code generated by the server&lt;/strong&gt;. Nil. Zero. Nada. That would only make things harder to port and reuse. Instead, the server generates the HTML attributes (some standard and &lt;a href="http://icoloma.blogspot.com/2008/06/extend-html-with-your-own-metadata.html"&gt;some not&lt;/a&gt;) to be used by javascript code. Combine this with progressive enhancement and you will have an almost functional interface to add your javascript icing.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;strong&gt;Javascript is always tested using static HTML files&lt;/strong&gt;, which represent the approved API between server and client software. You can test easily your javascript code, and the server only has to stick to this API.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;strong&gt;Javascript unit testing may be added later&lt;/strong&gt;. We use &lt;a href="http://github.com/madrobby/scriptaculous/wikis/unit-testing"&gt;UnitTest&lt;/a&gt;, but there are others: &lt;a href="http://docs.jquery.com/QUnit"&gt;JQuery&lt;/a&gt;, &lt;a href="http://fireunit.org/"&gt;Firebug&lt;/a&gt;, &lt;a href="http://blog.jeffhaynie.us/introducing-testmonkey.html"&gt;TestMonkey&lt;/a&gt;... the list goes on. Notice that I am not considering Selenium here, as we are not testing interfaces but javascript code.&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_5fcKwF7jItc/ScJvAYUd3kI/AAAAAAAAAWI/j1Gm6EqPo1s/s1600-h/testsuite-and-browser.gif"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 333px; height: 400px;" src="http://3.bp.blogspot.com/_5fcKwF7jItc/ScJvAYUd3kI/AAAAAAAAAWI/j1Gm6EqPo1s/s400/testsuite-and-browser.gif" alt="" id="BLOGGER_PHOTO_ID_5314932562569584194" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;No matter the programming language, life is easier if you do not try to generate your Controller. Now your test environment can be 100% static.&lt;br /&gt;&lt;br /&gt;These simple rules have made our lives so much easier. Instead of the classic mixup of technologies to test Ajax interfaces, there is just a javascript test engine and some html files to test things up (including some static JSON response examples). Before this, something like &lt;a href="http://loom.sourceforge.net/jstests/loom/test/js/validation/validation-test.html"&gt;testing our validation code&lt;/a&gt; was a killing effort. This can still be integrated with ant or maven without requiring a java server in the middle.&lt;br /&gt;&lt;br /&gt;Going static include some additional advantages:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;You can interact with the interface by hand&lt;/strong&gt;. When the tests are done, you still have a working HTML page to pick at.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;strong&gt;You can trace and debug any failing assertion&lt;/strong&gt; using Firebug or the IE debugger.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;strong&gt;There is no roundtrip to the server&lt;/strong&gt;. Things can only be fixed up to the HTML API, so there is no point in thinking in java at this point.&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;Think Divide-And-Conquer, but separating client from server using HTML. Once all javascript code is tested, you can start integrating things with the java server just by generating the expected HTML.&lt;img src="http://feeds.feedburner.com/~r/The90thPercentile/~4/fZBJes6hCyc" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://icoloma.blogspot.com/feeds/8250072958802885714/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://icoloma.blogspot.com/2008/12/start-testing-your-javascript-server.html#comment-form" title="4 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/10814242/posts/default/8250072958802885714?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/10814242/posts/default/8250072958802885714?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/The90thPercentile/~3/fZBJes6hCyc/start-testing-your-javascript-server.html" title="Start testing your Javascript server-less" /><author><name>Nacho Coloma</name><uri>https://plus.google.com/112843640399200525430</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh6.googleusercontent.com/-XL2pDJUO-0U/AAAAAAAAAAI/AAAAAAAAAxw/Wr1DmPB3TOA/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://4.bp.blogspot.com/_5fcKwF7jItc/ScJvAFPXXNI/AAAAAAAAAWA/c8UIS5uYsFM/s72-c/server-and-browser.gif" height="72" width="72" /><thr:total>4</thr:total><feedburner:origLink>http://icoloma.blogspot.com/2008/12/start-testing-your-javascript-server.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D0EMRns-fip7ImA9WxJWGUk.&quot;"><id>tag:blogger.com,1999:blog-10814242.post-4394735638085498102</id><published>2009-02-09T12:04:00.003+01:00</published><updated>2009-06-25T18:01:27.556+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-06-25T18:01:27.556+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="talks" /><title>My Devoxx slides: 15 minutes about improving your users cache experience</title><content type="html">I finally managed to save some time to upload my slides about the browser cache at &lt;a href="http://devoxx.com/"&gt;Devoxx 2008&lt;/a&gt;, in what seems like a life ago (long story short, converting to Powerpoint XP back and forth is not a nice idea if you plan to upload it sometime later).&lt;br /&gt;&lt;br /&gt;Most people missed it since it was scheduled as a quickie, which is fine since I'm a lame speaker (specially when throwing foreign languages into the mix). The slides were OK though, so I wanted to share. And here they are.&lt;br /&gt;&lt;br /&gt;&lt;div style="width: 425px; text-align: left;" id="__ss_999134"&gt;&lt;a style="margin: 12px 0pt 3px; font-family: Helvetica,Arial,Sans-serif; font-style: normal; font-variant: normal; font-weight: normal; font-size: 14px; line-height: normal; font-size-adjust: none; font-stretch: normal; display: block; text-decoration: underline;" href="http://www.slideshare.net/icoloma/caching-web-contents-in-the-browser-999134?type=powerpoint" title="Caching web contents in the browser"&gt;Caching web contents in the browser&lt;/a&gt;&lt;object style="margin: 0px;" height="355" width="425"&gt;&lt;param name="movie" value="http://static.slideshare.net/swf/ssplayer2.swf?doc=devoxx3-1233954709612026-3&amp;amp;stripped_title=caching-web-contents-in-the-browser-999134"&gt;&lt;param name="allowFullScreen" value="true"&gt;&lt;param name="allowScriptAccess" value="always"&gt;&lt;embed src="http://static.slideshare.net/swf/ssplayer2.swf?doc=devoxx3-1233954709612026-3&amp;amp;stripped_title=caching-web-contents-in-the-browser-999134" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" height="355" width="425"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;All in all, devoxx was an awesome experience. I'm looking forward to repeat this year!&lt;img src="http://feeds.feedburner.com/~r/The90thPercentile/~4/gI1ANtKXmM4" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://icoloma.blogspot.com/feeds/4394735638085498102/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://icoloma.blogspot.com/2009/02/my-devoxx-slides-15-minutes-about.html#comment-form" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/10814242/posts/default/4394735638085498102?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/10814242/posts/default/4394735638085498102?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/The90thPercentile/~3/gI1ANtKXmM4/my-devoxx-slides-15-minutes-about.html" title="My Devoxx slides: 15 minutes about improving your users cache experience" /><author><name>Nacho Coloma</name><uri>https://plus.google.com/112843640399200525430</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh6.googleusercontent.com/-XL2pDJUO-0U/AAAAAAAAAAI/AAAAAAAAAxw/Wr1DmPB3TOA/s512-c/photo.jpg" /></author><thr:total>1</thr:total><feedburner:origLink>http://icoloma.blogspot.com/2009/02/my-devoxx-slides-15-minutes-about.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DEMEQn4-fyp7ImA9WxRUFkU.&quot;"><id>tag:blogger.com,1999:blog-10814242.post-2273502664838923962</id><published>2008-11-26T09:00:00.000+01:00</published><updated>2008-11-26T09:00:03.057+01:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-11-26T09:00:03.057+01:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="loom" /><title>Loom 1.0 final is out. Let there be AWESOME.</title><content type="html">&lt;img border="0" title="Now, get out there and start kicking ass" src="http://photos1.blogger.com/blogger/4415/852/320/Attitude.jpg" style="margin: 0pt 10px 10px 0pt; float: left; "/&gt;&lt;br /&gt;After two years of work, 700+ java classes, 400+ tests, 35 javascript test pages and a hell of a week polishing rough edges, we finally got the release out the door. &lt;br /&gt;&lt;br /&gt;(I enjoy an entire night of pure insomnia once a month, which looking behind is a great boost for productivity :)&lt;br /&gt;&lt;br /&gt;With lots of &lt;em&gt;bottled awesomeness&lt;sup&gt;tm&lt;/sup&gt;&lt;/em&gt; inside, the following is a selection of the new features that come included in Loom 1.0:&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;This release includes a scaffolding tool&lt;/h3&gt;&lt;br /&gt;&lt;br /&gt;We finally got tired of starting projects from scratch, so Rafa got a week dedicated to it and prepared a tool that can kick start a project in five minutes, assuming a decent internet connection. Just download scaffolding.zip from &lt;a href="https://sourceforge.net/project/showfiles.php?group_id=200887"&gt;the download page&lt;/a&gt;, unzip and run:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;# Create a project named myapp &lt;br /&gt;scaffolding create myapp&lt;br /&gt;&lt;br /&gt;# Create the eclipse project &lt;br /&gt;# (requires GRADLE_HOME defined)&lt;br /&gt;cd myapp&lt;br /&gt;gradle eclipse&lt;br /&gt;&lt;br /&gt;# Deploy on your favorite web server &lt;br /&gt;# (requires CATALINA_HOME or JETTY_HOME defined)&lt;br /&gt;gradle deploy-tomcat&lt;br /&gt;gradle deploy-jetty&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Sometimes gradle timeouts while waiting for maven repositories; in that case, just wait a little and repeat the command.&lt;br /&gt;&lt;br /&gt;The created template will be based on &lt;a href="http://www.gradle.org/"&gt;gradle&lt;/a&gt;, which is (some artistic license, here) a combination of ivy + groovy + steroids. Notice that Loom does not use groovy but as a substitute for ant (and a great one at it!), the rest is 100% java.&lt;br /&gt;&lt;br /&gt;Open the project using eclipse, where compiled java files go directly into WEB-INF/classes. Open "Run as" and select "myapp - run tomcat" or "myapp - run jetty" (again, CATALINA_HOME or JETTY_HOME are required). No plugins are needed, no build process, nothing. Just compile and open a browser at http://localhost:8080/myapp&lt;br /&gt;&lt;br /&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center; width: 400px; height: 369px;" src="http://2.bp.blogspot.com/_5fcKwF7jItc/SSw7yXo-xoI/AAAAAAAAAVQ/dADtJM2m8ZI/s400/scaffolding.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5272655000269407874" /&gt;&lt;br /&gt;&lt;br /&gt;Now, create a new JPA entity with some attributes using eclipse (Loom does not require JPA, but this is easier this way) and add your own CRUD interface:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;# execute this in your project home dir after MyPersistentClass has been compiled&lt;br /&gt;scaffolding crud com.acme.model.MyPersistentClass&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;That's it. Restart the server and test your interface. The form should validate your JPA constraints, which can be modified at any time without re-creating the interface or anything.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;This release includes Javascript and CSS repositories&lt;/h3&gt;&lt;br /&gt;&lt;br /&gt;Your javascript and CSS files can be concatenated, minified and gzipped in production without involving any ant or maven script.&lt;br /&gt;&lt;br /&gt;This is similar to what &lt;a href="https://jawr.dev.java.net/"&gt;JAWR&lt;/a&gt; does, but easier to configure and use. Its main features are:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Configured using spring&lt;/strong&gt;: it does not require external files.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;All files (javascript, CSS, referenced images) can be &lt;strong&gt;loaded from the classpath&lt;/strong&gt;, which means that they can be provided by other jars. &lt;strong&gt;You can still use images inside CSS files&lt;/strong&gt;, and they will be served - even from the classpath.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;All files (again - css, javascript AND image files!) are &lt;strong&gt;aggressively cached based on their MD5 checksums&lt;/strong&gt;. If you have ever worked with GWT, you may be familiar with the concept - if not, drop by our &lt;a href="http://www.devoxx.com/display/JV08/Caching+web+contents+in+the+browser"&gt;Devoxx talk&lt;/a&gt; where we will be talking about this as long as time allows.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;You can choose between &lt;a href="http://developer.yahoo.com/yui/compressor/"&gt;YUICompressor&lt;/a&gt; (javascript and CSS) and &lt;a href="http://javascript.crockford.com/jsmin.html"&gt;JSMin&lt;/a&gt; (javascript) just by dropping them on the lib folder. They are included in the /lib/compressors folder of the &lt;a href="http://sourceforge.net/project/showfiles.php?group_id=200887"&gt;Loom distribution&lt;/a&gt;.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;This should be easier to digest using an example. The spring file:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;&amp;lt;bean class="org.loom.resources.WebResourceBundleRepository"&gt;&lt;br /&gt;&amp;lt;property name="bundles"&gt;&lt;br /&gt;&amp;lt;bean class="org.loom.config.SpringResourceMapFactory"&gt;&lt;br /&gt;&amp;lt;property name="resources"&gt;&lt;br /&gt;&amp;lt;value&gt;&lt;br /&gt;    css=/css/base.css /css/basemod.css &lt;br /&gt;    ie=/css/yaml-3.0.5/core/iehacks.css /css/my_iehacks.css&lt;br /&gt;    demo=classpath:/js/prototype/prototype-1.6.0.3.js classpath:/js/loom/core.js \&lt;br /&gt; classpath:/js/loom/format.js &lt;br /&gt;&amp;lt;/value&gt;&lt;br /&gt;&amp;lt;/property&gt;&lt;br /&gt;&amp;lt;/bean&gt;&lt;br /&gt;&amp;lt;/property&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The JSP file:&lt;br /&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;&amp;lt;l:css resource="css"/&gt;&lt;br /&gt;&amp;lt;l:css resource="ie" ie="any"/&gt;&lt;br /&gt;&amp;lt;l:script resource="demo"/&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The generated html with config.development=true:&lt;br /&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;&amp;lt;link href="/myapp/resources/css/0/2008a6c2ba1c6009f5f84db99e6a76e0/base.css" rel="stylesheet" type="text/css"&gt;&amp;lt;/link&gt;&lt;br /&gt;&amp;lt;link href="/myapp/resources/css/1/f6c4b21a2f6c9d265d14081243c2da40/basemod.css" rel="stylesheet" type="text/css"&gt;&amp;lt;/link&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;!--[if IE]&gt;&lt;br /&gt;&amp;lt;link href="/myapp/resources/ie/0/658801c193faa8e7173e2cd220093e67/iehacks.css" rel="stylesheet" type="text/css"&gt;&amp;lt;/link&gt;&lt;br /&gt;&amp;lt;link href="/myapp/resources/ie/1/658801c193faa8e7173e2cd220093e67/my_iehacks.css" rel="stylesheet" type="text/css"&gt;&amp;lt;/link&gt;&lt;br /&gt;&amp;lt;![endif]--&gt; &lt;br /&gt;&lt;br /&gt;&amp;lt;script src="/myapp/resources/demo/0/b5684120e496c310977713be34be4868/prototype-1.6.0.3.js" type="text/javascript"&gt;&amp;lt;/script&gt;&lt;br /&gt;&amp;lt;script src="/myapp/resources/demo/1/07139c2fd5d77ee9fa619ff335996068/core.js" type="text/javascript"&gt;&amp;lt;/script&gt;&lt;br /&gt;&amp;lt;script src="/myapp/resources/demo/2/bd7026fb11a52e8fe8c671e894885e78/format.js" type="text/javascript"&gt;&amp;lt;/script&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_5fcKwF7jItc/SSMg9JnOYNI/AAAAAAAAAUc/fd0i7rg8QOI/s1600-h/screenshot-debug-on.gif"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 253px;" src="http://4.bp.blogspot.com/_5fcKwF7jItc/SSMg9JnOYNI/AAAAAAAAAUc/fd0i7rg8QOI/s400/screenshot-debug-on.gif" border="0" title="Generated HTML with development=true" id="BLOGGER_PHOTO_ID_5270092223878160594" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;The generated html with config.development=false:&lt;br /&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;&amp;lt;link href="/myapp/resources/css/all/7c66dcfb53bb87201e74aef4f1211a63/" rel="stylesheet" type="text/css"&gt;&amp;lt;/link&gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;!--[if IE]&gt;&lt;br /&gt;&amp;lt;link href="/myapp/resources/ie/all/da188973e7712595d03da0bbbfa059f4/" rel="stylesheet" type="text/css"&gt;&amp;lt;/link&gt;&lt;br /&gt;&amp;lt;![endif]--&gt; &lt;br /&gt;&lt;br /&gt;&amp;lt;script src="/myapp/resources/demo/all/1c004c851402d83a84e3d7a2fb73a375/" type="text/javascript"&gt;&amp;lt;/script&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_5fcKwF7jItc/SSMg8kJ4xnI/AAAAAAAAAUU/uTjUANDa3u4/s1600-h/screenshot-debug-off.gif"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 115px;" src="http://4.bp.blogspot.com/_5fcKwF7jItc/SSMg8kJ4xnI/AAAAAAAAAUU/uTjUANDa3u4/s400/screenshot-debug-off.gif" border="0" alt="The same page, with development=false" id="BLOGGER_PHOTO_ID_5270092213822998130" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;These resources have been automatically concatenated, gzipped, MD5-hashed (just once!) and set to expire ten years from now. The browser will never ask for them again, as long as they do not get modified or the user hits the "refresh" button. During normal navigation only the html file will be retrieved.&lt;br /&gt;&lt;br /&gt;But there's more: during development, your changes will be detected on real time, but the browser will only ask for the resources that have changed. The typical 304 traffic between server and client will just not happen.&lt;br /&gt;&lt;br /&gt;Go test the feature live on &lt;a href="http://loom.extrema-sistemas.com/loomdemo/"&gt;the new demo&lt;/a&gt;, just turn on / off the debug switch (at the top right corner) and open firebug to see the number of files involved for each request. Now, navigate between pages and keep an eye on your firebug net tab. No files are being requested.&lt;br /&gt;&lt;br /&gt;Wait, there's more.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;This release includes partial JAX-RS support&lt;/h3&gt;&lt;br /&gt;&lt;br /&gt;We have added support for the mapping annotations of the JAX-RS spec. In fact, the CRUD interface created by scaffolding does just that:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;public class MortgagesAction extends AbstractAction {&lt;br /&gt;&lt;br /&gt; @RetrieveEntity(on={"save", "edit"})&lt;br /&gt; private Mortgage mortgage;&lt;br /&gt;&lt;br /&gt; @GET @Path("/")&lt;br /&gt; public Resolution list() { ... }&lt;br /&gt;&lt;br /&gt; @GET @Path("create")&lt;br /&gt; public Resolution create() { ... }&lt;br /&gt; &lt;br /&gt; @GET @Path("/{mortgage.id}")&lt;br /&gt; public Resolution edit() { ... }&lt;br /&gt; &lt;br /&gt; @POST @Path("/{mortgage.id?}")&lt;br /&gt; public Resolution save() { ... }&lt;br /&gt; &lt;br /&gt; @DELETE @Path("/{mortgage.id?}")&lt;br /&gt; public Resolution delete() { ... }&lt;br /&gt;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Combine with &lt;a href="http://www.zeroturnaround.com/javarebel/"&gt;JavaRebel&lt;/a&gt; to enjoy &lt;em&gt;The Extra Cool Development Experience&lt;/em&gt;. Open your Action, modify your mappings, refresh the browser - and only your Action class gets reloaded. New Actions cannot be added on-the-fly and there are other limitations, but this first release is really promising.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;This release includes some serious pagination components.&lt;/h3&gt;&lt;br /&gt;&lt;br /&gt;I have tested almost every pagination solution in the wild since 1999, checking them off with my old list of (almost) reasonable requirements:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Pagination should be done by the database&lt;/strong&gt; if possible. Having said that, sometimes I don't have a database, and the web framework should do its work.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;There is almost no difference between a &lt;strong&gt;paged list&lt;/strong&gt; (&amp;lt;ul&gt;) and a &lt;strong&gt;paged table&lt;/strong&gt; (&amp;lt;table&gt;).&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;strong&gt;Pagination and sorting should be transparent&lt;/strong&gt;, or almost. That is tricky when it involves the database.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;It should include a &lt;strong&gt;professional look and feel&lt;/strong&gt; by default. This is not my merit, kudos go to &lt;a href="http://woork.blogspot.com/2008/03/perfect-pagination-style-using-css.html"&gt;Antonio Lupetti&lt;/a&gt;.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;&amp;lt;l:pagedTable data="${action.mortgages}" id="mortgages"&gt;&lt;br /&gt;  &amp;lt;l:column sortable="false"&gt;&lt;br /&gt;     &amp;lt;l:inputCheckbox name="selectedRows" class="selectRow checkbox" value="${row.id}" renderLabel="false"/&gt;&lt;br /&gt;  &amp;lt;/l:column&gt;&lt;br /&gt;  &amp;lt;l:column property="id" /&gt;&lt;br /&gt;  &amp;lt;l:column property="name" action="Mortgages" event="edit"&gt;&lt;br /&gt;     &amp;lt;l:param name="mortgage.id" value="${row.id}"/&gt;&lt;br /&gt;  &amp;lt;/l:column&gt;&lt;br /&gt;  &amp;lt;l:column property="address" /&gt;&lt;br /&gt;  &amp;lt;l:column property="principalLoanBalance" title="Loan" /&gt;&lt;br /&gt;  &amp;lt;l:column property="creationDate" class="date" /&gt;&lt;br /&gt;&amp;lt;/l:pagedTable&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_5fcKwF7jItc/SSMiwCSnkiI/AAAAAAAAAUk/90j4X6zTkbw/s1600-h/paged-example.gif"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 150px;" src="http://1.bp.blogspot.com/_5fcKwF7jItc/SSMiwCSnkiI/AAAAAAAAAUk/90j4X6zTkbw/s400/paged-example.gif" border="0" alt=""id="BLOGGER_PHOTO_ID_5270094197597639202" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Again, you can check examples with tables and lists in &lt;a href="http://loom.extrema-sistemas.org/loomdemo/"&gt;the demo application&lt;/a&gt;. &lt;br /&gt;&lt;br /&gt;&lt;h3&gt;This release includes LocaleAwareException and HttpException&lt;/h3&gt;&lt;br /&gt;&lt;br /&gt;This is just a pet feature, but I love it. You can throw exceptions that will get to the user as any normal input validation errors, or throw HTTP errors like 404 at any point of the code. It's handy.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;This release includes extensibility&lt;/h3&gt;&lt;br /&gt;&lt;br /&gt;Two years are a lot of time, in framework years. We kept the original goal of a lightweight and focused web framework, and moved all the server and javascript components that started cropping everywhere to the &lt;a href="http://loom.extrema-sistemas.org/addonsdemo/"&gt;addons project&lt;/a&gt; (take it easy - it's not that mature yet). &lt;br /&gt;&lt;br /&gt;The point is not about the addons project but about this whole new approach to extensibility: just drop a META-INF/loom-descriptor.properties in your jar file like this:&lt;br /&gt;&lt;br /&gt;&lt;pre class="prettyprint"&gt;&lt;br /&gt;# The list of annotation processors provided by this jar&lt;br /&gt;annotation-processors=org.loom.addons.multiupload.MultiUploadAnnotationProcessor, \&lt;br /&gt; org.loom.addons.confirmation.RequiresConfirmationAnnotationProcessor, \&lt;br /&gt;&lt;br /&gt;# The list of Converter factories provided by this jar &lt;br /&gt;converter-factories=org.loom.addons.autocompleter.AutocompletedConverterFactory&lt;br /&gt;&lt;br /&gt;# The list of messages locations provided by this jar&lt;br /&gt;messages=classpath:resources/addons-messages&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Loom will load it from the classpath and the application developer may use its contents:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Java annotations&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Validators and Converters&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Javascript components, including CSS and image files&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Internationalized messages&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;And, of course, its own JSP tags as usual.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;And that's it?&lt;/h3&gt;&lt;br /&gt;&lt;br /&gt;Damn, no. This is the framework we are using to build applications, every day. We expect it to keep growing with a steady pace with all the know-how in our way for years to come. &lt;br /&gt;&lt;br /&gt;I only wish "steady" could mean "slower" in this context. I can live without the stress of the last few weeks :)&lt;img src="http://feeds.feedburner.com/~r/The90thPercentile/~4/xvz59dyz3Zo" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://icoloma.blogspot.com/feeds/2273502664838923962/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://icoloma.blogspot.com/2008/11/loom-10-final-is-out-let-there-be.html#comment-form" title="4 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/10814242/posts/default/2273502664838923962?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/10814242/posts/default/2273502664838923962?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/The90thPercentile/~3/xvz59dyz3Zo/loom-10-final-is-out-let-there-be.html" title="Loom 1.0 final is out. Let there be AWESOME." /><author><name>Nacho Coloma</name><uri>https://plus.google.com/112843640399200525430</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh6.googleusercontent.com/-XL2pDJUO-0U/AAAAAAAAAAI/AAAAAAAAAxw/Wr1DmPB3TOA/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/_5fcKwF7jItc/SSw7yXo-xoI/AAAAAAAAAVQ/dADtJM2m8ZI/s72-c/scaffolding.jpg" height="72" width="72" /><thr:total>4</thr:total><feedburner:origLink>http://icoloma.blogspot.com/2008/11/loom-10-final-is-out-let-there-be.html</feedburner:origLink></entry></feed>
