<?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;CEAFQn0_fip7ImA9WhBVFk4.&quot;"><id>tag:blogger.com,1999:blog-168828253523263225</id><updated>2013-04-22T08:11:53.346-04:00</updated><category term="Community" /><category term="Experience" /><category term="Technology" /><category term="Entrepreneurship" /><title>Sharing Technologies</title><subtitle type="html" /><link rel="http://schemas.google.com/g/2005#feed" type="application/atom+xml" href="http://domderrien.blogspot.com/feeds/posts/default" /><link rel="alternate" type="text/html" href="http://domderrien.blogspot.com/" /><link rel="next" type="application/atom+xml" href="http://www.blogger.com/feeds/168828253523263225/posts/default?start-index=26&amp;max-results=25&amp;redirect=false&amp;v=2" /><author><name>Dom Derrien</name><uri>https://plus.google.com/100819130946985751629</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-CAiJ6LOHBWE/AAAAAAAAAAI/AAAAAAAAErE/twENdW648fs/s512-c/photo.jpg" /></author><generator version="7.00" uri="http://www.blogger.com">Blogger</generator><openSearch:totalResults>56</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/atom+xml" href="http://feeds.feedburner.com/SharingTechnologies" /><feedburner:info uri="sharingtechnologies" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><link rel="license" type="text/html" href="http://creativecommons.org/licenses/by-nc-sa/2.5/" /><logo>http://creativecommons.org/images/public/somerights20.gif</logo><entry gd:etag="W/&quot;DEEHQn85fip7ImA9WhRaEUU.&quot;"><id>tag:blogger.com,1999:blog-168828253523263225.post-7487542450779400503</id><published>2012-02-13T21:29:00.001-05:00</published><updated>2012-02-13T21:30:33.126-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-02-13T21:30:33.126-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Technology" /><category scheme="http://www.blogger.com/atom/ns#" term="Experience" /><title>SoapUI / REST / JSON / variable namespaces</title><content type="html">As a developer of large Web applications, I'm used to relying on a few proven test strategies:&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;Unit tests with &lt;a href="http://junit.org/"&gt;JUnit&lt;/a&gt; / &lt;a href="http://cobertura.sourceforge.net/"&gt;Cobertura&lt;/a&gt; and &lt;a href="http://dojotoolkit.org/reference-guide/util/doh.html" title="Dojo Objective Harness"&gt;DOH&lt;/a&gt; / &lt;a href="http://siliconforks.com/jscoverage/"&gt;JSCoverage&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;Functional and Integration tests with &lt;a href="http://seleniumhq.org/docs/03_webdriver.html"&gt;Selenium 2&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
When I joined my current development team to work on the project &lt;a href="http://www.tradeinsight.com/"&gt;TradeInsight&lt;/a&gt;, I was introduced to &lt;a href="http://www.soapui.org/"&gt;SoapUI&lt;/a&gt; for the &lt;a href="http://www.soapui.org/Getting-Started/functional-testing.html"&gt;functional tests&lt;/a&gt;. I like it's ability to convert JSON to XML, enabling then the writing of &lt;i&gt;XPath match&lt;/i&gt; assertions.&lt;br /&gt;
&lt;br /&gt;
There's one little caveat related to the conversion and the XPath assertions:&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;When the server response contains a single JSON object, the conversion introduce a namespace into the generated XML.&lt;/li&gt;
&lt;li&gt;And this namespace depends on the server address.&lt;/li&gt;
&lt;/ul&gt;
The following figures show the original JSON response and its conversion to XML with the inferred namespace.&lt;br /&gt;
&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-BlcYHtUpqIc/TznAjCpjXoI/AAAAAAAADIU/__ZhXxv01Ls/s1600/soapui-1.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" src="http://2.bp.blogspot.com/-BlcYHtUpqIc/TznAjCpjXoI/AAAAAAAADIU/__ZhXxv01Ls/s1600/soapui-1.png" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;JSON response produced by a REST service.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-VmDOZdwfFcA/TznAj2ox_zI/AAAAAAAADIc/G91-up2xERM/s1600/soapui-2.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" src="http://2.bp.blogspot.com/-VmDOZdwfFcA/TznAj2ox_zI/AAAAAAAADIc/G91-up2xERM/s1600/soapui-2.png" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Transformation into a XML payload with an inferred namespace.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;br /&gt;
This automatic mapping to namespace server dependent does not allow to write server-agnostic code if you follow the suggested solution!&lt;br /&gt;
&lt;br /&gt;
The following figures show a XPath match expression as documented
 in the SoapUI training materials. Sadly, running it against an XML with
 a namespace, this error is reported:&lt;br /&gt;
&lt;br /&gt;
&lt;blockquote class="tr_bq" style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;
XPathContains assertion failed for path 
[//startDate/text()] : Exception missing content for xpath 
[//startDate/text()] in Response&lt;span&gt;.&lt;/span&gt;&lt;/blockquote&gt;
&lt;br /&gt;
&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-MPQpoazFvVU/TznAmxBH-_I/AAAAAAAADI0/D9svpfnjKfE/s1600/soapui-5.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" src="http://2.bp.blogspot.com/-MPQpoazFvVU/TznAmxBH-_I/AAAAAAAADI0/D9svpfnjKfE/s1600/soapui-5.png" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Simple XPath expression with the corresponding error message.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-43oEqsdOrAc/TznAnUbfTzI/AAAAAAAADI8/DxyGcjxixic/s1600/soapui-6.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" src="http://2.bp.blogspot.com/-43oEqsdOrAc/TznAnUbfTzI/AAAAAAAADI8/DxyGcjxixic/s1600/soapui-6.png" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Corrected XPath expression as suggested, now server dependent :(&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;br /&gt;&amp;nbsp;A simple solution consists in replacing the specified namespace with the meta-character '&lt;b&gt;*&lt;/b&gt;', which match any namespace. As all elements of the XML document are under the scope of the inferred namespace, it's important to prefix all nodes of the XPath expression with '&lt;b&gt;*:&lt;/b&gt;', as illustrated by the following figure.&lt;br /&gt;
&lt;br /&gt;
&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-RARSiLb-tw8/TznAnxZzjTI/AAAAAAAADJE/pQviuhWJDKw/s1600/soapui-7.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" src="http://1.bp.blogspot.com/-RARSiLb-tw8/TznAnxZzjTI/AAAAAAAADJE/pQviuhWJDKw/s1600/soapui-7.png" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Use of the '*' prefix to produce assertion server independent.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
I hope this helps.&lt;br /&gt;
&lt;br /&gt;
A+, Dom&lt;br /&gt;
&lt;ul&gt;
&lt;/ul&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=pB8pbtCt5IM:e_proVvQlbw:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=pB8pbtCt5IM:e_proVvQlbw:4cEx4HpKnUU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?i=pB8pbtCt5IM:e_proVvQlbw:4cEx4HpKnUU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=pB8pbtCt5IM:e_proVvQlbw:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?i=pB8pbtCt5IM:e_proVvQlbw:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SharingTechnologies/~4/pB8pbtCt5IM" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://domderrien.blogspot.com/feeds/7487542450779400503/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://domderrien.blogspot.com/2012/02/soapui-rest-json-variable-namespaces.html#comment-form" title="6 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/168828253523263225/posts/default/7487542450779400503?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/168828253523263225/posts/default/7487542450779400503?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SharingTechnologies/~3/pB8pbtCt5IM/soapui-rest-json-variable-namespaces.html" title="SoapUI / REST / JSON / variable namespaces" /><author><name>Dom Derrien</name><uri>https://plus.google.com/100819130946985751629</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-CAiJ6LOHBWE/AAAAAAAAAAI/AAAAAAAAErE/twENdW648fs/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/-BlcYHtUpqIc/TznAjCpjXoI/AAAAAAAADIU/__ZhXxv01Ls/s72-c/soapui-1.png" height="72" width="72" /><thr:total>6</thr:total><feedburner:origLink>http://domderrien.blogspot.com/2012/02/soapui-rest-json-variable-namespaces.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D0EASH06eip7ImA9WhZaEk8.&quot;"><id>tag:blogger.com,1999:blog-168828253523263225.post-6018660260923303495</id><published>2011-06-27T21:47:00.000-04:00</published><updated>2011-06-27T21:47:29.312-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-06-27T21:47:29.312-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Entrepreneurship" /><category scheme="http://www.blogger.com/atom/ns#" term="Experience" /><title>Un nouveau chapître</title><content type="html">Il y a quelques 18 moins, j'ai choisi de devenir un &lt;a href="http://domderrien.blogspot.com/2010/02/ma-nouvelle-vie-en-tant-quentrepreneur.html"&gt;entrepreneur à temps plein&lt;/a&gt;, consacrant mon temps et mes ressources financières au développement du service &lt;a href="http://anothersocialeconomy.com/"&gt;AnotherSocialEconomy&lt;/a&gt;. Cela a été une expérience très &lt;a href="http://domderrien.blogspot.com/2011/04/state-of-anothersocialeconomy.html"&gt;riche en enseignements&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
Ceux qui me connaissent savent à quel point il était important que je sois vraiment maître de mon destin professionnel&amp;nbsp;:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;Développer un outil qui offre une vraie valeur à son groupe d'utilisateurs.&lt;/li&gt;
&lt;li&gt;Baser les décisions sur les faits et leurs conséquences sans laisser places aux interférences politiques (tellement immobilisantes dans les grandes entreprises).&lt;/li&gt;
&lt;li&gt;Utiliser la technologie pour servir d'abord les usagers, puis pour construire les services et faciliter leur développement. En ce sens, la méthodologie &lt;a href="http://theleanstartup.com/"&gt;Lean Startup&lt;/a&gt; et son principe &lt;i&gt;Build-Measure-Learn&lt;/i&gt; offre un excellent cadre de travail.&lt;/li&gt;
&lt;li&gt;Utiliser la méthodologie Agile (sans oublier les &lt;a href="http://domderrien.blogspot.com/2009/04/agile-scrum-is-hype-but-xp-is-more.html"&gt;outils de XP&lt;/a&gt;) pour développer une application de grande envergure.&lt;/li&gt;
&lt;/ul&gt;&lt;br /&gt;
Avec mon associé &lt;a href="http://stevenmilstein.com/"&gt;Steven&lt;/a&gt;, cela a été très intéressant de définir mes propres objectifs, mon organisation de travail, et diriger le développement de l'entreprise.&lt;br /&gt;
&lt;br /&gt;
Mais il y a un mois, principalement à cause de l'absence d'une perspective de revenus stables dans un futur proche, je me suis mis à la recherche de contrats et de postes permanents. C'est sûr que c'est déchirant, mais l'outil est en production et je peux le faire évoluer dans mon temps libre. En appliquant donc un principe de &lt;i&gt;Lean&lt;/i&gt; une fois de plus (&lt;i&gt;"Failure to change is a vice!"&lt;/i&gt;, Hiroshi Okuda, Président de Toyota Motor Corp.), je replonge du côté salarié.&lt;br /&gt;
&lt;br /&gt;
Finalement, après plusieurs entretiens, mon choix s'est porté sur la compagnie MEI qui développe une plate-forme Web pour aider ses clients (des producteurs de biens de consommation emballés--&lt;i&gt;consumer packaged goods&lt;/i&gt;) à suivre leurs campagnes de promotion. Jusqu'il y a deux ans, MEI offrait principalement son service aux grands manufacturiers. Maintenant, dans une offre &lt;span style="border-bottom: 1px dotted grey;" title="Software as a Service"&gt;SAAS&lt;/span&gt;, MEI développe une nouvelle version pour les producteurs petits et intermédiaires, sous la marque &lt;a href="http://tradeinsight.com/"&gt;TradeInsight&lt;/a&gt;. La synergie entre mon expérience (Java, JavaScript, cloud, mobile, Agile) et l'équipe est indéniable.&lt;br /&gt;
&lt;br /&gt;
Au fait&amp;nbsp;: &lt;a href="http://www.tradeinsight.com/aboutus/careers"&gt;MEI embauche&lt;/a&gt; encore&amp;nbsp;!&lt;br /&gt;
&lt;br /&gt;
L'aventure d'AnotherSocialEconomy continue, à temps perdu de mon point de vue et dans mes interactions avec les groupes de la communauté technique de Montréal (NewTech, Android, NodeJS, etc.). Pour plus d'informations quant aux partenariats possibles, veuillez prendre contact avec &lt;a href="http://draft.blogger.com/post-edit.g?blogID=168828253523263225&amp;amp;postID=6018660260923303495"&gt;Steven&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
A+, Dom&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=ZEIYimU0G-M:YXRg2AgLFnE:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=ZEIYimU0G-M:YXRg2AgLFnE:4cEx4HpKnUU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?i=ZEIYimU0G-M:YXRg2AgLFnE:4cEx4HpKnUU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=ZEIYimU0G-M:YXRg2AgLFnE:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?i=ZEIYimU0G-M:YXRg2AgLFnE:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SharingTechnologies/~4/ZEIYimU0G-M" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://domderrien.blogspot.com/feeds/6018660260923303495/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://domderrien.blogspot.com/2011/06/un-nouveau-chapitre.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/168828253523263225/posts/default/6018660260923303495?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/168828253523263225/posts/default/6018660260923303495?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SharingTechnologies/~3/ZEIYimU0G-M/un-nouveau-chapitre.html" title="Un nouveau chapître" /><author><name>Dom Derrien</name><uri>https://plus.google.com/100819130946985751629</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-CAiJ6LOHBWE/AAAAAAAAAAI/AAAAAAAAErE/twENdW648fs/s512-c/photo.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://domderrien.blogspot.com/2011/06/un-nouveau-chapitre.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DkICRXk8eCp7ImA9WhZUFEo.&quot;"><id>tag:blogger.com,1999:blog-168828253523263225.post-1852953982359328834</id><published>2011-06-07T15:17:00.001-04:00</published><updated>2011-06-07T15:22:44.770-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-06-07T15:22:44.770-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Technology" /><category scheme="http://www.blogger.com/atom/ns#" term="Experience" /><title>OAuth authorization handling in a Android application</title><content type="html">&lt;div style="background-color: lightgrey; border: 1 solid darkgrey; padding: 3px;"&gt;Note : This post is part of the series "Lessons learned as an independent developer". Please refer to the &lt;a href="http://domderrien.blogspot.com/2011/06/partage-dexperience-dans-le.html"&gt;introduction&lt;/a&gt; (in French) for more information. &lt;i&gt;Cet article fait partie de la série intitulée « Leçons d'un développeur indépendant ». Au besoin, lisez mon&amp;nbsp;&lt;a href="http://domderrien.blogspot.com/2011/06/partage-dexperience-dans-le.html"&gt;introduction&lt;/a&gt; pour plus d'informations.&lt;/i&gt;&lt;/div&gt;&lt;br /&gt;
&lt;span class="Apple-style-span" style="font-size: large;"&gt;Context&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;a href="http://anothersocialeconomy.com/"&gt;AnotherSocialEconomy&lt;/a&gt; APIs are based on standards as much as possible: &lt;a href="http://openid.net/"&gt;OpenID&lt;/a&gt; and &lt;a href="http://oauth.net/"&gt;OAuth&lt;/a&gt; for the authentication and authorization, HTTP-based REST interface (&lt;a href="http://www.nordsc.com/ext/classification_of_http_based_apis.html" title="Classification of HTTP-based APIs"&gt;1&lt;/a&gt;, &lt;a blog"="" fielding's="" href="http://roy.gbiv.com/untangled/" roy="" title=""&gt;2&lt;/a&gt;) for the communication protocol, JSON for the data payload format, etc.&lt;br /&gt;
&lt;br /&gt;
This post is about setting up a Android application which get authorization tokens from a OAuth provider.&lt;br /&gt;
&lt;br /&gt;
&lt;span class="Apple-style-span" style="font-size: large;"&gt;OAuth provider&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-5DbVV9vT2v8/SZJKLxJPa4I/AAAAAAAACes/mXHa5xdMct0/s1600/GAE-toLeft.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" src="http://1.bp.blogspot.com/-5DbVV9vT2v8/SZJKLxJPa4I/AAAAAAAACes/mXHa5xdMct0/s1600/GAE-toLeft.png" /&gt;&lt;/a&gt;&lt;/div&gt;There are many known &lt;a href="http://wiki.oauth.net/w/page/12238551/ServiceProviders"&gt;OAuth providers&lt;/a&gt; like Netflix, Twitter, Facebook (coming to OAuth 2.0 soon), Yahoo!, Google, etc. If these providers are convenient, they don't offer much flexibility if some debugging is required.&lt;br /&gt;
&lt;br /&gt;
For this experiment, I'm going to use &lt;a href="http://code.google.com/"&gt;Google App Engine&lt;/a&gt; &lt;a href="http://code.google.com/appengine/docs/java/overview.html"&gt;Java&lt;/a&gt; and their &lt;a href="http://code.google.com/appengine/docs/java/oauth/overview.html"&gt;OAuth&lt;/a&gt; support. For a complete walk-through, refer to &lt;a href="http://ikaisays.com/2011/05/26/setting-up-an-oauth-provider-on-google-app-engine/"&gt;Ikai Lan's post: Setting up an OAuth provider on Google App Engine&lt;/a&gt;, especially for the part which describes how to get the public and secret keys for your client application to sign communications with the provider.&lt;br /&gt;
&lt;br /&gt;
&lt;span class="Apple-style-span" style="font-size: large;"&gt;OAuth client - Work flow&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;u&gt;Strategy:&lt;/u&gt;&amp;nbsp;Upon creation, the process checks if the authorization tokens have been saved as user preferences.&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;If they are present, they are loaded to be used to sign each future communication with the application on the server.&lt;/li&gt;
&lt;li&gt;If they are missing, the OAuth work flow is triggered:&lt;/li&gt;
&lt;ol&gt;&lt;li&gt;With the server application keys, a signed request is sent to get a &lt;i&gt;temporary&lt;/i&gt; request token.&lt;/li&gt;
&lt;li&gt;With this request token, a URL to the authentication page is required and an &lt;code&gt;Intent&lt;/code&gt; is created to load the corresponding page in a browser. At this step, the application is stopped.&lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;The user enters his credentials in the browser and grants access rights to the mobile application. The return URL has a custom format: &lt;code&gt;ase://oauthresponse&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The mobile application, which has an &lt;code&gt;Intent&lt;/code&gt; registered for that custom URL, is restarted and is given the return URL. A verification code is extracted from this URL.&lt;/li&gt;
&lt;li&gt;The verification code is used to issue a signed request asking for the access tokens.&lt;/li&gt;
&lt;/ol&gt;&lt;li&gt;The access tokens are saved as part of the user preferences only if she selected a &lt;code&gt;'Remember me'&lt;/code&gt; option.&lt;br /&gt;
&lt;/li&gt;
&lt;/ul&gt;&lt;a href="http://draft.blogger.com/post-edit.g?blogID=168828253523263225&amp;amp;postID=1852953982359328834" name="oauth-figure-1"&gt;&lt;/a&gt;&lt;br /&gt;
&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-n6KopKZo7uw/Tez2KqU8TKI/AAAAAAAAC8Y/aH7cwHykvxY/s1600/android-oauth-real.png" imageanchor="1" style="margin-left: auto; margin-right: auto;" rel="lightbox"&gt;&lt;img border="0" height="326" src="http://3.bp.blogspot.com/-n6KopKZo7uw/Tez2KqU8TKI/AAAAAAAAC8Y/aH7cwHykvxY/s400/android-oauth-real.png" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Figure 1: Authorization work flow&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;
&lt;u&gt;Alternative:&lt;/u&gt; If the mobile application offers anonymous services, like browsing the &lt;a href="http://anothersocialeconomy.appspot.com/widget/maps/stores.jsp"&gt;list of registered stores&lt;/a&gt; in the case of &lt;a href="http://anothersocialeconomy.com/"&gt;AnotherSocialEconomy.com&lt;/a&gt;, it can be friendlier to delay the authorization verification.&lt;br /&gt;
&lt;br /&gt;
&lt;span class="Apple-style-span" style="font-size: large;"&gt;OAuth client - Initiating the authorization process (1, 2, 3)&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
To simplify the application development, I have decided to use &lt;a href="http://code.google.com/p/oauth-signpost/"&gt;oauth-signpost&lt;/a&gt;, a library provided by Matthias Käppler who wanted a slick and simple way to access Netflix services.&lt;br /&gt;
&lt;blockquote&gt;Signpost is the easy and intuitive solution for signing HTTP messages on the Java platform in conformance with the OAuth Core 1.0a standard. Signpost follows a modular and flexible design, allowing you to combine it with different HTTP messaging layers&lt;span style="color:black;"&gt;.&lt;/span&gt;&lt;/blockquote&gt;Note that this library is also good to &lt;a href="https://github.com/kaeppler/signpost-examples/blob/master/OAuthTwitterExample/src/TwitterMain.java"&gt;manage remotely Twitter accounts&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
This section is about initiating the authorization process, which occurs if the application is not called by the application on the server (with the verification code, see next section) and if the OAuth token could not be found in the user preferences. This is the path with the steps {1, 2, 3} in &lt;a href="http://draft.blogger.com/post-edit.g?blogID=168828253523263225&amp;amp;postID=1852953982359328834#oauth-figure-1"&gt;Figure 1&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="prettyprint" style="font-size:smaller;"&gt;if (!justAuthenticated &amp;amp;&amp;amp; Preferences.get(Preferences.OAUTH_KEY, "").length() == 0) {
    // Display the pane with the warning message and the sign in button
    setContentView(R.layout.main_noauth);

    // Update the 'Remember me' checkbox with its last saved state, or the default one
    final String saveOAuthKeysPrefs = Preferences.get(Preferences.SAVE_OAUTH_KEYS, Preferences.SAVE_OAUTH_KEYS_DEFAULT);
    ((CheckBox) findViewById(R.id.app_noauth_keepmeconnected)).setChecked(Preferences.SAVE_OAUTH_KEYS_YES.equals(saveOAuthKeysPrefs));

    // Attach the event handler that will initiate the authorization process up to opening the browser with the authorization page
    findViewById(R.id.app_noauth_continue).setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            // Check if the 'Keep me connected' check box state changed and save its new state
            boolean keepMeConnected = ((CheckBox) findViewById(R.id.app_noauth_keepmeconnected)).isChecked();
            if (Preferences.SAVE_OAUTH_KEYS_YES.equals(saveOAuthKeysPrefs) != keepMeConnected) {
                Preferences.set(Preferences.SAVE_OAUTH_KEYS, keepMeConnected ? Preferences.SAVE_OAUTH_KEYS_YES : Preferences.SAVE_OAUTH_KEYS_NO);
            }
                    
            // Set up the OAuth library
            consumer = new CommonsHttpOAuthConsumer("&amp;lt;your_app_public_key&amp;gt;", "&amp;lt;your_app_secret_key&amp;gt;");

            provider = new CommonsHttpOAuthProvider(
                    "https://&amp;lt;your_app_id&amp;gt;.appspot.com/_ah/OAuthGetRequestToken",
                    "https://&amp;lt;your_app_id&amp;gt;.appspot.com/_ah/OAuthAuthorizeToken",
                    "https://&amp;lt;your_app_id&amp;gt;.appspot.com/_ah/OAuthGetAccessToken");
                    
            try {
                // Steps 1 &amp;amp; 2:
                // Get a request token from the application and prepare the URL for the authorization service
                // Note: the response is going to be handled by the application &amp;lt;intent/&amp;gt; registered for that custom return URL
                String requestTokenUrl = provider.retrieveRequestToken(consumer, "ase://oauthresponse");

                // Step 3:
                // Invoke a browser intent where the user will be able to log in
                startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(requestTokenUrl)));
            }
            catch(Exception ex) {
                Toast.makeText(Dashboard.this, R.string.app_noauth_requesttoken_ex, Toast.LENGTH_LONG).show();
                Log.e("Dashboard no auth", "Cannot initiate communication to get the request token\nException: " + ex.getClass().getName() + "\nMessage: " + ex.getMessage());
            }
        }
    });
}&lt;/pre&gt;&lt;br /&gt;
Figure 2 below illustrates the pane &lt;code&gt;main_noauth&lt;/code&gt; displaying the warning message and the action button, and figure&amp;nbsp;3 shows the authorization page as provided by Google for the hosted applications on App Engine.&lt;br /&gt;
&lt;br /&gt;
&lt;table align="center" cellpadding="0" cellspacing="0" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;
&lt;tr&gt; &lt;td style="text-align: center;" class="tr-caption-container"&gt;&lt;a rel="lightbox" href="http://4.bp.blogspot.com/-AO1A1GJs63o/Te2CLPD8FHI/AAAAAAAAC8c/4qEFAJZR0Sk/s1600/oauth-android-1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="400" src="http://4.bp.blogspot.com/-AO1A1GJs63o/Te2CLPD8FHI/AAAAAAAAC8c/4qEFAJZR0Sk/s400/oauth-android-1.png" width="240" /&gt;&lt;/a&gt;&lt;br /&gt;
&lt;span style="font-size:75%"&gt;Figure 2: Pane displayed if application not yet authorized&lt;/span&gt;&lt;/td&gt; &lt;td style="text-align: center;" class="tr-caption-container"&gt;&lt;a rel="lightbox" href="http://2.bp.blogspot.com/-72aEKhp7Qwc/Te2DFviW66I/AAAAAAAAC8o/_5u9_EAwXiE/s1600/oauth-android-2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="400" src="http://2.bp.blogspot.com/-72aEKhp7Qwc/Te2DFviW66I/AAAAAAAAC8o/_5u9_EAwXiE/s400/oauth-android-2.png" width="240" /&gt;&lt;/a&gt;&lt;br /&gt;
&lt;span style="font-size:75%"&gt;Figure 3: Google authorization page&lt;/span&gt;&lt;/td&gt; &lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;
Whatever action the user takes, the application is going to be called with the URL &lt;code&gt;ase://oauthresponse&lt;/code&gt;. The next section covers this work flow path.&lt;br /&gt;
&lt;br /&gt;
&lt;span class="Apple-style-span" style="font-size: large;"&gt;OAuth client - Processing the authorization (4, 5)&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
The application is registered with an &lt;code&gt;Intent&lt;/code&gt; associated to the &lt;i&gt;scheme&lt;/i&gt; &lt;code&gt;ase&lt;/code&gt; and the &lt;i&gt;host&lt;/i&gt; &lt;code&gt;oauthresponse&lt;/code&gt;. The labels themselves are not important, only their uniqueness and the correspondence with the return URL specified at Step 2.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="prettyprint" style="font-size:smaller;"&gt;&amp;lt;intent-filter&amp;gt;
    &amp;lt;action android:name="android.intent.action.VIEW"/&amp;gt;
    &amp;lt;category android:name="android.intent.category.DEFAULT" /&amp;gt;
    &amp;lt;category android:name="android.intent.category.BROWSABLE"/&amp;gt;
    &amp;lt;data android:scheme="ase" android:host="oauthresponse"/&amp;gt;
&amp;lt;/intent-filter&amp;gt;&lt;/pre&gt;&lt;br /&gt;
The following code snippet implements the steps 4 and 5 as described in &lt;a href="#oauth-figure-1"&gt;Figure&amp;nbsp;1&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="prettyprint" style="font-size:smaller;"&gt;private boolean checkOAuthReturn(Intent intent) {
    boolean returnFromAuth = false;
    Uri uri = intent.getData();

    if (uri != null &amp;amp;&amp;amp; uri.toString().startsWith("ase://oauthresponse")) {
        // Step 4:
        // Get the request token from the Authentication log in page
        String code = uri.getQueryParameter("oauth_verifier");
            
        try {
            // Step 5:
            // Get directly the access tokens
            provider.retrieveAccessToken(consumer, code);
            returnFromAuth = true;
                
            // Persist the tokens
            if (Preferences.SAVE_OAUTH_KEYS_YES.equals(Preferences.get(Preferences.SAVE_OAUTH_KEYS, Preferences.SAVE_OAUTH_KEYS_DEFAULT))) {
                Preferences.set(Preferences.OAUTH_KEY, consumer.getToken());
                Preferences.set(Preferences.OAUTH_SECRET, consumer.getTokenSecret());
            }
        }
        catch(Exception ex) {
            Toast.makeText(Dashboard.this, R.string.app_noauth_accesstoken_ex, Toast.LENGTH_LONG).show();
            Log.e("Dashboard no auth", "Cannot complete communication to get the request token\nException: " + ex.getClass().getName() + "\nMessage: " + ex.getMessage());
        }
    }
       
    return returnFromAuth;
}&lt;/pre&gt;&lt;br /&gt;
The &lt;code&gt;Dashboard&lt;/code&gt; class definitions are available in a &lt;a href="https://gist.github.com/1009626#file_dashboard.java"&gt;gist on GitHub&lt;/a&gt;. This gist contains also a &lt;a href="https://gist.github.com/1009626#file_preferences.java"&gt;wrapper of the &lt;code&gt;SharedPreferences&lt;/code&gt; class&lt;/a&gt;, the &lt;a href="https://gist.github.com/1009626#file_android_manifest.xml"&gt;application manifest&lt;/a&gt; with the declaration of the &lt;code&gt;Intent&lt;/code&gt; for the custom return URL, and the &lt;a href="https://gist.github.com/1009626#file_main_noauth.xml"&gt;layout definition&lt;/a&gt; of the pane with the warning and the sign in button.&lt;br /&gt;
&lt;br /&gt;
&lt;span class="Apple-style-span" style="font-size: large;"&gt;OAuth Client - The quirks&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
My Android application is very simple and is configured with the &lt;i&gt;launch mode&lt;/i&gt; &lt;code&gt;singleTop&lt;/code&gt;. As such, if the system does not destroy the application when the code starts an activity to browse the Authentication service URL, the invocation of the &lt;code&gt;ase://oauthresponse&lt;/code&gt; URL by the browser should trigger a call to the &lt;code&gt;onNewIntent()&lt;/code&gt; method. It never happened during my tests and on my phone... Every time, the application is recreated and a call to &lt;code&gt;onCreate()&lt;/code&gt; is issued. So both functions delegate to the helper &lt;code&gt;checkOAuthReturn()&lt;/code&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="prettyprint" style="font-size:smaller;"&gt;@Override
protected void onNewIntent(Intent intent) {
    checkOAuthReturn(intent);
}&lt;/pre&gt;&lt;br /&gt;
In this example, I've decided to select the view to associate to the first screen of the application according to the knowledge of the OAuth access token (read from the user preferences or retrieved dynamically thanks to the verification code coming with the &lt;code&gt;ase://oauthresponse&lt;/code&gt; URL). The following snippet illustrates this flow. In some occasions, it can be better to start a separate activity if the main pane is instrumented to disable the triggers to protected actions. This approach with a separate activity is also better for the portability.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="prettyprint" style="font-size:smaller;"&gt;@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Preferences.setPreferenceContext(PreferenceManager.getDefaultSharedPreferences(getBaseContext()));

    boolean justAuthenticated = checkOAuthReturn(getIntent());
        
    if (!justAuthenticated &amp;&amp; Preferences.get(Preferences.OAUTH_KEY, "").length() == 0) {
        setContentView(R.layout.main_noauth);

        // Instrumentation of the pane to initiate the authorization process on demand
        // ...
    }
    else {
        setContentView(R.layout.main);
    }
}&lt;/pre&gt;&lt;br /&gt;
I hope this helps.&lt;br /&gt;
A+, Dom&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=xuSz8jnwCOo:IjIt3J7TgAc:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=xuSz8jnwCOo:IjIt3J7TgAc:4cEx4HpKnUU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?i=xuSz8jnwCOo:IjIt3J7TgAc:4cEx4HpKnUU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=xuSz8jnwCOo:IjIt3J7TgAc:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?i=xuSz8jnwCOo:IjIt3J7TgAc:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SharingTechnologies/~4/xuSz8jnwCOo" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://domderrien.blogspot.com/feeds/1852953982359328834/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://domderrien.blogspot.com/2011/06/oauth-authorization-handling-in-android.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/168828253523263225/posts/default/1852953982359328834?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/168828253523263225/posts/default/1852953982359328834?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SharingTechnologies/~3/xuSz8jnwCOo/oauth-authorization-handling-in-android.html" title="OAuth authorization handling in a Android application" /><author><name>Dom Derrien</name><uri>https://plus.google.com/100819130946985751629</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-CAiJ6LOHBWE/AAAAAAAAAAI/AAAAAAAAErE/twENdW648fs/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://1.bp.blogspot.com/-5DbVV9vT2v8/SZJKLxJPa4I/AAAAAAAACes/mXHa5xdMct0/s72-c/GAE-toLeft.png" height="72" width="72" /><thr:total>0</thr:total><feedburner:origLink>http://domderrien.blogspot.com/2011/06/oauth-authorization-handling-in-android.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C08ERn09eyp7ImA9WhZUEkQ.&quot;"><id>tag:blogger.com,1999:blog-168828253523263225.post-7112196750491780099</id><published>2011-06-02T21:57:00.001-04:00</published><updated>2011-06-05T12:36:47.363-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-06-05T12:36:47.363-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Technology" /><category scheme="http://www.blogger.com/atom/ns#" term="Experience" /><title>Partage d'expérience dans le développement d'applications Android</title><content type="html">&lt;span style="font-size: large;"&gt;Contexte&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;div style="padding:3px;border:1px solid darkgrey;background-color:lightgrey;"&gt;Note : Cet article  est le premier d'une série intitulée « Leçons d'un développeur  indépendant ». Le niveau de la discussion dans cet article est général. Les articles  suivant iront plus en profondeur et seront illustrés de bouts de code.&lt;/div&gt;&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-4zxmIpPirQE/TeerTISFp_I/AAAAAAAAC8M/pisVIlmJbPo/s1600/network-ASE-75.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/-4zxmIpPirQE/TeerTISFp_I/AAAAAAAAC8M/pisVIlmJbPo/s1600/network-ASE-75.png" /&gt;&lt;/a&gt;&lt;/div&gt;Dans  le cadre de mon entreprise &lt;a href="http://anothersocialeconomy.com/"&gt;AnotherSocialEconomy.com&lt;/a&gt;, j'ai eu l'occasion  de mettre en œuvre plusieurs bonnes pratiques et je vais en partager  quelques unes ici. Je vais notamment me concentrer sur les  développements autour de l'application cliente Android, depuis  l'identification des usagers jusque l'émission de notifications  asynchrones.&lt;br /&gt;
&lt;br /&gt;
AnotherSocialEconomy.com, ou ASE, offre un service connectant consommateurs et détaillants :&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;Les  consommateurs à la recherche d'un produit ou service n'ont qu'à décrire  leur demande depuis l'un des multiples points d'entrée de l'application  : une &lt;a href="http://anothersocialeconomy.com/Automobiles/index.html"&gt;page&lt;/a&gt; Web faite pour AdWords, le &lt;a href="http://anothersocialeconomy.com/"&gt;site&lt;/a&gt; de ASE ou affilié, la &lt;a href="http://apps.facebook.com/AnotherSocialEconomy"&gt;page&lt;/a&gt;  de l'application Facebook, un message direct depuis &lt;a href="http://twitter.com/"&gt;Twitter&lt;/a&gt;, etc.&lt;/li&gt;
&lt;li&gt;Les  détaillants participants sont notifiés des demandes en fonction de  leurs préférence. Les détaillants sont libres de faire une ou plusieurs  propositions en fonction de leur disponibilité.&lt;/li&gt;
&lt;li&gt;Au fur et à  mesure que les propositions sont composées, les consommateurs en sont  notifiés et peuvent à tout moment les décliner ou les confirmer.&lt;/li&gt;
&lt;li&gt;Les confirmations sont notifiés aux détaillants qui réservent alors le produit ou le service pour le consommateur.&lt;/li&gt;
&lt;li&gt;Ce dernier n'a plus qu'à payer et à en prendre possession.&lt;/li&gt;
&lt;/ul&gt;Pour faire simple : ASE connecte les consommateurs avec les détaillants qui ont les produits ou services qu'ils recherchent en inversant le processus de recherche.&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/-ldFA7WIbJLQ/Sd_WGUS8IkI/AAAAAAAACqk/jM5vfEOXfoI/s1600/s200_h_ae_gwt_java.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/-ldFA7WIbJLQ/Sd_WGUS8IkI/AAAAAAAACqk/jM5vfEOXfoI/s1600/s200_h_ae_gwt_java.png" /&gt;&lt;/a&gt;&lt;/div&gt;Le  moteur de ASE est présentement codé en Java et est hébergé sur  l'infrastructure &lt;a href="http://code.google.com/appengine"&gt;Google App Engine&lt;/a&gt;. Dans la suite de cet article, pour  généraliser le propos, le moteur de ASE est référencé en tant  qu'application serveur.&lt;br /&gt;
&lt;div style="clear: both;"&gt;&lt;/div&gt;&lt;span style="font-size: large;"&gt;La gestion des utilisateurs sur le serveur&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://openid.net/wordpress-content/themes/openid/images/logo_openid.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" src="http://openid.net/wordpress-content/themes/openid/images/logo_openid.png" /&gt;&lt;/a&gt;&lt;/div&gt;Depuis  le départ, le service des usagers de l'application serveur repose sur  &lt;a href="http://openid.net/"&gt;OpenID&lt;/a&gt;. Avec OpenID, l'identification des utilisateurs est confiée à des  services tiers de confiance (Yahoo!, Google, AOL, etc.) sans que  l'application serveur ne voit le mot de passe des utilisateurs,  seulement leur identifiant OpenID. Ce mode de gestion règle aussi  plusieurs problèmes :&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;Les utilisateurs n'ont pas à créer un énième compte pour le service ASE.&lt;/li&gt;
&lt;li&gt;Le serveur est moins à risque car il n'y a pas de mots de passe enregistré là.&lt;/li&gt;
&lt;li&gt;La gestion des sauvegardes est plus simple (toujours parce qu'il n'y a pas de mots de passe).&lt;/li&gt;
&lt;li&gt;En  cas de bris de leur mot de passe, les utilisateurs peuvent assurément  mieux s'appuyer sur les services de leur fournisseur OpenID que sur les  miens :)&lt;/li&gt;
&lt;/ul&gt;Plus tard dans le cycle de développement,  notamment parce qu'il a s'agit de développer une application Facebook,  les &lt;a href="http://developers.facebook.com/docs/authentication/"&gt;mécanismes d'identification de Facebook&lt;/a&gt;, &lt;a href="http://dev.twitter.com/pages/sign_in_with_twitter"&gt;ceux de Twitter&lt;/a&gt; et de &lt;a href="http://msdn.microsoft.com/en-us/library/bb676641.aspx"&gt;ceux de Microsoft  Live&lt;/a&gt; ont été intégrés à l'application serveur. Des trois, le mécanisme  de Twitter est le plus standardisé (OAuth), donnant aussi accès aux  données de l'utilisateur du service. Mais tous ont été intégrés de  manière à agir comme des services OpenID.&lt;br /&gt;
&lt;br /&gt;
OpenID est un  bon système d'identification pour une application cliente Web. Avec les restrictions de sécurité des navigateurs (&lt;i&gt;SSL&lt;/i&gt; et  &lt;i&gt;sandbox&lt;/i&gt;), une fois que l'identité de l'utilisateur est confirmée par un  fournisseur OpenID, tant que cette identité reste associée à la session  Web, l'envoi de données vers les navigateurs reste protégé.&lt;br /&gt;
&lt;br /&gt;
Par  contre, quand l'application cliente est native (sur un ordinateur ou  sur un téléphone mobile), il n'est pas possible de s'appuyer sur un mode  de session Web robuste comme celui des navigateurs. Aussi une  application malicieuse pourrait intercepter l'identifiant de session et  s'en servir à l'insu de l'usager. Pour se prémunir contre cette attaque,  il est souhaitable d'utiliser OAuth qui signe chaque échange entre  l'application cliente et le serveur, rendant caduc l'utilisation de  l'identifiant de session Web.&lt;br /&gt;
&lt;br /&gt;
&lt;span style="font-size: large;"&gt;L'authentification des usagers sur le client&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
Chaque  téléphone Android est associé à un utilisateur. Si la carte SIM de  l'opérateur téléphonique est changée, les données de l'utilisateur  précédent ne sont plus accessibles. Chaque application à accès à son  propre espace de stockage protégé, mais l'utilisateur peut réclamer cet  espace à tout instant. Ce n'est donc pas une solution de stockage à long  terme.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://oauth.net/images/oauth-logo.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://oauth.net/images/oauth-logo.png" /&gt;&lt;/a&gt;&lt;/div&gt;Dans le modèle d'authentification &lt;a href="http://oauth.net/"&gt;OAuth&lt;/a&gt;, les  échanges de données sont signés par l'application cliente grâce à un  jeton émis par l'application serveur. Grâce à la signature,  l'application serveur est assurée de l'identité de l'utilisateur à  chaque échange de données.&lt;br /&gt;
&lt;br /&gt;
Pour avoir un jeton, le protocole à observer par l'application cliente est relativement simple :&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;Émettre une requête pour recevoir un premier jeton dit d'accès.&lt;/li&gt;
&lt;li&gt;Ce jeton est utilisé pour initier un appel vers une page d'autorisation.&lt;/li&gt;
&lt;li&gt;L'application  serveur présente alors une page d'identification où l'utilisateur doit,  s'il n'est pas déjà authentifié, entrer son identifiant et son mot de  passe, puis accepter que l'application cliente accède aux données qui  sont gérées par l'application serveur.&lt;/li&gt;
&lt;li&gt;L'application serveur  retourne un second jeton attestant de l'acceptation par l'utilisateur de  l'accès aux données. Ce jeton a une durée de vie limitée.&lt;/li&gt;
&lt;li&gt;Ce  second jeton peut être utilisé pour obtenir deux jetons (clé publique et  clé secrète) qui permettront à l'application cliente de signer les  échanges de données de telle sorte que l'application serveur les  associera à l'utilisateur concerné.&lt;/li&gt;
&lt;li&gt;Souvent ces deux jetons ont  une grande durée de vie (pas d'expiration dans le cas de Twitter), et  peuvent donc être sauvegardés par l'application cliente pour signer de  manière transparente tous les futurs échanges.&lt;/li&gt;
&lt;li&gt;Il faut cependant  tenir compte que l'utilisateur peut révoquer ces deux jetons n'importe  quand, ou qu'ils peuvent expirer n'importe quand (à cause d'un  changement de stratégie du côté de l'application serveur, par exemple)  aussi il faut être prêt à exécuter le processus pour obtenir deux  nouveaux jetons à n'importe quel moment.&lt;/li&gt;
&lt;/ul&gt;Il est  important de noter que la sauvegarde des jetons d'authentification doit  être très sécuritaire. Il n'est pas acceptable de les sauvegarder dans un simple fichier texte situé &amp;nbsp;sur une carte d'extension mémoire par exemple. Si le risque d'accès à  ces jetons est trop grand, il faut mieux rejouer le scénario ci-dessus  pour obtenir un nouveau jeu de jetons.&lt;br /&gt;
&lt;br /&gt;
Au moment où j'écris cet article, le matériel de la série Samsumg S et la tablette Motorola Xoom ont des systèmes de fichiers  encryptés. À ma connaissance, même Android 3.1 n'offre toujours pas de solution  bas niveau de sécurité maximale...&lt;br /&gt;
&lt;br /&gt;
&lt;span style="font-size: large;"&gt;La réception des notifications asynchrones&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
Si  de plus en plus de fondeurs de silicium mettent l'accent sur la  puissance du processeur central (&lt;a href="http://androidandme.com/2011/05/carriers/verizon/video-qualcomm-wants-to-show-you-why-their-single-core-processor-is-better-than-the-dual-core-competition/"&gt;Qualcomm&lt;/a&gt;) et leur nombre (&lt;a href="http://androidandme.com/2011/05/news/nvidia-shows-off-the-power-of-kal-el-in-4-minutes-of-video-glory/"&gt;NVidia&lt;/a&gt; vient  d'annoncer un Tegra avec 4 cœurs), si l'augmentation de la bande  passante (de HPSA+ à LTE par exemple) permet des échanges de données de  plus en plus rapide même loin de tout réseau informatique, la capacité  énergétique des téléphones portables modernes reste leur point faible.  Par le passé, j'ai eu des téléphones Nokia et Sony Ericsson capables de  rester en veille plus d'une semaine. Maintenant, je dois brancher mon  téléphone HTC Desire chaque soir, et cela même avec une navigation somme toute  restreinte !&lt;br /&gt;
&lt;br /&gt;
Dans ces conditions, maintenir une application éveillée pour pouvoir interroger l'application serveur à intervalles réguliers (technique dite de &lt;i&gt;polling&lt;/i&gt;) est à  proscrire.&lt;br /&gt;
&lt;br /&gt;
Il y a deux ans, en développant une application pour la plate-forme BlackBerry 5, j'ai utilisé la technique suivante :&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;L'application client sur le téléphone écoutait un certain nombre de  messages du système (changement de type de réseau, perte du réseau,  etc.) et les colligeait dans une base de données interne.&lt;/li&gt;
&lt;li&gt;C'était l'application serveur qui décidait du moment de  transmission de ces données statistiques en envoyant un SMS à chaque  téléphone.&lt;/li&gt;
&lt;li&gt;À la réception de ce SMS, l'application client ouvrait une connexion HTTP pour transmettre en rafale ses données colligés.&lt;/li&gt;
&lt;li&gt;Une fois l'ensemble de données rapatriés de chaque téléphone,  l'application serveur établissaient des rapports de couverture pour l'opérateur.&lt;/li&gt;
&lt;/ul&gt;Depuis la version 2.2, il existe le protocole AC2DM: &lt;i&gt;&lt;a href="http://code.google.com/android/c2dm/"&gt;Android  Cloud to Device Messaging&lt;/a&gt;&lt;/i&gt;. Quand une application cliente configurée pour  AC2DM s'initialise, elle doit s'enregistrer auprès du serveur local  AC2DM et reçoit en retour un identifiant d'enregistrement. C'est la  responsabilité de l'application cliente d'envoyer cet identifiant à  l'application serveur pour que celle-ci ait la clé pour envoyer les  notifications asynchrones à cette application cliente, et à elle seule.&lt;br /&gt;
&lt;br /&gt;
Quelque  part, l'approche du AC2DM est semblable à ma méthode d'activation par  SMS. Il se peut même qu'elle utilise en sous main cette technique ;) La  principale différence réside dans l'aspect service : avec AC2DM,  l'application cliente n'a pas à rester active pour recevoir les  notifications, c'est le serveur local de notifications qu'il l'activera  au besoin.&lt;br /&gt;
&lt;br /&gt;
&lt;span style="font-size: large;"&gt;L'application pour les consommateurs&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
L'application cliente pour les consommateurs doit offrir plusieurs fonctionnalités :&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;recevoir les notifications concernant les demandes et les propositions en attente&lt;/li&gt;
&lt;li&gt;gérer la liste des demandes et propositions en attente&lt;/li&gt;
&lt;li&gt;créer de nouvelles demandes&lt;/li&gt;
&lt;li&gt;avec un accès au carnet d'adresses du téléphone pour pouvoir inclure ses « amis » en copie des demandes&lt;/li&gt;
&lt;li&gt;avec un accès au système de localisation géographique du téléphone pour faciliter la création des demandes &lt;/li&gt;
&lt;li&gt;modifier ou annuler des demandes en attente&lt;/li&gt;
&lt;li&gt;confirmer ou annuler des propositions en attente&lt;/li&gt;
&lt;/ul&gt;Le principal objectif de l'application cliente sur les  téléphones mobiles est le relais des notifications de mise-à-jour de demande, en réaction à la réception de nouvelles propositions ou de  modifications de proposition de la part de détaillants. En quelques «  clics », l'utilisateur doit pouvoir accéder rapidement au détail de la  demande concernée, au détail de la proposition et à des informations sur  le magasin ou bureau du détaillant. Pour faciliter cet accès, la plupart  des informations sont sauvegardées sur le téléphone au fur et à mesure  qu'elles sont requises. Pour garder une structure proche du modèle de  données produit par l'application serveur, le stockage utilisé est le  service de base de données interne du mobile (SQLite sur Android, par  exemple).&lt;br /&gt;
&lt;br /&gt;
&lt;span style="font-size: large;"&gt;L'application pour les détaillants&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
L'application cliente pour les consommateurs doit offrir plusieurs fonctionnalités :&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;recevoir les notifications de nouvelles demandes&lt;/li&gt;
&lt;li&gt;créer et gérer des propositions (possiblement avec un accès à la caméra pour scanner les codes barre)&lt;/li&gt;
&lt;li&gt;confirmer les livraisons&lt;/li&gt;
&lt;li&gt;gérer la liste des demandes et propositions en attente&lt;/li&gt;
&lt;/ul&gt;Parce que les services offerts aux consommateurs sont très  différents de ceux offerts aux détaillants, ils sont proposés dans deux  applications différentes. Cela réduit les risques de confusion de contexte pour les utilisateurs agissant autant en tant que consommateur que détaillant.&lt;br /&gt;
&lt;br /&gt;
&lt;span class="Apple-style-span" style="font-size: large;"&gt;À suivre...&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
Dans les prochains articles, je décrirai en détail les différentes implantations que j'ai réalisées. Il y a plusieurs techniques qui ne sont pas évidentes, comme celle gérant l'authentification avec OAuth, et j'imagine que cela sera utile à bien des développeurs ;)&lt;br /&gt;
&lt;br /&gt;
A+, Dom&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=5R2bgi4wjkI:jD2HnQeixTI:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=5R2bgi4wjkI:jD2HnQeixTI:4cEx4HpKnUU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?i=5R2bgi4wjkI:jD2HnQeixTI:4cEx4HpKnUU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=5R2bgi4wjkI:jD2HnQeixTI:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?i=5R2bgi4wjkI:jD2HnQeixTI:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SharingTechnologies/~4/5R2bgi4wjkI" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://domderrien.blogspot.com/feeds/7112196750491780099/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://domderrien.blogspot.com/2011/06/partage-dexperience-dans-le.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/168828253523263225/posts/default/7112196750491780099?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/168828253523263225/posts/default/7112196750491780099?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SharingTechnologies/~3/5R2bgi4wjkI/partage-dexperience-dans-le.html" title="Partage d'expérience dans le développement d'applications Android" /><author><name>Dom Derrien</name><uri>https://plus.google.com/100819130946985751629</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-CAiJ6LOHBWE/AAAAAAAAAAI/AAAAAAAAErE/twENdW648fs/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://4.bp.blogspot.com/-4zxmIpPirQE/TeerTISFp_I/AAAAAAAAC8M/pisVIlmJbPo/s72-c/network-ASE-75.png" height="72" width="72" /><thr:total>0</thr:total><feedburner:origLink>http://domderrien.blogspot.com/2011/06/partage-dexperience-dans-le.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D08FQHw8fCp7ImA9WhZRGUs.&quot;"><id>tag:blogger.com,1999:blog-168828253523263225.post-6599301892451796655</id><published>2011-04-16T10:56:00.000-04:00</published><updated>2011-04-16T10:56:51.274-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-04-16T10:56:51.274-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Technology" /><category scheme="http://www.blogger.com/atom/ns#" term="Experience" /><title>Google App Engine, scheduled tasks, and persisting changes into the datastore: the risk of a race condition</title><content type="html">This post is about a race condition I've accidentally discovered and hopefully fixed. It occurred in App Engine and was generated by tasks I created for immediate execution...&lt;br /&gt;
&lt;br /&gt;
&lt;span style="font-size: large;"&gt;Context&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
When I started developing in Java for Google App Engine, I decided to give a try with JDO, mainly because it is &lt;a href="http://db.apache.org/jdo/jdo_v_jpa.html"&gt;datastore agnostic&lt;/a&gt; (*). Operations managing my entities are organized in DAOs with set of methods like the followings.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="prettyprint"&gt;public Demand update(Demand demand) {
    PersistenceManager pm = getPersistenceManager();
    try {
      return update(pm, demand);
    }
    finally {
        pm.close();
    }
}

public Demand update(PersistenceManager pm, Demand demand) {
    // Check if this instance comes from memcache
    ObjectState state = JDOHelper.getObjectState(consumer);
    if (ObjectState.TRANSIENT.equals(state)) {
        // Get a fresh copy from the data store
        ...
        // Merge old copy attributes into the fresh one
        ...
    }
    // Persists the changes
    return pm.makePersistent(demand);
}&lt;/pre&gt;&lt;br /&gt;
I knew that changes are persisted only when the &lt;i&gt;PersistenceManager&lt;/i&gt; is closed; closing it after an update is safe attitude. I decided anyway to separate the &lt;i&gt;PersistenceManager&lt;/i&gt; instance management from the business logic updating the entity for clarity.&lt;br /&gt;
&lt;br /&gt;
This decision offers the additional benefit of being able to share &lt;i&gt;PersistenceManager&lt;/i&gt; instance with many operations. The following code snippet illustrates my point: a unique &lt;i&gt;PersistenceManager&lt;/i&gt; instance is used for two entity loads and one save.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="prettyprint"&gt;public void processDemandUpdateCommand(Long demandKey, JsonObject command, Long ownerKey) throws ... {
    PersistenceManager pm = getPersistenceManager();
    try {
        // Get the identified demand (can come from the memcache)
        Demand demand = getDemandOperations().getDemand(pm, demandKey, ownerKey);

        // Check if the demand's location is changed
        if (command.contains(Location.POSTAL_CODE) || command.contains(Location.COUNTRY_CODE) {
            Location location = getLocationOperations().getLocation(pm, command);
            if (!location.getKey().equals(demand.getLocationKey())) {
                command.put(Demand.LOCATION_KEY, location.getKey());
            }
        }

        // Merge the changes
        demand.fromJson(command);

        // Validate the demand attributes
        ...

        // Persist them
        demand = getDemandOperations().updateDemand(pm, demand);

        // Report the demand state to the owner
        ...
    }
    finally {
        pm.close();
    }
}&lt;/pre&gt;&lt;br /&gt;
For my service &lt;a href="http://anothersocialeconomy.com/"&gt;AnotherSocialEconomy&lt;/a&gt; which connects Consumers to Retailers, the life cycle for a Demand is made of many steps:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;State &lt;code&gt;open&lt;/code&gt;: raw data just submitted by a Consumer;&lt;/li&gt;
&lt;li&gt;State &lt;code&gt;invalid&lt;/code&gt;: one verification step failed, requires an update from the Consumer;&lt;/li&gt;
&lt;li&gt;State &lt;code&gt;published&lt;/code&gt;: verification is OK, and Demand broadcasted to Retailers;&lt;/li&gt;
&lt;li&gt;State &lt;code&gt;confirmed&lt;/code&gt;: Consumer confirmed one Proposal; Retailer reserves the product for pick-up, or delivers it;&lt;/li&gt;
&lt;li&gt;State &lt;code&gt;closed&lt;/code&gt;: Consumer notified the system that the transaction is closed successfully;&lt;/li&gt;
&lt;li&gt;State &lt;code&gt;cancelled&lt;/code&gt;: ...&lt;/li&gt;
&lt;li&gt;State &lt;code&gt;expired&lt;/code&gt;: ...&lt;/li&gt;
&lt;/ul&gt;&lt;br /&gt;
In my system, some operations take time:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;Because of some congestion in the environment, which occurs sometimes when sending e-mails.&lt;/li&gt;
&lt;li&gt;Because some operations require a large data set to be processed–like when a Demand has to be broadcasted to selected Retailers.&lt;/li&gt;
&lt;/ul&gt;&lt;br /&gt;
Because this time constraint and the &lt;a href="http://code.google.com/appengine/docs/java/runtime.html#The_Request_Timer"&gt;30 second limit&lt;/a&gt;, I decided to use tasks extensively (tasks can run for &lt;a href="http://code.google.com/appengine/docs/java/taskqueue/overview.html#Task_Execution"&gt;10 minutes&lt;/a&gt;). In some ways, my code is very modular now, easier to maintain and test.&lt;br /&gt;
&lt;br /&gt;
So I updated my code to trigger a validation task once the Demand has been updated with the raw data submitted by the Consumer. The code snippet shows the task scheduling in the context of the &lt;code&gt;processDemandUpdate()&lt;/code&gt; method illustrated above.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="prettyprint"&gt;public void processDemandUpdateCommand(Long demandKey, JsonObject command, Long ownerKey) throws ... {
    PersistenceManager pm = getPersistenceManager();
    try {
        ...

        // Update the state so the entity is ready for the validation process
        demand.setState(State.OPEN);

        // Persist them
        demand = getDemandOperations().updateDemand(pm, demand);

        &lt;b&gt;// Create a task for that demand validation
        getQueue().add(
            withUrl("/_tasks/validateOpenDemand").
                param(Demand.KEY, demandKey.toString()).
                method(Method.GET)
        );&lt;/b&gt;
    }
    finally {
        pm.close();
    }
}&lt;/pre&gt;&lt;br /&gt;
&lt;span style="font-size: large;"&gt;Issue&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
Until I activated the &lt;a href="http://code.google.com/appengine/docs/adminconsole/instances.html#Always_On"&gt;Always On&lt;/a&gt; feature, no issue has been reported for that piece of code: my unit tests worked as expected, my smoke tests were fine, the live site behaved correctly, etc.&lt;br /&gt;
&lt;br /&gt;
Then the issue started to appear randomly: &lt;b&gt;sometimes, updated Demand instances were not processed by the validation task anymore!&lt;/b&gt; A manual trigger of this task from a browser or &lt;code&gt;curl&lt;/code&gt; had however the expected result...&lt;br /&gt;
&lt;br /&gt;
For the task to be &lt;a href="http://code.google.com/appengine/docs/java/taskqueue/overview.html#Task_Execution"&gt;idempotent&lt;/a&gt;, the state of the Demand instance to be validated is checked: if set with &lt;code&gt;open&lt;/code&gt;, the Demand attributes are checked with the result of the state being set with &lt;code&gt;invalid&lt;/code&gt; or &lt;code&gt;published&lt;/code&gt;. Otherwise nothing happens. With that approach, Demands already validated are not processed a second time...&lt;br /&gt;
&lt;br /&gt;
What occurred?&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;Without the Always On feature activated, because of the low traffic in my application, the infrastructure was delaying the process of the validation task a bit and it was executed once the request process finished.&lt;/li&gt;
&lt;li&gt;Thanks to that soft process serialization, the datastore update commanded by the instruction &lt;code&gt;pm.close()&lt;/code&gt; had all chances to be completed before the start of the validation task!&lt;/li&gt;
&lt;li&gt;With the Always On feature activated, the infrastructure had much more chance to get one of the two other application instances to process the validation task... which could happen before the datastore update...&lt;/li&gt;
&lt;li&gt;As it started before the datastore update, the validation task found a task in the state set by the previous run of the task for this Demand instance: &lt;code&gt;invalid&lt;/code&gt; or &lt;code&gt;published&lt;/code&gt;. Then it exited without reporting any error.&lt;/li&gt;
&lt;/ul&gt;&lt;br /&gt;
&lt;span style="font-size: large;"&gt;Solutions&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;The ugly one:&lt;/b&gt;&lt;br /&gt;
Add a delay before executing the task with the &lt;code&gt;countdownMillis()&lt;/code&gt; method.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="prettyprint"&gt;// Create a task for that demand validation
        getQueue().add(
            withUrl("/_tasks/validateOpenDemand").
                param(Demand.KEY, demandKey.toString()).
                method(Method.GET).
                &lt;b&gt;countdownMillis(2000)&lt;/b&gt;
        );
    }
    finally {
        pm.close();
    }
}&lt;/pre&gt;&lt;br /&gt;
&lt;b&gt;A tricky one:&lt;/b&gt;&lt;br /&gt;
Use &lt;a href="http://code.google.com/appengine/docs/java/memcache/overview.html"&gt;memcache&lt;/a&gt; to store a copy of the Demand, which the validation will use instead of the reading it from the datastore. Because there's no warranty that your entity won't be evicted before the the run of the validation task, this is not a solution I can recommend.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;The simplest one:&lt;/b&gt;&lt;br /&gt;
Move the code scheduling the code outside the &lt;code&gt;try...finally...&lt;/code&gt; block. The task will be scheduled only if the updates of the Demand instance have been persisted.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="prettyprint"&gt;public void processDemandUpdateCommand(Long demandKey, JsonObject command, Long ownerKey) throws ... {
    PersistenceManager pm = getPersistenceManager();
    try {
        ...

        // Update the state so the entity is ready for the validation process
        demand.setState(State.OPEN);

        // Persist them
        demand = getDemandOperations().updateDemand(pm, demand);
    }
    finally {
        &lt;b&gt;pm.close();&lt;/b&gt;
    }

    // Create a task for that demand validation
    getQueue().add(
        withUrl("/_tasks/validateOpenDemand").
            param(Demand.KEY, demandKey.toString()).
            method(Method.GET)
    );
}&lt;/pre&gt;&lt;br /&gt;
&lt;b&gt;The most robust one:&lt;/b&gt;&lt;br /&gt;
Wrap everything withing a &lt;a href="http://code.google.com/appengine/docs/java/datastore/transactions.html"&gt;transaction&lt;/a&gt;. When a task is scheduled within a transaction, it's really &lt;a href="http://code.google.com/appengine/docs/java/taskqueue/overview.html#Tasks_Within_Transactions"&gt;enqueued when the transaction is committed&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
Be aware that adopting this solution may require a major refactoring.&lt;br /&gt;
&lt;br /&gt;
&lt;span style="font-size: large;"&gt;Conclusion&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
Now I understand the issue, I'm a bit ashamed of it. For my defense, I should say the defect has been introduced as part of an iteration which came with a series of unit tests. Before the activation of the Always On feature, it stayed undetected, and later it occurred only rarely.&lt;br /&gt;
&lt;br /&gt;
Anyway, verifying the impact of all calls to external tasks before persisting any changes is one point in my review check list.&lt;br /&gt;
&lt;br /&gt;
I hope this helps,&lt;br /&gt;
A+, Dom&lt;br /&gt;
&lt;br /&gt;
--&lt;br /&gt;
&lt;span style="font-size: large;"&gt;Notes:&lt;/span&gt;&lt;br /&gt;
* These days, I would start my application with &lt;a href="http://code.google.com/p/objectify-appengine/"&gt;Objectify&lt;/a&gt;. This &lt;a href="http://borglin.net/gwt-project/?page_id=491"&gt;blog post&lt;/a&gt; summarizes many arguments I agree on too in favor to Objectify.&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=He_7xM9ZpKE:522aOFyZ-iY:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=He_7xM9ZpKE:522aOFyZ-iY:4cEx4HpKnUU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?i=He_7xM9ZpKE:522aOFyZ-iY:4cEx4HpKnUU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=He_7xM9ZpKE:522aOFyZ-iY:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?i=He_7xM9ZpKE:522aOFyZ-iY:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SharingTechnologies/~4/He_7xM9ZpKE" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://domderrien.blogspot.com/feeds/6599301892451796655/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://domderrien.blogspot.com/2011/04/google-app-engine-scheduled-tasks-and.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/168828253523263225/posts/default/6599301892451796655?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/168828253523263225/posts/default/6599301892451796655?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SharingTechnologies/~3/He_7xM9ZpKE/google-app-engine-scheduled-tasks-and.html" title="Google App Engine, scheduled tasks, and persisting changes into the datastore: the risk of a race condition" /><author><name>Dom Derrien</name><uri>https://plus.google.com/100819130946985751629</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-CAiJ6LOHBWE/AAAAAAAAAAI/AAAAAAAAErE/twENdW648fs/s512-c/photo.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://domderrien.blogspot.com/2011/04/google-app-engine-scheduled-tasks-and.html</feedburner:origLink></entry><entry gd:etag="W/&quot;AkICRHo9fCp7ImA9WhZRGE0.&quot;"><id>tag:blogger.com,1999:blog-168828253523263225.post-6833757880225685415</id><published>2011-04-14T11:26:00.002-04:00</published><updated>2011-04-14T15:16:05.464-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-04-14T15:16:05.464-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Entrepreneurship" /><category scheme="http://www.blogger.com/atom/ns#" term="Experience" /><title>State of the AnotherSocialEconomy Initiative</title><content type="html">When my partner Steven and I started our startup adventure a few years ago, our main goal was to demonstrate our ability to convert an idea into a live project. As we used our experience to build a viable product, we knew it would add value to our resume.&lt;br /&gt;
&lt;br /&gt;
Over the months, the project evolved slowly:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;The core idea is: help the consumers who look for a specific product to find the retailer who has it in stock, and help retailers to connect with consumers&amp;nbsp;online&amp;nbsp;and drive them in-store. Our moto: &lt;i&gt;the missing link between shopping online and buying offline&lt;/i&gt;.&lt;/li&gt;
&lt;li&gt;The proof-of-concept was made of screenshots, live Twitter accounts, and a piece of Python code connecting those accounts together. This material allowed us to be among the semi-finalist companies of&amp;nbsp;&lt;a href="http://stevenmilstein.com/2009/09/16/homage-to-techcrunch50-2009-its-organizers-and-participants/"&gt;TechCrunch50 in 2009&lt;/a&gt;!&lt;/li&gt;
&lt;li&gt;The first implementation of the engine connected consumers and retailers, each of them interacting with the system with direct messages (DMs), sent&amp;nbsp; from their own Twitter account. At that time, the tool was named &lt;i&gt;Twetailer&lt;/i&gt;.&lt;/li&gt;
&lt;li&gt;Later, we figured out Twitter was too geeky and we added a connector to accept and generate e-mails. Since then, the engine has a XMPP (instant messaging) connector, another one for &lt;a href="http://apps.facebook.com/anothersocialeconomy/?ref=bookmarks&amp;amp;count=0"&gt;Facebook&lt;/a&gt;, and a plan for VOIP (with Twilio).&lt;/li&gt;
&lt;li&gt;At one point, we were approached to start an experiment for golfers: usually avid golfers have to spend a lot of time on the phone to get three buddies to play with and to book a tee-time. In two months, we created &lt;i&gt;&lt;a href="http://eztoff.com/"&gt;ezToff.com&lt;/a&gt;&lt;/i&gt;, developed an embeddable widget to ease the creation of a tee-off request, and developed a Web console for the golf course staff. The experiment was shut down because of a lack of traction...&lt;/li&gt;
&lt;li&gt;Recently, we started another experiment in the &lt;a href="http://anothersocialeconomy.com/CarDealers/index.html"&gt;used car market&lt;/a&gt;, under the name &lt;i&gt;&lt;a href="http://anothersocialeconomy.com/"&gt;AnotherSocialEconomy&lt;/a&gt;&lt;/i&gt;.&amp;nbsp;Our market researches found that the average time to buy a used car is six weeks&amp;nbsp;in Quebec. Typically, consumers start on the Web, grab listings, and call dealerships one after the other. In the Montreal area, there are 300,000 pre-owned cars bought per month: ⅓ from dealerships, ⅓ from wholesalers, and ⅓ from individuals. Dealerships control 45% of the market value.&lt;/li&gt;
&lt;li&gt;So far, this experiment has been a partial success: we get demands from consumers and forward proposals from used car sales&amp;nbsp;people. We helped our first customer finding a car in only two weeks! But, as the dealership staff is not used to new technologies (sic), we manage the service for them and it's very time consuming...&lt;/li&gt;
&lt;/ul&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;What's next?&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;
&lt;/div&gt;&lt;div&gt;We are very happy with consumers trusting us. We work hard to continue to improve their experience, on our landing pages and in our communication by e-mail. The priority is to have them qualifying more their demands upfront.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;
&lt;/div&gt;&lt;div&gt;Our focus right now is more on the retailer-side, in order to have sales people in the dealerships interacting with the system by e-mail too. If around 80% of them agree to work with us to serve our users, we have to prepare the proposal details and to reach them out for approval. For the business to scale, they should prepare and post the proposals themselves.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;
&lt;/div&gt;&lt;div&gt;For now, we need more data to determine trends. This is a prerequisite&amp;nbsp;for used car dealers to adopt our methodology. It is also possible that will lead to another pivot.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;
&lt;/div&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;Lessons learned?&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
The first one is an obvious one now: nobody can be as committed as the founders! Since I left the company Compuware to become a full time entrepreneur, Steven and I have met many people we expected to work/partner with: a technology company CEO, a former manufacture owner and now real estate agent, a UX designer, a few VCs, a marketer, two successful startup founders, etc. If we sometimes got excellent feedback, none joined us.&lt;br /&gt;
&lt;br /&gt;
The second one is related to the product development: two techies are not enough to make a great product! They can talk about their product at length, but they don't know how to convince decision makers. They need the help of a marketing&amp;nbsp;genius!&lt;br /&gt;
&lt;br /&gt;
Another one is related to the importance of the contact network. If you don't know the right people, very few will listen to you. Having a large address book or friends with deep pockets definitively helps a lot.&lt;br /&gt;
&lt;br /&gt;
And the last one: developing a tool for the general public is difficult! Following the Lean Startup process can really help. Check Ash Maurya's &lt;a href="http://www.ashmaurya.com/2010/02/customer-development-checklist-for-my-web-startup-part-1/"&gt;blog&lt;/a&gt;, for example.&lt;br /&gt;
&lt;br /&gt;
&lt;span class="Apple-style-span" style="font-size: large;"&gt;Technologies learned?&lt;/span&gt;&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;a href="http://1.bp.blogspot.com/-3CHEdHSEbnA/SZJKMKPkCEI/AAAAAAAACe0/osCFBEsBFR4/s1600/GAE-toRight.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://1.bp.blogspot.com/-3CHEdHSEbnA/SZJKMKPkCEI/AAAAAAAACe0/osCFBEsBFR4/s1600/GAE-toRight.png" /&gt;&lt;/a&gt;&lt;/div&gt;I continue to find Google App Engine an awesome environment. The recent addition of the &lt;a href="http://code.google.com/appengine/docs/java/channel/overview.html"&gt;Channel API&lt;/a&gt; which allows the back-end logic to push asynchronous notifications into Web consoles really improves the user experience. On the maintenance side, I appreciate the Java &lt;a href="http://code.google.com/appengine/docs/java/tools/remoteapi.html"&gt;Remote API&lt;/a&gt; which simplifies the development of maintenance and data extraction tasks.&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;a href="http://4.bp.blogspot.com/-Pjw5EBQjxmE/TacPOfOIgII/AAAAAAAAC7Q/zrz3T8IsPlc/s1600/Selection_009.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/-Pjw5EBQjxmE/TacPOfOIgII/AAAAAAAAC7Q/zrz3T8IsPlc/s1600/Selection_009.png" /&gt;&lt;/a&gt;&lt;/div&gt;Web console side, I've started upgrading the code to &lt;a href="http://dojotoolkit.org/reference-guide/releasenotes/1.6.html"&gt;Dojo 1.6&lt;/a&gt; and its new HTML5 compliant syntax. I don't use the AMD loader yet, but I'm waiting for the one coming with 1.7. I have recently started to use &lt;a href="http://code.google.com/p/selenium/wiki/ArchitecturalOverview"&gt;Selenium 2&lt;/a&gt; for my smoke tests and I really like it!&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-mol70Yhy8U4/TacO6WNVH3I/AAAAAAAAC7M/mSqfz0Zu_to/s1600/Selection_010.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://1.bp.blogspot.com/-mol70Yhy8U4/TacO6WNVH3I/AAAAAAAAC7M/mSqfz0Zu_to/s1600/Selection_010.png" /&gt;&lt;/a&gt;&lt;/div&gt;Mobile side, I wish to have more spare time to update &lt;a href="https://github.com/domderrien/dretailer/"&gt;my Android application&lt;/a&gt; for &lt;i&gt;ezToff&lt;/i&gt; and to benefit from the Android Cloud To Device Messaging (&lt;a href="http://android-developers.blogspot.com/2010/05/android-cloud-to-device-messaging.html"&gt;C2DM&lt;/a&gt;) API. But I'm also thinking of building application with the awesome&amp;nbsp;&lt;a href="http://davidwalsh.name/dojox-mobile"&gt;dojox.mobile&lt;/a&gt;.&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;a href="http://upload.wikimedia.org/wikipedia/commons/f/fd/Adwords_logo.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" src="http://upload.wikimedia.org/wikipedia/commons/f/fd/Adwords_logo.png" /&gt;&lt;/a&gt;&lt;/div&gt;To develop our customer base in the used car experiment, we have created two &lt;a href="https://adwords.google.com/"&gt;AdWords&lt;/a&gt; campaigns: one for each language, both in the Montreal area. Using AdWords and optimizing the campaigns was very instructive. There are many concepts to master: &lt;a href="http://www.sayu.co.uk/keyword-expansion-white-paper-1.html"&gt;long tail&lt;/a&gt;, auto bid, average CPC, conversion rate, landing page quality score, etc. I know understand why so many people choose to become an AdWords certified partner ;)&lt;br /&gt;
&lt;ul&gt;&lt;/ul&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=HmQslE5ZSz8:s4E25dIy9Wo:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=HmQslE5ZSz8:s4E25dIy9Wo:4cEx4HpKnUU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?i=HmQslE5ZSz8:s4E25dIy9Wo:4cEx4HpKnUU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=HmQslE5ZSz8:s4E25dIy9Wo:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?i=HmQslE5ZSz8:s4E25dIy9Wo:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SharingTechnologies/~4/HmQslE5ZSz8" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://domderrien.blogspot.com/feeds/6833757880225685415/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://domderrien.blogspot.com/2011/04/state-of-anothersocialeconomy.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/168828253523263225/posts/default/6833757880225685415?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/168828253523263225/posts/default/6833757880225685415?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SharingTechnologies/~3/HmQslE5ZSz8/state-of-anothersocialeconomy.html" title="State of the AnotherSocialEconomy Initiative" /><author><name>Dom Derrien</name><uri>https://plus.google.com/100819130946985751629</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-CAiJ6LOHBWE/AAAAAAAAAAI/AAAAAAAAErE/twENdW648fs/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://1.bp.blogspot.com/-3CHEdHSEbnA/SZJKMKPkCEI/AAAAAAAACe0/osCFBEsBFR4/s72-c/GAE-toRight.png" height="72" width="72" /><thr:total>0</thr:total><feedburner:origLink>http://domderrien.blogspot.com/2011/04/state-of-anothersocialeconomy.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D0YDRXg-fSp7ImA9Wx9XE0o.&quot;"><id>tag:blogger.com,1999:blog-168828253523263225.post-5610405347014299744</id><published>2011-01-06T23:39:00.000-05:00</published><updated>2011-01-06T23:39:34.655-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-01-06T23:39:34.655-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Technology" /><category scheme="http://www.blogger.com/atom/ns#" term="Entrepreneurship" /><category scheme="http://www.blogger.com/atom/ns#" term="Experience" /><category scheme="http://www.blogger.com/atom/ns#" term="Community" /><title>Wrapping up 2010, preparing 2011</title><content type="html">&lt;span style="font-size: large;"&gt;2010 Summary&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
2010 was an interesting year for me professionally. Inspired by similar lists online, I present what I did (or can remember at least):&lt;br /&gt;
&lt;ol&gt;&lt;li&gt;Left &lt;a href="http://www.compuware.com/"&gt;Compuware&lt;/a&gt; and joined my partner Steven at &lt;a href="http://milstein-assoc.com/"&gt;Milstein and Associates inc.&lt;/a&gt; end-of-January, to focus 150% of my time on &lt;a href="http://anothersocialeconomy.com/"&gt;AnotherSocialEconomy (&lt;/a&gt;formely known as Twetailer).&lt;/li&gt;
&lt;li&gt;Adapted the &lt;a href="http://domderrien.blogspot.com/2010/03/httpdomderrien-preblogspotcom201003amaz.html"&gt;Amazon Flexible Payment Service (FPS) library&lt;/a&gt; to the App Engine environment—freely available on &lt;a href="https://github.com/DomDerrien/amazon-fps-gaej/"&gt;github&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Refactored the communication layer to be able to send details e-mails to customers, in addition to short ones sent over Twitter and Instant Messaging services.&lt;/li&gt;
&lt;li&gt;Built the first Web consoles for Golf players and Golf courses staff, based on Dojo and using the freshly delivered REST API—check &lt;a href="http://eztoff.com/"&gt;ezToff.com&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Built the first Android application for Golf players using their GPS &amp;amp; Address book to ease the tee-off booking process with AnotherSocialEconomy—freely available on &lt;a href="https://github.com/DomDerrien/dretailer/"&gt;github&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Helped preparing pitches to Golf Canada representatives and to Golf staff members and owners.&lt;/li&gt;
&lt;li&gt;Developed the AnotherSocialEconomy &lt;a href="http://anothersocialeconomy.com/whats-with-widgets/"&gt;widget&lt;/a&gt;, ready to be embedded in participant websites and loading the AnotherSocialEconomy wizard on demand&lt;/li&gt;
&lt;li&gt;Reviewed the book &lt;a href="http://domderrien.blogspot.com/2010/12/reviewed-book-google-app-engine-java.html"&gt;Google App Engine Java and GWT Application Development&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Continued to develop my open-sourced library offering tools for globalizable generic resource bundles (TMX)—on &lt;a href="https://github.com/DomDerrien/two-tiers-utils/"&gt;github&lt;/a&gt; too.&lt;/li&gt;
&lt;li&gt;Developed a prototype of a &lt;a href="http://apps.facebook.com/anothersocialeconomy/"&gt;Facebook application&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Augmented the AnotherSocialEconomy engine to support the used car dealers: buyers don't buy immediately, but collect car information and offers for a while before committing with one dealer =&amp;gt; the engine work flow has been adapted to support this slower path of interaction.&lt;/li&gt;
&lt;li&gt;Attended presentations to few car dealerlship owners.&lt;/li&gt;
&lt;li&gt;Attended meetings with various mentors and potential investors.&lt;/li&gt;
&lt;li&gt;Attended meetings of &lt;a href="http://www.mtlnewtech.com/"&gt;Montreal NewTech&lt;/a&gt;, &lt;a href="http://www.android-montreal.com/"&gt;Android Montreal&lt;/a&gt;, &lt;a href="http://www.meetup.com/Realite-Augmentee-Montreal-Augmented-Reality/"&gt;Augmented Reality Montreal&lt;/a&gt; communities&lt;/li&gt;
&lt;/ol&gt;&lt;br /&gt;
I’m pretty happy with what I have done so far and am looking forward to doing even more.&lt;br /&gt;
&lt;br /&gt;
&lt;span style="font-size: large;"&gt;New technologies&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
It was also fun to play around some hot new technologies:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;Ubuntu 10.04 and 10.10&lt;/li&gt;
&lt;li&gt;Android 2.2 and push mechanism on my HTC Desire &lt;/li&gt;
&lt;li&gt;App Engine 1.4.0 and channel api&lt;/li&gt;
&lt;li&gt;Node.js and WebSockets&lt;/li&gt;
&lt;/ul&gt;&lt;br /&gt;
&lt;span style="font-size: large;"&gt;2011 Goals and Plans&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
2011 is going to be critical for AnotherSocialEconomy. The application runs and passed usability tests. The focus point is now on the business development!&lt;br /&gt;
&lt;ol&gt;&lt;li&gt;Concentrate on one domain (used car market) and get a significant traffic in the &lt;a href="http://maps.google.com/maps?q=montreal,qc,canada"&gt;Montreal area&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Gather customer feedback (consumer looking for second hand cars and used car dealers), tune the system, and increase traffic. Repeat until 100% satisfaction ;)&lt;/li&gt;
&lt;li&gt;Once the system is proven by the traffic and testimonies, involve investors and/or partners to 1) expand the business to other areas or 2) to target another domain or 3) both expand geographically and vertically.&lt;/li&gt;
&lt;li&gt;Develop &lt;a href="http://en.wikipedia.org/wiki/Data_mining"&gt;data mining&lt;/a&gt; tools for retailers.&lt;/li&gt;
&lt;li&gt;Develop domain oriented interfaces for consumers (Web/HTML5 for tablets and PC, native apps for iPhone, Android, BlackBerry).&lt;/li&gt;
&lt;li&gt;Add more communication channels (like voice messages with &lt;a href="http://www.twilio.com/"&gt;Twilio&lt;/a&gt;, for example).&lt;/li&gt;
&lt;li&gt;Offer my services as Software Architect &amp;amp; Developer consultant on designing &amp;amp; developing highly scalable and highly available applications on Google&lt;a href="http://code.google.com/appengine/"&gt; App Engine&lt;/a&gt; and mobile applications on &lt;a href="http://www.android.com/"&gt;Android&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;A+, Dom&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=FNXDyMHxRCI:A5Y1-Dqafxs:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=FNXDyMHxRCI:A5Y1-Dqafxs:4cEx4HpKnUU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?i=FNXDyMHxRCI:A5Y1-Dqafxs:4cEx4HpKnUU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=FNXDyMHxRCI:A5Y1-Dqafxs:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?i=FNXDyMHxRCI:A5Y1-Dqafxs:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SharingTechnologies/~4/FNXDyMHxRCI" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://domderrien.blogspot.com/feeds/5610405347014299744/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://domderrien.blogspot.com/2011/01/wrapping-up-2010-preparing-2011.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/168828253523263225/posts/default/5610405347014299744?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/168828253523263225/posts/default/5610405347014299744?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SharingTechnologies/~3/FNXDyMHxRCI/wrapping-up-2010-preparing-2011.html" title="Wrapping up 2010, preparing 2011" /><author><name>Dom Derrien</name><uri>https://plus.google.com/100819130946985751629</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-CAiJ6LOHBWE/AAAAAAAAAAI/AAAAAAAAErE/twENdW648fs/s512-c/photo.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://domderrien.blogspot.com/2011/01/wrapping-up-2010-preparing-2011.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D0MCQ3o_fyp7ImA9Wx9SE0Q.&quot;"><id>tag:blogger.com,1999:blog-168828253523263225.post-1743022830778412789</id><published>2010-12-03T11:57:00.000-05:00</published><updated>2010-12-03T11:57:42.447-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-12-03T11:57:42.447-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Technology" /><category scheme="http://www.blogger.com/atom/ns#" term="Experience" /><category scheme="http://www.blogger.com/atom/ns#" term="Community" /><title>Reviewed book 'Google App Engine Java and GWT Application Development' is out!</title><content type="html">I know that's a pity not to post more regularly! It's just I'm too busy with the developments for &lt;a href="http://anothersocialeconomy.com/"&gt;AnotherSocialEconomy.com&lt;/a&gt; ;)&lt;br /&gt;
&lt;br /&gt;
Here is a little news for Google App Engine developers:&lt;br /&gt;
&lt;blockquote&gt;Over the summer, I've been asked to review the draft of the book &lt;a href="https://www.packtpub.com/google-app-engine-java-and-gwt-application-development/book"&gt;Google App Engine Java and GWT Application Development&lt;/a&gt;. Even with my experience, I learn few techniques, like with the object relationships (chapter 5). A very good book for beginners/intermediates, and still an interesting book for experts&lt;span&gt;.&lt;/span&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;div style="clear: both; text-align: center;"&gt;&lt;a href="https://www.packtpub.com/google-app-engine-java-and-gwt-application-development/book" target="_blank"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/_VZZAFHl2_Og/TPkfk6W9YuI/AAAAAAAAC4s/CgqIRkUqcIM/s1600/Selection_012.png" title="The book 'Google App Engine Java and GWT Application Development' on Packt Publishing website" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
Enjoy!&lt;br /&gt;
A+, Dom&lt;br /&gt;
&lt;br /&gt;
Note: I've no incentive to sell the book, just the pleasure to share a good reference ;)&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=xS8cA6cG-RE:1tqsnFo972E:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=xS8cA6cG-RE:1tqsnFo972E:4cEx4HpKnUU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?i=xS8cA6cG-RE:1tqsnFo972E:4cEx4HpKnUU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=xS8cA6cG-RE:1tqsnFo972E:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?i=xS8cA6cG-RE:1tqsnFo972E:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SharingTechnologies/~4/xS8cA6cG-RE" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://domderrien.blogspot.com/feeds/1743022830778412789/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://domderrien.blogspot.com/2010/12/reviewed-book-google-app-engine-java.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/168828253523263225/posts/default/1743022830778412789?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/168828253523263225/posts/default/1743022830778412789?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SharingTechnologies/~3/xS8cA6cG-RE/reviewed-book-google-app-engine-java.html" title="Reviewed book 'Google App Engine Java and GWT Application Development' is out!" /><author><name>Dom Derrien</name><uri>https://plus.google.com/100819130946985751629</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-CAiJ6LOHBWE/AAAAAAAAAAI/AAAAAAAAErE/twENdW648fs/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://3.bp.blogspot.com/_VZZAFHl2_Og/TPkfk6W9YuI/AAAAAAAAC4s/CgqIRkUqcIM/s72-c/Selection_012.png" height="72" width="72" /><thr:total>0</thr:total><feedburner:origLink>http://domderrien.blogspot.com/2010/12/reviewed-book-google-app-engine-java.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D04HQ3c7cCp7ImA9Wx5WEE4.&quot;"><id>tag:blogger.com,1999:blog-168828253523263225.post-6197557307103404375</id><published>2010-09-20T22:22:00.001-04:00</published><updated>2010-09-20T22:25:32.908-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-09-20T22:25:32.908-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Technology" /><category scheme="http://www.blogger.com/atom/ns#" term="Experience" /><title>Securing accounts on the Web</title><content type="html">&lt;span style="font-size: large;"&gt;Situation&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
Few days ago, my partner Steven got his Google account compromised for a short period of time:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;Tweet &lt;a href="http://twitter.com/stevenmilstein/statuses/24054949446"&gt;#1&lt;/a&gt; at 8:52 PM on Sept. 9: Just received 2 calls from friends wondering if I'm being held at a London hotel. FYI, I'm not.&lt;/li&gt;
&lt;li&gt;Tweet &lt;a href="http://twitter.com/stevenmilstein/statuses/24071815656"&gt;#2&lt;/a&gt; at 12:23 AM on Sept. 10: Re: Being held in London. My Google account password was changed by an IP address in Nigeria. I've got it back now but with no Contacts.&lt;/li&gt;
&lt;li&gt;Tweet &lt;a href="http://twitter.com/stevenmilstein/statuses/24126191330"&gt;#3&lt;/a&gt;: at 3:04 PM on Sept. 10: Re Stuck in London: I thought I had everything under control last night but needed &lt;a href="http://bit.ly/czgYdg"&gt;http://bit.ly/czgYdg&lt;/a&gt; Google Security Breach help to fix.&lt;/li&gt;
&lt;/ul&gt;The thieves used his account to send a &lt;a href="http://en.wikipedia.org/wiki/Scam"&gt;scam&lt;/a&gt;&amp;nbsp;to few of his friends&amp;nbsp;asking for money because he was supposedly blocked in London without resources.&lt;br /&gt;
&lt;br /&gt;
If Steven's password was not very strong, there's no chance it has been discovered after only few attempts. At no time, Google reported that attempts to log into his account were conducted from computers with IP addresses in Liberia! Steven saw the first warning only when he recovered the access!&lt;br /&gt;
&lt;br /&gt;
&lt;span style="font-size: large;"&gt;Encountered risk&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
The goal of these thieves was limited to getting money as soon as possible. So they reached out few of Steven's contacts, ones he contacts only&amp;nbsp;occasionally, and they asked for a money to be transferred by Western Union. As they kept the control of his account, they would have been able to get the transaction MTCN (money transfer control number)&amp;nbsp;via his inbox. Western Union maintains a page listing the &lt;a href="http://www.westernunion.com/WUCOMWEB/staticMid.do?method=load&amp;amp;pagename=fraudScams"&gt;Common Scams&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
Others could have decided to change his password, to just spy his incoming message stream (these ones enabled the POP3 and IMAP accesses), to ask for password reset when Steven is not online, and then to steal his identity in many online services.&lt;br /&gt;
&lt;br /&gt;
Because Steven reacted promptly and because his contacts detected the scam, the thieves did not get any benefit from this operation. They are probably trying to get someone else now, maybe someone from his contact list.&lt;br /&gt;
&lt;br /&gt;
&lt;span class="Apple-style-span" style="font-size: xx-large;"&gt;How to reduce the exposure&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
The first protection consists in defining &lt;b&gt;strong passwords&lt;/b&gt;. A lot of services offer information about how to produce strong passwords. I would recommend this Microsoft site &lt;a href="http://www.microsoft.com/protect/fraud/passwords/create.aspx"&gt;Strong Passwords | Microsoft Security&lt;/a&gt;—I'm confident that they don't provide the &lt;a href="https://www.microsoft.com/protect/fraud/passwords/checker.aspx"&gt;online password checker&lt;/a&gt; to enhance a grey&amp;nbsp;&lt;a href="http://en.wikipedia.org/wiki/Dictionary_attack"&gt;dictionary&lt;/a&gt; ;)&lt;br /&gt;
&lt;br /&gt;
The second protection would be to use &lt;b&gt;a unique and strong password per account&lt;/b&gt;. This is probably the most difficult part! I may use probably 20 to 30 online services, some I use regularly, others I use very rarely. There's no way I can remember so many &lt;i&gt;strong&lt;/i&gt; passwords...&lt;br /&gt;
&lt;br /&gt;
My solution: Keepass + DropBox&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;&lt;a href="http://keepass.info/"&gt;Keepass&lt;/a&gt; is an open source password manager. The tool has been ported on many platforms: Windows, Mac, Linux, iPhone, Android, etc.—Full list on the &lt;a href="http://keepass.info/download.html"&gt;download&lt;/a&gt; page.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.dropbox.com/referrals/NTE0NDA5MTk"&gt;DropBox&lt;/a&gt; (link with my referral id ;) is an online file sharing system that, thanks to a program installed on each computer/mobile in your network, maintains in sync the corresponding set of files. DropBox is a nice companion to Keepass as it duplicates your password database transparently, reducing the risk to loose the passwords if the original computer is lost.&lt;/li&gt;
&lt;/ul&gt;&lt;br /&gt;
The combination of the&amp;nbsp;&lt;a href="http://keepass.info/help/base/pwgenerator.html"&gt;password generator&lt;/a&gt; and Keepass &lt;a href="http://keepass.info/help/base/secedits.html"&gt;secure edit controls&lt;/a&gt; makes the tool especially useful:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;It's &lt;b&gt;easy to generate a strong passwords&lt;/b&gt; (remember: 16 characters or plus ;)&lt;/li&gt;
&lt;li&gt;You &lt;b&gt;don't have to remember them&lt;/b&gt; as a simple Ctrl+C / Ctrl+V allows to copy securely&amp;nbsp;them in your browser! (the computer clipboard is automatically flushed after few seconds.)&lt;/li&gt;
&lt;/ul&gt;In final, I just have one &lt;i&gt;very strong&lt;/i&gt;&amp;nbsp;password (30+ characters) to remember and to change periodically.&lt;br /&gt;
&lt;br /&gt;
&lt;span style="font-size: large;"&gt;Known limitations&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
Some sites ask users to give a &lt;i&gt;secret &lt;/i&gt;answer for a series of predefined questions. If you look at the Apple page below, you'll see that some questions might weaken users more than offering a protection... These days, it's pretty simple to find the responses online!&lt;br /&gt;
&lt;br /&gt;
&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_VZZAFHl2_Og/TJgJIbQe3jI/AAAAAAAAC4I/ia8ZIK9X36s/s1600/Apple+-+My+Apple+ID+-+Mozilla+Firefox_011.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" src="http://1.bp.blogspot.com/_VZZAFHl2_Og/TJgJIbQe3jI/AAAAAAAAC4I/ia8ZIK9X36s/s1600/Apple+-+My+Apple+ID+-+Mozilla+Firefox_011.png" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;List of predefined security questions on Apple.com website&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;
Many sites only accepts alphanumerical characters only or don't accept passwords over 20 characters. Oddly enough, most of the bank websites I use prevent too long and too complex passwords! I guess they have other tools to detect intrusions...&lt;br /&gt;
&lt;br /&gt;
&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_VZZAFHl2_Og/TJgJq0iLiHI/AAAAAAAAC4M/aaVKHTbLto8/s1600/Selection_012.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="226" src="http://2.bp.blogspot.com/_VZZAFHl2_Og/TJgJq0iLiHI/AAAAAAAAC4M/aaVKHTbLto8/s400/Selection_012.png" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;List of predefined security questions on Apple.com website&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;
&lt;span class="Apple-style-span"&gt;&lt;span class="Apple-style-span" style="font-size: x-large;"&gt;Last minute update&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
Today, Google announced on its Online Security blog that they will offer a &lt;a href="http://googleonlinesecurity.blogspot.com/2010/09/moving-security-beyond-passwords.html"&gt;Two-Step Authentication&lt;/a&gt; mechanism to log into Google services. This &lt;a href="http://en.wikipedia.org/wiki/One-time_password"&gt;One-Time Password&lt;/a&gt; authentication is simpler than distributing a one-time password generator, &lt;a href="http://aws.amazon.com/mfa/"&gt;as Amazon does&lt;/a&gt; for example, while providing a still strong security enhancement.&lt;br /&gt;
&lt;br /&gt;
I hope it helps.&lt;br /&gt;
A+, Dom&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=q9bpAmIWAaw:VUJibneVp7U:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=q9bpAmIWAaw:VUJibneVp7U:4cEx4HpKnUU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?i=q9bpAmIWAaw:VUJibneVp7U:4cEx4HpKnUU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=q9bpAmIWAaw:VUJibneVp7U:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?i=q9bpAmIWAaw:VUJibneVp7U:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SharingTechnologies/~4/q9bpAmIWAaw" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://domderrien.blogspot.com/feeds/6197557307103404375/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://domderrien.blogspot.com/2010/09/securing-accounts-on-web.html#comment-form" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/168828253523263225/posts/default/6197557307103404375?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/168828253523263225/posts/default/6197557307103404375?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SharingTechnologies/~3/q9bpAmIWAaw/securing-accounts-on-web.html" title="Securing accounts on the Web" /><author><name>Dom Derrien</name><uri>https://plus.google.com/100819130946985751629</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-CAiJ6LOHBWE/AAAAAAAAAAI/AAAAAAAAErE/twENdW648fs/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://1.bp.blogspot.com/_VZZAFHl2_Og/TJgJIbQe3jI/AAAAAAAAC4I/ia8ZIK9X36s/s72-c/Apple+-+My+Apple+ID+-+Mozilla+Firefox_011.png" height="72" width="72" /><thr:total>1</thr:total><feedburner:origLink>http://domderrien.blogspot.com/2010/09/securing-accounts-on-web.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CEMESHw6fCp7ImA9Wx5XFEo.&quot;"><id>tag:blogger.com,1999:blog-168828253523263225.post-596900976299676303</id><published>2010-09-14T09:53:00.000-04:00</published><updated>2010-09-14T09:53:29.214-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-09-14T09:53:29.214-04:00</app:edited><title>Dilbert: Social Media age</title><content type="html">I cannot resist to re-post this strip!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;center&gt;&lt;a href="http://dilbert.com/strips/comic/2010-09-13/" title="Dilbert.com"&gt;&lt;img alt="Dilbert.com" border="0" src="http://dilbert.com/dyn/str_strip/000000000/00000000/0000000/100000/00000/0000/100/100155/100155.strip.gif" /&gt;&lt;/a&gt;&lt;/center&gt;&lt;br /&gt;
&lt;br /&gt;
Are such attitudes only due to managers that need to keep a straight control over their reports in order to seamlessly justify their position? I already covered that aspect in my blog post &lt;a href="http://domderrien.blogspot.com/2008/12/manager-attitude.html"&gt;Manager Attitude&lt;/a&gt; back in Dec. 2008. I then stated that the situation would be smoother is such managers could fully assume their &lt;i&gt;facilitator&lt;/i&gt; role...&lt;br /&gt;
&lt;br /&gt;
These days, I think it's more related to the difference in each person's &lt;a href="http://www.acrwebsite.org/volumes/display.asp?id=5867"&gt;cognitive age&lt;/a&gt;!&lt;br /&gt;
&lt;br /&gt;
In the traditional chronological order, you could become a manager because you knew the company internal mechanics, the management dynamic, and its long term vision. People finger-pointing the Generation Y (see my post &lt;a href="http://domderrien.blogspot.com/2010/05/work-around-general-laziness.html"&gt;Work around the general laziness&lt;/a&gt;, for example) have good reasons to ask new employees to first acquire good experiences before giving them rewards and responsibilities.&lt;br /&gt;
&lt;br /&gt;
However, with the emergence of multi-stream platforms, young people have a tremendous possibilities to learn so much in short periods of time and to overpass older ones. My involvement in &lt;a href="http://edu.cyn.in/"&gt;edu.cyn.in&lt;/a&gt; proved this statement many times: kids are much more prolific at producing online content and sharing with their peers than their own teachers!&lt;br /&gt;
&lt;br /&gt;
On the other end of the spectrum, a lot of retired people who don't have to compete again to maintain their social rank, have gain the possibility to become &lt;i&gt;relevant&lt;/i&gt; again by being more connected with younger crowds, more socially involved.&lt;br /&gt;
&lt;br /&gt;
This Dilbert strip illustrates a direct confrontation of the &lt;i&gt;socially aged&lt;/i&gt; person and the &lt;i&gt;just chronologically aged&lt;/i&gt; one!&lt;br /&gt;
&lt;br /&gt;
I hope you like it too.&lt;br /&gt;
A+, Dom&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=9h00GMrBl64:cmcT6wIpUt8:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=9h00GMrBl64:cmcT6wIpUt8:4cEx4HpKnUU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?i=9h00GMrBl64:cmcT6wIpUt8:4cEx4HpKnUU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=9h00GMrBl64:cmcT6wIpUt8:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?i=9h00GMrBl64:cmcT6wIpUt8:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SharingTechnologies/~4/9h00GMrBl64" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://domderrien.blogspot.com/feeds/596900976299676303/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://domderrien.blogspot.com/2010/09/dilbert-social-media-age.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/168828253523263225/posts/default/596900976299676303?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/168828253523263225/posts/default/596900976299676303?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SharingTechnologies/~3/9h00GMrBl64/dilbert-social-media-age.html" title="Dilbert: Social Media age" /><author><name>Dom Derrien</name><uri>https://plus.google.com/100819130946985751629</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-CAiJ6LOHBWE/AAAAAAAAAAI/AAAAAAAAErE/twENdW648fs/s512-c/photo.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://domderrien.blogspot.com/2010/09/dilbert-social-media-age.html</feedburner:origLink></entry><entry gd:etag="W/&quot;AkAFSHw9eSp7ImA9Wx5QGE8.&quot;"><id>tag:blogger.com,1999:blog-168828253523263225.post-7153701713028671877</id><published>2010-09-06T23:05:00.000-04:00</published><updated>2010-09-06T23:05:19.261-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-09-06T23:05:19.261-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Entrepreneurship" /><category scheme="http://www.blogger.com/atom/ns#" term="Experience" /><title>Update business-side</title><content type="html">It's been a long time since my last post. The summer went by so quickly. With my partner &lt;a href="http://stevenmilstein.com/"&gt;Steven Milstein&lt;/a&gt;, we have focused on building a generic tool for golfers and golf courses. This post summarizes the latest developments.&lt;br /&gt;
&lt;br /&gt;
&lt;span style="font-size: large;"&gt;The product name&lt;/span&gt; &lt;br /&gt;
&lt;br /&gt;
The product name &lt;a href="http://twetailer.com/"&gt;Twetailer&lt;/a&gt; is now &lt;a href="http://anothersocialeconomy.com/"&gt;AnotherSocialEconomy.com&lt;/a&gt;. &lt;b&gt;Twetailer&lt;/b&gt; was chosen when &lt;a href="http://twitter.com/"&gt;Twitter&lt;/a&gt; started to become mainstream and was made of a combination of &lt;b&gt;Tw&lt;/b&gt;itter and R&lt;b&gt;etailer&lt;/b&gt;. The project &lt;b&gt;Dretailer&lt;/b&gt; (more in a future post) was name from An&lt;b&gt;dr&lt;/b&gt;oid and R&lt;b&gt;etailer&lt;/b&gt;, and we thought about having a variation per connector type.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;AnotherSocialEconomy.com&lt;/b&gt; is better because it really illustrates that our offer is about a business platform in a global economy, with social networks involved, in a new way of connecting people—and it is not tinted by any service &lt;i&gt; du jour&lt;/i&gt;. More information on Steven's blog post &lt;a href="http://stevenmilstein.com/2010/08/26/the-twouble-with-twetailer/"&gt;The Twouble with Twetailer&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;span style="font-size: large;"&gt;&lt;b&gt;ezToff.com&lt;/b&gt;: a dedicated implementation&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;AnotherSocialEconomy.com&lt;/b&gt; provides a generic multi-channel communication engine, conveying messages among consumers and retailers according to the given location and some search criteria. Whenever we explained what's the product is about, potential &lt;i&gt;consumers&lt;/i&gt; understood but were blocked when it was time to formulate one of their needs... We knew then we needed to offer a very simple interface where users can post demands without thinking twice about it!&lt;br /&gt;
&lt;br /&gt;
At one point, we met &lt;a href="http://ca.linkedin.com/pub/marc-bienstock/1/86a/860"&gt;Marc Bienstock&lt;/a&gt; who offered to help us building such an interface for golfers! According to Marc:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;When a golfer has a chance to play (because his wife gave him the permission ;), he usually calls his buddies to find three of them who are free too.&lt;/li&gt;
&lt;li&gt;Then he has to call a golf courses to book a round at the agreed on time, maybe with one or many pull carts, one or two golf carts, etc.&lt;/li&gt;
&lt;li&gt;If the booking goes well, he calls his buddies to give them the golf course coordinates.&lt;/li&gt;
&lt;li&gt;If it does not work, he calls another club or calls his buddies with the time of the available rounds.&lt;/li&gt;
&lt;li&gt;Way too many phone calls to be able to spend a minimum of $200 for 4 players!&lt;/li&gt;
&lt;li&gt;On the golf course-side, they have to deal with so many phone calls; some of them have a system queuing calls up to 50!&lt;/li&gt;
&lt;li&gt;Average time spent on the phone per caller is too long, and many are useless because they cannot screen them.&lt;/li&gt;
&lt;li&gt;When a round stays free, it's an average of $200 lost.&lt;/li&gt;
&lt;/ul&gt;So end-of-July, we launched &lt;a href="http://eztoff.com/"&gt;ezToff.com&lt;/a&gt; which allows:&lt;br /&gt;
&lt;ol&gt;&lt;li&gt;One golfer can submit a request for a tee-off from one central place and it will be broadcasted to all participating golf courses around the given location. The golfer can specify the e-mail addresses of his buddies so they'll be cc'ed for all exchanged messages.&lt;/li&gt;
&lt;li&gt;Golf courses are provided a Web console which displays all requests posted in their area. According to their schedule, they can propose rounds—the price per round and the total cost should be documented. Golf courses don't have to watch the console indefinitely as &lt;b&gt;ezToff.com&lt;/b&gt; can notify them by e-mail, SMS or tweet.&lt;/li&gt;
&lt;li&gt;Proposals are sent back to request initiator by e-mail (his buddies receive a copy if he gave their e-mail addresses). At this step, the golfer can wait for more proposals to come. If he wants to book it, a simple link in the e-mail will generate a response to be sent by e-mail to &lt;b&gt;ezToff.com&lt;/b&gt;.&lt;/li&gt;
&lt;/ol&gt;With &lt;b&gt;ezToff.com&lt;/b&gt;, golfers can get tee-off proposals with just few clicks. Golf courses get all demands electronically and can focus on the ones they can propose a round to (others are simply declined). Buddies of the golfers are notified at each step of the process automatically. No more infinite phone calls ;)&lt;br /&gt;
&lt;br /&gt;
We interviewed many golfers and they confirmed the golfer's pain. With some visits to golf courses, we've confirmed the courses' pain too. The difficulty we have is that we're not &lt;i&gt;sales people&lt;/i&gt; and it's hard to close a deal with golf courses... Knowing that the season is almost finished here, that our business model is probably too expensive for the golf courses, that we need good marketing materials, we're going to tune the offer and be ready for the 2011 season.&lt;br /&gt;
&lt;br /&gt;
&lt;span style="font-size: large;"&gt;The &lt;b&gt;AnotherSocialEconomy.com&lt;/b&gt; reseller and influencer programs&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
The &lt;b&gt;ezToff.com&lt;/b&gt; project has been beneficial for us on many points:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;With the widget, we have another way to reach consumers: influencers (bloggers, Facebook groups, associations' site, for example) can embed it into their webpages.&lt;/li&gt;
&lt;li&gt;If dedicated Web consoles are available to golfers and golf courses, all operations can be done with e-mails, which is the most pervasive communication tool.&lt;/li&gt;
&lt;li&gt;The original messages produced by the engine were short to accommodate the Twitter limitation of 140 characters per message. These ones were cryptic to too many people. The variation of these messages for e-mail are now much friendlier and contains links ready to forward the readers' response.&lt;/li&gt;
&lt;li&gt;We'll simplify the pricing model thanks to the received feedback.&lt;/li&gt;
&lt;/ul&gt;But the most significant development is the offer of the influencer program:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;Each influencer will receive 25% of the revenues generated by consumers confirming demands posted with their copies of the widget.&lt;/li&gt;
&lt;li&gt;An influencer can propose one or many widgets anywhere on the Web he reaches his/her community.&lt;/li&gt;
&lt;li&gt;We'll work with influencers to customize the widgets for his/her community.&lt;/li&gt;
&lt;/ul&gt;In parallel, we developed the reseller program to share 25% of the revenues generated by retailers proposing goods or services with&lt;b&gt; AnotherSocialEconomy.com&lt;/b&gt;.&lt;br /&gt;
&lt;br /&gt;
It's possible that some well organized enterprises will be their own reseller (cut of 25%) and will drive requests with their copies of the widget (another cut of 25%) but that's fine. We'll make some money if they make some, and they'll have a good discount if they help driving more traffic (which means more business to them).&lt;br /&gt;
&lt;br /&gt;
&lt;span style="font-size: large;"&gt;Next steps&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
On business side, Steven and I are looking for resellers that will help to bring &lt;b&gt;ezToff.com&lt;/b&gt; to the next level. We are also trying to develop another domain specific implementation, another &lt;i&gt;skin&lt;/i&gt; on the top of the &lt;b&gt;AnotherSocialEconomy.com&lt;/b&gt; engine.&lt;br /&gt;
&lt;br /&gt;
On the technical side, if everything works perfectly for the &lt;b&gt;ezToff.com&lt;/b&gt; users, I need to document the widget usage and the REST API used by the Web consoles. I need also to enable the &lt;b&gt;Facebook connector&lt;/b&gt; for influencers to communicate on this platform as they can do on Twitter. There's also the Android application (project &lt;b&gt;&lt;a href="http://github.com/domderrien/dretailer/"&gt;dretailer&lt;/a&gt;&lt;/b&gt; mentioned above) to update to benefit from the &lt;b&gt;Android Cloud to Device Messaging&lt;/b&gt; (c2dm) mechanism and then to push notifications asynchronously. Still a lot to do but no road block ;)&lt;br /&gt;
&lt;br /&gt;
A+, Dom&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=5FhA-0ryJmA:LTW7TkinELk:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=5FhA-0ryJmA:LTW7TkinELk:4cEx4HpKnUU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?i=5FhA-0ryJmA:LTW7TkinELk:4cEx4HpKnUU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=5FhA-0ryJmA:LTW7TkinELk:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?i=5FhA-0ryJmA:LTW7TkinELk:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SharingTechnologies/~4/5FhA-0ryJmA" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://domderrien.blogspot.com/feeds/7153701713028671877/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://domderrien.blogspot.com/2010/09/update-business-side.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/168828253523263225/posts/default/7153701713028671877?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/168828253523263225/posts/default/7153701713028671877?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SharingTechnologies/~3/5FhA-0ryJmA/update-business-side.html" title="Update business-side" /><author><name>Dom Derrien</name><uri>https://plus.google.com/100819130946985751629</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-CAiJ6LOHBWE/AAAAAAAAAAI/AAAAAAAAErE/twENdW648fs/s512-c/photo.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://domderrien.blogspot.com/2010/09/update-business-side.html</feedburner:origLink></entry><entry gd:etag="W/&quot;Dk8EQ3k6fSp7ImA9WhVVF00.&quot;"><id>tag:blogger.com,1999:blog-168828253523263225.post-5251967564888404659</id><published>2010-07-19T23:56:00.001-04:00</published><updated>2012-05-10T22:53:22.715-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-05-10T22:53:22.715-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Technology" /><category scheme="http://www.blogger.com/atom/ns#" term="Community" /><title>How to resize VirtualBox disk images?</title><content type="html">-- Update on May 8, 2012 --&lt;br /&gt;
&lt;br /&gt;
Having to resize an image again, I looked at the VirtualBox documentation before following the CloneVDI route a second time. And I was pleasantly surprised that the version 4.1 of VBoxManage accepts a parameter &lt;code&gt;--resize&lt;/code&gt; to the command &lt;a href="http://www.virtualbox.org/manual/ch08.html#idp14334672"&gt;modifyhd&lt;/a&gt;!&lt;br /&gt;
&lt;br /&gt;
The process can be done in a matter of minutes:&lt;br /&gt;
&lt;ol&gt;
&lt;li&gt;Stop the hosted system (Win7 in my case). &lt;/li&gt;
&lt;li&gt;Run the following command in the folder of your VDI file&lt;br /&gt;&amp;nbsp; &amp;nbsp;&lt;code&gt;VBoxManage modifyhd &amp;lt;name&amp;gt;.vdi --resize &amp;lt;size-in-mb&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Start the virtual machine.&lt;/li&gt;
&lt;li&gt;Open the disk manager tool (Use the Menu Windows, type &lt;code&gt;disk man&lt;/code&gt; in the search box, and select 'Create and format hard disk partitions').&lt;/li&gt;
&lt;li&gt;You should see your drive with the initial partition(s) and new free space.&lt;/li&gt;
&lt;li&gt;Click on the partition to extend and choose the command 'Extend Volume' in the contextual menu.&lt;/li&gt;
&lt;li&gt;And voilà.&lt;/li&gt;
&lt;/ol&gt;
No need to copy the VDI on the host machine. Very fast and robust process.&lt;br /&gt;
&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-PCT1HzeMl6s/T6x8gPno15I/AAAAAAAADf4/eCVvoT_mhrc/s1600/ill.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" src="http://2.bp.blogspot.com/-PCT1HzeMl6s/T6x8gPno15I/AAAAAAAADf4/eCVvoT_mhrc/s1600/ill.png" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;'Extend Volume' option in the contextual menu.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Original post on July 19, 2010 --&lt;br /&gt;
&lt;br /&gt;
Quick post to share a wonderful VirtualBox companion: &lt;a href="http://forums.virtualbox.org/viewtopic.php?f=6&amp;amp;t=22422"&gt;CloneVDI&lt;/a&gt;!&lt;br /&gt;
&lt;br /&gt;
Almost two months ago, I switched from Windows XP to &lt;a href="http://domderrien.blogspot.com/2010/06/joy-of-ubuntu.html"&gt;GNU/Linux with Ubuntu 10.04&lt;/a&gt; distribution. Everything goes very well and I do not regret the move.&lt;br /&gt;
&lt;br /&gt;
In my day-to-day job, as the architect/developer/tester of the &lt;a href="http://domderrien.blogspot.com/2010/03/ladies-and-gentlemen-here-is-twetailer.html"&gt;Twetailer project&lt;/a&gt;, engine and of many of its clients, I still need to run programs on the Windows OS, especially the series of Internet Explorer 7 &amp;amp; 8 (IE 6 is killed, isn't it?).&lt;br /&gt;
&lt;br /&gt;
To verify my test suites for Internet Explorer, I rely on &lt;a href="http://virtualbox.org/"&gt;VirtualBox&lt;/a&gt; running the initial Windows XP release. Because the disk size requirement for the initial version is low, I &lt;i&gt;stupidly&lt;/i&gt; created a  small&amp;nbsp; &lt;b&gt;4 GB disk&lt;/b&gt; image! Then it required hours to load and install the service packs 2 &amp;amp; 3, plus IE 8, plus the latest .Net framework, plus the security updates. &lt;u&gt;Be careful:&lt;/u&gt; 4 GB is way too small to store the system, the virtual memory page file, and the additional stock coming with the service packs and others!&lt;br /&gt;
&lt;br /&gt;
Few days after the initial setup, I was facing the "Not enough space available" warning :-( Instead of wasting another set of hours in a re-installation, I googled &lt;a href="http://www.google.com/search?hl=en&amp;amp;q=virtualbox+increase+vdi+size"&gt;virtualbox increase vdi size&lt;/a&gt;, and the first article I found was from the VirtualBox forums. I thought it was a good sign and I started reading... but I became quickly disappointed because the given explanations required many tricks and time to setup too. Then I jumped to the last pages (&lt;a href="http://forums.virtualbox.org/viewtopic.php?f=1&amp;amp;t=364&amp;amp;start=75"&gt;page 6&lt;/a&gt; to be precise) to read:&lt;br /&gt;
&lt;blockquote&gt;
...&lt;br /&gt;
&lt;br /&gt;
The new way:&lt;br /&gt;
Run the &lt;a href="http://forums.virtualbox.org/viewtopic.php?f=6&amp;amp;t=22422"&gt;CloneVDI&lt;/a&gt; tool. Enter name of source VDI and desired disk size into dialog box. Click "Proceed" button. Expect to spend maybe 5 minutes for a typical VDI, longer of course with big drives.&lt;br /&gt;
&lt;br /&gt;
The CloneVDI tool has existed since mid September 2009. It was created specifically so as to remove the need to recommend an embarassingly complicated rigmarole for performing what should have been a simple task. So, in late May 2010 it is quite disheartening to see people still joining the site in order to provide uninformed endorsement of the obsolete procedure.&lt;/blockquote&gt;
The post was from Don Milne, alias mpack, the creator of the CloneVDI tool.&lt;br /&gt;
&lt;br /&gt;
Then I loaded CloneVDI from the referenced post, installed Wine with the Ubuntu Software Center, ran CloneVDI, selected my initial image, specified a new name and a new size (now 10 GB), and all the magic transformation occurred in less than 1 minute!&lt;br /&gt;
&lt;br /&gt;
&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;img border="0" height="409" src="http://1.bp.blogspot.com/_VZZAFHl2_Og/TEUbEMByMCI/AAAAAAAAC3k/GmCiR8vtUTQ/s640/Virtual+HD+Clone+and+Optimize+v2.02_004.png" width="550" /&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;CloneVDI pane: clone an virtual image with an increased size in just a few clicks!&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;br /&gt;
Warning: It seems the cloning only works up to the first snapshot, as my clone did not get the snapshots. This was not a issue for me but be careful on your side because it might be necessary to merge the snapshots. As the cloning is very fast, producing a new image per snapshot should work-around the issue.&lt;br /&gt;
&lt;br /&gt;
Anyway, I wholeheartedly recommend &lt;a href="http://forums.virtualbox.org/viewtopic.php?f=6&amp;amp;t=22422"&gt;CloneVDI&lt;/a&gt; when it's time to allocate more disk space to a VirtualBox machine!&lt;br /&gt;
&lt;br /&gt;
A+, Dom&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=Wrtuhs5lOmw:3lZQvto7CuY:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=Wrtuhs5lOmw:3lZQvto7CuY:4cEx4HpKnUU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?i=Wrtuhs5lOmw:3lZQvto7CuY:4cEx4HpKnUU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=Wrtuhs5lOmw:3lZQvto7CuY:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?i=Wrtuhs5lOmw:3lZQvto7CuY:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SharingTechnologies/~4/Wrtuhs5lOmw" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://domderrien.blogspot.com/feeds/5251967564888404659/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://domderrien.blogspot.com/2010/07/how-to-resize-virtualbox-disk-images.html#comment-form" title="3 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/168828253523263225/posts/default/5251967564888404659?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/168828253523263225/posts/default/5251967564888404659?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SharingTechnologies/~3/Wrtuhs5lOmw/how-to-resize-virtualbox-disk-images.html" title="How to resize VirtualBox disk images?" /><author><name>Dom Derrien</name><uri>https://plus.google.com/100819130946985751629</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-CAiJ6LOHBWE/AAAAAAAAAAI/AAAAAAAAErE/twENdW648fs/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/-PCT1HzeMl6s/T6x8gPno15I/AAAAAAAADf4/eCVvoT_mhrc/s72-c/ill.png" height="72" width="72" /><thr:total>3</thr:total><feedburner:origLink>http://domderrien.blogspot.com/2010/07/how-to-resize-virtualbox-disk-images.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DUQHQ3g_cCp7ImA9WxFWGU4.&quot;"><id>tag:blogger.com,1999:blog-168828253523263225.post-2911333838832619867</id><published>2010-06-07T12:21:00.001-04:00</published><updated>2010-06-07T15:02:12.648-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-06-07T15:02:12.648-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Technology" /><category scheme="http://www.blogger.com/atom/ns#" term="Community" /><title>The joy of Ubuntu</title><content type="html">Few decades ago, most of my work was done on a SUN hardware running &lt;a href="http://en.wikipedia.org/wiki/Solaris_%28operating_system%29" target="_blank"&gt;Solaris&lt;/a&gt;. The &lt;i&gt;Université de Rennes I&lt;/i&gt;, France, was providing the Internet access and &lt;a href="http://en.wikipedia.org/wiki/Mosaic_%28web_browser%29" target="_blank"&gt;Mosaic&lt;/a&gt;, followed few times later by Netscape, was my favorite browser. At that time, I was preparing a PhD and most of the &lt;i&gt;online&lt;/i&gt; discussions were conducted in newsgroups and emails.&lt;br /&gt;
&lt;br /&gt;
One week ago, I definitively switched the OS of my Thinkpad T61 from Windows XP to &lt;a href="http://www.ubuntu.com/" target="_blank"&gt;Ubuntu&lt;/a&gt; Lucid Lynx (10.04). In some ways, the Ubuntu environment is not that different from the Solaris I used to work on. For example, I was very pleased to use again an efficient Virtual Desktop system and to benefit from the native Compose key mechanism that allows me to type any crazy sequences producing foreign characters.&lt;br /&gt;
&lt;br /&gt;
Having been pushed on the Microsoft side by my various employers, it seems I forgot all the good sides of the Unix environments. Microsoft marketing has been very brilliant: as many others, I accepted the PC environment &lt;i&gt;limitations&lt;/i&gt;! Do you remember the "cooperative multitasking" of Windows 3.1? Do you remember that changing a registry key transformed your Windows NT Workstation in a much capable Windows NT Server? Do you remember that Windows 7 is really the first shiny interface with transparency and gadgets? (Sorry: Vis-what? I don't know ;)&lt;br /&gt;
&lt;br /&gt;
To be honest, let's recognize that the Unix environments did not progress much. On the open source side, the Linux distributions provide a very fragmented offer. The success of some &lt;i&gt;shiny&lt;/i&gt; distributions like &lt;a href="http://www.knopper.net/knoppix/index-en.html" target="_blank"&gt;Knoppix&lt;/a&gt; and Ubuntu is fairly recent.&lt;br /&gt;
&lt;br /&gt;
As a geek, I would say that the most impressive feature in these distributions is the 3D rendering engine &lt;a href="http://www.compiz.org/" target="_blank"&gt;Compiz&lt;/a&gt; and its &lt;a href="http://wiki.compiz.org/Plugins/Cube" target=_blank"&gt;Cube plugin&lt;/a&gt;! It's just amazing.&lt;br /&gt;
&lt;br /&gt;
&lt;center&gt;&lt;object width="640" height="385"&gt;&lt;param name="movie" value="http://www.youtube.com/v/WturcjzIjXg&amp;hl=en_US&amp;fs=1&amp;rel=0"&gt;&lt;/param&gt;&lt;param name="allowFullScreen" value="true"&gt;&lt;/param&gt;&lt;param name="allowscriptaccess" value="always"&gt;&lt;/param&gt;&lt;embed src="http://www.youtube.com/v/WturcjzIjXg&amp;hl=en_US&amp;fs=1&amp;rel=0" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="640" height="385"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;object width="480" height="385"&gt;&lt;param name="movie" value="http://www.youtube.com/v/5baDknt6Z7w&amp;hl=en_US&amp;fs=1&amp;rel=0"&gt;&lt;/param&gt;&lt;param name="allowFullScreen" value="true"&gt;&lt;/param&gt;&lt;param name="allowscriptaccess" value="always"&gt;&lt;/param&gt;&lt;embed src="http://www.youtube.com/v/5baDknt6Z7w&amp;hl=en_US&amp;fs=1&amp;rel=0" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="480" height="385"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;/center&gt;&lt;br /&gt;
&lt;br /&gt;
When I decided to switch to Ubuntu, I wondered if I would lose some features. So far, all have their counterpart for the Linux environment, sometimes with many additional benefits. I did not have to even install Wine, the Windows emulator! Here are the tools that were important to me and I carried over:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;&lt;a href="https://www.dropbox.com/downloading?os=lnx" target="_blank"&gt;Dropbox&lt;/a&gt;: has Linux/MacOS/Windows distributions.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://keepass.info/" target="_blank"&gt;Keepass&lt;/a&gt;: had to convert the database to 1.x format and now use &lt;a href="http://www.keepassx.org/downloads" target="_blank"&gt;KeepassX&lt;/a&gt; which has Linux/MacOS/Windows distributions.&lt;/li&gt;
&lt;li&gt;Aptana/Android SDK/App Engine SDK: equivalent Java packages.&lt;/li&gt;
&lt;li&gt;Firefox/Chrome/Add-ons: have Linux/MacOS/Windows distributions.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.skype.com/intl/en-us/get-skype/on-your-computer/linux/" target="_blank"&gt;Skype&lt;/a&gt; (for VOIP calls and screen shares)...&lt;/li&gt;
&lt;li&gt;OpenOffice.&lt;/li&gt;
&lt;li&gt;VirtualBox.&lt;/li&gt;
&lt;li&gt;git.&lt;/li&gt;
&lt;/ul&gt;&lt;br /&gt;
I am so impressed with the system that I've also switched my old Toshiba M40 (one P4 core and a NVidia card) and now my kids have an easy access to many &lt;i&gt;free&lt;/i&gt; games and educational tools!&lt;br /&gt;
&lt;br /&gt;
A+, Dom&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=KShGkBNWQh8:BtcU6zVAX2I:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=KShGkBNWQh8:BtcU6zVAX2I:4cEx4HpKnUU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?i=KShGkBNWQh8:BtcU6zVAX2I:4cEx4HpKnUU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=KShGkBNWQh8:BtcU6zVAX2I:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?i=KShGkBNWQh8:BtcU6zVAX2I:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SharingTechnologies/~4/KShGkBNWQh8" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://domderrien.blogspot.com/feeds/2911333838832619867/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://domderrien.blogspot.com/2010/06/joy-of-ubuntu.html#comment-form" title="3 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/168828253523263225/posts/default/2911333838832619867?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/168828253523263225/posts/default/2911333838832619867?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SharingTechnologies/~3/KShGkBNWQh8/joy-of-ubuntu.html" title="The joy of Ubuntu" /><author><name>Dom Derrien</name><uri>https://plus.google.com/100819130946985751629</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-CAiJ6LOHBWE/AAAAAAAAAAI/AAAAAAAAErE/twENdW648fs/s512-c/photo.jpg" /></author><thr:total>3</thr:total><feedburner:origLink>http://domderrien.blogspot.com/2010/06/joy-of-ubuntu.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CE4FRn44eyp7ImA9WxFWEEs.&quot;"><id>tag:blogger.com,1999:blog-168828253523263225.post-1881803729057471072</id><published>2010-05-28T10:16:00.001-04:00</published><updated>2010-05-28T12:08:37.033-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-05-28T12:08:37.033-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Entrepreneurship" /><category scheme="http://www.blogger.com/atom/ns#" term="Experience" /><category scheme="http://www.blogger.com/atom/ns#" term="Community" /><title>What motivates us?</title><content type="html">One year and half ago, I wrote the blog post &lt;a href="http://domderrien.blogspot.com/2008/12/career-advice.html"&gt;Career Advice '08&lt;/a&gt;. The key idea was about encouraging people to develop their own expertise to be successful in their career. Don't wait for your management to give you something exciting, do it yourself!&lt;br /&gt;
&lt;br /&gt;
Two weeks ago, I wrote another blog post &lt;a href="http://domderrien.blogspot.com/2010/05/work-around-general-laziness.html"&gt;Work around the general laziness&lt;/a&gt;. In some ways, I reported on my disappointment of not seeing enough &lt;i&gt;autonomous experts&lt;/i&gt;, and that entrepreneurs have to compose with variously skilled teams.&lt;br /&gt;
&lt;br /&gt;
I finished my second expose by mentioning the Twetailer's &lt;a href="http://stevenmilstein.com/2010/04/19/kool-aid-being-served-here/" target="_blank"&gt;work-for-attribution&lt;/a&gt; offer. I explained that many people liked the concept and that few of them have committed to deliver something with their own schedule. Each one at its own pace builds an extended skill set.&lt;br /&gt;
&lt;br /&gt;
What are the commonalities between the contributors?&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;They have a regular job so the money is not an issue.&lt;/li&gt;
&lt;li&gt;They are performers and have a high level of satisfaction.&lt;/li&gt;
&lt;/ul&gt;I was such a contributor when I started working with Steven, before jumping on the entrepreneur-side to help developing the business from the inside.&lt;br /&gt;
&lt;br /&gt;
I had not really identified the sources of my motivation until I saw Dan Pink's illustration of "What motivates us", for a talk given by the Royal Society for the encouragement of Arts, Manufactures and Commerce (&lt;a href="http://draft.blogger.com/thersa.org" target="_blank"&gt;theRSA.org&lt;/a&gt;).&lt;br /&gt;
&lt;blockquote&gt;The three factors that lead to performance and personal satisfaction:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;Autonomy&lt;/li&gt;
&lt;li&gt;Mastery&lt;/li&gt;
&lt;li&gt;Purpose.&lt;/li&gt;
&lt;/ul&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;center&gt;&lt;object height="340" width="560"&gt;&lt;param name="movie" value="http://www.youtube.com/v/u6XAPnuFjJc&amp;hl=en_US&amp;fs=1&amp;color1=0x5d1719&amp;color2=0xcd311b"&gt;&lt;/param&gt;&lt;param name="allowFullScreen" value="true"&gt;&lt;/param&gt;&lt;param name="allowscriptaccess" value="always"&gt;&lt;/param&gt;&lt;embed src="http://www.youtube.com/v/u6XAPnuFjJc&amp;hl=en_US&amp;fs=1&amp;color1=0x5d1719&amp;color2=0xcd311b" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="560" height="340"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;/center&gt;&lt;br /&gt;
&lt;br /&gt;
I strongly encourage you to look at that video, part for the fun of viewing Dan Pink in action, part for the delivered message. I encourage you also to share your experience as a comment below ;)&lt;br /&gt;
&lt;br /&gt;
A+, Dom&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=6G5GJXi2wz0:FYm_IB7XGDU:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=6G5GJXi2wz0:FYm_IB7XGDU:4cEx4HpKnUU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?i=6G5GJXi2wz0:FYm_IB7XGDU:4cEx4HpKnUU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=6G5GJXi2wz0:FYm_IB7XGDU:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?i=6G5GJXi2wz0:FYm_IB7XGDU:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SharingTechnologies/~4/6G5GJXi2wz0" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://domderrien.blogspot.com/feeds/1881803729057471072/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://domderrien.blogspot.com/2010/05/what-motivates-is.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/168828253523263225/posts/default/1881803729057471072?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/168828253523263225/posts/default/1881803729057471072?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SharingTechnologies/~3/6G5GJXi2wz0/what-motivates-is.html" title="What motivates us?" /><author><name>Dom Derrien</name><uri>https://plus.google.com/100819130946985751629</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-CAiJ6LOHBWE/AAAAAAAAAAI/AAAAAAAAErE/twENdW648fs/s512-c/photo.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://domderrien.blogspot.com/2010/05/what-motivates-is.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CEEGQHk_eCp7ImA9WxFXEkU.&quot;"><id>tag:blogger.com,1999:blog-168828253523263225.post-1672668254130890811</id><published>2010-05-18T15:48:00.007-04:00</published><updated>2010-05-19T11:23:41.740-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-05-19T11:23:41.740-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Entrepreneurship" /><category scheme="http://www.blogger.com/atom/ns#" term="Experience" /><title>Work around the general laziness ;)</title><content type="html">This blog entry is primarily inspired by a rant from &lt;a href="http://thisweekinstartups.com/about-jason/" target="_blank"&gt;Jason Calacanis&lt;/a&gt;, during the episode #47 of &lt;a href="http://thisweekinstartups.com/" target="_blank"&gt;This Week in Startups&lt;/a&gt; (&lt;a href="http://www.youtube.com/watch?v=4XpQ7YVXv_0&amp;amp;autoplay=1" target="_blank"&gt;full show&lt;/a&gt;, or directly &lt;a href="http://www.youtube.com/watch?v=4XpQ7YVXv_0&amp;amp;autoplay=1#t=5m20s" target="_blank"&gt;starting near 5:20&lt;/a&gt;). During that show, Jason goes against the &lt;a href="http://en.wikipedia.org/wiki/Generation_Y" target="_blank"&gt;Generation Y people&lt;/a&gt; who are &lt;i&gt;usually lazy&lt;/i&gt; and &lt;i&gt;want everything before actually delivering&lt;/i&gt;...&lt;br /&gt;
&lt;br /&gt;
Another source of inspiration is &lt;a href="http://www.bothsidesofthetable.com/about-2/" target="_blank"&gt;Mark Suster&lt;/a&gt;, the co-host of &lt;a href="http://thisweekin.com/thisweekin-venture-capital/" target="_blank"&gt;This Week in Venture&lt;/a&gt;&lt;a href="http://thisweekin.com/thisweekin-venture-capital/" target="_blank"&gt;Capital&lt;/a&gt;, with a series of posts culminating with &lt;a href="http://www.cloudave.com/link/the-long-term-value-of-loyalty" target="_blank"&gt;The Long-Term Value of Loyalty&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;span style="font-size: large;"&gt;What's their point-of-view?&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
Jason is mostly focusing on a group of people, a group that has been educated with a different mindset from his own one. Gen-Y people have more concerns about ecology in general, for example, they are used to getting free or cheap digital goods, and many of them live with their parents longer than before...&lt;br /&gt;
&lt;br /&gt;
If I can agree on the fact that many Gen-Y people seem lazier than the the Gen-X or the baby boomers at the same age, I think the key differentiators is their relationship with money: the Gen-Y people think they need less money, so they don't work that hard to get always more!&lt;br /&gt;
&lt;br /&gt;
In his &lt;a href="http://www.bothsidesofthetable.com/2010/04/22/never-hire-job-hoppers-never-they-make-terrible-employees/" target="_blank"&gt;first post of the series&lt;/a&gt;, Mark pinpoints &lt;i&gt;job hoppers&lt;/i&gt;, employees who can resign anytime, without much respect for their commitment or for the situation of the company. If Mark readjusts the context in the &lt;a href="http://www.bothsidesofthetable.com/2010/04/25/job-hoppers-redux-an-employees-perspective/" target="_blank"&gt;second post&lt;/a&gt; by writing that “quitting a job because it's a mess is OK”, Mark still thinks that being “loyal” is important and everyone should stay loyal for long term benefits...&lt;br /&gt;
&lt;br /&gt;
I can agree with Mark that working with such individuals is risky, that developing a business with not-so-loyal people is tough. However, I think that building a business with people from different mindsets and backgrounds is more valuable. To me, diversity, if you can manage it, is more important than loyalty!&lt;br /&gt;
&lt;br /&gt;
&lt;span style="font-size: large;"&gt;Is laziness a syndrome limited to Generation Y?&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;div style="float:right;text-align:center;padding:0 0 10px 10px;font-size:smaller;"&gt;&lt;img src="http://www.snorgtees.com/images/Laziness_Fullpic_1.gif" title="Laziness Pays Off Now" width="254" height="204"/&gt;&lt;br /&gt;
T-shirt &lt;a href="http://www.snorgtees.com/lazinesspaysoffnow-p-991.html?osCsid=6be25d378d6709b3ca66a9cc6630b693" target="_blank"&gt;Laziness Pays Off Now&lt;/a&gt; from &lt;a href="http://www.snorgtees.com/" target="_blank"&gt;&lt;b&gt;SNORG&lt;/b&gt;TEES&lt;/a&gt;&lt;/div&gt;Before becoming an &lt;a href="http://domderrien.blogspot.com/2010/03/ladies-and-gentlemen-here-is-twetailer.html" target="_blank"&gt;entrepreneur&lt;/a&gt;, I was an employee like many others. In France, I worked for a very small company and then for a multinational corporation before immigrating to Canada. My first job here was for a medium Montreal company which was later bought by Oracle. I quitted Oracle to go with IBM Rational, which I quitted to go with Compuware. In addition to my professional life, I've been involved in many non governmental organizations.&lt;br /&gt;
&lt;br /&gt;
Along my life, I've always been curious, interested in learning new subjects, debating around them, and trying to implement the best ideas, especially when I'm passionate. I've been lucky to team up with great people (note that I learned a lot too from the failed partnerships) and I've gotten  excellent mentors. When I write “lucky”, I mean “I worked hard to consider myself as lucky.”&lt;br /&gt;
&lt;br /&gt;
All in all, I can honestly say I haven't seen a lot of people working very hard at work, not that they are lazy, just that they have different priorities, different motivations. Although I've already proposed many times to be a mentor, to give lunch-n-learn talks, to organize specific trainings, etc., -on my own time, for the only benefit of the recipient- only few people have followed.&lt;br /&gt;
&lt;br /&gt;
I've observed the “good enough” attitude with any types of people: old or young, men or women, immigrants or native country, Europeans or Americans, etc. In Western countries, we don't have to fight for a shelter or to find food, so the sense of urgency is blunted. Why would people with already enough (enough money, enough responsibilities, enough social involvement, etc.)  go for more?&lt;br /&gt;
&lt;br /&gt;
Maybe the Gen-Y people are &lt;i&gt;worse&lt;/i&gt; than the others,because their “good enough” level is lower than before. IMHO, they are just like the common crowd, just normal people in our modern world.&lt;br /&gt;
&lt;br /&gt;
&lt;span style="font-size: large;"&gt;How to deal with the general laziness?&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
As mentioned before, I think the diversity is important. Trying to stay in closed vacuum with the elite can help a bit, but not for a long time. The key point is to compose teams with top elements and less skilled ones. The newbies can learn from the experienced people and more help around &lt;span style="font-weight: normal;"&gt;them &lt;/span&gt;will reduce the “single point of failure” risk.&lt;br /&gt;
&lt;br /&gt;
For sure, you cannot reward the &lt;i&gt;in-learning&lt;/i&gt; people as you do reward the top team members. As I explain to my kids, there's a consequence to everything! If you work hard, if your help is valuable, if you go beyond your tasks, the system should reward you accordingly. If it does not come immediately, it should be clear that it will come eventually if everything goes well. If the hard workers don't get a tangible ROI, I think it's normal to expect them to slow down or to quit... (to expect them slowing down ?)&lt;br /&gt;
&lt;br /&gt;
To me, the key factor in a successful project is the &lt;b&gt;commitment&lt;/b&gt; of the participants. It's not that important that this intern has a lower &lt;i&gt;velocity&lt;/i&gt; than an experienced engineer because we can plan accordingly. What's more important is that you trust that he's going to deliver as expected, or if he has some troubles that he's going to report them as soon as possible.&lt;br /&gt;
&lt;br /&gt;
In the case of software developers evolving in an Agile environment, measuring their work progress is not really an issue. If the team is correctly equipped, everyone is accountable. In the peripheral teams (product managers, marketing, sales representatives, etc.), commitments are more difficult to get, and the more sources of non-productivity there are, the fewer chances of success you get.&lt;br /&gt;
&lt;br /&gt;
&lt;span style="font-size: large;"&gt;A real case, please!&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
My partner Steven and I have developed the concept of &lt;a href="http://stevenmilstein.com/2010/04/19/kool-aid-being-served-here/" target="_blank"&gt;work for attribution&lt;/a&gt;. Because we're a startup, we cannot offer salaries in exchange to work. However, we offer to attribute back the work to the ones who have delivered it. Immediately, contributors can use our environment as a lab to test and develop new ideas. We have a working product and it's up to them to adapt it, to make it better.&lt;br /&gt;
&lt;br /&gt;
If our projects are successful, if we can &lt;i&gt;cash&lt;/i&gt; them at one point, the contributors will be part of the success and then we'll try to reward them. If a big player wants to acquire us, top contributors will be probably part of the deal.&lt;br /&gt;
&lt;br /&gt;
So far, many people have liked the concept and a few of them have committed to deliver something specific. Some of them are Gen-Y people, some are immigrants, and everyone work at different rates. If I can rely on people I trust, on people who are going to deliver what they have committed to, whoever they are, it's very cool!&lt;br /&gt;
&lt;br /&gt;
A+, Dom&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=Gby_SQ9AfnY:h7m5LCzY1CU:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=Gby_SQ9AfnY:h7m5LCzY1CU:4cEx4HpKnUU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?i=Gby_SQ9AfnY:h7m5LCzY1CU:4cEx4HpKnUU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=Gby_SQ9AfnY:h7m5LCzY1CU:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?i=Gby_SQ9AfnY:h7m5LCzY1CU:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SharingTechnologies/~4/Gby_SQ9AfnY" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://domderrien.blogspot.com/feeds/1672668254130890811/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://domderrien.blogspot.com/2010/05/work-around-general-laziness.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/168828253523263225/posts/default/1672668254130890811?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/168828253523263225/posts/default/1672668254130890811?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SharingTechnologies/~3/Gby_SQ9AfnY/work-around-general-laziness.html" title="Work around the general laziness ;)" /><author><name>Dom Derrien</name><uri>https://plus.google.com/100819130946985751629</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-CAiJ6LOHBWE/AAAAAAAAAAI/AAAAAAAAErE/twENdW648fs/s512-c/photo.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://domderrien.blogspot.com/2010/05/work-around-general-laziness.html</feedburner:origLink></entry><entry gd:etag="W/&quot;Ck4BQn8yfip7ImA9WxFSGEU.&quot;"><id>tag:blogger.com,1999:blog-168828253523263225.post-7163312978686602603</id><published>2010-04-21T12:22:00.003-04:00</published><updated>2010-04-21T16:15:53.196-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-04-21T16:15:53.196-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Technology" /><category scheme="http://www.blogger.com/atom/ns#" term="Experience" /><category scheme="http://www.blogger.com/atom/ns#" term="Community" /><title>Social Software, Cynapse, and Open Platforms</title><content type="html">&lt;span style="font-size: large;"&gt;What's a Social Software?&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
These days, most of white collar workers have to deal with computers on a daily basis. Computers are everywhere: from the front desk to allow receptionists to get to the company directory up to garage doors checking who's in, who's out. There are so different types of computers that some of them go unnoticed!&lt;br /&gt;
&lt;br /&gt;
The scope of this post is limited computers used to collaborate:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;Make appointments&lt;/li&gt;
&lt;li&gt;Produce documents&lt;/li&gt;
&lt;li&gt;Review &amp;amp; comment documents&lt;/li&gt;
&lt;li&gt;Forward documents&lt;/li&gt;
&lt;li&gt;Poll users&lt;/li&gt;
&lt;li&gt;Manage tasks&lt;/li&gt;
&lt;/ul&gt;When it's time to collaborate, the vast majority of computer users rely on e-mails. E-mail is probably the most essential tool now, and without e-mails, many are lost. Do you remember the BlackBerry syndrome of the RIM product early adopters? Yeah, the one that wakes up people middle of the night so they can get new e-mails ;)&lt;br /&gt;
&lt;br /&gt;
With tools like Microsoft Outlook, Apple iCal, and Google Calendar, more users relying on calendar tools to organize their time. This is neat because with such tools you can sometimes see up front if the targeted time slot is good for other attendees. And when attendance confirmation comes, the meeting information are updated automatically without requiring a specific triage in the mailbox.&lt;br /&gt;
&lt;br /&gt;
&lt;img src="http://www.missiontolearn.com/wp-content/uploads/2009/08/community-300x199.jpg" style="float: right; margin: 0pt 0pt 10px 10px;" title="Collaboration" /&gt;When people have to share pictures, because the ones modern digital camera can generate are so big and very few people have the knowledge to reduce their size nicely, more and more people resort to online hosting services like &lt;a href="http://www.flickr.com/"&gt;flickr&lt;/a&gt; or &lt;a href="http://picasaweb.google.com/"&gt;Picasa&lt;/a&gt;. In addition to process&amp;nbsp; images to transit on the Web (while still available in high resolution for printing purposes), sharing pictures via links is way lighter for the mail system. It has also the additional benefit that pictures can be removed safely without one someone else to purge his/her mailbox.&lt;br /&gt;
&lt;br /&gt;
With review sites like &lt;a href="http://www.cnet.com/"&gt;cnet&lt;/a&gt; or &lt;a href="http://www.epinions.com/"&gt;Epinions.com&lt;/a&gt;, more and more users have started to give their opinions online to share the joy of being a owner of a wonderful gadget, or to inform others that such a gadget is crap! Involving readers and customers to share their experiences is the key element of the social software movement.&lt;br /&gt;
&lt;br /&gt;
So what's the relation between Cynapse and social software:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;Cynapse is a central place where people can collaborate online with the most practical tools without relying on e-mails, a place where collaboration and communication do not loose their context.&lt;/li&gt;
&lt;/ul&gt;&lt;span style="font-size: large;"&gt;Why Cynapse?&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
When my partner &lt;a href="http://stevenmilstein.com/" target="_blank"&gt;Steven&lt;/a&gt; and I started to work on the &lt;a href="http://twetailer.com/"&gt;&lt;span class="twetailer"&gt;&lt;span class="twetailerBang"&gt;!&lt;/span&gt;twetailer&lt;/span&gt;&lt;/a&gt;, we knew that we wanted to collaborate online, within a reliable and protected environment that could handle many document types: user stories, specifications, diagrams, mock ups, pictures, bookmarks, discussions, etc.&lt;br /&gt;
&lt;br /&gt;
&lt;img src="http://greenhouse.lotus.com/images/gh_icon.gif" style="float: left; margin: 0pt 10px 10px 0;" /&gt;As IBM-ers, we looked among the tremendous IBM products and we selected &lt;a href="http://greenhouse.lotus.com/"&gt;Lotus GreenHouse&lt;/a&gt; which was still in &lt;a href="http://en.wikipedia.org/wiki/Software_release_life_cycle"&gt;beta&lt;/a&gt;. The main flaw we experienced was its disruptive slowness. Another issue to us was our quasi-impossibility to influence the development path to fix the painful issues. Just 2 guys in a big user community have a very little impact...&lt;br /&gt;
&lt;br /&gt;
&lt;img height="109" src="http://elgg.org/images/elgg.png" style="float: right; margin: 0pt 0pt 10px 10px;" width="200" /&gt;We then decided to switch to &lt;a href="http://elgg.org/"&gt;Elgg&lt;/a&gt;, a free and open source software, that our hosting service offered. Elgg is probably a good tool but it did not match our needs. The variety of entities was limited as our ability to fix the issues. Probably, it would have been better to host the service at home and to extend its model. Because it would have distracted us from our project, we looked for a better solution.&lt;br /&gt;
&lt;br /&gt;
At this time, Steven was studying the &lt;a href="http://stevenmilstein.com/2009/03/19/lessons-learned-from-social-content-20-circle-of-life-part-2/"&gt;social software offering&lt;/a&gt; and Cynapse came out as a good fit to us:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;The service out-of-the-box was promising and was still under active development.&lt;/li&gt;
&lt;li&gt;They had an affordable offer: free self hosted service, managed service for a small subscription, managed hardware also for a fee.&lt;/li&gt;
&lt;li&gt;They had an active user community which was hosted on the tool itself (the “&lt;a href="http://en.wikipedia.org/wiki/Eating_one%27s_own_dog_food"&gt;eat your own dog food&lt;/a&gt;” principle).&lt;/li&gt;
&lt;li&gt;Whatever solution we choose, we are free to upgrade/downgrade/exit.&lt;/li&gt;
&lt;/ul&gt;&lt;br /&gt;
Cynapse is an Indian company and its managers are really great. For practical reasons, we decided to go with the managed service online. When Steven contacted them, asking for a rebate in exchange to us blogging about our experience with the tool and us feeding them with enhancement requests from the development point of view, they accepted and sustained our involvement.&lt;br /&gt;
&lt;br /&gt;
Disclosure: Because the tool and the team is great, &lt;a href="http://milstein-assoc.com/" target="_blank"&gt;Milstein &amp;amp; associates&lt;/a&gt; is now a business partner and can resell and offer services on the top of the tool.&lt;br /&gt;
&lt;br /&gt;
&lt;img src="http://edu.cyn.in/logo.jpg" style="float: right; margin: 0pt 0pt 10px 10px;" /&gt;After one full year and two upgrades, we are very happy with the tool and its support. At one point, we have developed an offer for schools available at &lt;b&gt;&lt;a _blank="" href="http://edu.cyn.in/%20target="&gt;&lt;span style="color: red;"&gt;edu&lt;/span&gt;&lt;span style="color: #666666;"&gt;.cyn.in&lt;/span&gt;&lt;/a&gt;&lt;/b&gt; which has been extremely well adapted by the kids!&lt;br /&gt;
&lt;br /&gt;
In addition to the collaboration aspect, Cynapse with its offering of various tools in one platform allows users to develop their online reputation in a controlled environment. As Craig Newmark, the founder of &lt;a href="http://www.craigslist.org/"&gt;craigslist&lt;/a&gt;, &lt;a href="http://www.cnewmark.com/2010/04/trust-and-reputation-systems-redistributing-power-and-influence.html"&gt;mentions in a blog post&lt;/a&gt;:&lt;br /&gt;
&lt;blockquote&gt;People use social networking tools to figure out who they can trust and  rely on for decision making. &lt;b&gt;By the end of this decade, power  and influence will shift largely to those people with the best  reputations and trust networks, from people with money and nominal  power.&lt;/b&gt; That is, peer networks will confer legitimacy on people  emerging from the grassroots&lt;span&gt;.&lt;/span&gt;&lt;/blockquote&gt;The Cynapse environment allows users to highlight their work: statistics about contributions and comments are shared on the main page, readers can note the published materials, etc. When people are new to social software, Cynapse offers a simple way to identify the people others “trust” and allows good contributors to build their reputation.&lt;br /&gt;
&lt;br /&gt;
Aside our work on the &lt;a href="http://twetailer.com/"&gt;&lt;span class="twetailer"&gt;&lt;span class="twetailerBang"&gt;!&lt;/span&gt;twetailer&lt;/span&gt;&lt;/a&gt; project, because the tool fits our needs, because kids adopted very well, we have proposed it to traditional companies (the ones that relies on Microsoft Outlook and shared folders for their collaboration). For now, the feedback is positive but it's too early to advance any adoption rate ;)&lt;br /&gt;
&lt;br /&gt;
&lt;span style="font-size: large;"&gt;Why Open Platforms?&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
As a developer, I'm an heavy user of open source software (development environment, source control, build system, etc.). I am also a contributor myself with two open libraries hosted on github:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;A set of utilities for Web application developers (Java, Python, JavaScript) which offers:&lt;/li&gt;
&lt;ul&gt;&lt;li&gt;Globalization features: from one set of central repositories (TMX format) to programming language dependent resource bundles;&lt;/li&gt;
&lt;li&gt;JSON related objects: to ease the parsing/generation of JSON structure &lt;br /&gt;
&lt;/li&gt;
&lt;/ul&gt;&lt;li&gt;An adaptation of the Amazon Flexible Payment Service (FPS) library for the Java environment of Google App Engine.&lt;br /&gt;
&lt;/li&gt;
&lt;/ul&gt;And the content of this blog is offered under the Common Creative Licence By-NC-SA, which allows using beyond the traditional “fair-use” as long as you cite the source ;)  I very like open platform for the reasons Larry Lessig gave in speech on &lt;a href="http://randomfoo.net/oscon/2002/lessig/" target="_blank"&gt;&lt;code&gt;&lt;free culture=""&gt;&lt;/free&gt;&lt;/code&gt;&lt;/a&gt;. Open source is good for innovation for geeks like myself. For enterprises, I would argue that the key point is the data access: at anytime, someone can get the data out of an open source project without risking any patent infringement, without risky reverse-engineering. For sure, it won't be free as in “free beer” but they'll be free to get their data back. Some people will argue that close software often offer a way to export data in a standard format (like a &lt;a href="http://en.wikipedia.org/wiki/Database_dump" target="_blank"&gt;SQL dump for a database&lt;/a&gt;) and allow then to import them somewhere else, but that's only true if no feature are dropped during the export and if all features from one can be activated in two during the import.&lt;br /&gt;
&lt;br /&gt;
To summarize my point, I would say that open source software allow anyone to exit at anytime while continuing to control the data.&lt;br /&gt;
&lt;br /&gt;
As a collaboration tool, Cynapse is maybe not the best one but it offers competitive advantages for the right price (for free if you have the team and expertise to manage it, for fee if you choose the hassle-free solution of the hosted service on Amazon AWS) while letting users migrating to another platform anytime.&lt;br /&gt;
&lt;br /&gt;
&lt;img src="http://4.bp.blogspot.com/_VZZAFHl2_Og/S88bxpdR7hI/AAAAAAAAC0k/hjU7d8hMYDs/s1600/cynapse-community.png" style="float: left; margin: 0pt 10px 10px 0pt;" title="Cynapse Community" /&gt;Important point to consider during the selection of an open source solution: the quality of its community. More active is the community, better are your chances that issues you are facing have been documented, better your chances to see your (excellent) enhancement requests supported by others. Being able to contribute to an active community (by submitting bug reports, by answering others' questions, by submitting patches) have also the side benefit of improving your reputation.&lt;br /&gt;
&lt;br /&gt;
A+, Dom&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=LUpR3qgfprk:bjG0PmtdY1E:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=LUpR3qgfprk:bjG0PmtdY1E:4cEx4HpKnUU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?i=LUpR3qgfprk:bjG0PmtdY1E:4cEx4HpKnUU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=LUpR3qgfprk:bjG0PmtdY1E:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?i=LUpR3qgfprk:bjG0PmtdY1E:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SharingTechnologies/~4/LUpR3qgfprk" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://domderrien.blogspot.com/feeds/7163312978686602603/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://domderrien.blogspot.com/2010/04/social-software-cynapse-and-open.html#comment-form" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/168828253523263225/posts/default/7163312978686602603?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/168828253523263225/posts/default/7163312978686602603?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SharingTechnologies/~3/LUpR3qgfprk/social-software-cynapse-and-open.html" title="Social Software, Cynapse, and Open Platforms" /><author><name>Dom Derrien</name><uri>https://plus.google.com/100819130946985751629</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-CAiJ6LOHBWE/AAAAAAAAAAI/AAAAAAAAErE/twENdW648fs/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://4.bp.blogspot.com/_VZZAFHl2_Og/S88bxpdR7hI/AAAAAAAAC0k/hjU7d8hMYDs/s72-c/cynapse-community.png" height="72" width="72" /><thr:total>2</thr:total><feedburner:origLink>http://domderrien.blogspot.com/2010/04/social-software-cynapse-and-open.html</feedburner:origLink></entry><entry gd:etag="W/&quot;AkYDSHc7cCp7ImA9WxBaEk8.&quot;"><id>tag:blogger.com,1999:blog-168828253523263225.post-1947501483083628645</id><published>2010-03-21T23:14:00.002-04:00</published><updated>2010-03-21T23:22:59.908-04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-03-21T23:22:59.908-04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Technology" /><category scheme="http://www.blogger.com/atom/ns#" term="Community" /><title>Amazon FPS library for the Google App Engine environment</title><content type="html">Here is a post on &lt;a href="http://aws.amazon.com/fps/"&gt;Amazon Flexible Payments System (FPS)&lt;/a&gt; and the beauty of the open source model!&lt;br /&gt;
&lt;br /&gt;
&lt;span style="font-size: large;"&gt;Amazon FPS as the payment platform&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;span class="twetailer"&gt;&lt;span class="twetailerBang"&gt;!&lt;/span&gt;twetailer&lt;/span&gt; is an application connecting people to people, consumers to retailers and vice-versa. The business model is &lt;a href="http://en.wikipedia.org/wiki/Freemium" target="_blank"&gt;&lt;i&gt;freemiun&lt;/i&gt;&lt;/a&gt;-based:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;Posting demands, receiving proposals, and accepting proposals is available for free to any customers.&lt;/li&gt;
&lt;li&gt;Listening to demands, proposing products or services, and closing deals is available for free to any &lt;i&gt;registered&lt;/i&gt; retailers.&lt;/li&gt;
&lt;li&gt;At one point, &lt;span class="twetailer"&gt;&lt;span class="twetailerBang"&gt;!&lt;/span&gt;twetailer&lt;/span&gt; is going to offer retailers to ask consumers to pay for the accepted products or services. The payment platform is going to be Amazon FPS&lt;sup&gt;&lt;small&gt;&lt;a href="http://domderrien.blogspot.com/2010/03/amazon-fps-library-for-google-app.html#amazonFPSNote1"&gt;1&lt;/a&gt;&lt;/small&gt;&lt;/sup&gt;.&lt;/li&gt;
&lt;/ul&gt;&lt;br /&gt;
Amazon FPS is a really neat platform, the only one to my knowledge allowing to organize money transfers between third-parties. With Amazon FPS, &lt;span class="twetailer"&gt;&lt;span class="twetailerBang"&gt;!&lt;/span&gt;twetailer&lt;/span&gt; will be able to convey money from the consumers directly to the retailers, without the money transiting on &lt;span class="twetailer"&gt;&lt;span class="twetailerBang"&gt;!&lt;/span&gt;twetailer&lt;/span&gt; own account! This is a really safe mechanism.&lt;br /&gt;
&lt;br /&gt;
As a quick introduction to Amazon FPS, I would strongly suggest you listen to that one hour webcast introduced on &lt;a href="http://www.amazonpaymentsblog.com/amazon_payments_blog/"&gt;Amazon Payments&lt;/a&gt; blog on April 7, 2009: &lt;a href="http://www.amazonpaymentsblog.com/amazon_payments_blog/2009/04/our-next-webinar-monetize-your-innovation-with-amazon-fps-.html" target="_blank"&gt;Monetize your innovation with Amazon FPS&lt;/a&gt;. If you use the open-source tool &lt;a href="http://www.videolan.org/" target="_blank"&gt;VideoLAN VLC&lt;/a&gt;, you can load the ASX file directly from Akamai from &lt;a href="http://mfile.akamai.com/23543/wmv/citrixvar.download.akamai.com/23543/www/596/433/8935354780199596433/1-8935354780199596433-12310131507.asx" target="_blank"&gt;here&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;span style="font-size: large;"&gt;Amazon and the open-source model&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
Amazon FPS, as many others &lt;a href="http://en.wikipedia.org/wiki/Amazon_Web_Services" target="_blank"&gt;Amazon Web Services (AWS)&lt;/a&gt;, allows third-party applications to use its services through very simple APIs which are HTTP based! The libraries that developers need to use are mostly wrappers over HTTP connections with some specific controllers formatting the requests and signing them (to avoid a man-in-the-middle process tampering them).&lt;br /&gt;
&lt;br /&gt;
Because HTTP is an open protocol and because Amazon could not probably develop its libraries for all possible Web servers, Amazon opened the libraries' source and attached to them a very liberal license&lt;sup&gt;&lt;small&gt;&lt;a href="http://domderrien.blogspot.com/2010/03/amazon-fps-library-for-google-app.html#amazonFPSNote2"&gt;2&lt;/a&gt;&lt;/small&gt;&lt;/sup&gt;.&lt;br /&gt;
&lt;br /&gt;
This is a very respectable attitude regarding their customers and also very well thought on the business-side: if developers can adopt their libraries for their own needs, Amazon won't have to pay for the corresponding development and it will enlarge the set of applications their platform can serve!&lt;br /&gt;
&lt;br /&gt;
&lt;span style="font-size: large;"&gt;Amazon FPS on Google App Engine platform&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
The &lt;span class="twetailer"&gt;&lt;span class="twetailerBang"&gt;!&lt;/span&gt;twetailer&lt;/span&gt; server-side logic is &lt;a href="http://domderrien.blogspot.com/2009/04/google-app-engine-meets-java.html"&gt;Java based&lt;/a&gt; and dropping the Amazon FPS library freshly compiled in &lt;code&gt;war/WEB-INF/lib&lt;/code&gt; is simple. However, the Amazon FPS code cannot run as-is because of few App Engine limitations...&lt;br /&gt;
&lt;br /&gt;
The first one is encountered when the application needs to build the URL that will launch the &lt;a href="http://docs.amazonwebservices.com/AmazonFPS/latest/FPSAggregatedGuide/index.html?CBUI.html" target="_blank"&gt;co-branded service&lt;/a&gt;, a page that will allow consumers to pay for the service or product previously proposed by a retailer.&lt;br /&gt;
&lt;blockquote&gt;The static method &lt;code&gt;HttpURLConnection.setFollowRedirects(boolean)&lt;/code&gt; controls the VM behavior and is then guarded by a JVM permission&lt;span&gt;.&lt;/span&gt;&lt;/blockquote&gt;Read the &lt;a href="http://code.google.com/p/googleappengine/issues/detail?id=1556" target="_blank"&gt;incident report&lt;/a&gt; in the Google App Engine discussion group.&lt;br /&gt;
&lt;br /&gt;
Fixing this issue is simple: tune the ability to follow redirection on the connection itself instead of applying the settings globally.&lt;br /&gt;
&lt;br /&gt;
The second issue is really major:&lt;br /&gt;
&lt;blockquote&gt;The library uses the &lt;a href="http://hc.apache.org/httpclient-3.x/" target="_blank"&gt;Jakarta Commons HttpClient component&lt;/a&gt; to convey payment requests from the application to the Amazon infrastructure&lt;span&gt;. And many of its underlying calls are blocked in Google App Engine Java environment.&lt;/span&gt;&lt;/blockquote&gt;I &lt;a href="http://developer.amazonwebservices.com/connect/thread.jspa?threadID=43352&amp;tstart=0" target="_blank"&gt;asked for advices&lt;/a&gt; on &lt;a href="http://developer.amazonwebservices.com/connect/forum.jspa?forumID=35&amp;start=0" target="_blank"&gt;AWS FPS forums&lt;/a&gt;. But without response, I have decided to go with my own wrapper of the &lt;a href="http://code.google.com/appengine/docs/java/urlfetch/overview.html" target="_blank"&gt;Google URL Fetch&lt;/a&gt; mimicking the HttpClient &lt;code&gt;HttpConnectionManager&lt;/code&gt; and &lt;code&gt;HttpConnection&lt;/code&gt; classes.&lt;br /&gt;
&lt;br /&gt;
&lt;span style="font-size: large;"&gt;Wrappers of Google URL Fetch for Amazon FPS&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
Following Amazon's leadership, I offer the URL Fetch wrappers that allows Amazon FPS to work on Google App Engine platform:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;Same Apache license, version 2.0.&lt;/li&gt;
&lt;li&gt;Code hosted on &lt;a href="http://github.com" target="_blank"&gt;GitHub&lt;/a&gt;, in the repository &lt;a href="http://github.com/DomDerrien/amazon-fps-gaej" target="_blank"&gt;DomDerrien/amazon-fps-gaej&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;For now, only two Amazon classes have been modified: &lt;code&gt;&lt;a href="http://github.com/DomDerrien/amazon-fps-gaej/blob/master/src/com/amazonaws/ipnreturnurlvalidation/SignatureUtilsForOutbound.java" target="_blank"&gt;com.amazonaws.ipnreturnurlvalidation.SignatureUtilsForOutbound&lt;/a&gt;&lt;/code&gt; and &lt;code&gt;&lt;a href="http://github.com/DomDerrien/amazon-fps-gaej/blob/master/src/com/amazonaws/fps/AmazonFPSClient.java" target="_blank"&gt;com.amazonaws.fps.AmazonFPSClient&lt;/a&gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;My two additions are the classes &lt;code&gt;&lt;a href="http://github.com/DomDerrien/amazon-fps-gaej/blob/master/src/domderrien/wrapper/UrlFetch/UrlFetchConnectionManager.java" target="_blank"&gt;domderrien.wrapper.UrlFetch.UrlFetchConnectionManager&lt;/a&gt;&lt;/code&gt; and &lt;code&gt;&lt;a href="http://github.com/DomDerrien/amazon-fps-gaej/blob/master/src/domderrien/wrapper/UrlFetch/UrlFetchHttpConnection.java" target="_blank"&gt;domderrien.wrapper.UrlFetch.UrlFetchHttpConnection&lt;/a&gt;&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;The code currently available works in the simple scenario &lt;span class="twetailer"&gt;&lt;span class="twetailerBang"&gt;!&lt;/span&gt;twetailer&lt;/span&gt; needs. But it is still under development. And the test suite covering it is not yet completed.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="codeSnippetTitle"&gt;&lt;code&gt;UrlFetchConnectionManager&lt;/code&gt; class definition&lt;/div&gt;&lt;pre class="prettyprint limitSnippetHeight"&gt;/******************************************************************************* 
 *  Adaptation for the Amazon FPS library to work on the Java platform of
 *  Google App Engine.
 *  
 *  Copyright 2010 Dom Derrien
 *  Licensed under the Apache License, Version 2.0
 */

package domderrien.wrapper.UrlFetch;

import org.apache.commons.httpclient.ConnectionPoolTimeoutException;
import org.apache.commons.httpclient.HostConfiguration;
import org.apache.commons.httpclient.HttpConnection;
import org.apache.commons.httpclient.HttpConnectionManager;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.params.HttpConnectionManagerParams;

public class UrlFetchConnectionManager implements HttpConnectionManager {

 private HttpConnectionManagerParams params;
 private HttpConnection connection;
 
 public void closeIdleConnections(long timeout) {
  throw new RuntimeException("closeIdleConnections(long)");
 }

 public HttpConnection getConnection(HostConfiguration hostConfiguration) {
  throw new RuntimeException("getConnection(HostConfiguration)");
  // return null;
 }

 public HttpConnection getConnection(HostConfiguration hostConfiguration, long timeout) throws HttpException {
  throw new RuntimeException("getConnection(HostConfiguration, long)");
  // return null;
 }

 public HttpConnection getConnectionWithTimeout(HostConfiguration hostConfiguration, long timeout) throws ConnectionPoolTimeoutException {
  // As reported in http://code.google.com/appengine/docs/java/urlfetch/usingjavanet.html#Java_Net_Features_Not_Supported
  // &gt; The app cannot set explicit connection timeouts for the request.
  if (connection != null) {
   releaseConnection(connection);
  }
  connection = new UrlFetchHttpConnection(hostConfiguration);
  return connection;
 }

 public HttpConnectionManagerParams getParams() {
  return params;
 }

 public void releaseConnection(HttpConnection connection) {
  connection.releaseConnection();
 }

 public void setParams(HttpConnectionManagerParams params) {
  // Parameters set in AmazonFPSClient#configureHttpClient:
        // - ConnectionTimeout: 50000 ms
  // - SoTimeout: 50000 ms
  // - StaleCheckingEnabled: true
  // - TcpNoDelay: true
  // - MaxTotalConnections: 100 (as proposed in the default config.properties file)
  // - MaxConnectionsPerHost: 100 (as proposed in the default config.properties file)

  this.params = params;
 }
 
}&lt;/pre&gt;&lt;br /&gt;
&lt;div class="codeSnippetTitle"&gt;&lt;code&gt;UrlFetchConnection&lt;/code&gt; class definition&lt;/div&gt;&lt;pre class="prettyprint limitSnippetHeight"&gt;/******************************************************************************* 
 *  Adaptation for the Amazon FPS library to work on the Java platform of
 *  Google App Engine.
 *  
 *  Copyright 2010 Dom Derrien
 *  Licensed under the Apache License, Version 2.0
 */

package domderrien.wrapper.UrlFetch;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.Socket;
import java.net.SocketException;
import java.net.URL;

import javamocks.io.MockInputStream;
import javamocks.io.MockOutputStream;

import org.apache.commons.httpclient.HostConfiguration;
import org.apache.commons.httpclient.HttpConnection;
import org.apache.commons.httpclient.HttpConnectionManager;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.params.HttpConnectionParams;
import org.apache.commons.httpclient.protocol.Protocol;

import com.google.appengine.api.urlfetch.FetchOptions;
import com.google.appengine.api.urlfetch.HTTPHeader;
import com.google.appengine.api.urlfetch.HTTPMethod;
import com.google.appengine.api.urlfetch.HTTPRequest;
import com.google.appengine.api.urlfetch.HTTPResponse;
import com.google.appengine.api.urlfetch.URLFetchService;
import com.google.appengine.api.urlfetch.URLFetchServiceFactory;

public class UrlFetchHttpConnection extends HttpConnection {

 private static URLFetchService urlFS = URLFetchServiceFactory.getURLFetchService();

 private HostConfiguration hostConfiguration;
 private HTTPRequest _request;
 private HTTPResponse _response;
 private MockOutputStream _requestBody = new MockOutputStream();
 private MockInputStream _responseBody = new MockInputStream();

 
 private HTTPRequest getRequest() throws MalformedURLException {
  if (_request == null) {
   _request = new HTTPRequest(
     new URL(hostConfiguration.getHostURL()), 
     HTTPMethod.POST, // AmazonFPSClient#invoke(Class&lt;t&gt;, Map&lt;String,String&gt;) uses only POST method
     FetchOptions.Builder.disallowTruncate().followRedirects()
   );
  }
  return _request;
 }
 
 private static final String SEPARATOR = ": ";
 private static final int SEPARATOR_LENGTH = SEPARATOR.length();
 private static final String NEW_LINE = "\r\n";

 private HTTPResponse getResponse() throws MalformedURLException, IOException {
  if (_response == null) {
   // Get the response from the remote service
   _response = urlFS.fetch(getRequest());
   // Rebuild stream of HTTP headers (except the HTTP status retrieved from readLine(String) method)
   StringBuilder buffer = new StringBuilder();
   for (HTTPHeader header: _response.getHeaders()) {
    buffer.append(header.getName()).append(SEPARATOR).append(header.getValue()).append(NEW_LINE);
   }
   buffer.append("Content-Length: ").append(_response.getContent().length).append(NEW_LINE);
   buffer.append(NEW_LINE);
   // Rebuild stream of HTTP content (chunked-encoded)
   buffer.append(Integer.toString(_response.getContent().length, 16)).append(";chunk size").append(NEW_LINE);
   buffer.append(new String(_response.getContent())).append(NEW_LINE);
   buffer.append("0;").append(NEW_LINE);
   _responseBody.resetActualContent(buffer.toString());
  }
  return _response;
 }

 /**
  * Default constructor
  * @param hostConfiguration
  */
 public UrlFetchHttpConnection(HostConfiguration hostConfiguration) {
  super(hostConfiguration);
  this.hostConfiguration = hostConfiguration;
 }

 @Override
 protected void assertNotOpen() throws IllegalStateException {
  throw new RuntimeException("assertNotOpen()");
 }

 @Override
 protected void assertOpen() throws IllegalStateException {
  assert(_response != null);
 }

 @Override
 public void close() {
  // Nothing to do!
 }

 @Override
 public boolean closeIfStale() throws IOException {
  // Safe call, passed to the inherited method
  return super.closeIfStale();
 }

 @Override
 protected void closeSocketAndStreams() {
  throw new RuntimeException("closeSocketAndStreams()");
 }

 @Override
 public void flushRequestOutputStream() throws IOException {
  getRequest().setPayload(_requestBody.getStream().toString().getBytes());
 }

 @Override
 public String getHost() {
  return hostConfiguration.getHost();
 }

 @Override
 public HttpConnectionManager getHttpConnectionManager() {
  throw new RuntimeException("getHttpConnectionManager()");
 }

 @Override
 public InputStream getLastResponseInputStream() {
  throw new RuntimeException("getLastResponseInputStream()");
 }

 @Override
 public InetAddress getLocalAddress() {
  throw new RuntimeException("getLocalAddress()");
 }

 @Override
 public HttpConnectionParams getParams() {
  return new HttpConnectionParams();
 }

 @Override
 public int getPort() {
  return hostConfiguration.getPort();
 }

 @Override
 public Protocol getProtocol() {
  return hostConfiguration.getProtocol();
 }

 @Override
 public String getProxyHost() {
  throw new RuntimeException("getProxyHost()");
 }

 @Override
 public int getProxyPort() {
  throw new RuntimeException("getProxyPort()");
 }

 @Override
 public OutputStream getRequestOutputStream() throws IOException, IllegalStateException {
  return _requestBody;
 }

 @Override
 public InputStream getResponseInputStream() throws IOException {
  return _responseBody;
 }

 @Override
 public int getSendBufferSize() throws SocketException {
  throw new RuntimeException("getSendBufferSize()");
 }

 @Override
 protected Socket getSocket() {
  throw new RuntimeException("getSocket()");
 }

 @Override
 public int getSoTimeout() throws SocketException {
  throw new RuntimeException("getSoTimeout()");
 }

 @Override
 public String getVirtualHost() {
  throw new RuntimeException("getVirtualHost()");
 }

 @Override
 protected boolean isLocked() {
  throw new RuntimeException("isLocked()");
 }

 @Override
 public boolean isOpen() {
  // Safe call, passed to inherited method
  return super.isOpen();
 }

 @Override
 public boolean isProxied() {
  // Safe call, passed to inherited method
  return super.isProxied();
 }

 @Override
 public boolean isResponseAvailable() throws IOException {
  return _response != null;
 }

 @Override
 public boolean isResponseAvailable(int timeout) throws IOException {
  return _response != null;
 }

 @Override
 public boolean isSecure() {
  return hostConfiguration.getPort() == 443;
 }

 @Override
 protected boolean isStale() throws IOException {
  throw new RuntimeException("isStale()");
 }

 @Override
 public boolean isStaleCheckingEnabled() {
  throw new RuntimeException("isStaleCheckingEnabled()");
 }

 @Override
 public boolean isTransparent() {
  // Safe call, passed to the inherited method
  return super.isTransparent();
 }
 
 @Override
 public void open() throws IOException {
  // Nothing to do
 }

 @Override
 public void print(String data, String charset) throws IOException, IllegalStateException {
  // Save the passed HTTP headers for the request
  int idx = data.indexOf(SEPARATOR);
  if (idx != -1) {
   String name = data.substring(0, idx);
   String value = data.substring(idx + SEPARATOR_LENGTH).trim();
   getRequest().addHeader(new HTTPHeader(name, value));
  }
  // Other information are just ignored safely 
 }

 @Override
 public void print(String data) throws IOException, IllegalStateException {
  throw new RuntimeException("print(string): " + data);
 }

 @Override
 public void printLine() throws IOException, IllegalStateException {
  throw new RuntimeException("printLine()");
 }

 @Override
 public void printLine(String data, String charset) throws IOException, IllegalStateException {
  throw new RuntimeException("printLine(string, String): " + data + " -- " + charset);
 }

 @Override
 public void printLine(String data) throws IOException, IllegalStateException {
  throw new RuntimeException("printLine(string): " + data);
 }

 @Override
 public String readLine() throws IOException, IllegalStateException {
  throw new RuntimeException("readLine()");
 }

 private boolean waitForHttpStatus = true;
 
 @Override
 public String readLine(String charset) throws IOException, IllegalStateException {
  if (waitForHttpStatus) {
   // Dom Derrien: called only once to get the HTTP status, other information being read from the response output stream
   int responseCode = getResponse().getResponseCode();
   String line = "HTTP/1.1 " + responseCode;
   switch(responseCode) {
    case HttpStatus.SC_OK: line += " OK"; break;
    case HttpStatus.SC_BAD_REQUEST: line += " BAD REQUEST"; break;
    case HttpStatus.SC_UNAUTHORIZED: line += " UNAUTHORIZED"; break;
    case HttpStatus.SC_FORBIDDEN: line += " FORBIDDEN"; break;
    case HttpStatus.SC_NOT_FOUND: line += " NOT FOUND"; break;
    case HttpStatus.SC_INTERNAL_SERVER_ERROR: line += " INTERNAL SERVER ERROR"; break;
    case HttpStatus.SC_SERVICE_UNAVAILABLE: line += " SERVICE UNAVAILABLE"; break;
    default: line = "HTTP/1.1 " + HttpStatus.SC_BAD_REQUEST + " BAD REQUEST";
   }
   waitForHttpStatus = false;
   return line;
  }
  throw new RuntimeException("readLine(String)");
 }

 @Override
 public void releaseConnection() {
  // Do nothing, connection closed automatically...
 }

 @Override
 public void setConnectionTimeout(int timeout) {
  throw new RuntimeException("setConnectionTimeout(int)");
 }

 @Override
 public void setHost(String host) throws IllegalStateException {
  throw new RuntimeException("setHost(String");
 }

 @Override
 public void setHttpConnectionManager(HttpConnectionManager httpConnectionManager) {
  throw new RuntimeException("setHttpConnectionManager(HttpConnectionManager");
 }

 @Override
 public void setLastResponseInputStream(InputStream inStream) {
  // Safe call, passed to inherited method
  super.setLastResponseInputStream(inStream);
 }

 @Override
 public void setLocalAddress(InetAddress localAddress) {
  throw new RuntimeException("setLocalAddress(InetAddress)");
 }

 @Override
 protected void setLocked(boolean locked) {
  // Safe call, passed to inherited method
  super.setLocked(locked);
 }

 @Override
 public void setParams(HttpConnectionParams params) {
  throw new RuntimeException("setParams(HttpConnectionParams)");
 }

 @Override
 public void setPort(int port) throws IllegalStateException {
  throw new RuntimeException("setPort(int)");
 }

 @Override
 public void setProtocol(Protocol protocol) {
  throw new RuntimeException("setProtocol(Protocol)");
 }

 @Override
 public void setProxyHost(String host) throws IllegalStateException {
  throw new RuntimeException("setProxyHost(String)");
 }

 @Override
 public void setProxyPort(int port) throws IllegalStateException {
  throw new RuntimeException("setProxyPort(int)");
 }

 @Override
 public void setSendBufferSize(int sendBufferSize) throws SocketException {
  throw new RuntimeException("setSendBufferSize(int)");
 }

 @Override
 public void setSocketTimeout(int timeout) throws SocketException, IllegalStateException {
  // Safe call, passed to inherited method
  super.setSocketTimeout(timeout);
 }

 @Override
 public void setSoTimeout(int timeout) throws SocketException, IllegalStateException {
  throw new RuntimeException("setSoTimeout(int)");
 }

 @Override
 public void setStaleCheckingEnabled(boolean staleCheckEnabled) {
  throw new RuntimeException("setStaleCheckingEnabled(boolean)");
 }

 @Override
 public void setVirtualHost(String host) throws IllegalStateException {
  throw new RuntimeException("setVirtualHost(String)");
 }

 @Override
 public void shutdownOutput() {
  throw new RuntimeException("shutdownOutput()");
 }

 @Override
 public void tunnelCreated() throws IllegalStateException, IOException {
  throw new RuntimeException("tunnelCreated()");
 }

 @Override
 public void write(byte[] data, int offset, int length) throws IOException, IllegalStateException {
  throw new RuntimeException("write(byte[], int, int): " + new String(data) + ", " + offset + ", " + length);
 }

 @Override
 public void write(byte[] data) throws IOException, IllegalStateException {
  throw new RuntimeException("write(byte[]): " + new String(data));
 }

 @Override
 public void writeLine() throws IOException, IllegalStateException {
  // Safe call, new line being inserted automatically by the HTTPRequest renderer
 }

 @Override
 public void writeLine(byte[] data) throws IOException, IllegalStateException {
  throw new RuntimeException("writeLine(byte[]): " + new String(data));
 }
}&lt;/pre&gt;&lt;br /&gt;
Anyone is free to fork it for his own needs. Be careful with the code because I deliver it without warranties! If you have issues to report, if you can document how to reproduce them, depending on my workload, I will help you. If you fix the issue on your side, I will be happy to merge the corresponding patches into my main branch.&lt;br /&gt;
&lt;br /&gt;
I hope this helps,&lt;br /&gt;
A+, Dom&lt;br /&gt;
--&lt;br /&gt;
Notes:&lt;br /&gt;
&lt;ol&gt;&lt;li&gt;&lt;a href="#" name="amazonFPSNote1"&gt;&lt;/a&gt;At least in United States of America until Amazon extends its coverage to company without a US bank account.&lt;/li&gt;
&lt;li&gt;&lt;a href="#" name="amazonFPSNote2"&gt;&lt;/a&gt;Apache License, Version 2.0, January 2004, which allows users to make modifications while keeping them private.&lt;/li&gt;
&lt;/ol&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=9OC70jvEUXs:nWGYuo8VPj0:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=9OC70jvEUXs:nWGYuo8VPj0:4cEx4HpKnUU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?i=9OC70jvEUXs:nWGYuo8VPj0:4cEx4HpKnUU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=9OC70jvEUXs:nWGYuo8VPj0:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?i=9OC70jvEUXs:nWGYuo8VPj0:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SharingTechnologies/~4/9OC70jvEUXs" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://domderrien.blogspot.com/feeds/1947501483083628645/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://domderrien.blogspot.com/2010/03/httpdomderrien-preblogspotcom201003amaz.html#comment-form" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/168828253523263225/posts/default/1947501483083628645?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/168828253523263225/posts/default/1947501483083628645?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SharingTechnologies/~3/9OC70jvEUXs/httpdomderrien-preblogspotcom201003amaz.html" title="Amazon FPS library for the Google App Engine environment" /><author><name>Dom Derrien</name><uri>https://plus.google.com/100819130946985751629</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-CAiJ6LOHBWE/AAAAAAAAAAI/AAAAAAAAErE/twENdW648fs/s512-c/photo.jpg" /></author><thr:total>1</thr:total><feedburner:origLink>http://domderrien.blogspot.com/2010/03/httpdomderrien-preblogspotcom201003amaz.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C08DRnY9fyp7ImA9WxBbE08.&quot;"><id>tag:blogger.com,1999:blog-168828253523263225.post-405754375531762932</id><published>2010-03-11T10:37:00.000-05:00</published><updated>2010-03-11T10:37:57.867-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-03-11T10:37:57.867-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Entrepreneurship" /><category scheme="http://www.blogger.com/atom/ns#" term="Experience" /><title>Ladies and gentlemen, here is !twetailer</title><content type="html">On Tuesday March 9 evening, my partner &lt;a href="http://stevenmilstein.com/" target="_blank"&gt;Steven Milstein&lt;/a&gt; and I attended a &lt;a href="http://www.mtlnewtech.com/2010/03/03/lineup-for-the-newtech-demo-march-2010/" target="_blank"&gt;Montreal NewTech event&lt;/a&gt;. As three other companies, Steven pitched our project &lt;a href="http://twetailer.com/" target="_blank"&gt;!twetailer&lt;/a&gt; to a crowd of 30-40 people. After few closed presentations, it was our first public pitch and it went very well! Read &lt;a href="http://stevenmilstein.com/2010/03/10/my-first-pitch-to-more-than-person-over-the-age-of-12-what-a-rush/" target="_blank"&gt;Steven's blog post&lt;/a&gt; for the details.&lt;br /&gt;
&lt;br /&gt;
&lt;span style="font-size: large;"&gt;Background&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
I met Steven while working for IBM Rational. He was the Business Analyst while I was the Software Architect for the Web client of the &lt;a href="http://www.ibm.com/software/awdtools/portfolio/" target="_blank"&gt;Rational Portfolio Manager product&lt;/a&gt;. When Steven came to me with the &lt;i&gt;Reverse Retailing&lt;/i&gt; idea, we agreed to develop it as a proof-of-concept for our own expertise.&lt;br /&gt;
&lt;br /&gt;
We worked so well that we submitted our project to &lt;a href="http://www.techcrunch50.com/" target="_blank"&gt;TechCrunch50&lt;/a&gt; (TC50). If we were not among the 50 finalists, we passed the first selection round on 1000+ applicants and we had the chance to present it to &lt;a href="http://calacanis.com/" target="_blank"&gt;Jason Calacanis&lt;/a&gt; during 15 minutes last August.&lt;br /&gt;
&lt;br /&gt;
Boosted by the appreciation we got from Jason, we continued to focus on developing user stories and the corresponding code. We were ready to demo the full cycle early this January. Two months later, after many tests and fine tuning, we are opening &lt;span class="twetailer"&gt;&lt;span class="twetailerBang"&gt;!&lt;/span&gt;twetailer&lt;/span&gt; to broader audience!&lt;br /&gt;
&lt;br /&gt;
&lt;span style="font-size: large;"&gt;Presentation&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;span class="twetailer"&gt;&lt;span class="twetailerBang"&gt;!&lt;/span&gt;twetailer&lt;/span&gt; first objective is to “connect supply and demand”, specifically “connect consumers to retailers” in its first version.&lt;br /&gt;
&lt;br /&gt;
Look at this original presentation to get the sense of &lt;span class="twetailer"&gt;&lt;span class="twetailerBang"&gt;!&lt;/span&gt;twetailer&lt;/span&gt; ;) We made it months ago but it's still very accurate. Don't miss the part describing the &lt;i&gt;hub&lt;/i&gt;, starting at 3:00.&lt;br /&gt;
&lt;center&gt;&lt;object height="405" width="500"&gt;&lt;param name="movie" value="http://www.youtube.com/v/mWMUhxHjUCE&amp;hl=en_US&amp;fs=1&amp;color1=0x5d1719&amp;color2=0xcd311b"&gt;&lt;/param&gt;&lt;param name="allowFullScreen" value="true"&gt;&lt;/param&gt;&lt;param name="allowscriptaccess" value="always"&gt;&lt;/param&gt;&lt;embed src="http://www.youtube.com/v/mWMUhxHjUCE&amp;hl=en_US&amp;fs=1&amp;color1=0x5d1719&amp;color2=0xcd311b" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="500" height="405"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;/center&gt;&lt;br /&gt;
After the presentation, we got many feedback from the audience and most of our interlocutors got it right. Here is the interpretation of Max Maheu, the presenter of the &lt;a href="http://www.facebook.com/pages/Montreal-QC/solidwild/41234103682?ref=ts"&gt;SolidWild&lt;/a&gt; company:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;&lt;b&gt;Max:&lt;/b&gt; If I register my company and listen for the tags “&lt;i&gt;3d printing prototype logo&lt;/i&gt;”, all people using !twetailer will get to me?&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Me:&lt;/b&gt; Exactly. All demands posted with one or many corresponding tags in the Montreal area will be forwarded to you, will be routed to you for free ;)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;...&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Me:&lt;/b&gt; At this stage, it's possible you'll get unrealistic demands, many of them with the &lt;b&gt;#demo&lt;/b&gt; tag. But if you respond to them, that means if you propose something, your message will be routed back to the consumers and your business information will be displayed to them!&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Max:&lt;/b&gt; It's like advertising my business then! Cool.&lt;/li&gt;
&lt;/ul&gt;&lt;span style="font-size: large;"&gt;What's next?&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
As mentioned by &lt;a href="http://domderrien.blogspot.com/2009/03/guy-kawasakis-keynote-in-montreal.html"&gt;Guy Kawasaki in Montreal in March 2009&lt;/a&gt;, we have chosen to "launch early and to correct progressively". If all the delivered features are fully functional, there is still a long road to go in delivering the full feature set.&lt;br /&gt;
&lt;br /&gt;
At this step, we need to get users, that means consumers and retailers, playing with the system and giving us their feedback.&lt;br /&gt;
&lt;br /&gt;
To collect the information, we have organize the community site &lt;a href="http://twetailer.cyn.in" target="_blank"&gt;twetailer.cyn.in&lt;/a&gt;, implemented with the excellent social software from &lt;a href="http://www.cynapse.com" target="_blank"&gt;Cynapse&lt;/a&gt;. To value the contributions, we have setup a Work-for-attribution protocol: any community member that makes a significant contribution will have his/her work publicly recognized. Any volunteer?&lt;br /&gt;
&lt;br /&gt;
We need also to work on the marketing side:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;The brand &lt;span class="twetailer"&gt;&lt;span class="twetailerBang"&gt;!&lt;/span&gt;twetailer&lt;/span&gt; seems too tightly related to &lt;a href="http://twitter.com/" target="_blank"&gt;Twitter&lt;/a&gt;, while using Twitter is just one among the various set of connectors taking to our engine.&lt;/li&gt;
&lt;li&gt;If &lt;span class="twetailer"&gt;&lt;span class="twetailerBang"&gt;!&lt;/span&gt;twetailer&lt;/span&gt; initial targets are consumers and retailers in a public market, it can work with closed markets, where wholesalers communicate with manufacturers, for example. This aspect needs to be documented and illustrated.&lt;/li&gt;
&lt;li&gt;To ensure a vibrant life to &lt;span class="twetailer"&gt;&lt;span class="twetailerBang"&gt;!&lt;/span&gt;twetailer&lt;/span&gt;, we have plan to open its API to third-party developers, a bit &lt;i&gt;a-la&lt;/i&gt; Twitter. Closed friends have already accepted to develop clients (under the Work-for-Attribution CLA) that will exercise it, but we need a stronger communication there too.&lt;/li&gt;
&lt;/ul&gt;Thanks a lot to our families and closed friends for encouraging us on the entrepreneurship path. Thanks to anyone for the feedback because they help us improving &lt;span class="twetailer"&gt;&lt;span class="twetailerBang"&gt;!&lt;/span&gt;twetailer&lt;/span&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;span style="font-size: large;"&gt;Call to contributors&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;span class="twetailer"&gt;&lt;span class="twetailerBang"&gt;!&lt;/span&gt;twetailer&lt;/span&gt; is a big project with a development still growing, so there's a lot of room for anyone to showcase their knowledge!&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;If you're a &lt;b&gt;developer&lt;/b&gt; with Java/JavaScript skills,&lt;/li&gt;
&lt;li&gt;If you're a &lt;b&gt;tester&lt;/b&gt; with automation experience,&lt;/li&gt;
&lt;li&gt;If you're a &lt;b&gt;UI designer &amp; Interactivity specialist&lt;/b&gt;,&lt;/li&gt;
&lt;li&gt;If you're &lt;b&gt;marketer&lt;/b&gt; with a Web 2.0 &amp; Social software experience,&lt;/li&gt;
&lt;li&gt;If you're a &lt;b&gt;simple consumer&lt;/b&gt; in touch with a vibrant community,&lt;/li&gt;
&lt;li&gt;If you're a &lt;b&gt;business owner&lt;/b&gt; looking for new ways to reach your customers,&lt;/li&gt;
&lt;li&gt;Etc.&lt;/li&gt;
&lt;/ul&gt;Don't hesitate to contact Steven (&lt;a href="http://twitter.com/stevenmilstein"&gt;Steven@Twitter&lt;/a&gt; or &lt;a href="mailto:steven.milstein@twetailer.com"&gt;Steven@Twetailer.com&lt;/a&gt;) or myself (&lt;a href="http://twitter.com/domderrien"&gt;Dom@Twitter&lt;/a&gt; or &lt;a href="mailto:dom.derrien@twetailer.com"&gt;Dom@Twetailer.com&lt;/a&gt;) and we'll exchange on our community site at &lt;a href="http://twetailer.cyn.in"&gt;twetailer.cyn.in&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
A+, Dom&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=gLCB4xrPRKs:_DG9oW8Vfho:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=gLCB4xrPRKs:_DG9oW8Vfho:4cEx4HpKnUU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?i=gLCB4xrPRKs:_DG9oW8Vfho:4cEx4HpKnUU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=gLCB4xrPRKs:_DG9oW8Vfho:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?i=gLCB4xrPRKs:_DG9oW8Vfho:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SharingTechnologies/~4/gLCB4xrPRKs" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://domderrien.blogspot.com/feeds/405754375531762932/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://domderrien.blogspot.com/2010/03/ladies-and-gentlemen-here-is-twetailer.html#comment-form" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/168828253523263225/posts/default/405754375531762932?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/168828253523263225/posts/default/405754375531762932?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SharingTechnologies/~3/gLCB4xrPRKs/ladies-and-gentlemen-here-is-twetailer.html" title="Ladies and gentlemen, here is &lt;span class=&quot;twetailer&quot;&gt;&lt;span class=&quot;twetailerBang&quot;&gt;!&lt;/span&gt;twetailer&lt;/span&gt;" /><author><name>Dom Derrien</name><uri>https://plus.google.com/100819130946985751629</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-CAiJ6LOHBWE/AAAAAAAAAAI/AAAAAAAAErE/twENdW648fs/s512-c/photo.jpg" /></author><thr:total>1</thr:total><feedburner:origLink>http://domderrien.blogspot.com/2010/03/ladies-and-gentlemen-here-is-twetailer.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A08FRHc6fSp7ImA9WxBbEks.&quot;"><id>tag:blogger.com,1999:blog-168828253523263225.post-7597693722668436662</id><published>2010-02-08T23:43:00.003-05:00</published><updated>2010-03-10T20:10:15.915-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-03-10T20:10:15.915-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Experience" /><title>Ma nouvelle vie en tant qu'entrepreneur !</title><content type="html">&lt;h2&gt;La démission&lt;/h2&gt;Il y a trois semaines, j'ai démissionné de mon poste de Consultant technique pour la compagnie &lt;a href="http://www.compuware.com/"&gt;Compuware&lt;/a&gt;, à Montréal. Plusieurs facteurs m'ont poussé à prendre cette décision&amp;nbsp;:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;Mon projet [toujours secret] personnel arrive à maturité et les conditions de sa mise en marché sont à envisager sérieusement;&lt;/li&gt;
&lt;li&gt;Mon boss &lt;a href="http://www.compuware.com/about/management.asp#CZARNIK"&gt;Paul Czarnik&lt;/a&gt; m'assigne sur un projet en relation avec l'intégration des outils de &lt;a href="http://www.gomez.com/"&gt;Gomez&lt;/a&gt; (récemment achetée par Compuware) dans le produit phare &lt;a href="http://www.compuware.com/solutions/vantage.asp"&gt;Vantage&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Le récent chaos provoqué par le tremblement de terre en Haïti offre une opportunité de proposer mon projet personnel aux organismes d'aide, dans la même veine de ce qui sera proposé aux organismes de Microfinance, comme &lt;a href="http://diku-dilenga.org/"&gt;Diku Dilenga&lt;/a&gt; et &lt;a href="http://www.jamiibora.org/"&gt;Jamii Bora&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;D'un côté, il y avait un assignement plus prenant avec mon employeur, et de l'autre, il y avait une demande d'attention plus importante pour aborder une phase critique. J'ai finalement opté pour la démission. Youpi, je suis maintenant indépendant&amp;nbsp;!&lt;br /&gt;
&lt;br /&gt;
Certains amis m'ont demandé si cela avait un rapport avec mon âge (ils se souviennent de mon articule &lt;a href="http://domderrien.blogspot.com/2009/05/le-cap-des-40-ans.html"&gt;Le cap des 40 ans&lt;/a&gt;). Réponse simple: pas du tout, pas de &lt;i&gt;middle-age crisis&lt;/i&gt; en vue ;)&lt;br /&gt;
&lt;br /&gt;
C'est plus le cotoiement, même par Internet ou livre interposé, avec des gens inspirant comme Guy Kawasaki (&lt;a hreaf="http://domderrien.blogspot.com/2009/03/guy-kawasakis-keynote-in-montreal.html" href="http://domderrien.blogspot.com/2009/03/guy-kawasakis-keynote-in-montreal.html"&gt;GK's keynote in Montreal&lt;/a&gt;) qui me poussent à aller de l'avant, pour «&amp;nbsp;faire quelque chose qui a du sens.&amp;nbsp;»&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;Et maintenant&amp;nbsp;?&lt;/h2&gt;Du temps où je travaillais en tant qu'Architecte logiciel pour IBM Rational, à Montréal, j'avais eu le plaisir de travailler avec &lt;a href="http://stevenmilstein.com/"&gt;Steven Milstein&lt;/a&gt; qui avait le rôle d'Analyste d'affaires.&lt;br /&gt;
&lt;br /&gt;
L'idée originale du projet qui m'occupe dorénavant est de Steven. Avec mon expérience, j'ai pris en charge le développement en nous appuyant sur des outils très accessibles comme&amp;nbsp;:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;&lt;a href="http://git-scm.com/"&gt;git&lt;/a&gt; et &lt;a href="http://github.com/"&gt;GitHub&lt;/a&gt; pour la gestion des sources;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pivotaltracker.com/"&gt;Pivotal Tracker&lt;/a&gt; pour le suivi des tâches de développement;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://appengine.google.com/"&gt;Google App Engine&lt;/a&gt; pour l'infrastructure en production, initiallement avec la version Python et puis en version Java (voir &lt;a href="http://domderrien.blogspot.com/2009/04/google-app-engine-meets-java.html"&gt;Google App Engine Meets Java&lt;/a&gt;);&lt;/li&gt;
&lt;li&gt;&lt;a href="http://eclipse.org/"&gt;Eclipse&lt;/a&gt; et &lt;a href="http://ant.apache.org/"&gt;ant&lt;/a&gt; comme principaux outils de développement (il y a aussi JUnit, Corbertura, Dojo Toolkit, JSUnit, etc.);&lt;/li&gt;
&lt;/ul&gt;Le travail de collaboration (&lt;i&gt;user stories&lt;/i&gt;, blogues techniques, support à la communauté des participants, etc.) est colligé sur la plateforme &lt;a href="http://www.cynapse.com/cynin"&gt;cyn.in&lt;/a&gt;, sélectionnée par Steven pour sa grande qualité et l'évolution rapide de son développement.&lt;br /&gt;
&lt;br /&gt;
Dans l'immédiat, je me concentre sur la mise au point de l'outil, passant d'un rythme de 15 à 20 heures par semaine à quelques 60 heures et plus. En ce moment, le code représente environ 13.000 lignes de code pour 33.000 lignes de test, rien que pour la partie serveur&amp;nbsp;!&lt;br /&gt;
&lt;br /&gt;
Si nous avons fait plusieurs présentations réelles du produit, nous sommes fin prêts pour faire des présentations plus larges, toujours sous le coup d'accord de non divulgation cependant. Idéalement, nous souhaitons avoir des partenariats pour avoir une masse critique et aller de l'avant en ouvrant les portes à tout à chacun&amp;nbsp;! Excitant, non&amp;nbsp;?&lt;br /&gt;
&lt;br /&gt;
Parce que nous pensons que notre produit peut aider à la coordination des équipes d'aide aux sinistrés en Haïti, nous avons pris des contacts pour le leur proposer à titre gratuit, mais pour lequel il faut assumer les frais d'exploitation. Comme les contacts se font au niveau gouvernemental, du Canada et du Québec, ou au niveau de grandes ONGs comme la Croix Rouge ou MSF, il risque de se passer un certain temps avant que notre proposition trouve un écho favorable. Le suivi des contacts se poursuit.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;Le futur&lt;/h2&gt;Pour le moment, je suis sans revenu. N'ayant pas de dettes colossales, ayant un train de vie modeste, je pense pouvoir être en roue libre pendant quelques mois sans que ma famille en patisse.&lt;br /&gt;
&lt;br /&gt;
Avec Steven, en attendant que le grand projet avance, et en exploitant notre expérience des outils de socialisation, nous avons développé une offre reposant sur cyn.in et dirigée vers les écoles et l'initiation des élèves. Le projet s'appelle &lt;a href="http://edu.cyn.in/"&gt;edu.cyn.in&lt;/a&gt;—voir le blog de &lt;a href="http://stevenmilstein.com/"&gt;Steven&lt;/a&gt; pour plus de détails.&lt;br /&gt;
&lt;br /&gt;
Pendant la période de lancement, étant toujours «&amp;nbsp;programmeur&amp;nbsp;» sur des technologies récentes, j'ai le sentiment que mon capital de «&amp;nbsp;connaissance technologique&amp;nbsp;» restera solide. Ma communication sur ce blogue en témoignera ;) Le cas échéant, je pourrais toujours aller à la recherche de contrat de consultant pour partager mes connaissances.&lt;br /&gt;
&lt;br /&gt;
Mon activité pour l'organisme de microfinance Diku Dilenga continue, toujours pour le soutien technique, moins en tant qu'investisseur privé. Si le grand projet décolle par contre, je consacrerai du temps pour son adaptation aux organismes de microfinance avec pour objectif d'aider les microentrepreneurs et de d'offrir une source de revenus aux dits organismes.&lt;br /&gt;
&lt;br /&gt;
À suivre donc,&lt;br /&gt;
A+, Dom&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=1u6gsojN63g:XYHrbdD7Gbw:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=1u6gsojN63g:XYHrbdD7Gbw:4cEx4HpKnUU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?i=1u6gsojN63g:XYHrbdD7Gbw:4cEx4HpKnUU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=1u6gsojN63g:XYHrbdD7Gbw:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?i=1u6gsojN63g:XYHrbdD7Gbw:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SharingTechnologies/~4/1u6gsojN63g" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://domderrien.blogspot.com/feeds/7597693722668436662/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://domderrien.blogspot.com/2010/02/ma-nouvelle-vie-en-tant-quentrepreneur.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/168828253523263225/posts/default/7597693722668436662?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/168828253523263225/posts/default/7597693722668436662?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SharingTechnologies/~3/1u6gsojN63g/ma-nouvelle-vie-en-tant-quentrepreneur.html" title="Ma nouvelle vie en tant qu'entrepreneur !" /><author><name>Dom Derrien</name><uri>https://plus.google.com/100819130946985751629</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-CAiJ6LOHBWE/AAAAAAAAAAI/AAAAAAAAErE/twENdW648fs/s512-c/photo.jpg" /></author><thr:total>0</thr:total><georss:featurename>Montreal, QC, Canada</georss:featurename><georss:point>45.545447 -73.639076</georss:point><georss:box>45.305000500000006 -74.10599500000001 45.7858935 -73.172157</georss:box><feedburner:origLink>http://domderrien.blogspot.com/2010/02/ma-nouvelle-vie-en-tant-quentrepreneur.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D0cBSXw_cCp7ImA9WxBTGE4.&quot;"><id>tag:blogger.com,1999:blog-168828253523263225.post-7433418658313596224</id><published>2009-11-20T18:49:00.010-05:00</published><updated>2009-12-14T18:57:38.248-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-12-14T18:57:38.248-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Technology" /><category scheme="http://www.blogger.com/atom/ns#" term="Community" /><title>Unit tests, Mock objects, and App Engine</title><content type="html">&lt;p&gt;
For my [still a secret] project which is running on Google App
Engine infrastructure [1], I want to make it as solid as possible from
the beginning by applying most of the best practices of the Agile
methodology [2].&lt;/p&gt;
&lt;div class="updateNotice"&gt;
&lt;u&gt;Update 2009/12/05:&lt;/u&gt;&lt;br/&gt;
With the release of the App Engine Java SDK 1.2.8 (read &lt;a href="http://code.google.com/p/googleappengine/wiki/SdkForJavaReleaseNotes"&gt;release notes&lt;/a&gt;, I had to update my code and this post on two points:&lt;ul&gt;
&lt;li&gt;Without the specification of the JDO inheritance type, the environment assumes it's &lt;code&gt;superclass-table&lt;/code&gt;. This type is not supported by App Engine. Only &lt;code&gt;subclass-table&lt;/code&gt; and &lt;code&gt;complete-table&lt;/code&gt; are supported. In the &lt;code&gt;Entity&lt;/code&gt; class described below, I had to add &lt;code&gt;@Inheritance(strategy = InheritanceStrategy.SUBCLASS_TABLE)&lt;/code&gt;. Read the documentation about &lt;a href="http://code.google.com/appengine/docs/java/datastore/dataclasses.html#Inheritance"&gt;Defining data classes&lt;/a&gt; for more information.&lt;/li&gt;
&lt;li&gt;With the automation of the task execution, the &lt;code&gt;MockAppEngineEnvironment&lt;/code&gt; class listed below had to be updated to serve an expected value when the &lt;code&gt;Queue&lt;/code&gt; runs in the live environment. Read the details on the thread announcing the &lt;a href="http://groups.google.com/group/google-appengine-java/browse_thread/thread/fe334c9e461026fa/8f5872b052144c8d?#8f5872b052144c8d"&gt;1.2.8. SDK prerelease&lt;/a&gt; on Google Groups.&lt;/li&gt;&lt;/ul&gt;
Now, all tests pass again ;)&lt;/div&gt;
&lt;p&gt;
As written on my &lt;a
 href="http://domderrien.blogspot.com/2009/09/progress-update.html"&gt;post
from September 18&lt;/a&gt;, I had to develop many mock classes to keep reaching
the mystical 100% of code coverage (by unit tests) [3]. A good
introduction of mock objects is given by Vincent Massol in his book
“JUnit in Action” [4]. To summarize, mock objects are especially useful
to inject behavior and force the code using them to exercise complex
control flows.&lt;/p&gt;
&lt;p&gt;
Developing applications for Google App Engine is not that complex
because the system has a good documentation and an Eclipse plug-in ease
the first steps.&lt;/p&gt;
&lt;p style="font-size: larger;"&gt;
Use case description&lt;/p&gt;
&lt;p&gt;
Let's consider a simple class organization implementing a common
J2EE pattern:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A &lt;span style="border-bottom: dotted 1px #666666;"
  title="Data Transfer Object"&gt;DTO&lt;/span&gt; class for a &lt;code&gt;Consumer&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;The &lt;span style="border-bottom: dotted 1px #666666;"
  title="Data Access Object"&gt;DAO&lt;/span&gt; class getting the &lt;code&gt;Consumer&lt;/code&gt;
from the persistence layer, and sending it back with updates; and&lt;/li&gt;
&lt;li&gt;A Controller class routing &lt;span
  style="border-bottom: dotted 1px #666666;"
  title="Representational State Transfer"&gt;REST&lt;/span&gt; requests. The
Controller is an element of the implemented &lt;span
  style="border-bottom: dotted 1px #666666;"
  title="Model-View-Controller pattern"&gt;MVC pattern&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;center&gt;
&lt;div class="codeSnippetTitle"&gt;
Use case illustration&lt;/div&gt;
&lt;object data="http://domderrien.github.com/diagrams/2009-11-20-illustration.svg" height="580"
 style="border: 2px solid transparent;" title="Use case illustration"
 type="image/svg+xml" width="670"&gt; &lt;img src="http://domderrien.github.com/diagrams/2009-11-20-illustration.png"
  title="Use case illustration" /&gt; &lt;/object&gt;&lt;/center&gt;
&lt;p&gt;
The code for the DTO class is instrumented with JDO annotations
[5]:&lt;/p&gt;
&lt;div class="codeSnippetTitle"&gt;
&lt;code&gt;Consumer&lt;/code&gt; DTO class
definition&lt;/div&gt;
&lt;pre class="prettyprint limitSnippetHeight"&gt;@PersistenceCapable(identityType = IdentityType.APPLICATION, detachable="true")
@Inheritance(strategy = InheritanceStrategy.SUBCLASS_TABLE)
public class Consumer extends Entity {
    @Persistent
    private String address;
 
    @Persistent
    private String displayName;
 
    @Persistent
    private String email;
 
    @Persistent
    private String facebookId;
 
    @Persistent
    private String jabberId;
 
    @Persistent
    private Long locationKey;
 
    @Persistent
    private String twitterId;
 
    /** Default constructor */
    public Consumer() {
        super();
    }
 
    /**
     * Creates a consumer
     * @param in HTTP request parameters
     */
    public Consumer(JsonObject parameters) {
        this();
        fromJson(parameters);
    }
 
    public String getAddress() {
        return address;
    }
    
    public void setAddress(String address) {
        this.address = address;
    }
    
    //...
}&lt;/pre&gt;
&lt;p&gt;
My approach for the DAO class is modular:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;When the calling code is doing just one call, like the &lt;code&gt;ConsumerOperations.delete(String)&lt;/code&gt;
method deleting the identified &lt;code&gt;Consumer&lt;/code&gt; instance, the call
can be done without the persistence layer knowledge.&lt;/li&gt;
&lt;li&gt;When many calls to the persistence layer are required, the DAO
API offers the caller to pass a &lt;code&gt;PersistenceManager&lt;/code&gt;
instance that can be re-used from call to call. With the combination of
the &lt;code&gt;detachable="true"&lt;/code&gt; parameter specified in the JDO
annotation for the &lt;code&gt;Consumer&lt;/code&gt; class, it saves many cycles.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="codeSnippetTitle"&gt;
Excerpt from the &lt;code&gt;ConsumerOperations&lt;/code&gt;
DAO class definition&lt;/div&gt;
&lt;pre class="prettyprint"&gt;/**
 * Persist the given (probably updated) resource
 * @param consumer Resource to update
 * @return Updated resource
 * @see ConsumerOperations#updateConsumer(PersistenceManager, Consumer)
 */
public Consumer updateConsumer(Consumer consumer) {
    PersistenceManager pm = getPersistenceManager();
    try {
        // Persist updated consumer
        return updateConsumer(pm, consumer);
    }
    finally {
        pm.close();
    }
}
 
/**
 * Persist the given (probably updated) resource while leaving the given persistence manager open for future updates
 * @param pm Persistence manager instance to use - let opened at the end to allow possible object updates later
 * @param consumer Resource to update
 * @return Updated resource
 */
public Consumer updateConsumer(PersistenceManager pm, Consumer consumer) {
    return pm.makePersistent(consumer);
}&lt;/pre&gt;
&lt;p&gt;
The following piece of the abstract class &lt;code&gt;BaseOperations&lt;/code&gt;
shows the accessor made availabe to any controller code to get one
handle of a valid &lt;code&gt;PersistenceManager&lt;/code&gt; instance.&lt;/p&gt;
&lt;div class="codeSnippetTitle"&gt;
Excerpt from the abstract &lt;code&gt;BaseOperations&lt;/code&gt;
DAO class definition&lt;/div&gt;
&lt;pre class="prettyprint limitSnippetHeight"&gt;/**
 * Accessor isolated to facilitate tests by IOP
 * @return Persistence manager instance
 */
public PersistenceManager getPersistenceManager() {
    PersistenceManager pm = getPersistenceManagerFactory().getPersistenceManager();
    pm.setDetachAllOnCommit(true);
    pm.setCopyOnAttach(false);
    return pm;
}&lt;/pre&gt;
&lt;p&gt;
To finish the use case setup, here is a part of the controller
code which deals with incoming HTTP requests and serves or operates
accordingly. This specific piece of code replies to a GET request like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Invocation: &lt;code&gt;http://&amp;lt;host:port&amp;gt;/API/Consumer/43544"&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Response:
&lt;ul&gt;
&lt;li&gt;&lt;pre&gt;{key:43544, displayName:"John", address:"75, Queen, Montréal, Qc, Canada", 
locationKey:3245, location: {id:3245, postalCode:"H3C2N6", countryCode:"CA",
latitude:43.3, longitude:-73.4}, ...}&lt;/pre&gt;
&lt;/li&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/ul&gt;
&lt;div class="codeSnippetTitle"&gt;
Excerpt from the &lt;code&gt;ConsumerRestlet&lt;/code&gt; Controller class definition&lt;/div&gt;
&lt;pre class="prettyprint"&gt;@Override
protected JsonObject getResource(JsonObject parameters, String resourceId, User loggedUser) throws DataSourceException {
    PersistenceManager pm = getBaseOperations().getPersistenceManager();
    try {
        // Get the consumer instance
        Consumer consumer = getConsumerOperations().getConsumer(pm, Long.valueOf(resourceId));
        JsonObject output = consumer.toJson();
        // Get the related information
        Long locationKey = consumer.getLocationKey();
        if (locationKey != null) {
            Location location = getLocationOperations().getLocation(pm, locationKey);
            output.put(Consumer.LOCATION, location.toJson());
        }
        // Return the complete set of information
        return output;
    }
    finally {
        pm.close();
    }
}&lt;/pre&gt;
&lt;p&gt;
&lt;/p&gt;
&lt;p style="font-size: larger;"&gt;
Simple mock&lt;/p&gt;
&lt;p&gt;
Now, it's time to test! To start slowly, let's deal with the Restlet &lt;code&gt;getResource()&lt;/code&gt; method to verify:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Just one and only one instance of &lt;code&gt;PersistenceManager&lt;/code&gt; is loaded by the function;&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;PersistenceManager&lt;/code&gt; instance is cleanly closed at the end of the process;&lt;/li&gt;
&lt;li&gt;There's a call issued to get the identified &lt;code&gt;Consumer&lt;/code&gt; instance;&lt;/li&gt;
&lt;li&gt;There's possibly a call issued to get the identified &lt;code&gt;Location&lt;/code&gt; instance;&lt;/li&gt;
&lt;li&gt;The output value has the expected information.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
In the corresponding unit test series, we don't want to interfere with the App Engine infrastructure (the following chapter will address that aspect). So we'll rely on a mock for the &lt;code&gt;PersistenceManager&lt;/code&gt; class that will be injected into the &lt;code&gt;ConsumerRestlet&lt;/code&gt; code. The full source of this class is available on my open source project &lt;a href="http://github.com/DomDerrien/two-tiers-utils"&gt;two-tiers-utils&lt;/a&gt;: &lt;code&gt;&lt;a href="http://github.com/DomDerrien/two-tiers-utils/blob/master/src/Java/javax/jdo/MockPersistenceManager.java"
&gt;javax.jdo.MockPersistenceManager&lt;/a&gt;&lt;/code&gt;.&lt;/p&gt;
&lt;div class="codeSnippetTitle"&gt;
Custom part of the mock for the &lt;code&gt;PersistenceManager&lt;/code&gt; class&lt;/div&gt;
&lt;pre class="prettyprint"&gt;public class MockPersistenceManager implements PersistenceManager {
    private boolean closed = false; // To keep track of the "closed" state
    public void close() {
        closed = true;
    }
    public boolean isClosed() {
        return closed;
    }

    // ...
}&lt;/pre&gt;
&lt;p&gt;
Here are the unit tests verifying the different flow paths:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;When an exception is thrown, because the back-end does not serve the data for example;&lt;/li&gt;
&lt;li&gt;When the &lt;code&gt;Consumer&lt;/code&gt; instance returns without location coordinates;&lt;/li&gt;
&lt;li&gt;When the &lt;code&gt;Consumer&lt;/code&gt; instance is fully documented.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="codeSnippetTitle"&gt;
Three tests validating the behavior of the &lt;code&gt;ConsumerRestlet.getResource()&lt;/code&gt; method&lt;/div&gt;
&lt;pre class="prettyprint limitSnippetHeight"&gt;@Test(expected=IllegalArgumentException.class)
public void testUnexpectedError() {
    // Test prepration
    final PersistenceManager pm = new MockPersistenceManager();
    final BaseOperations baseOps = new BaseOperations() {
        boolean askedOnce = false;
        @Override
        PersistenceManager getPersistenceManager() {
            if (askedOnce) {
                fail("Expects only one call");
            }
            askedOnce = true;
            return pm;
        }
    };
    final Long consumerId = 12345L;
    final ConsumerOperations consumerOps = new ConsumerOperations() {
        @Override
        Consumer getConsumer(PersistenceManager pm, Long id) {
            assertEquals(consumerId, id);
            throw new IllegalArgumentException("Done in purpose!");
        }
    };
    ConsumerRestlet restlet = new ConsumerRestlet() {
        @Override BaseOperation getBaseOperations() { return baseOps; }
        @Override ConsumerOperation getConsumerOperations() { return consumerOps; }
    }
    
    // Test itself
    JsonObject response = restlet.getResource(null, consumerId.toString, null);
}&lt;/pre&gt;
&lt;pre class="prettyprint limitSnippetHeight"&gt;@Test
public void testGettingOneConsumer() {
    // Test prepration
    final PersistenceManager pm = new MockPersistenceManager();
    final BaseOperations baseOps = new BaseOperations() {
        boolean askedOnce = false;
        @Override
        PersistenceManager getPersistenceManager() {
            if (askedOnce) {
                fail("Expects only one call");
            }
            askedOnce = true;
            return pm;
        }
    };
    final Long consumerId = 12345L;
    final ConsumerOperations consumerOps = new ConsumerOperations() {
        @Override
        Consumer getConsumer(PersistenceManager pm, Long id) {
            assertEquals(consumerId, id);
            Consumer consumer = new Consumer();
            consumer.setId(consumerId);
            return consumer;
        }
    };
    final Long locationId = 67890L;
    final LocationOperations locationOps = new LocationOperations() {
        @Override
        Location getLocation(PersistenceManager pm, Long id) {
            fail("Call not expected here!");
            return null;
        }
    };
    ConsumerRestlet restlet = new ConsumerRestlet() {
        @Override BaseOperation getBaseOperations() { return baseOps; }
        @Override ConsumerOperation getConsumerOperations() { return consumerOps; }
        @Override LocationOperation getLocationOperations() { return locationOps; }
    }
    
    // Test itself
    JsonObject response = restlet.getResource(null, consumerId.toString, null);
    
    // Post-test verifications
    assertTrue(pm.isClosed());
    assertNotSame(0, response.size());
    assertTrue(response.containsKey(Consumer.ID);
    assertEquals(consumerId, response.getLong(Consumer.ID));
}&lt;/pre&gt;
&lt;pre class="prettyprint limitSnippetHeight"&gt;@Test
public void testGettingConsumerWithLocation() {
    // Test prepration
    final PersistenceManager pm = new MockPersistenceManager();
    final BaseOperations baseOps = new BaseOperations() {
        boolean askedOnce = false;
        @Override
        PersistenceManager getPersistenceManager() {
            if (askedOnce) {
                fail("Expects only one call");
            }
            askedOnce = true;
            return pm;
        }
    };
    final Long consumerId = 12345L;
    final Long locationId = 67890L;
    final ConsumerOperations consumerOps = new ConsumerOperations() {
        @Override
        Consumer getConsumer(PersistenceManager pm, Long id) {
            assertEquals(consumerId, id);
            Consumer consumer = new Consumer();
            consumer.setId(consumerId);
            consumer.setLocationId(locationId);
            return consumer;
        }
    };
    final LocationOperations locationOps = new LocationOperations() {
        @Override
        Location getLocation(PersistenceManager pm, Long id) {
            assertEquals(locationId, id);
            Location location = new Location();
            location.setId(locationId);
            return location;
        }
    };
    ConsumerRestlet restlet = new ConsumerRestlet() {
        @Override BaseOperation getBaseOperations() { return baseOps; }
        @Override ConsumerOperation getConsumerOperations() { return consumerOps; }
        @Override LocationOperation getLocationOperations() { return locationOps; }
    }
    
    // Test itself
    JsonObject response = restlet.getResource(null, consumerId.toString, null);
    
    // Post-test verifications
    assertTrue(pm.isClosed());
    assertNotSame(0, response.size());
    assertTrue(response.containsKey(Consumer.ID);
    assertEquals(consumerId, response.getLong(Consumer.ID));
    assertTrue(response.containsKey(Consumer.LOCATION_ID);
    assertEquals(locationId, response.getLong(Consumer.LOCATION_ID));
    assertTrue(response.containsKey(Consumer.LOCATION);
    assertEquals(consumerId, response.getJsonObject(Consumer.LOCATION).getLong(Location.ID));
}
&lt;/pre&gt;
&lt;p&gt;
Note that I would have been able to override just the &lt;code&gt;PersistenceManager&lt;/code&gt; class to have the &lt;code&gt;Object getObjectById(Object arg0)&lt;/code&gt; method returning the expected exception, &lt;code&gt;Consumer&lt;/code&gt;, and &lt;code&gt;Location&lt;/code&gt; instances. But I would have pass over the strict limit of a unit test by then testing also the behavior of the &lt;code&gt;ConsumerOperations.getConsumer()&lt;/code&gt; and &lt;code&gt;LocationOperations.getLocation()&lt;/code&gt; methods.&lt;/p&gt;
&lt;p style="font-size: larger;"&gt;
App Engine environment mock&lt;/p&gt;
&lt;p&gt;
Now, testing the &lt;code&gt;ConsumerOperations&lt;/code&gt; class offers a better challenge.&lt;/p&gt;
&lt;p&gt;
As suggested above, I could override many pieces of the &lt;code&gt;PersistenceManager&lt;/code&gt; class to be sure to control the flow. But to do a nice simulation, I almost need to have the complete specification of the Google App Engine infrastructure to be sure I mock it correctly. This is especially crucial when processing &lt;code&gt;Query&lt;/code&gt; because Google data store has many limitations [6] that others traditional database, like MySQL, don't have...&lt;/p&gt;
&lt;p&gt;
Because this documentation is partially available and because Google continues to update its infrastructure, I looked for a way to use the standalone environment made available with the App Engine SDK [1]. This has not been easy because I wanted to have the test running independently from the development server itself. I found first some documentation on Google Code website: &lt;a href="http://code.google.com/appengine/docs/java/howto/unittesting.html"
&gt;Unit Testing With Local Service Implementations&lt;/a&gt;, but it was very low level and did not fit with my JDO instrumentation of the DTO classes. Hopefully, I found this article &lt;a href="http://blog.appenginefan.com/2009/05/jdo-and-unit-tests.html"
&gt;JDO and unit tests&lt;/a&gt; from &lt;a href="http://blog.appenginefan.com/"&gt;App Engine Fan&lt;/a&gt;, a great community contributor I mentioned many times in previous posts!&lt;/p&gt;
&lt;p&gt;
By cooking information gathered on Google Code website and on App Engine Post, I've produced a &lt;code&gt;&lt;a href="http://github.com/DomDerrien/two-tiers-utils/blob/master/src/Java/com/google/apphosting/api/MockAppEngineEnvironment.java"
&gt;com.google.apphosting.api.MockAppEngineEnvironment&lt;/a&gt;&lt;/code&gt; I can use for my JUnit4 tests.&lt;/p&gt;
&lt;div class="codeSnippetTitle"&gt;
Three tests validating the behavior of the &lt;code&gt;ConsumerRestlet.getResource()&lt;/code&gt; method&lt;/div&gt;
&lt;pre class="prettyprint limitSnippetHeight"&gt;package com.google.apphosting.api;
 
// import ...
 
/**
 * Mock for the App Engine Java environment used by the JDO wrapper.
 *
 * These class has been build with information gathered on:
 * - App Engine documentation: http://code.google.com/appengine/docs/java/howto/unittesting.html
 * - App Engine Fan blog: http://blog.appenginefan.com/2009/05/jdo-and-unit-tests.html
 *
 * @author Dom Derrien
 */
public class MockAppEngineEnvironment {
 
    private class ApiProxyEnvironment implements ApiProxy.Environment {
        public String getAppId() {
          return "test";
        }
 
        public String getVersionId() {
          return "1.0";
        }
 
        public String getEmail() {
          throw new UnsupportedOperationException();
        }
 
        public boolean isLoggedIn() {
          throw new UnsupportedOperationException();
        }
 
        public boolean isAdmin() {
          throw new UnsupportedOperationException();
        }
 
        public String getAuthDomain() {
          throw new UnsupportedOperationException();
        }
 
        public String getRequestNamespace() {
          return "";
        }
 
        public Map&lt;String, Object&gt; getAttributes() {
            Map&lt;String, Object&gt; out = new HashMap&lt;String, Object&gt;();

            // Only necessary for tasks that are added when there is no "live" request
            // See: http://groups.google.com/group/google-appengine-java/msg/8f5872b05214...
            out.put("com.google.appengine.server_url_key", "http://localhost:8080");

            return out;
        }
    };
 
    private final ApiProxy.Environment env;
    private PersistenceManagerFactory pmf;
 
    public MockAppEngineEnvironment() {
        env = new ApiProxyEnvironment();
    }
 
    /**
     * Setup the mock environment
     */
    public void setUp() throws Exception {
        // Setup the App Engine services
        ApiProxy.setEnvironmentForCurrentThread(env);
        ApiProxyLocalImpl proxy = new ApiProxyLocalImpl(new File(".")) {};
 
        // Setup the App Engine data store
        proxy.setProperty(LocalDatastoreService.NO_STORAGE_PROPERTY, Boolean.TRUE.toString());
        ApiProxy.setDelegate(proxy);
    }
 
    /**
     * Clean up the mock environment
     */
    public void tearDown() throws Exception {
        // Verify that there's no pending transaction (ie pm.close() has been called)
        Transaction transaction = DatastoreServiceFactory.getDatastoreService().getCurrentTransaction(null);
        boolean transactionPending = transaction != null;
        if (transactionPending) {
            transaction.rollback();
        }
 
        // Clean up the App Engine data store
        ApiProxyLocalImpl proxy = (ApiProxyLocalImpl) ApiProxy.getDelegate();
        if (proxy != null) {
            LocalDatastoreService datastoreService = (LocalDatastoreService) proxy.getService("datastore_v3");
            datastoreService.clearProfiles();
        }
 
        // Clean up the App Engine services
        ApiProxy.setDelegate(null);
        ApiProxy.clearEnvironmentForCurrentThread();
 
        // Report the issue with the transaction still open
        if (transactionPending) {
            throw new IllegalStateException("Found a transaction nor commited neither rolled-back." +
                    "Probably related to a missing PersistenceManager.close() call.");
        }
    }
 
    /**
     * Creates a PersistenceManagerFactory on the fly, with the exact same information
     * stored in the &lt;war-dir&gt;/WEB-INF/META-INF/jdoconfig.xml file.
     */
    public PersistenceManagerFactory getPersistenceManagerFactory() {
        if (pmf == null) {
            Properties newProperties = new Properties();
            newProperties.put("javax.jdo.PersistenceManagerFactoryClass",
                    "org.datanucleus.store.appengine.jdo.DatastoreJDOPersistenceManagerFactory");
            newProperties.put("javax.jdo.option.ConnectionURL", "appengine");
            newProperties.put("javax.jdo.option.NontransactionalRead", "true");
            newProperties.put("javax.jdo.option.NontransactionalWrite", "true");
            newProperties.put("javax.jdo.option.RetainValues", "true");
            newProperties.put("datanucleus.appengine.autoCreateDatastoreTxns", "true");
            newProperties.put("datanucleus.appengine.autoCreateDatastoreTxns", "true");
            pmf = JDOHelper.getPersistenceManagerFactory(newProperties);
        }
        return pmf;
    }
 
    /**
     * Gets an instance of the PersistenceManager class
     */
    public PersistenceManager getPersistenceManager() {
        return getPersistenceManagerFactory().getPersistenceManager();
    }
}
&lt;/pre&gt;
&lt;p&gt;
With such a class, the unit test part is easy and I can build complex test cases without worrying about the pertinence of my mock classes! That's really great. &lt;div class="codeSnippetTitle"&gt;
Excerpt of the &lt;code&gt;TestConsumerOperations&lt;/code&gt; class&lt;/div&gt;
&lt;pre class="prettyprint limitSnippetHeight"&gt;public class TestConsumerOperations {
 
    private MockAppEngineEnvironment mockAppEngineEnvironment;
 
    @Before
    public void setUp() throws Exception {
        mockAppEngineEnvironment = new MockAppEngineEnvironment();
        mockAppEngineEnvironment.setUp();
    }
 
    @After
    public void tearDown() throws Exception {
        mockAppEngineEnvironment.tearDown();
    }
 
    @Test
    public void testCreateVI() throws DataSourceException, UnsupportedEncodingException {
        final String email = "unit@test.net";
        final String name = "Mr Unit Test";
        Consumer newConsumer = new Consumer();
        newConsumer.setDisplayName(name);
        newConsumer.setEmail(email);
        assertNull(newConsumer.getId());
 
        // Verify there's no instance
        Query query = new Query(Consumer.class.getSimpleName());
        assertEquals(0, DatastoreServiceFactory.getDatastoreService().prepare(query).countEntities());
 
        // Create the user once
        ConsumerOperations ops = new ConsumerOperations();
        Consumer createdConsumer = ops.createConsumer(newConsumer);
 
        // Verify there's one instance
        query = new Query(Consumer.class.getSimpleName());
        assertEquals(1, DatastoreServiceFactory.getDatastoreService().prepare(query).countEntities());
 
        assertNotNull(createdConsumer.getId());
        assertEquals(email, createdConsumer.getEmail());
        assertEquals(name, createdConsumer.getName());
    }
    
    // ...
}&lt;/pre&gt;
&lt;p style="font-size: larger;"&gt;
Conclusion&lt;/p&gt;
&lt;p&gt;
As a big fan of &lt;span style="border-bottom: dotted 1px #666666;" title="Test Driven Development"&gt;TDD&lt;/span&gt;, I'm now all set to cover the code of my [still a secret] project efficiently. It does not mean everything is correct, more that everything I thought about is correctly covered. At the time of this writing, just for the server-side logic, the code I produced covers more than 10,000 lines and the unit tests bring  an additional set of 23,400 lines.&lt;/p&gt;
&lt;p&gt;
When it's time to refactor a bit or to add new features (plenty of them are aligned in my task list ;), I feel comfortable because I know I can detect most of regressions (if not all) after 3 minutes of running the test suite.&lt;/p&gt;
&lt;p&gt;
If you want to follow this example, feel free to get the various mock classes I have added to my &lt;a href="http://github.com/DomDerrien/two-tiers-utils"&gt;two-tiers-utils&lt;/a&gt; open-source project. In addition to mock classes for the App Engine environment,  you'll find:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Basic mock classes for the servlet (see &lt;code&gt;&lt;a href="http://github.com/DomDerrien/two-tiers-utils/tree/master/src/Java/javax/servlet/"
&gt;javax.servlet&lt;/a&gt;&lt;/code&gt;) and &lt;code&gt;&lt;a href="http://github.com/DomDerrien/two-tiers-utils/tree/master/src/Java/javamocks/io/"
&gt;javamocks.io&lt;/a&gt;&lt;/code&gt; packages -- I had to adopt the root &lt;code&gt;javamocks&lt;/code&gt; because the JVM class loader does not accept the creation
on the fly of classes in the &lt;code&gt;java&lt;/code&gt; root).&lt;/li&gt;
&lt;li&gt;A mock class for &lt;code&gt;&lt;a href="http://github.com/DomDerrien/two-tiers-utils/tree/master/src/Java/twitter4j"
&gt;twitter4j.TwitterUser&lt;/a&gt;&lt;/code&gt; -- I needed a class with public constructor and a easy way to create a default account.&lt;/li&gt;
&lt;li&gt;A series of mock class for &lt;a href="http://github.com/DomDerrien/two-tiers-utils/tree/b8ef96bd09a684651cd4164c1fe6abf6c64ef419/src/Java/com/dyuproject"
&gt;David Yu's Project&lt;/a&gt; which I use to allow users with OpenID credentials
to log in. Read the &lt;a href="http://code.google.com/p/dyuproject/issues/detail?id=16"&gt;discussion&lt;/a&gt;
I had with David on ways to test his code, in fact the code he produced and I customized for my own needs
and for other security reasons.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
For other details on my library, read my post &lt;a href="http://domderrien.blogspot.com/2009/10/internationalization-and-my-two-tiers.html"
&gt;Internationalization and my two-tiers-utils library&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;
I hope this helps.&lt;/p&gt;
&lt;p&gt;
A+, Dom&lt;br/&gt;--&lt;br/&gt;References:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Google App Engine: the &lt;a href="http://appengine.google.com/"&gt;homepage&lt;/a&gt;
and the &lt;a href="http://code.google.com/appengine/"&gt;SDK page&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;See my post on &lt;a
  href="http://domderrien.blogspot.com/2009/04/agile-scrum-is-hype-but-xp-is-more.html"&gt;Agile:
SCRUM is Hype, but XP is More Important...&lt;/a&gt; where I mentionned the
following techniques: Continuous Integration (CI), Unit testing and
code coverage (CQC), and Continuous refactoring.&lt;/li&gt;
&lt;li&gt;I know that keeping 100% as the target for code coverage
numbers is a bit extreme. I read this article &lt;a
  href="http://www.ibm.com/developerworks/java/library/j-cq01316/index.html?ca=drs"&gt;Don't
be fooled by the coverage report&lt;/a&gt; soon after I started using &lt;a
  href="http://cobertura.sourceforge.net/index.html"&gt;Cobertura&lt;/a&gt;. In
addition to reducing the exposition to bugs, the 100% coverage gives a
very high chance to detect regressions before pushing the updates to
the source control system!&lt;/li&gt;
&lt;li&gt;Vincent Massol; &lt;i&gt;JUnit in Action&lt;/i&gt;; Editions Manning; &lt;a
  href="http://www.manning.com/massol/"&gt;www.manning.com/massol&lt;/a&gt; and
Petar Tahchiev, Felipe Leme, Vincent Massol, and Gary Gregory; &lt;i&gt;JUnit
in Action, Second Edition&lt;/i&gt;; Editions Massol; &lt;a
  href="http://www.manning.com/tahchiev/"&gt;www.manning.com/tahchiev&lt;/a&gt;.
I was used to asking any new developer joigning my team to read at
least this &lt;a
  href="http://www.manning-source.com/books/massol/massol_ch07.pdf"&gt;chapter
7: Testing in isolation with mock objects&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;JDO stands for &lt;a href="http://java.sun.com/jdo/index.jsp"&gt;Java
Data Objects&lt;/a&gt; and is an attempt abstract the data storage manipulation.
The code is instrumented with Java annotations like &lt;code&gt;@Persistent&lt;/code&gt;,
it is instrumented at compile time, and dynamically connect to the data
source thanks few properties files. Look at the &lt;a
  href="http://code.google.com/appengine/docs/java/gettingstarted/usingdatastore.html"&gt;App
Engine - Using the Datastore with JDO&lt;/a&gt; documentation for more
information.&lt;/li&gt;
&lt;li&gt;For general limitations, check this page &lt;a href="http://groups.google.com/group/google-appengine-java/web/will-it-play-in-app-engine?pli=1"
 &gt;Will it play in App Engine&lt;/a&gt;. For JDO related limitations, check the bottom of the
page &lt;a href="http://code.google.com/appengine/docs/java/datastore/usingjdo.html"&gt;Usin JDO with App Engine&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=3gEJWggYGdQ:UdLMqU0CvC8:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=3gEJWggYGdQ:UdLMqU0CvC8:4cEx4HpKnUU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?i=3gEJWggYGdQ:UdLMqU0CvC8:4cEx4HpKnUU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=3gEJWggYGdQ:UdLMqU0CvC8:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?i=3gEJWggYGdQ:UdLMqU0CvC8:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SharingTechnologies/~4/3gEJWggYGdQ" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://domderrien.blogspot.com/feeds/7433418658313596224/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://domderrien.blogspot.com/2009/11/unit-tests-mock-objects-and-app-engine.html#comment-form" title="4 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/168828253523263225/posts/default/7433418658313596224?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/168828253523263225/posts/default/7433418658313596224?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SharingTechnologies/~3/3gEJWggYGdQ/unit-tests-mock-objects-and-app-engine.html" title="Unit tests, Mock objects, and App Engine" /><author><name>Dom Derrien</name><uri>https://plus.google.com/100819130946985751629</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-CAiJ6LOHBWE/AAAAAAAAAAI/AAAAAAAAErE/twENdW648fs/s512-c/photo.jpg" /></author><thr:total>4</thr:total><feedburner:origLink>http://domderrien.blogspot.com/2009/11/unit-tests-mock-objects-and-app-engine.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D0cNQn0zeyp7ImA9WxBTGE4.&quot;"><id>tag:blogger.com,1999:blog-168828253523263225.post-623874656538219093</id><published>2009-10-16T19:58:00.000-04:00</published><updated>2009-12-14T18:58:13.383-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-12-14T18:58:13.383-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Technology" /><title>Canadian Wireless Management Forum - my review</title><content type="html">Last week, I attended the &lt;a href="http://www.forumgestionsansfils.ca/index_en.php"&gt;Canadian Wireless Management Forum&lt;/a&gt; in Montreal. Honestly, I've been a bit disappointed because none of the presenters were as great as &lt;a href="http://domderrien.blogspot.com/2008/10/canadian-wireless-management-forum.html"&gt;the ones I met last year&lt;/a&gt;. Over the day, I've still been able to gather bits of information I want to share here ;) Thanks to my company &lt;a href="http://www.compuware.com/"&gt;Compuware&lt;/a&gt; to have allowed sparing one day there.&lt;br /&gt;
&lt;br /&gt;
&lt;span style="font-size: large;"&gt;iPhone and other smart phones deployed in enterprises&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
The main focus of the conference is around managing wireless (read mobile phone) communications in enterprises. Some presenters talked about how to control expenses, from sharing guidelines up to using monitoring tools suggesting policies from the statistical analysis of the monthly bills. Testimonies showed that applying a strict control on the mobile phone usage and on the contracts allowed cutting costs from 10 to 30%!&lt;br /&gt;
&lt;br /&gt;
At one point, &lt;a href="http://www.forumgestionsansfils.ca/speaker.php?cid=3"&gt;Nicolas Arsenault&lt;/a&gt; made a strange point. Here is what I remind from his talk:&lt;br /&gt;
&lt;br /&gt;
&lt;div style="margin: 0pt 30px;"&gt;&lt;div style="float: left; margin: 0pt 10px 10px 0pt; text-align: center;"&gt;&lt;img src="http://farm1.static.flickr.com/80/243093440_9e6c67c6cc_t.jpg" title="BlackBerry by RIM" /&gt;&lt;br /&gt;
&lt;span style="font-size: smaller;"&gt;Credits: &lt;a href="http://www.flickr.com/photos/smoothouse/"&gt;smoothouse&lt;/a&gt;&lt;/span&gt;&lt;br /&gt;
&lt;/div&gt;&lt;div style="float: right; margin: 0pt 10px 10px 0pt; text-align: center;"&gt;&lt;img src="http://farm2.static.flickr.com/1144/1361954749_d35a3acd54_t.jpg" title="iPhone by Apple" /&gt;&lt;br /&gt;
&lt;span style="font-size: smaller;"&gt;Credits:&lt;a href="http://www.flickr.com/photos/joshb/"&gt;Josh Bancroft&lt;/a&gt;&lt;/span&gt;&lt;br /&gt;
&lt;/div&gt;Deploying an iPhone application for your employees, when compared to a BlackBerry application, has a much lower cost because the employee already owns the device or he's more likely to buy it. With the employee paying for the phone, probably paying to get new phones regularly (like to move from a iPhone 3G to the latest 3GS), employers can just assume the data plans and can spend more resources on the application development, that at one point can be offered to the company customers. Another benefit is related to the technical support: phone owners stop annoying enterprises' help desk for their phone, they contact the manufacturers or the software vendors directly (they are on their own)...&lt;br /&gt;
&lt;/div&gt;&lt;br /&gt;
If this approach have evident economical benefits, I disagree with it because:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;In general, letting employees owning their work mobile phones cause problems when they leave the company. Most of employees have a non concurrence clause in their contract, usually valid 6 months after the employment contract termination. During that period, they cannot compete with their previous employer. Imagine a salesperson, an account manager, or a consultant who owns his phone number, who is referenced in the phone directories all his previous contacts. When these contacts want to deal with the company, who do you think they'll call: the mobile phone or the company front line? Now that these employees are out, they don't have to submit their phone bills anymore (as they did with their expenses reports). No one can detect the issues anymore… And this is without mentioning that the employee replacement or his colleagues have no way to get the mobile phone contact list to continue the business as usual!&lt;/li&gt;
&lt;/ul&gt;&lt;ul&gt;&lt;li&gt;In the company I worked, none let me use my own computer on their network! For the IT departments, this practice would raise too much security concerns. I even know cases that just installing a VPN software on your own machine install silently a bunch of monitoring tools that can mess up your systems (thanks to VirtualBox, it easy to limit these nasty side effects ;). In the old days, when phones were just stupid, just able to handle voice call and exchange text messages (SMS), the risk of phone infection by viruses were pretty negligible. The widely used phone operating system is Symbian, used on Nokia and Sony Ericsson phones, and J2ME is a common application framework, even on phones running Windows Mobile. If the identified viruses are not a lot, and if they are not some damageable (send on your behalf SMS to expensive services, for example), the newest smart platforms provide much more threats because the corresponding phones can host tons of applications. In such an environment, how can a company force software upgrades on systems it does not own? How can it force an employee to upgrade to a new hardware because the current one is compromised?&lt;/li&gt;
&lt;/ul&gt;&lt;ul&gt;&lt;li&gt;My last point is more ethical: how far companies should go with putting the burden on their employees? An iPhone costs around 800$. It's often offered around 200$ with a 3-year contract, which costs basically around 60$/month with a simple data plan. If the telecom operators (telco) subsidize so aggressively such a phone, they surely expect higher average revenue per user (ARPU). In addition to be linked to the telco for a long period of time (compare 3 years with the 3 to 6 months between technological evolution: 6 to 12 times longer!), employees have to support costs the company should assume. Usually, companies try to provide a comfortable work environment to get most of their employees, and that's fair to me: if the company gives more than the salary, employees are more likely to deliver better work. With the incitation of employees assuming the cost of the new mobile phones, I see a regression: companies give less while expecting more (reachable—possibly traceable—outside the office hours, for example). IMO, it's yet another example of a technological progress that might worsen fragile people condition...&lt;/li&gt;
&lt;/ul&gt;&lt;br /&gt;
&lt;span style="font-size: large;"&gt;Mobile payment and Near-field-communication (NFC)&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
Shortly on this topic, I want to mention presentation made by &lt;a href="http://www.forumgestionsansfils.ca/speaker.php?cid=2"&gt;Daniel Martin&lt;/a&gt; for Atlas Telecom Mobile, &lt;a href="http://www.forumgestionsansfils.ca/speaker.php?cid=5"&gt;David Robinson&lt;/a&gt; for Rogers Wireless, and &lt;a href="http://www.forumgestionsansfils.ca/speaker.php?cid=31"&gt;Prakash Hariramani&lt;/a&gt; for Visa. They talked about an experiment conducted downtown Toronto where they were able, thanks to Motorola phones equipped with a NFC emitter and a good number of retailers there, to allow mobile payment over the mobile phone network. Visa's solution, called PayWave™ (MasterCard's one is called PayPass™), was inserted into the Motorola phone extension and allow consumers to pay for their purchase quite easily.&lt;br /&gt;
&lt;br /&gt;
During the discussion, Mr. Robinson talked about Rogers Wireless approach being strictly based on standards. He mentioned the recent u-turn of telco who continued to invest in closed and proprietary solutions (like CDMA) and now move to standardized ones (like HSPA which is an upgrade of GSM, on the road to LTE—see example of &lt;a href="http://www.broadbandreports.com/shownews/Bell-Canada-Telus-To-Deploy-HSPA-98332"&gt;Bell and Telus&lt;/a&gt; in Canada). Rogers Wireless' approach is then to work with the rest of the major industry players (Orange, Vodafone, etc.) to define a solution for anyone anywhere. Mr. Robinson talked about the possibility to define an extended SIM card (for Subscriber Identity Module; the card contains a micro-SIMCARD processor which manage very securely information). This new SIM card will have NFC capabilities and will be able to interact with contactless payment terminals. It's possible that these SIM cards will contain additional information like driver license identifier that police officers will be able to read, insurance number for the government agencies, etc. The phone will provide the interface to enable the data access, and smart phones with touch screens open the door to various and robust verification techniques.&lt;br /&gt;
&lt;br /&gt;
Because more and more people are more attached to their phone than to their wallet, this approach will possibly be more convenient, more secure, and smaller (no more cash, business cards, credits cards, etc. ;) Who said that implanting the SIM under anyone's skin, a SIM that can unlock your phones, cars, houses, computers, etc, is just science fiction?&lt;br /&gt;
&lt;br /&gt;
&lt;span style="font-size: large;"&gt;Telco business model in danger!&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;div style="background-color: grey; border: 1px solid black; float: right; margin: 10px 0pt 10px 10px; padding: 10px; width: 20%;"&gt;iBwave offers solutions improving in-building wireless coverage. Knowing the fact that 60%-80% of mobile usages are indoor, that telco have difficulties to boost the power or to multiply outdoor antennas near high density areas, these places stay mostly uncovered. Mr. Bouchard illustrated his point with the simulation of the poor performance of the traditional networks on the McGill campus—really amazing! In conclusion, iBwave sits in a very nice and promising niche ;)&lt;br /&gt;
&lt;/div&gt;The last point of interest to me came from &lt;a href="http://www.forumgestionsansfils.ca/speaker.php?cid=25"&gt;Mario Bouchard&lt;/a&gt;, from iBwave.&lt;br /&gt;
&lt;br /&gt;
To introduce his company activities, Mr. Bouchard shows two diagrams which made me think for a while. Let me try to reproduce them.&lt;br /&gt;
&lt;br /&gt;
&lt;center&gt;&lt;a href="http://1.bp.blogspot.com/_VZZAFHl2_Og/StkC-VWE6GI/AAAAAAAACxc/tRCdJJPqVxk/s1600/chain-15yearsago.png" rel="lightbox"&gt;&lt;img border="0" src="http://1.bp.blogspot.com/_VZZAFHl2_Og/StkC-VWE6GI/AAAAAAAACxc/tRCdJJPqVxk/s400/chain-15yearsago.png" /&gt;&lt;/a&gt;&lt;/center&gt;&lt;br /&gt;
Mr. Bouchard states that 15 years ago, the innovation came from the network manufacturers: they invented the technology, others created cell phones to connect to the new network, some operators offered (very expensive) plans, and consumers (locked with long term contracts) tried to communicate.&lt;br /&gt;
&lt;br /&gt;
&lt;center&gt;&lt;a href="http://4.bp.blogspot.com/_VZZAFHl2_Og/StkDUiE6HGI/AAAAAAAACxk/4SfK_foyZi4/s1600/chain-today.png" rel="lightbox"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/_VZZAFHl2_Og/StkDUiE6HGI/AAAAAAAACxk/4SfK_foyZi4/s400/chain-today.png" /&gt;&lt;/a&gt;&lt;/center&gt;&lt;br /&gt;
&lt;div style="float: right; margin: 0pt 0pt 10px 10px; text-align: center;"&gt;&lt;img src="http://farm4.static.flickr.com/3406/3565116428_90840c4240_t.jpg" title="First generation cellphone" /&gt;&lt;br /&gt;
&lt;span style="font-size: smaller;"&gt;Credits: &lt;a href="http://www.flickr.com/photos/digitalalan/"&gt;DigitalAlan&lt;/a&gt;&lt;/span&gt;&lt;br /&gt;
&lt;/div&gt;Today, the order has been scrambled:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;Thanks to the rapid technology evolution, designers of mobile devices can embed many types of sensors into communicant machines, and with the increasing miniaturization, such machines are pervasiveness!&lt;/li&gt;
&lt;li&gt;Because of the reduced delay between big technology evolutions (think about the iPhone which is just 2 years old), consumers choose their devices carefully, and bargain more to get the best quality-price ratio.&lt;/li&gt;
&lt;li&gt;Network manufacturers now to their best to provide networks than can deliver at the rhythm the devices can consume. When the European community created the Groupe Spécial Mobile (initial meaning of the GSM acronym, known now for Global System for Mobile communications) in 1982, we had to wait up to 1991 to experiment the first GSM network. GSM is also known the 2G technology. The EDGE (Enhanced Data rates for GSM Evolution, or 2.5G) has been introduced in 1999, the first 3G technology (HSPA/UMTS, and EV-DO/CDMA) has been delivered late 2001, and the coming 4G ones (LTE for Long Term Evolution or WiMax) are on the bench.&lt;/li&gt;
&lt;li&gt;At the end of the line, now there are the telcos:&lt;/li&gt;
&lt;ul&gt;&lt;li&gt;Investments are phenomenal&lt;/li&gt;
&lt;li&gt;The competition is rude (not rude enough in Canada, IMO)&lt;/li&gt;
&lt;li&gt;Customers are volatile and they always want the latest phone&lt;/li&gt;
&lt;li&gt;They have to subsidize the phones to lower the barriers to entry&lt;/li&gt;
&lt;li&gt;They have to provide the best coverage everywhere&lt;/li&gt;
&lt;li&gt;Customers are quick to leverage social tools to complain about them&lt;/li&gt;
&lt;li&gt;Customers don't respect the old rules (read: jailbreak their phone)&lt;/li&gt;
&lt;li&gt;Customers are not the cash cows they used to be...&lt;/li&gt;
&lt;/ul&gt;&lt;/ul&gt;&lt;br /&gt;
I don't think the telco future looks very nice. As traditional telecommunication service providers, they are &lt;a href="http://domderrien.blogspot.com/2009/03/telcos-vs-internet-providers.html"&gt;more and more just Internet providers&lt;/a&gt;. Personally, I'm fine with communication on Internet (VOIP/SIP), with the possibility to stream on Internet (Qik.com, Layar.com), to receive instant messages (IM) instead of text messages (SMS). And look, when I've a chance to connect &lt;a href="http://domderrien.blogspot.com/2009/06/android-dev-phone-1-setup.html"&gt;my phone&lt;/a&gt; to a wifi network, I'm happy to get a better connectivity while saving few bucks.&lt;br /&gt;
&lt;br /&gt;
Interesting developments to follow, aren't they?&lt;br /&gt;
&lt;br /&gt;
A+, Dom&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=Rc5_hl-aE1M:Xmx-pj9rufc:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=Rc5_hl-aE1M:Xmx-pj9rufc:4cEx4HpKnUU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?i=Rc5_hl-aE1M:Xmx-pj9rufc:4cEx4HpKnUU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=Rc5_hl-aE1M:Xmx-pj9rufc:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?i=Rc5_hl-aE1M:Xmx-pj9rufc:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SharingTechnologies/~4/Rc5_hl-aE1M" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://domderrien.blogspot.com/feeds/623874656538219093/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://domderrien.blogspot.com/2009/10/canadian-wireless-management-forum-my.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/168828253523263225/posts/default/623874656538219093?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/168828253523263225/posts/default/623874656538219093?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SharingTechnologies/~3/Rc5_hl-aE1M/canadian-wireless-management-forum-my.html" title="Canadian Wireless Management Forum - my review" /><author><name>Dom Derrien</name><uri>https://plus.google.com/100819130946985751629</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-CAiJ6LOHBWE/AAAAAAAAAAI/AAAAAAAAErE/twENdW648fs/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://farm1.static.flickr.com/80/243093440_9e6c67c6cc_t.jpg" height="72" width="72" /><thr:total>0</thr:total><feedburner:origLink>http://domderrien.blogspot.com/2009/10/canadian-wireless-management-forum-my.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D0YER3czeyp7ImA9WxBTGE4.&quot;"><id>tag:blogger.com,1999:blog-168828253523263225.post-7194928105258424846</id><published>2009-10-05T21:31:00.006-04:00</published><updated>2009-12-14T18:58:26.983-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-12-14T18:58:26.983-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Technology" /><category scheme="http://www.blogger.com/atom/ns#" term="Community" /><title>Internationalization and my two-tiers-utils library</title><content type="html">This is a follow-up article of &lt;a href="/2009/06/internationalization-of-gae.html"&gt;Internationalization of GAE applications&lt;/a&gt;, which itself is part of the series &lt;a href="/2009/01/web-application-on-resources-in-cloud.html"&gt;Web Application on Resources in the Cloud&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
In my initial article, I explained some hurdles of globalizing applications, especially the ones being implemented with many programming languages. In this article, I'm going to describe few use-cases and how my open-source library &lt;a href="http://github.com/DomDerrien/two-tiers-utils"&gt;two-tiers-utils&lt;/a&gt; can ease the implementation. Here are the covered topics:&lt;br /&gt;
&lt;ol&gt;&lt;li&gt;&lt;a href="#get"&gt;Get the user's preferred locale&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#diffLocales"&gt;Display messages in different locales&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#diffLanguages"&gt;Handle localized messages with different programming languages&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#generate"&gt;Generate the localized bundles per programming language&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="bonus"&gt;Bonus&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;&lt;br /&gt;
&lt;div style="font-size: large;"&gt;&lt;a name="get"&gt;&lt;/a&gt;Get the user's preferred locale&lt;/div&gt;&lt;br /&gt;
For this use-case, let's only consider the Java programming language. Another assumption is the availability of the localized resources in the corresponding Java format (&lt;span style="font-style: italic;"&gt;i.e.&lt;/span&gt; accessible &lt;span style="font-style: italic;"&gt;via&lt;/span&gt; a &lt;a href="http://java.sun.com/javase/6/docs/api/java/util/PropertyResourceBundle.html"&gt;PropertyResourceBundle&lt;/a&gt; instance).&lt;br /&gt;
&lt;br /&gt;
In a Web application, the user's preferred locale can be retrieved from:&lt;ul&gt;&lt;li&gt;The HTTP headers:&lt;br /&gt;
&lt;pre class="prettyprint limitSnippetHeight"&gt;locale = ((HttpServletRequest) request).getLocale();&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;The HTTP session (if saved there previously):&lt;br /&gt;
&lt;pre class="prettyprint limitSnippetHeight"&gt;HttpSession session = ((HttpServletRequest) request).getSession(false);
if (session != null) {
  locale = new Locale((String) session.getAttribute(SESSION_USER_LOCALE_ID));
}&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;The record containing the user's information:&lt;br /&gt;
&lt;pre class="prettyprint limitSnippetHeight"&gt;locale = ((UserDTO) user).getPreferredLocale();&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;To ease this information retrieval, the two-tiers-utils library provides the &lt;code&gt;domderrien.i18n.LocaleController&lt;/code&gt; class&lt;br /&gt;
&lt;br /&gt;
&lt;div class="codeSnippetTitle"&gt;Excerpt of public methods offered within &lt;code&gt;domderrien.i18n.LocaleController&lt;/code&gt;&lt;/div&gt;&lt;img src="http://lh4.ggpht.com/_VZZAFHl2_Og/SsI4xfyUObI/AAAAAAAACwM/P6gp3nuw2BQ/s800/2tu-javadoc-detectLocale.png" style="margin: 0pt auto 10px; display: block; text-align: center;" title="Methods from domderrien.i18n.LocaleController" alt="" /&gt;&lt;br /&gt;
This class can be used in two situations:&lt;ol&gt;&lt;li&gt;In a login form, for example, when we can just guess the desired locale from the browser preferred language list or from an argument in the URL.&lt;/li&gt;
&lt;li&gt;In pages accessible to identified users thanks to the HTTP session.&lt;/li&gt;
&lt;/ol&gt;&lt;div class="codeSnippetTitle"&gt;Usage example of the &lt;code&gt;domderrien.i18n.LocaleController.detectLocale()&lt;/code&gt;&lt;/div&gt;&lt;pre class="prettyprint limitSnippetHeight"&gt;&amp;lt;!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"&amp;gt;
&amp;lt;%@page
    language="java"
    contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"
    import="org.domderrien.i18n.LabelExtractor"
    import="org.domderrien.i18n.LocaleController"
%&amp;gt;&amp;lt;%
    // Locale detection
    Locale locale = LocaleController.detectLocale(request);
%&amp;gt;&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;title&amp;gt;&amp;lt;%= LabelExtractor.get("dd2tu_applicationName", locale) %&amp;gt;&amp;lt;/title&amp;gt;
    ...
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
    ...
    &amp;lt;img
        class="anchorLogo"
        src="images/iconHelp.png"
        width="16"
        height="16"
        title="&amp;lt;%= LabelExtractor.get("dd2tu_topCommandBox_helpIconLabel", locale) %&amp;gt;"
    /&amp;gt;
    ...
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/pre&gt;&lt;br /&gt;
&lt;div style="font-size: large;"&gt;&lt;a name="diffLocales"&gt;&lt;/a&gt;The same message in different locales&lt;/div&gt;&lt;br /&gt;
The previous example introduces also a second class: &lt;code&gt;domderrien.i18n.LabelExtractor&lt;/code&gt;. Being given an identifier, an optional array of Object references, and a locale, the &lt;code&gt;get&lt;/code&gt; static method loads the corresponding string from the localized resource bundle.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="codeSnippetTitle"&gt;Excerpt of public methods offered within domderrien.i18n.LabelExtractor&lt;/div&gt;&lt;img src="http://lh5.ggpht.com/_VZZAFHl2_Og/SsJFlYbJw8I/AAAAAAAACwQ/AtEke9cg8NU/s800/2tu-javadoc-get.png" style="margin: 0pt auto 10px; display: block; text-align: center;" title="Methods from domderrien.i18n.LabelExtractor alt=" /&gt;A series of localized entries like &lt;code&gt;en:“Welcome {0} {1}”&lt;/code&gt;, &lt;code&gt;fr:“Bonjour {2} {1}”&lt;/code&gt;, and &lt;code&gt;ja:“お早う {0}{1}”&lt;/code&gt; can be easily invoked with a simple command like: &lt;code&gt;LabelExtractor.get("welcome_message", new Object[] { user.getFirstName(), user.getLastName() }, user.getLocale());&lt;/code&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;div style="font-size: large;"&gt;&lt;a name="diffLanguages"&gt;&lt;/a&gt;The same message used from different programming languages&lt;/div&gt;&lt;br /&gt;
Java is a pretty neat language with a large set of editors and code inspectors. But Java is not the only languages used for Web applications. If the two-tiers-utils library provides nice Java features, the delivery of the same library interfaces for the programming languages JavaScript and Python libraries makes it way more valuable!&lt;br /&gt;
&lt;br /&gt;
&lt;div class="codeSnippetTitle"&gt;Code of the &lt;code&gt;domderrien.i18n.LabelExtractor.get()&lt;/code&gt; method for the JavaScript language.&lt;/div&gt;&lt;pre class="prettyprint limitSnippetHeight"&gt;(function() { // To limit the scope of the private variables

    /**
     * @author dom.derrien
     * @maintainer dom.derrien
     */
    var module = dojo.provide("domderrien.i18n.LabelExtractor");

    var _dictionnary = null;

    module.init = function(/*String*/ namespace, /*String*/ filename, /*String*/ locale) {
        // Dojo uses dash-separated (e.g en-US not en_US) and uses lower case names (e.g en-us not en_US)
        locale = (locale || dojo.locale).replace('_','-').toLowerCase();

        // Load the bundle
        try {
            // Notes:
            // - Cannot use the notation "dojo.requirelocalization" because dojo parser
            //   will try to load the bundle when this file is interpreted, instead of
            //   waiting for a call with meaningful "namespace" and "filename" values
            dojo["requireLocalization"](namespace, filename, locale); // Blocking call getting the file per XHR or &amp;lt;iframe/&amp;gt;

            _dictionary = dojo.i18n.getLocalization(namespace, filename, locale);
        }
        catch(ex) {
            alert("Deployment issue:" +
                    "\nCannot get localized bundle " + namespace + "." + filename + " for the locale " + locale +
                    "\nMessage: " + ex
                );
        }

        return module;
    };

    module.get = function(/*String*/key, /*Array*/args) {
        if (_dictionary == null) {
            return key;
        }
        var message = _dictionary[key] || key;
        if (args != null) {
            dojo.string.substituteParams(message, args);
        }
        return message;
    };

})(); // End of the function limiting the scope of the private variables&lt;/pre&gt;&lt;br /&gt;
The following piece of code illustrates how the JavaScript &lt;code&gt;domderrien.i18n.LabelExtractor&lt;/code&gt; class instance should be initialized (the value of the &lt;code&gt;locale&lt;/code&gt; variable can come from &lt;code&gt;dojo.locale&lt;/code&gt; or a value injected server-side into a JSP page) and how it can be invoked to get a localized label.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="codeSnippetTitle"&gt;Usage example of the &lt;code&gt;domderrien.i18n.LocaleController.get()&lt;/code&gt;&lt;/div&gt;&lt;pre class="prettyprint limitSnippetHeight"&gt;(function() { // To limit the scope of the private variables

    var module = dojo.provide("domderrien.blog.Test");

    dojo.require("domderrien.i18n.LabelExtractor");

    var _labelExtractor;

    module.init = function(/*String*/ locale) {
        // Get the localized resource bundle
        _labelExtractor = domderrien.i18n.LabelExtractor.init(
                "domderrien.blog",
                "TestBundle",
                locale // The library is going to fallback on dojo.locale if this parameter is null
            );

        ...
    };

    module._postData = function(/*String*/ url, /*Object*/ jsonParams) {
        var transaction = dojo.xhrPost({
            content : jsonParams,
            handleAs : "json",
            load : function(/*object*/ response, /*Object*/ioargs) {
                if (response == null) {
                    // Message prepared client-side
                    _reportError(_labelExtractor.get("dd2tu_xhr_unexpectedError"), [ioargs.xhr.status]);
                }
                if (!response.success) {
                    // Message prepared server-side
                    _reportError(_labelExtractor.get(response.messageKey), response.msgParams);
                }
                ...
            },
            error : function(/*Error*/ error, /*Object*/ ioargs) {
                    // Message prepared client-side
                _reportError(error.message, [ioargs.xhr.status]);
            },
            url : url
        });
    };

    var _reportError = function(/*String*/ message, /*Number ?*/xhrStatus) {
        var console = dijit.byId("errorConsole");
        ...
    };

    ...

})(); // End of the function limiting the scope of the private variables&lt;/pre&gt;&lt;br /&gt;
The following series of code excerpts show the pieces involved in getting the localized resources with the Python programming language.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="codeSnippetTitle"&gt;LabelExtractor methods definitions from &lt;code&gt;domderrien/i18n/LabelExtractor.py&lt;/code&gt;&lt;/div&gt;&lt;pre class="prettyprint limitSnippetHeight"&gt;# -*- coding: utf-8 -*-

import en
import fr
 
def init(locale):
    """Initialize the global dictionary for the specified locale"""
    global dict
    if locale == "fr":
        dict = fr._getDictionary()
    else: # "en" is the default language
        dict = en._getDictionary()
    return dict&lt;/pre&gt;&lt;br /&gt;
&lt;div class="codeSnippetTitle"&gt;Sample of a localized dictionary from &lt;code&gt;domderrien/i18n/en.py&lt;/code&gt;&lt;/div&gt;&lt;pre class="prettyprint limitSnippetHeight"&gt;# -*- coding: utf-8 -*-
 
dict_en = {}
 
def _getDictionary():
    global dict_en
    if (len(dict_en) == 0):
        _fetchDictionary(dict_en)
    return dict_en
 
def _fetchDictionary(dict):
    dict["_language"] = "English"
    dict["dd2tu_applicationName"] = "Test Application"
    dict["dd2tu_welcomeMsg"] = "Welcome {0}."
    ...&lt;/pre&gt;&lt;br /&gt;
&lt;div class="codeSnippetTitle"&gt;Definitions of filters used by the Django templates, from &lt;code&gt;domderrien/i18n/filters.py&lt;/code&gt;&lt;/div&gt;&lt;pre class="prettyprint limitSnippetHeight"&gt;from google.appengine.ext import webapp
 
def get(dict, key):
    return dict[key]
 
def replace0(pattern, value0):
    return pattern.replace("{0}", str(value0))
 
def replace1(pattern, value1):
    return pattern.replace("{1}", str(value1))
 
...
 
# http://javawonders.blogspot.com/2009/01/google-app-engine-templates-and-custom.html
# http://daily.profeth.de/2008/04/using-custom-django-template-helpers.html
 
register = webapp.template.create_template_register()
register.filter(get)
register.filter(replace0)
register.filter(replace1)
...&lt;/pre&gt;&lt;br /&gt;
&lt;div class="codeSnippetTitle"&gt;Django template from &lt;code&gt;domderrien/blog/Test.html&lt;/code&gt;&lt;/div&gt;&lt;pre class="prettyprint limitSnippetHeight"&gt;&amp;lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"&amp;gt;
&amp;lt;html xmlns="http://www.w3.org/1999/xhtml"&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;title&amp;gt;{{ dictionary|get:dd2tu_applicationName }}&amp;lt;/title&amp;gt;
    ....
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
    ...
    &amp;lt;div class="..."&amp;gt;{{ dictionary|get:dd2tu_welcomeMsg|replace0:parameters.loggedUser }}&amp;lt;/div&amp;gt;
    ...
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/pre&gt;&lt;br /&gt;
&lt;div class="codeSnippetTitle"&gt;Test handler from &lt;code&gt;domderrien/blog/Test.py&lt;/code&gt;&lt;/div&gt;&lt;pre class="prettyprint limitSnippetHeight"&gt;from google.appengine.api import users
from google.appengine.ext import webapp
from google.appengine.ext.webapp import template

def prepareDictionary(Request):
    locale = request.get('lang', 'en')
    return LabelExtractor.init(locale)

class MainPage(webapp.RequestHandler):
    def get(self):
        parameters = {}
        parameters ['dictionary'] = domderrien.i18n.LabelExtractor.init(self.request)
        parameters ['loggedUser'] = users.get_current_user()
        path = os.path.join(os.path.dirname(__file__), 'domderrien/blog/Test.html')
        self.response.out.write(template.render(path, parameters))

application = webapp.WSGIApplication(
    [('/', MainPage)],
    debug=True
)
 
def main():
    webapp.template.register_template_library('domderrien.i18n.filters')
    run_wsgi_app(application)
 
if __name__ == "__main__":
    main()&lt;/pre&gt;&lt;br /&gt;
&lt;div style="font-size: large;"&gt;&lt;a name="generate"&gt;&lt;/a&gt;Generate the localized bundles per programming language&lt;/div&gt;&lt;br /&gt;
In my previous post &lt;a href="/2009/06/internationalization-of-gae.html"&gt;Internationalization of GAE applications&lt;/a&gt;, I suggest to use a dictionary format that would be programming lnaguage agnostic while being known by translator: TMX, for Tanslation Memory eXchange.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="codeSnippetTitle"&gt;Snippet of a translation unit definition for a TMX formatted file&lt;/div&gt;&lt;pre class="prettyprint limitSnippetHeight"&gt;&amp;lt;tu tuid="&lt;span style="color: red;"&gt;dd2tu_welcomeMessage&lt;/span&gt;" datatype="Text"&amp;gt;
 &amp;lt;tuv xml:lang="en"&amp;gt;
  &amp;lt;seg&amp;gt;Welcome {0}&amp;lt;/seg&amp;gt;
 &amp;lt;/tuv&amp;gt;
 &amp;lt;note&amp;gt;{0} is going to be replaced by the logged user's display name&amp;lt;/note&amp;gt;
 &amp;lt;prop type="x-tier"&amp;gt;dojotk&amp;lt;/prop&amp;gt;
 &amp;lt;prop type="x-tier"&amp;gt;javarb&amp;lt;/prop&amp;gt;
 &amp;lt;prop type="x-tier"&amp;gt;python&amp;lt;/prop&amp;gt;
&amp;lt;/tu&amp;gt;&lt;/pre&gt;&lt;br /&gt;
The two-tiers-utils library provides a Java runtime &lt;code&gt;domderrien.build.TMXConverter&lt;/code&gt; that generates the  resource bundles for Java/JavaScript/Python. If a simple series of XSL-Transform runs can do the job, the &lt;code&gt;TMXConverter&lt;/code&gt; does a bit more by:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;Comparing the modification dates of the generated files with the TMX one to generate them only if needed&lt;/li&gt;
&lt;li&gt;Check the uniqueness of the label keys&lt;/li&gt;
&lt;li&gt;Generate the list of supported languages&lt;/li&gt;
&lt;/ul&gt;Invoking the &lt;code&gt;TMXConverter&lt;/code&gt; runtime from an &lt;code&gt;ant&lt;/code&gt; build file is very simple, while a bit verbose:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="codeSnippetTitle"&gt;&lt;code&gt;Ant&lt;/code&gt; target definition invoking the &lt;code&gt;TMXConverter&lt;/code&gt;&lt;/div&gt;&lt;pre class="prettyprint limitSnippetHeight"&gt;&amp;lt;target name="step-tmx-convert"&amp;gt;
    &amp;lt;mkdir dir="${temp.dir}/resources" /&amp;gt;
    &amp;lt;mkdir dir="src/WebContent/js/domderrien/i18n/nls" /&amp;gt;
    &amp;lt;java classname="domderrien.build.TMXConverter" fork="true" failonerror="true"&amp;gt;
        &amp;lt;classpath refid="tmxconverter.classpath" /&amp;gt;
        &amp;lt;classpath location="${temp.dir}/resources" /&amp;gt;
        &amp;lt;jvmarg value="-Dfile.encoding=UTF-8" /&amp;gt;
        &amp;lt;arg value="-tmxFilenameBase" /&amp;gt;
        &amp;lt;arg value="${dd2tu.localizedLabelBaseFilename}" /&amp;gt;
        &amp;lt;arg value="-sourcePath" /&amp;gt;
        &amp;lt;arg value="${basedir}\src\resources" /&amp;gt;
        &amp;lt;arg value="-jsDestPath" /&amp;gt;
        &amp;lt;arg value="${basedir}\src\WebContent\js\domderrien\i18n\nls" /&amp;gt;
        &amp;lt;arg value="-javaDestPath" /&amp;gt;
        &amp;lt;arg value="${temp.dir}/resources" /&amp;gt;
        &amp;lt;arg value="-languageFilenameBase" /&amp;gt;
        &amp;lt;arg value="${dd2tu.languageListFilename}" /&amp;gt;
        &amp;lt;arg value="-buildStamp" /&amp;gt;
        &amp;lt;arg value="${dd2tu.stageId}" /&amp;gt;
    &amp;lt;/java&amp;gt;
    &amp;lt;native2ascii
        src="${temp.dir}/resources"
        dest="${temp.dir}/resources"
        encoding="UTF8"
        includes="*.properties-utf8"
        ext=".properties"
    /&amp;gt;
    &amp;lt;copy
        file="${temp.dir}/resources/${dd2tu.localizedLabelBaseFilename}.properties"
        tofile="${temp.dir}/resources/${dd2tu.localizedLabelBaseFilename}_en.properties"
    /&amp;gt;
    &amp;lt;mkdir dir="src/WebContent/js/domderrien/i18n/nls/en" /&amp;gt;
    &amp;lt;copy
        file="src/WebContent/js/domderrien/i18n/nls/{dd2tu.localizedLabelBaseFilename}.js"
        todir="src/WebContent/js/domderrien/i18n/nls/en"
    /&amp;gt;
&amp;lt;/target&amp;gt;&lt;/pre&gt;&lt;br /&gt;
With the TMX file as the source of thruth for the label definitions, it is just a matter of altering the value a &lt;code&gt;&amp;lt;prop/&amp;gt;&lt;/code&gt; tag and running the build once again to move one label definition from one programming language to another. No more error prone copy-and-paste of text between different file formats!&lt;br /&gt;
&lt;br /&gt;
&lt;div class="codeSnippetTitle"&gt;Excerpt of the generated Java resource bundle&lt;/div&gt;&lt;pre class="prettyprint limitSnippetHeight"&gt;bundle_language=English
unit_test_sample=N/A
dd2tu_applicationName="Test Application"
dd2tu_welcomeMessage=Welcome {0}
...
x_timeStamp=20091001.1001&lt;/pre&gt;&lt;br /&gt;
&lt;div class="codeSnippetTitle"&gt;Excerpt of the generated JavaScript resource bundle&lt;/div&gt;&lt;pre class="prettyprint limitSnippetHeight"&gt;({bundle_language:"English",
unit_test_sample:"N/A",
dd2tu_applicationName:"Test Application",
dd2tu_welcomeMessage:"Welcome ${0}",
...
x_timeStamp:"20091001.1001"})&lt;/pre&gt;&lt;br /&gt;
&lt;div class="codeSnippetTitle"&gt;Excerpt of the generated Python class definition&lt;/div&gt;&lt;pre class="prettyprint limitSnippetHeight"&gt;# -*- coding: utf-8 -*-
 
dict_en = {}
 
def _getDictionary():
    global dict_en
    if (len(dict_en) == 0):
        _fetchDictionary(dict_en)
    return dict_en
 
def _fetchDictionary(dict):
    dict["_language"] = "English"
    dict["dd2tu_applicationName"] = "Test Application"
    dict["dd2tu_welcomeMsg"] = "Welcome {0}."
    ...
    dict["x_timestamp"] = "20091001.1001"&lt;/pre&gt;&lt;br /&gt;
&lt;div style="font-size: large;"&gt;&lt;a name="bonus"&gt;&lt;/a&gt;Bonus&lt;/div&gt;&lt;br /&gt;
The &lt;code&gt;TMXConverter&lt;/code&gt; being part of the build process and going over all localized TMX files, it generates the list of supported languages.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="codeSnippetTitle"&gt;JSP code fetching a HTML &amp;amp;ltselect/&amp;gt; box with the list of supported languages&lt;/div&gt;&lt;pre class="prettyprint limitSnippetHeight"&gt;&amp;lt;span class="topCommand topCommandLabel"&amp;gt;&amp;lt;%= LabelExtractor.get("rwa_loginLanguageSelectBoxLabel", locale) %&amp;gt;&amp;lt;/span&amp;gt;
&amp;lt;select
    class="topCommand"
    dojotType="dijit.form.FilteringSelect"
    id="languageSelector"
    onchange="switchLanguage();"
    title="&amp;lt;%= LabelExtractor.get("rwa_loginLanguageSelectBoxLabel", locale) %&amp;gt;"
&amp;gt;&amp;lt;%
    ResourceBundle languageList = LocaleController.getLanguageListRB();
    Enumeration&amp;lt;String&amp;gt; keys = languageList.getKeys();
    while(keys.hasMoreElements()) {
        String key = keys.nextElement();%&amp;gt;
        &amp;lt;option&amp;lt;% if (key.equals(localeId)) { %&amp;gt; selected&amp;lt;% } %&amp;gt; value="&amp;lt;%= key %&amp;gt;"&amp;gt;&amp;lt;%= languageList.getString(key) %&amp;gt;&amp;lt;/option&amp;gt;&amp;lt;%
    } %&amp;gt;
&amp;lt;/select&amp;gt;&lt;/pre&gt;&lt;br /&gt;
The following figures illustrates the corresponding code in action.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="codeSnippetTitle"&gt;Part of a login screen as defined with the default (English) TMX file.&lt;/div&gt;&lt;img src="http://lh3.ggpht.com/_VZZAFHl2_Og/SspLfUP_bSI/AAAAAAAACxE/-JYmKG2paVA/s800/2009-10-05_1537.png" title="UI defined with the default TMX file" style="text-align:center; display:block;margin:auto;" /&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;div class="codeSnippetTitle"&gt;Part of a login screen as defined with the French TMX file.&lt;/div&gt;&lt;img src="http://lh6.ggpht.com/_VZZAFHl2_Og/SspLfW9y7VI/AAAAAAAACxI/GscDMHylSzg/s800/2009-10-05_1538.png" title="UI defined with the French TMX file" style="text-align:center; display:block;margin:auto;" /&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;div style="font-size: large;"&gt;&lt;a name="bonus"&gt;&lt;/a&gt;Conclusion&lt;/div&gt;&lt;br /&gt;
The &lt;a href="http://github.com/DomDerrien/two-tiers-utils"&gt;two-tiers-utils&lt;/a&gt; library is offered with the &lt;a href="http://github.com/DomDerrien/two-tiers-utils/blob/master/src/Java/domderrien/LICENSE"&gt;BSD-like license&lt;/a&gt;. Anyone is free to use it for his own purposes. but I'll appreciate any feedback, contribution, and feature requirement.&lt;br /&gt;
&lt;br /&gt;
See you on github.com ;)&lt;br /&gt;
“May the &lt;i&gt;fork&lt;/i&gt; be with you.”&lt;br /&gt;
&lt;br /&gt;
A+, Dom&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=e5D4AGClnyU:s68xHs0aHSk:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=e5D4AGClnyU:s68xHs0aHSk:4cEx4HpKnUU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?i=e5D4AGClnyU:s68xHs0aHSk:4cEx4HpKnUU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=e5D4AGClnyU:s68xHs0aHSk:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?i=e5D4AGClnyU:s68xHs0aHSk:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SharingTechnologies/~4/e5D4AGClnyU" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://domderrien.blogspot.com/feeds/7194928105258424846/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://domderrien.blogspot.com/2009/10/internationalization-and-my-two-tiers.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/168828253523263225/posts/default/7194928105258424846?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/168828253523263225/posts/default/7194928105258424846?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SharingTechnologies/~3/e5D4AGClnyU/internationalization-and-my-two-tiers.html" title="Internationalization and my two-tiers-utils library" /><author><name>Dom Derrien</name><uri>https://plus.google.com/100819130946985751629</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-CAiJ6LOHBWE/AAAAAAAAAAI/AAAAAAAAErE/twENdW648fs/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://lh4.ggpht.com/_VZZAFHl2_Og/SsI4xfyUObI/AAAAAAAACwM/P6gp3nuw2BQ/s72-c/2tu-javadoc-detectLocale.png" height="72" width="72" /><thr:total>0</thr:total><feedburner:origLink>http://domderrien.blogspot.com/2009/10/internationalization-and-my-two-tiers.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D0cGR38-eCp7ImA9WxBTGE4.&quot;"><id>tag:blogger.com,1999:blog-168828253523263225.post-3558122177910541593</id><published>2009-09-18T23:39:00.001-04:00</published><updated>2009-12-14T18:57:06.150-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-12-14T18:57:06.150-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Experience" /><title>Progress update</title><content type="html">Almost three months without publishing anything! I am definitively not proud of this score...&lt;br /&gt;
&lt;br /&gt;
I have been busy on three fronts:&lt;br /&gt;
&lt;ol&gt;&lt;li&gt;At work,  I continue working on mobile related development. The easiest platform to play with is the Android one (see my post on &lt;a href="http://domderrien.blogspot.com/2009/06/android-dev-phone-1-setup.html"&gt;Android Dev Phone 1&lt;/a&gt;) and I thrilled to see new handsets being made available, like the HTC Dream and Motorola Cliq, with their respective HTC Sense and Moto BLUR custom UIs (see &lt;a href="#ProgressUpdate-Videos"&gt;videos below&lt;/a&gt;). Even if its SDK is not as rich as Android's one, even if the gadgets are not as polished as Android's ones, I also like developing for BlackBerry phones, like the BlackBerry Storm.&lt;/li&gt;
&lt;/ol&gt;&lt;ol start="2"&gt;&lt;li&gt;For my side project, running on Google App Engine infrastructure, it is progressing very well. Today, I reached a milestone: the first part of [still a secret] runs live. In terms of coding, it represents ~6,000 lines for the source files and ~12,000 lines for the test. I have always considered source &lt;i&gt;vs&lt;/i&gt; test as being 50-50; it seems I should re-evaluate the balance to 1/3-2/3 ;)&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;img border="0" src="http://1.bp.blogspot.com/_VZZAFHl2_Og/SrRHN7_L6zI/AAAAAAAACvs/NSQROYxK_ZE/s1600/line-numbers.png" /&gt;&lt;br /&gt;
&lt;/div&gt;&lt;br /&gt;
Thanks to different contributors, I have developed a series of &lt;i&gt;mock&lt;/i&gt; classes allowing to test transactions with BigTable, the database used by App Engine. A really neat piece of code I am going to describe here later. &lt;/li&gt;
&lt;/ol&gt;&lt;ol start="3"&gt;&lt;li&gt;On the social side, I am working with the board of &lt;a href="http://www.diku-dilenga.org/"&gt;Diku Dilenga&lt;/a&gt; Canada (board I am member of) to move as its Executive Director (still as a volunteer). The move has been inspired by Jean-Pierre Tchang, founder of &lt;a href="http://irismundial.ca/"&gt;IRIS Mundial&lt;/a&gt;. I hope this update will make Diku Dilenga (Canada) as successful as IRIS Mundial.&lt;br /&gt;
&lt;br /&gt;
I had a lot of activities on this front recently: a fundraiser thanks to &lt;a href="http://diku-dilenga.org/lang-en/partenaires/dd-canada/94-le-parcours-en-direct-de-louis-lamontagne-sur-le-chemin-de-compostelle"&gt;Louis Lamontagne walking between Saint Jean Pied de Port (France) and Santiago de Compostela (Spain)&lt;/a&gt;, a trip of 780km, a first series of computers to be shipped to Kananga, Democratic Republic of the Congo, etc.&lt;br /&gt;
&lt;br /&gt;
The plan to link the [still a secret] project with Diku Dilenga activities have been formally approved by the board during the summer. This side, there is a possibility I will do a presentation to the &lt;a href="http://www.microcreditsummit.org/partner_with_us/"&gt;2010 Africa/Middle East Regional Microcredit Summit (AMERMS)&lt;/a&gt; to be held in Nairobi, Kenya April 7-10, 2010. It would be nice to participate, isn't it?&lt;/li&gt;
&lt;/ol&gt;&lt;br /&gt;
Stay tuned, I should be back in few days with technical information about unit testing transactional code on Google App Engine ;)&lt;br /&gt;
&lt;br /&gt;
A+, Dom&lt;br /&gt;
--&lt;br /&gt;
Videos:&lt;br /&gt;
&lt;br /&gt;
&lt;a name="ProgressUpdate-Videos"&gt;&lt;/a&gt;&lt;center&gt;&lt;object height="340" width="560"&gt;&lt;param name="movie" value="http://www.youtube.com/v/of-5IFSREVk&amp;hl=en&amp;fs=1&amp;"&gt;&lt;/param&gt;&lt;param name="allowFullScreen" value="true"&gt;&lt;/param&gt;&lt;param name="allowscriptaccess" value="always"&gt;&lt;/param&gt;&lt;embed src="http://www.youtube.com/v/of-5IFSREVk&amp;hl=en&amp;fs=1&amp;" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="560" height="340"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;br /&gt;
HTC Hero, the first phone with the HTC Sense, a customized UI scheme on the top of Android.&lt;/center&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;center&gt;&lt;object height="340" width="560"&gt;&lt;param name="movie" value="http://www.youtube.com/v/8LtKAnJf1Ss&amp;hl=en&amp;fs=1&amp;"&gt;&lt;/param&gt;&lt;param name="allowFullScreen" value="true"&gt;&lt;/param&gt;&lt;param name="allowscriptaccess" value="always"&gt;&lt;/param&gt;&lt;embed src="http://www.youtube.com/v/8LtKAnJf1Ss&amp;hl=en&amp;fs=1&amp;" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="560" height="340"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;br /&gt;
Motorola Cliq, the first phone with Moto BLUR, a customized UI scheme on the top of Android.&lt;/center&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;center&gt;&lt;object height="344" width="425"&gt;&lt;param name="movie" value="http://www.youtube.com/v/6IRVs4hXDIU&amp;hl=en&amp;fs=1&amp;"&gt;&lt;/param&gt;&lt;param name="allowFullScreen" value="true"&gt;&lt;/param&gt;&lt;param name="allowscriptaccess" value="always"&gt;&lt;/param&gt;&lt;embed src="http://www.youtube.com/v/6IRVs4hXDIU&amp;hl=en&amp;fs=1&amp;" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="344"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;br /&gt;
BlackBerry Storm, first BlackBerry phone with a touch screen&lt;/center&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=UUiFvogykSc:44ojE5G_vY4:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=UUiFvogykSc:44ojE5G_vY4:4cEx4HpKnUU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?i=UUiFvogykSc:44ojE5G_vY4:4cEx4HpKnUU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=UUiFvogykSc:44ojE5G_vY4:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?i=UUiFvogykSc:44ojE5G_vY4:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SharingTechnologies/~4/UUiFvogykSc" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://domderrien.blogspot.com/feeds/3558122177910541593/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://domderrien.blogspot.com/2009/09/progress-update.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/168828253523263225/posts/default/3558122177910541593?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/168828253523263225/posts/default/3558122177910541593?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SharingTechnologies/~3/UUiFvogykSc/progress-update.html" title="Progress update" /><author><name>Dom Derrien</name><uri>https://plus.google.com/100819130946985751629</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-CAiJ6LOHBWE/AAAAAAAAAAI/AAAAAAAAErE/twENdW648fs/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://1.bp.blogspot.com/_VZZAFHl2_Og/SrRHN7_L6zI/AAAAAAAACvs/NSQROYxK_ZE/s72-c/line-numbers.png" height="72" width="72" /><thr:total>0</thr:total><feedburner:origLink>http://domderrien.blogspot.com/2009/09/progress-update.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D0YHRXk9eCp7ImA9WxBTGE4.&quot;"><id>tag:blogger.com,1999:blog-168828253523263225.post-7579446453191855570</id><published>2009-06-12T14:55:00.003-04:00</published><updated>2009-12-14T18:58:54.760-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-12-14T18:58:54.760-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Technology" /><category scheme="http://www.blogger.com/atom/ns#" term="Community" /><title>JavaOne Conference</title><content type="html">&lt;div class="separator" style="float: right; font-size: smaller; margin: 0pt 0pt 5px 5px; text-align: center;"&gt;&lt;img border="0" src="http://1.bp.blogspot.com/_VZZAFHl2_Og/SjAPUre00fI/AAAAAAAACto/DcNkqJVwssc/s320/dom-at-javaone-with-duke-2.png" /&gt;&lt;br /&gt;
Duke and myself ;)&lt;/div&gt;It has been a long week in San Francisco while I was attending the JavaOne conference. Sessions started at 8:30 AM and finished after 9:30 PM!&lt;br /&gt;
&lt;br /&gt;
Among all reviews that have been published, read this ones: &lt;a href="http://ctpjava.blogspot.com/2009/06/java-one-2009-summary-monday.html"&gt;Community day&lt;/a&gt; on Monday, &lt;a href="http://ctpjava.blogspot.com/2009/06/java-one-2009-summary-tuesday-day-1.html"&gt;Conference day 1&lt;/a&gt; on Tuesday, &lt;a href="http://ctpjava.blogspot.com/2009/06/javaone-2009-summary-wednesday-day-2.html"&gt;Day 2&lt;/a&gt; on Wednesday, &lt;a href="http://ctpjava.blogspot.com/2009/06/javaone-2009-summary-thursday-day-3.html"&gt;Day 3&lt;/a&gt; on Thursday, and &lt;a href="http://ctpjava.blogspot.com/2009/06/javaone-2009-summary-friday-day-4.html"&gt;Day 4&lt;/a&gt; on Friday. Sun's website contains also a complete list of &lt;a href="http://java.sun.com/javaone/2009/articles/listing.jsp"&gt;conference articles&lt;/a&gt;. Pictures of the event are available on &lt;a href="http://photos.sun.com/page/3371"&gt;Sun's Photo Center website&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
I went there with two objectives:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;See JavaFX in action;&lt;/li&gt;
&lt;li&gt;Look at all efforts around JavaME and the MSA initiative.&lt;/li&gt;
&lt;/ul&gt;&lt;span style="font-size: large;"&gt;JavaFX technology stack&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;div style="float: right; font-size: smaller; margin: 0pt 0pt 5px 5px; text-align: center;"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/_VZZAFHl2_Og/SjAKyZCwEhI/AAAAAAAACtY/2xkfUnw3gdQ/s320/_DSC1018.1024x768.jpg" /&gt;&lt;br /&gt;
Eric Klein, Vice President, Java Marketing, Sun Microsystems&lt;/div&gt;I came without preconceived idea around JavaFX, just with my background as a JavaScript/Java developer and my knowledge of Flex and AIR.&lt;br /&gt;
&lt;br /&gt;
The first deception came from looking at the scripting language: Man! they invented yet another language :( For sure, the JavaFX scripting language is nicely handled by NetBeans 6.5+ (except the code formatting) but new paradigms and new conventions are a big barrier to adoption to me. Can you figure out what the following code is doing?&lt;br /&gt;
&lt;br /&gt;
&lt;div class="codeSnippetTitle"&gt;&lt;/div&gt;&lt;pre class="prettyprint"&gt;public class Button extends CustomNode {
  public var up: Node;
  content: Node = up;
  public override function create(): Node {
    return Group {
      content: bind content
    }
}&lt;/pre&gt;If the visual effects to animate texts, pictures, and video are really great, the native support of a sound library is really missing! For example, I would expect to be able to synchronize &lt;code&gt;KeyFrame&lt;/code&gt; objects with sound tracks but the &lt;code&gt;KeyFrame.time&lt;/code&gt; attribute has to be set manually—not very flexible when it's time to change the sound track...&lt;br /&gt;
&lt;br /&gt;
The pros are:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;A coming visual editor to prepare clips;&lt;/li&gt;
&lt;li&gt;A large library of image and video effects;&lt;/li&gt;
&lt;li&gt;The multi-platform support, especially for mobile devices.&lt;/li&gt;
&lt;/ul&gt;Platform dependent packaging is nicely handled: NetBeans project properties pane provides choices among {Standard Execution, Web Start Execution, Run in Browser, Run in Mobile Emulator}. As a Web developer, I am just sorry to see that the application window size cannot be expressed in percentage, that the auto-resizing is handled transparently, which is not better than usual Flex applications.&lt;br /&gt;
&lt;br /&gt;
Last point: I have not seen how to invoke JavaFX handlers from JavaScript ones, and &lt;i&gt;vice-versa&lt;/i&gt;, when the application is deployed to run in browsers. If you have a source for these information, please, drop the link in a comment.&lt;br /&gt;
&lt;br /&gt;
&lt;span style="font-size: large;"&gt;JavaME technology stack&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="float: right; font-size: smaller; margin: 0pt 0 5px 5px; text-align: center;"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/_VZZAFHl2_Og/SjAiRM5ZcsI/AAAAAAAACuI/EN72W-xNdt4/s320/sonyericsson.jpg" /&gt;&lt;br /&gt;
Christopher David, Rikko Sakaguchi and Patrik Olsson&lt;br /&gt;
during Sony Ericsson General Session&lt;/div&gt;This was definitively the most interesting domain to me. During one session, it has been announced that 60% of shipped mobile devices in 2008 are Java-enabled. In 2009, the market share should grow up to 70%. In relation with the iPhone, &lt;a href="http://photos.sun.com/download?acton=download&amp;amp;id=16892"&gt;Scott McNeally did this joke&lt;/a&gt;: the possible future Sun owner Larry Ellison might succeed to open the iPhone platform to Java because he is a well known friend of Steve Jobs.&lt;br /&gt;
&lt;br /&gt;
Compared to the Java Standard Edition (J2SE) and the Java Enterprise Edition (J2EE), the Java Mobile Edition (J2ME) has more external contributors. Under the &lt;a href="http://developers.sun.com/mobility/midp/articles/msaintro/"&gt;Mobile Service Architecture&lt;/a&gt; (MSA) initiative, a lot of mobile device manufacturers and telecommunication operators participate to the Java Community Process (JCP) to deliver Java Specification Requests (JSRs). Note that MSA itself is defined as a JSR: &lt;a href="http://jcp.org/en/jsr/detail?id=248"&gt;JSR 248&lt;/a&gt;. As of today, most of the recent phones are MSA 1.1 compliant (this is a mandatory requirement for the telco Orange, for example). Nokia and Sony Ericsson have shipped a lot of MSA-compliant handsets, LG, Samsung, and Motorola shipped very few ones. The standard MSA 2 (&lt;a href="http://jcp.org/en/jsr/detail?id=249"&gt;JSR 249&lt;/a&gt;) is being finalized and it contains the promising JSRs:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;&lt;a href="http://jcp.org/en/jsr/detail?id=256"&gt;JSR 256 Sensor API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://jcp.org/en/jsr/detail?id=293"&gt;JSR 293 Location API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://jcp.org/en/jsr/detail?id=239"&gt;JSR 239 OpenGL ES API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://jcp.org/en/jsr/detail?id=180"&gt;JSR 180 SIP API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://jcp.org/en/jsr/detail?id=177"&gt;JSR 177 Security and trust services API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://jcp.org/en/jsr/detail?id=205"&gt;JSR 205 Wireless messaging API&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;Additional APIs I imagine many developers are looking forward: &lt;a href="http://jcp.org/en/jsr/detail?id=257"&gt;JSR 257 Contactless communication API&lt;/a&gt; and &lt;a href="http://jcp.org/en/jsr/detail?id=229"&gt;JSR 229 Payment API&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="font-size: smaller; margin: 0pt 5px 5px 0pt; text-align: center;"&gt;&lt;a title="MSA evolution and its JSR set" xDojoType="dojox.image.Lightbox" href="http://2.bp.blogspot.com/_VZZAFHl2_Og/SjE2RAaG1hI/AAAAAAAACuQ/2kbzmM8EpPY/s1600-h/MSA-JSR%20set.png"&gt;&lt;img border="0" src="http://2.bp.blogspot.com/_VZZAFHl2_Og/SjE2RAaG1hI/AAAAAAAACuQ/2kbzmM8EpPY/s320/MSA-JSR%20set.png" width="320" /&gt;&lt;/a&gt;&lt;br /&gt;
MSA evolution and its JSR set&lt;br /&gt;
(from the &lt;a href="http://cds-esd.sun.com/ESD6/JSCDL/msa/0.19-pr/msa-0_19-pr-spec.pdf"&gt;MSA specification documentation&lt;/a&gt;)&lt;br /&gt;
(click to enlarge)&lt;/div&gt;&lt;br /&gt;
All major manufacturers have opened or are opening "App Stores" a-la Apple. They open also their development platforms. More companies will be able to adapt their software offering to mobile devices. Even Sony allows anyone to write application to run in Blu-ray Disc players. The main difficulty on developer-side is the fragmentation: there is no standard API allowing to discover the features supported by a device! Developers have to rely on each manufacgturer's feature list and on exception handling :(&lt;br /&gt;
&lt;br /&gt;
The Blackberry platform is pretty well controlled and should be easy to develop on. Then follows Sony Ericsson which provides consistent phone classes (&lt;i&gt;i.e.&lt;/i&gt; what works for one phone in the JP 8.4 class work for all phones in that class). The delivery of the Sun &lt;a href="http://java.sun.com/javame/downloads/sdk30.jsp"&gt;JavaME SDK 3.0&lt;/a&gt; containing many third-party emulators (even one for Windows Mobile devices) added to better on-device deployment and on-device debugging capabilities, should motivate more and more developers.&lt;br /&gt;
&lt;br /&gt;
I have not enough experience with &lt;a href="http://developer.android.com/"&gt;Android&lt;/a&gt; (just got one &lt;a href="http://domderrien.blogspot.com/2009/06/android-dev-phone-1-setup.html"&gt;Android dev phone&lt;/a&gt; two weeks ago) to compare it to the JavaME technology stack. I don't know neither about &lt;a href="http://www.symbian.org/"&gt;Symbian&lt;/a&gt; (Nokia devices) or &lt;a href="http://www.limofoundation.org/"&gt;LiMo&lt;/a&gt; (Motorola devices) platforms.&lt;br /&gt;
&lt;br /&gt;
&lt;span style="font-size: large;"&gt;Exhibition hall&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
Besides the visit of mobile device manufacturers (RIM and Ericsson) booths, I visited:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;Sun's project &lt;a href="https://fuji.dev.java.net/"&gt;Fuji&lt;/a&gt; (open ESB) with a Web console using &lt;code&gt;&amp;lt;canvas/&amp;gt;&lt;/code&gt; from HTML5, like Yahoo! Pipes.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.sun.com/software/convergence/"&gt;Convergence&lt;/a&gt;, the Web client for Sun's communication suite, built on the top of Dojo toolkit ;)&lt;/li&gt;
&lt;li&gt;INRIA (French national R&amp;D lab) for its static code analysis &lt;a href="http://nit.gforge.inria.fr/eclipse"&gt;Nit&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Isomorphic Software for its &lt;a href="http://www.smartclient.com/"&gt;SmartClient Ajax RIA System&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;eXo Platform (on OW2 booth) for its &lt;a href="http://www.exoplatform.com/portal/public/website/product/exoproducts/portal/portaloverview"&gt;eXo Portal&lt;/a&gt; offering.&lt;/li&gt;
&lt;li&gt;Liferay, Inc. for its &lt;a href="http://www.liferay.com/web/guest/products/portal"&gt;eponymous portal&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;&lt;span style="font-size: large;"&gt;Other discoveries&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="float: right; font-size: smaller; margin: 0pt 0pt 5px 5px; text-align: center;"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/_VZZAFHl2_Og/SjAMbSNPYwI/AAAAAAAACtg/-Bl9IGayNmM/s320/dom-at-javaone-with-goshling-2.png" /&gt;&lt;br /&gt;
James Gosling and myself ;)&lt;/div&gt;I attended very good presentations, like the opening keynote which was fun. Among the good presenters, I can mention (ordered alphabetically):&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;&lt;a href="http://benzilla.galbraiths.org/"&gt;Ben Galbraith&lt;/a&gt; and &lt;a href="http://almaer.com/blog/"&gt;Dion Almaer&lt;/a&gt; (see also &lt;a href="http://ajaxian.com/"&gt;Ajaxian.com&lt;/a&gt;), Mozilla.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://blog.bjhargrave.com/"&gt;BJ Hargrave&lt;/a&gt;, IBM, and &lt;a href="http://www.aqute.biz/Main/HomePage"&gt;Peter Kriens&lt;/a&gt;, aQute,  about OSGi.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://twitter.com/doug_tidwell"&gt;Doug Tidwell&lt;/a&gt;, IBM,  who gave a funny speech about &lt;a href="http://blogs.sun.com/javaone2009/entry/using_rest_and_ws_services"&gt;REST-WS* in the cloud&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://blogs.sonyericsson.com/erikhellman/"&gt;Erik Hellman&lt;/a&gt;, Sony Ericsson.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://blogs.sun.com/jag/"&gt;James Gosling&lt;/a&gt;, Sun Microsystems.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://wordpress.chanezon.com/"&gt;Patrick Chanezon&lt;/a&gt;, Google, about Open Web.&lt;/li&gt;
&lt;/ul&gt;If you have a Sun Developer Network (SDN) account (by the way, it's free), you can view the slides of the technical sessions at: &lt;a href="http://developers.sun.com/learning/javaoneonline/"&gt;http://developers.sun.com/learning/javaoneonline/&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;span style="font-size: large;"&gt;Special mention&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
I want also to mention the call to developers by &lt;a href="http://www.mifos.org/"&gt;MifOS&lt;/a&gt; people who have been awarded by James Gosling during the Friday morning general session. This organization develops open source software for microfinance institutions (MFIs) to help them managing loans and borrowers (see &lt;a href="http://www.mifos.org/product/demo-the-software"&gt;demo&lt;/a&gt;). Really nice initiative started by the Grameem Bank!&lt;br /&gt;
&lt;br /&gt;
Excerpt from &lt;a href="http://java.sun.com/javaone/2009/articles/toyshow.jsp"&gt;James Gosling Toy Show report&lt;/a&gt;:&lt;br /&gt;
&lt;blockquote&gt;&lt;b&gt;Microfinancing Through Java EE Technology&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
Gosling next introduced a group whose great innovation with Java technology was social and not technical. Sam Birney, engineering manager and Mifos alumnus, and Van Mittal-Hankle, senior software engineer at the Grameen Foundation, took the stage to receive &lt;a href="http://java.sun.com/javaone/2009/articles/2009dukeschoiceawards.jsp"&gt;Duke's Choice&lt;/a&gt; awards for their work using Java EE to serve 60,000 clients worldwide in microfinancing, a highly successful means of helping poor people get small loans and start businesses.&lt;br /&gt;
&lt;br /&gt;
&lt;a href="http://www.mifos.org/developers" target="_blank"&gt;Mifos&lt;/a&gt; is open-source technology for microfinance that is spearheaded by the &lt;a href="http://www.grameenfoundation.org/" target="_blank"&gt;Grameen Foundation&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
"Sometimes excellence comes not from technical innovation but in how technology is used," explained Gosling. "This is an example of using Java technology to really improve people's lives."&lt;br /&gt;
&lt;br /&gt;
With an estimated 1.6 billion people left in the world who could benefit from microfinance, the men put out a call for volunteers to contribute to the Mifos project.&lt;span&gt;.&lt;/span&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;span style="font-size: large;"&gt;What's next?&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
What are my resolutions? Get a Mac as the development platform (Eclipse works on MacOS and I can use a Win7 image within VirtualBox), and start development on Java enabled phone (at least MSA 1.1 compliant).&lt;br /&gt;
&lt;br /&gt;
A+, Dom&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=r762uciQV3M:9MBXOsWEf0E:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=r762uciQV3M:9MBXOsWEf0E:4cEx4HpKnUU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?i=r762uciQV3M:9MBXOsWEf0E:4cEx4HpKnUU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=r762uciQV3M:9MBXOsWEf0E:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?i=r762uciQV3M:9MBXOsWEf0E:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SharingTechnologies/~4/r762uciQV3M" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://domderrien.blogspot.com/feeds/7579446453191855570/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://domderrien.blogspot.com/2009/06/javaone-conference.html#comment-form" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/168828253523263225/posts/default/7579446453191855570?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/168828253523263225/posts/default/7579446453191855570?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SharingTechnologies/~3/r762uciQV3M/javaone-conference.html" title="JavaOne Conference" /><author><name>Dom Derrien</name><uri>https://plus.google.com/100819130946985751629</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-CAiJ6LOHBWE/AAAAAAAAAAI/AAAAAAAAErE/twENdW648fs/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://1.bp.blogspot.com/_VZZAFHl2_Og/SjAPUre00fI/AAAAAAAACto/DcNkqJVwssc/s72-c/dom-at-javaone-with-duke-2.png" height="72" width="72" /><thr:total>2</thr:total><feedburner:origLink>http://domderrien.blogspot.com/2009/06/javaone-conference.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D0cNQn0zfCp7ImA9WxBTGE4.&quot;"><id>tag:blogger.com,1999:blog-168828253523263225.post-2566717152095533715</id><published>2009-06-10T15:08:00.000-04:00</published><updated>2009-12-14T18:58:13.384-05:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-12-14T18:58:13.384-05:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Technology" /><title>Internationalization of GAE applications</title><content type="html">&lt;span style="font-size: large;"&gt;Costs of badly planned internationalization&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
In my experience, internationalizing an application is very expensive if it is not planned upfront.&lt;br /&gt;
&lt;br /&gt;
The first source of costs is due to developers who are used to lazily hard-coding labels. Extracting them at the end of the development process is always error prone because developers may have done assumptions on exact labels. Good regression test suites can help detecting such situations but they cannot avoid the cost of the required fix and its corresponding test runs. With late label extraction, developers without enough context tend to add extra dictionary entries. With the label extraction near the release milestone, developers in a rush and without the initial context tend also to produce non documented dictionaries—that is limit their re-usability and increase the difficulty of possible defect fixings.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="codeSnippetTitle"&gt;Non localized Java code&lt;/div&gt;&lt;pre class="prettyprint"&gt;protected String formulatePageIndex(int index, int total) {
    String out = "Page " + index;
    if(0 &amp;lt; total) {
        out += " of " + total;
    }
    return out;
}&lt;/pre&gt;&lt;br /&gt;
In the example above, I have seen the extraction of the labels &lt;code&gt;Page&lt;/code&gt; and &lt;code&gt;of&lt;/code&gt; instead of &lt;code&gt;Page %0&lt;/code&gt; and &lt;code&gt;Page %0 of %1&lt;/code&gt;. The quick extraction leads to the impossibility to invert the two arguments! Think about the people naming conventions: in many countries, the last name is displayed before the first name, in others the first name precedes the last name (&lt;code&gt;%last %first&lt;/code&gt; compared to &lt;code&gt;%first %last&lt;/code&gt;), and in Japan both names are printed without separator in between (&lt;code&gt;%last%first&lt;/code&gt;).&lt;br /&gt;
&lt;br /&gt;
&lt;div class="codeSnippetTitle"&gt;Localized Java code&lt;/div&gt;&lt;pre class="prettyprint"&gt;protected String formulatePageIndex(int index, int total) {
    // Get the resource bundle already initialized for the correct locale
    ResourceBundle rb = &lt;i&gt;getCurrentResourceBundle();&lt;/i&gt;
    // Prepare values
    Object[] values = Object[] {Integer.valueOf(index), Integer.valueOf(total)};
    // Get the right localized label
    String label = rb.getString("PageIndexLabel_Page");
    if(0 &amp;lt; total) {
        label = rb.getString("PageIndexLabel_PageOf");
    }
    // Return the localized label with injected values
    return Message.format(label, values);
}&lt;/pre&gt;&lt;br /&gt;
The second source of costs is due to the missed opportunities of deploying localized builds early in the development process. In Agile environments, we expect to get runnable builds on regular basis (at the end of each sprints, each 4 to 6 weeks, for example). And these fool-proof builds can be demoed to customers to get early feedback. If the development organization can work with translators iteratively, there are a lot of chance to detect localization defects while their fixing cost is not too high. In the past, I have seen product developments hugely hit when bi-directional languages (like Arabic and Hebrew) had been introduced...&lt;br /&gt;
&lt;br /&gt;
&lt;span style="font-size: large;"&gt;Different aspects of the internationalization&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
Internationalization (i18n) [1] has two aspects:  &lt;br /&gt;
&lt;ul&gt;&lt;li&gt;The translation of the labels;&lt;/li&gt;
&lt;li&gt;The localization (l10n) of these labels.&lt;/li&gt;
&lt;/ul&gt;The localization takes into account the language and the country, sometimes with variants in a country. For example, the Spanish language spoken 19 identified countries. In Mexico (&lt;code&gt;ES_MX&lt;/code&gt;), the language is slightly different from the one generally spoken in Spain (&lt;code&gt;ES&lt;/code&gt; or &lt;code&gt;ES_ES&lt;/code&gt;). In Spain, there are many regional languages like the Catalan (&lt;code&gt;CA_ES&lt;/code&gt;).  The different locales are normalized by the Unicode consortium (ISO-639 and ISO-3166). Codes are composed of a sequence of two letters for the language plus two letters for the country plus two letters for the region. If letters are missing after the language, most of programming languages fallback on common defaults.&lt;br /&gt;
&lt;br /&gt;
In order to ease application localizations, Unicode references a Common Locale Data Repository (CLDR) [2]. This repository is used and updated by many companies like IBM, Sun, Microsoft, Oracle, etc. The repository describes rules on how to:  &lt;br /&gt;
&lt;ul&gt;&lt;li&gt;Localize currencies;&lt;/li&gt;
&lt;li&gt;Localize metrics (distance, speed, temperature, etc.);&lt;/li&gt;
&lt;li&gt;Localize dates and calendars.&lt;/li&gt;
&lt;/ul&gt;As of today, I think only timezone definitions are still not centrally managed... This is especially bad because conversions between Universal Time (UTC) dates and local dates are operating system dependent (Sun Solaris have small differences with Microsoft Windows, for example).  Many tools use the Unicode CDLR information. For example, each release of the Dojo toolkit use its information to provide the Calendar widget for 27 locales [3].&lt;br /&gt;
&lt;br /&gt;
&lt;span style="font-size: large;"&gt;Internationalization with different programming languages&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
Almost all programming languages have ways to facilitate application globalization. Java provide resource bundles (&lt;code&gt;*.properties&lt;/code&gt; file), Microsoft .Net has resource files (&lt;code&gt;.rc&lt;/code&gt; files), Python has dictionaries, etc. If JavaScript lacks of native support for globalization, some libraries offer various support. To my knowledge, Dojo toolkit is the first providing a full support.&lt;br /&gt;
If developing an application on Google App Engine infrastructure can be done with only one programming language, Python or Java as this time of writing, it is highly possible that developers will use some JavaScript libraries to speed up their development. This is without counting the delivery of a similar program front-end as a native application (made with Adobe AIR, Microsoft .Net, C/C++, Groovy, etc.).&lt;br /&gt;
&lt;br /&gt;
In different situations, I have seen developers moving manually label definitions from one environment to another one. Sometimes, definitions were left over, cluttering the system. In Agile environments, developers should focus on the requirements for the current sprint, leaving some tuning for later sprints. For example, at one point during the development, some labels defined in a JavaScript bundle might be moved to a Java bundle because the localization will be done server-side into a JSP file.&lt;br /&gt;
&lt;br /&gt;
My solution is to put all labels in one localized central repository. The dispatch among the different programming languages is done at build time. When I looked for this repository format, my solution was selected against the following criteria:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;Easily editable;&lt;/li&gt;
&lt;li&gt;Has a standard format;&lt;/li&gt;
&lt;li&gt;Usable by static validation processes;&lt;/li&gt;
&lt;li&gt;Has excellent re-usability factors;&lt;/li&gt;
&lt;li&gt;Easily extensible to new programming languages.&lt;/li&gt;
&lt;/ul&gt;I chose the &lt;code&gt;TMX&lt;/code&gt; format (TMX for Translation Memory eXchange [4]). This is an XML based format (good for edition, extensibility, and use by static validation tools) which has been defined to allow translation memory export/import between different translation tools like DejaVu. The &lt;code&gt;XlDiff&lt;/code&gt; format would have been another good candidate.&lt;br /&gt;
&lt;br /&gt;
The following table illustrates the flow of interactions between the different actors in a development team. This sequence diagram shows that, once the developers have delivered a first TMX file, testers and translators can work independently to push tested and localized builds to the customer. As explained later, if developers tune the TMX entries without updating the labels themselves, translators and testers (at least from the l10n point-of-view) can stay out of the loop—only steps [1, 7, 8] are replayed.&lt;br /&gt;
&lt;br /&gt;
&lt;center class="sequenceDiagram"&gt;&lt;table border="0" cellmargin="0" cellspacing="0"&gt;&lt;caption&gt;Simplified view of the overall interaction flow &lt;/caption&gt; &lt;thead&gt;
&lt;tr&gt; &lt;th&gt;Developers&lt;/th&gt; &lt;th&gt;Testers&lt;/th&gt; &lt;th&gt;Translators&lt;/th&gt; &lt;th&gt;Build process&lt;/th&gt; &lt;th&gt;End-users&lt;/th&gt; &lt;/tr&gt;
&lt;/thead&gt; &lt;tbody&gt;
&lt;tr&gt; &lt;td&gt;1. Write labels in one language into the TMX file. These labels are extracted from design documents.&lt;/td&gt; &lt;td&gt;&lt;/td&gt; &lt;td&gt;&lt;/td&gt; &lt;td&gt;&lt;/td&gt; &lt;td&gt;&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;&lt;/td&gt; &lt;td&gt;&lt;/td&gt; &lt;td&gt;&lt;/td&gt; &lt;td&gt;2. Generate the application with for one locale.&lt;/td&gt; &lt;td&gt;&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;&lt;/td&gt; &lt;td&gt;3. Produce a generic bundle to identify non extracted labels.&lt;/td&gt; &lt;td&gt;&lt;/td&gt; &lt;td&gt;&lt;/td&gt; &lt;td&gt;&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;&lt;/td&gt; &lt;td&gt;&lt;/td&gt; &lt;td&gt;&lt;/td&gt; &lt;td&gt;4. Generate the application with for two locales.&lt;/td&gt; &lt;td&gt;&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;&lt;/td&gt; &lt;td&gt;&lt;/td&gt; &lt;td&gt;&lt;/td&gt; &lt;td&gt;&lt;/td&gt; &lt;td&gt;5. Use the application in one locale (switching to the &lt;code&gt;test&lt;/code&gt; language is hidden).&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;&lt;/td&gt; &lt;td&gt;&lt;/td&gt; &lt;td&gt;6. Use the initial TMX to produce &lt;code&gt;n&lt;/code&gt; localized TMXs.&lt;/td&gt; &lt;td&gt;&lt;/td&gt; &lt;td&gt;&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;&lt;/td&gt; &lt;td&gt;&lt;/td&gt; &lt;td&gt;&lt;/td&gt; &lt;td&gt;7. Generate the application with for &lt;code&gt;2 + n&lt;/code&gt; locales.&lt;/td&gt; &lt;td&gt;&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;&lt;/td&gt; &lt;td&gt;&lt;/td&gt; &lt;td&gt;&lt;/td&gt; &lt;td&gt;&lt;/td&gt; &lt;td&gt;8. Can use the application in &lt;code&gt;1 + n&lt;/code&gt; locales.&lt;/td&gt; &lt;/tr&gt;
&lt;/tbody&gt; &lt;/table&gt;&lt;/center&gt;&lt;br /&gt;
&lt;br /&gt;
The following code snippet shows  how an entry into the base TMX file is defined.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="codeSnippetTitle"&gt;Snippet of a translation unit definition for a TMX formatted file&lt;/div&gt;&lt;pre class="prettyprint"&gt;&amp;lt;tu tuid="&lt;span style="color: red;"&gt;entry identifier&lt;/span&gt;" datatype="Text"&amp;gt;
 &amp;lt;tuv xml:lang="locale identifier"&amp;gt;
  &amp;lt;seg&amp;gt;localized content&amp;lt;/seg&amp;gt;
 &amp;lt;/tuv&amp;gt;
 &amp;lt;note&amp;gt;contextual information on the entry and relations with other entries&amp;lt;/note&amp;gt;
 &amp;lt;prop type="x-tier"&amp;gt;dojotk&amp;lt;/prop&amp;gt;
 &amp;lt;prop type="x-tier"&amp;gt;javarb&amp;lt;/prop&amp;gt;
&amp;lt;/tu&amp;gt;&lt;/pre&gt;&lt;br /&gt;
The key features of the TMX format are:  &lt;br /&gt;
&lt;ul&gt;&lt;li&gt;The format can be validated with an external XSD (XML Schema Description);&lt;/li&gt;
&lt;li&gt;One entry (&lt;code&gt;tu&lt;/code&gt;: translation unit) can contain many localized contents (&lt;code&gt;tuv&lt;/code&gt;: translation unit value);&lt;/li&gt;
&lt;li&gt;Developers have a normalized placeholder (&lt;code&gt;&amp;lt;note/&amp;gt;&lt;/code&gt;) to register contextual information;&lt;/li&gt;
&lt;li&gt;Extensions are used by the build process to target the type of resource bundle to receive the localized label.&lt;/li&gt;
&lt;/ul&gt;With such an approach, I have seen a drastic reduction of translation mistakes, especially thanks to the &lt;code&gt;&amp;lt;note.&amp;gt;&lt;/code&gt; tag. Sometimes, graphical elements contain inter-related labels that cannot be grouped under a generic entity. The following set of elements illustrates the situation. The TMX approach saves translators headaches because they are simply informed about the relation between four entities.  &lt;br /&gt;
&lt;br /&gt;
&lt;center&gt; &lt;label for="testInputField"&gt;Search:&lt;/label&gt; &lt;input id="testInputField" style="color: lightgrey;" type="text" value="keyword" /&gt; &lt;button&gt;Go&lt;/button&gt; &lt;button&gt;Advanced&lt;/button&gt; &lt;/center&gt;&lt;br /&gt;
The conversion from the TMX to the various resource bundles is done by an XSL-Transform. With the continuous integration handled by &lt;code&gt;ant&lt;/code&gt;, the corresponding task generates the output after having appended the XSLT file coordinates to a copy of the TMX file and after asked for the transformation with the corresponding &lt;code&gt;&amp;lt;xsl/&amp;gt; ant&lt;/code&gt; task. Depending on the machine performance, depending on the TMX file size, I found that the process can be time consuming. If this is your case too, I suggest you write your own little Java program to handle it. You can also use &lt;a href="http://github.com/DomDerrien/two-tiers-utils/tree/master"&gt;mine&lt;/a&gt; ;)&lt;br /&gt;
&lt;br /&gt;
&lt;div class="codeSnippetTitle"&gt;Stylesheet transforming label definitions for the Dojo toolkit&lt;/div&gt;&lt;pre class="prettyprint"&gt;&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;
&amp;lt;xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&amp;gt;
 &amp;lt;xsl:output method="text" /&amp;gt;
 &amp;lt;xsl:template match="/tmx/body"&amp;gt;
  {
  &amp;lt;xsl:for-each select="tu"&amp;gt;
    &amp;lt;xsl:for-each select="prop"&amp;gt;
      &amp;lt;xsl:if test="@type='x-tier' and .='dojotk'"&amp;gt;
        "&amp;lt;xsl:value-of select="../@tuid" /&amp;gt;":"&amp;lt;xsl:value-of select="../tuv/seg" /&amp;gt;",
      &amp;lt;/xsl:if&amp;gt;
    &amp;lt;/xsl:for-each&amp;gt;
  &amp;lt;/xsl:for-each&amp;gt;
  "build", "@rwa.stageId@"}
 &amp;lt;/xsl:template&amp;gt;
&amp;lt;/xsl:stylesheet&amp;gt;&lt;/pre&gt;&lt;br /&gt;
&lt;div class="codeSnippetTitle"&gt;Use of the stylesheet above to convert Dojo toolkit related definitions from the TMX files by an &lt;code&gt;Ant&lt;/code&gt; task [5]&lt;/div&gt;&lt;pre class="prettyprint"&gt;&amp;lt;target name="convert-tmx"&amp;gt;
  &amp;lt;style
    basedir="src/resources"
    destdir="src/resources"
    extension=".js"
    includes="*.tmx"
    style="src/resources/tmx2dojotkxsl"
  /&amp;gt;
&amp;lt;/target&amp;gt;&lt;/pre&gt;&lt;br /&gt;
A+, Dom&lt;br /&gt;
--&lt;br /&gt;
Sources:  &lt;br /&gt;
&lt;ol&gt;&lt;li&gt;Introduction to &lt;a href="http://en.wikipedia.org/wiki/Internationalization_and_localization"&gt;internationalization and localization&lt;/a&gt; on Wikipedia.&lt;/li&gt;
&lt;li&gt;Unicode &lt;a href="http://cldr.unicode.org/"&gt;Common Locale Data Repository&lt;/a&gt; (CLDR).&lt;/li&gt;
&lt;li&gt;Dojo toolkit API: &lt;a href="http://api.dojotoolkit.org/jsdoc/1.3/dojo.cldr"&gt;dojo.cldr&lt;/a&gt;, &lt;a href="http://api.dojotoolkit.org/jsdoc/1.3/dojo.i18n"&gt;dojo.i18n&lt;/a&gt;, &lt;i&gt;private&lt;/i&gt; &lt;a href="http://api.dojotoolkit.org/jsdoc/1.3/dijit._Calendar"&gt;dijit._Calendar&lt;/a&gt;, &lt;a href="http://api.dojotoolkit.org/jsdoc/1.3/dojox.widget.Calendar"&gt;dojox.widget.Calendar&lt;/a&gt;, and &lt;a href="http://api.dojotoolkit.org/jsdoc/1.3/dojox.widget.DailyCalendar"&gt;dojox.widget.DailyCalendar&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Definition of the &lt;a href="http://www.lisa.org/Translation-Memory-e.34.0.html"&gt;Translation Memory eXchange&lt;/a&gt; (TMX) format.&lt;/li&gt;
&lt;li&gt;Reference of the &lt;a href="http://ant.apache.org/manual/CoreTasks/style.html"&gt;XSLT/tyle&lt;/a&gt; task for Ant scripts.&lt;/li&gt;
&lt;/ol&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=mKw8K58qZJU:pHh06_z85QE:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=mKw8K58qZJU:pHh06_z85QE:4cEx4HpKnUU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?i=mKw8K58qZJU:pHh06_z85QE:4cEx4HpKnUU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SharingTechnologies?a=mKw8K58qZJU:pHh06_z85QE:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SharingTechnologies?i=mKw8K58qZJU:pHh06_z85QE:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SharingTechnologies/~4/mKw8K58qZJU" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://domderrien.blogspot.com/feeds/2566717152095533715/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://domderrien.blogspot.com/2009/06/internationalization-of-gae.html#comment-form" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/168828253523263225/posts/default/2566717152095533715?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/168828253523263225/posts/default/2566717152095533715?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SharingTechnologies/~3/mKw8K58qZJU/internationalization-of-gae.html" title="Internationalization of GAE applications" /><author><name>Dom Derrien</name><uri>https://plus.google.com/100819130946985751629</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-CAiJ6LOHBWE/AAAAAAAAAAI/AAAAAAAAErE/twENdW648fs/s512-c/photo.jpg" /></author><thr:total>2</thr:total><feedburner:origLink>http://domderrien.blogspot.com/2009/06/internationalization-of-gae.html</feedburner:origLink></entry></feed>
