<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' 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'><id>tag:blogger.com,1999:blog-168828253523263225</id><updated>2025-07-03T20:52:24.010-04:00</updated><category term="Technology"/><category term="Experience"/><category term="Community"/><category term="Entrepreneurship"/><title type='text'>Sharing Technologies</title><subtitle type='html'></subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://domderrien.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/168828253523263225/posts/default?redirect=false'/><link rel='alternate' type='text/html' href='http://domderrien.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.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'/><author><name>Dom Derrien</name><uri>http://www.blogger.com/profile/15197441189507302313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>61</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-168828253523263225.post-596868830072563407</id><published>2014-05-29T21:03:00.000-04:00</published><updated>2014-05-30T09:04:41.171-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Community"/><category scheme="http://www.blogger.com/atom/ns#" term="Experience"/><category scheme="http://www.blogger.com/atom/ns#" term="Technology"/><title type='text'>The JavaScript technology stack</title><content type='html'>&lt;div style=&quot;float: right; margin: 0 0 10px 10px;&quot;&gt;
&lt;img src=&quot;http://upload.wikimedia.org/wikipedia/commons/thumb/9/99/Unofficial_JavaScript_logo_2.svg/240px-Unofficial_JavaScript_logo_2.svg.png&quot; style=&quot;height: 100px; width: 100px;&quot; /&gt;&lt;/div&gt;
&lt;h1&gt;
Context&lt;/h1&gt;
I&#39;ve been developing with the JavaScript language since 1999. I was then involved in the development of an administrative console for clusters of calendar servers. Coming from an experience in Windows development with the MFC environment, I quickly implemented the MVC pattern in the browser with a set of frames: one &lt;code&gt;frame&lt;/code&gt; taking all the visible space to render the interface, another &lt;code&gt;frame&lt;/code&gt; kept hidden (with &lt;code&gt;height=&quot;0&quot;&lt;/code&gt;) for exchanging data with my service (implemented in C++ as a FastCGI module), and the &lt;code&gt;frameset&lt;/code&gt; to save the state.&lt;br /&gt;
Later, while working for IBM Rational, I had the chance to discover the Dojo Toolkit in its early days (v0.4). After that experience, even if I extended my expertise on mobile (mainly native Android and cross-platform with Unity), I continued to contribute to Web projects. However, I did not have a chance to start a project from scratch, always having to deal with legacy code and constrained schedules... until I joined &lt;a href=&quot;http://ubisoft.com/&quot;&gt;Ubisoft&lt;/a&gt; last February!&lt;br /&gt;
&lt;h1&gt;
The presentation layer and user&#39;s input handling&lt;/h1&gt;
Most of &quot;Web developers&quot; are more &lt;i&gt;hackers&lt;/i&gt; than &lt;i&gt;developers&lt;/i&gt;: they use PHP on the server to assemble HTML components, they use JavaScript to control the application behaviour in the browser, they control the interface from any place, and they deploy often without the safety net of automated tests... Their flexibility is a real asset for editorial teams but a nightmare for the quality assurance teams!&lt;br /&gt;
Here are my recommendations for the development of Web applications:&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;Use the server as little as possible when preparing the views, just to detect the browser user agent and the user&#39;s preferred language in order to deliver the adequate HTML template;&lt;/li&gt;
&lt;li&gt;Define all the presentation elements with HTML, CSS, and images. Don&#39;t use the JavaScript to create HTML fragments on the fly, only if it clones parts of the HTML template;&lt;/li&gt;
&lt;li&gt;Use the JavaScript to inject data and behaviour: switch from one section to another, submit a request to the server, display data and notifications, etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;float: left; margin: 0 10px 10px 0;&quot;&gt;
&lt;img src=&quot;http://upload.wikimedia.org/wikipedia/commons/thumb/6/61/HTML5_logo_and_wordmark.svg/200px-HTML5_logo_and_wordmark.svg.png&quot; style=&quot;height: 50px; width: 50px;&quot; /&gt;&lt;/div&gt;
The main advantage of letting the presentation coded with HTML, CSS, and images is that it can be delegated to designers. Developers can take the materials designers have produced with Dreamweaver, for example, and insert the identifiers required to connect the JavaScript handlers. Or they provided the initial skeleton instrumented with these identifiers, and designers iterate over them freely. There are so many tools to optimize the presentation layer:&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;HTML minifier, going up to drop &lt;a href=&quot;http://google-styleguide.googlecode.com/svn/trunk/htmlcssguide.xml?showone=Optional_tags#Optional_tags&quot;&gt;optional tags&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;CSS minifier and tools to detect unused rules, like &lt;a href=&quot;http://davidwalsh.name/uncss&quot;&gt;uncss&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;Image optimizer and sprite generators.&lt;/li&gt;
&lt;/ul&gt;
Designers can then focus on defining on the best interface regardless optimization. The only limitations: don&#39;t let them introduce or depend on any JavaScript library!&lt;br /&gt;
&lt;div style=&quot;float: right; margin: 0 0 10px 10px;&quot;&gt;
&lt;img src=&quot;http://upload.wikimedia.org/wikipedia/commons/thumb/f/f8/Dojo_toolkit_logo.svg/200px-Dojo_toolkit_logo.svg.png&quot; style=&quot;height: 50px; width: 100px;&quot; /&gt;&lt;/div&gt;
As for choosing a JavaScript library, I recommend to consider the following points:&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;A mechanism to define modules and dependencies. The best specification is &lt;a href=&quot;http://requirejs.org/docs/whyamd.html&quot;&gt;AMD&lt;/a&gt; (for Asynchronous Module Definition), implemented by RequireJS, Dojo, and jQuery, among others. Note that AngularJS has its own dependency injection mechanism.&lt;/li&gt;
&lt;li&gt;Until &lt;a href=&quot;http://wiki.ecmascript.org/doku.php?id=harmony:specification_drafts&quot;&gt;ES6&lt;/a&gt; (or ECMAScript 6) makes it standard, use a library that provide an implementation of &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise&quot;&gt;Promise&lt;/a&gt;, especially useful for its &lt;code&gt;all()&lt;/code&gt; method;&lt;/li&gt;
&lt;li&gt;A test framework that reports about the code coverage. IMHO, without the code coverage statistics, it&#39;s difficult to judge the quality of the tests, then determine our ability to detect regressions early...&lt;/li&gt;
&lt;li&gt;Even if you cannot rely on a dispatcher that detects user-agents and preferred languages, use the functionality of the &lt;a href=&quot;https://github.com/phiggins42/has.js/blob/master/has.js&quot;&gt;has.js&lt;/a&gt; library or equivalent to expose methods for test purposes. Coupled with a smart build system, the exposed methods will be hidden in production.&lt;/li&gt;
&lt;li&gt;Just minifying the JavaScript code is not sufficient. It&#39;s important to have the dead code removed (especially the one exposed just for test purposes). The &lt;a href=&quot;https://developers.google.com/closure/compiler/&quot;&gt;Google Closure&lt;/a&gt; compiler should be part of your tool set.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;
The data access layer&lt;/h1&gt;
In the last 10 years, my back-end services were always implemented in Java, sometimes with my &lt;a href=&quot;https://github.com/DomDerrien/twetailerJ/blob/master/src/java/twetailer/j2ee/BaseRestlet.java&quot;&gt;own REST compliant library&lt;/a&gt;, sometimes with other libraries like Spring, RestEasy, Guice, etc. Java is an easy language to develop with and, with all the available tooling, ramping up new developers is not difficult. Without counting services like Google App Engine which hosts low profile applications for free.&lt;br /&gt;
On the other end, Java is really verbose and not done for asynchronicity. It lacks also the support of &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Closures&quot;&gt;closures&lt;/a&gt;. And, as with many programming languages, nothing prevents you to use patterns to clarify the code behaviour.&lt;br /&gt;
&lt;div style=&quot;float: left; margin: 0 10px 10px 0;&quot;&gt;
&lt;img src=&quot;http://upload.wikimedia.org/wikipedia/commons/thumb/d/d9/Node.js_logo.svg/200px-Node.js_logo.svg.png&quot; style=&quot;height: 25px; width: 100px;&quot; /&gt;&lt;/div&gt;
At Ubisoft, I&#39;ve been given the opportunity to host the back-end services on &lt;a href=&quot;http://nodejs.org/&quot;&gt;Node.js&lt;/a&gt;. The major factor in favor to Node.js is its &lt;a href=&quot;https://draft.blogger.com/blogger.g?blogID=168828253523263225&quot;&gt;WebSocket&lt;/a&gt; support (not yet decided between &lt;a href=&quot;https://github.com/einaros/ws&quot;&gt;ws&lt;/a&gt; and &lt;a href=&quot;https://github.com/LearnBoost/engine.io&quot;&gt;engine.io&lt;/a&gt;). The second factor is related to the nature of the application: 99% of the transactions between the clients and the server are short lived. Operations that require long computations are handled by services based on Redis and Hadoop. And finally, &lt;a href=&quot;http://www.ebaytechblog.com/2013/05/17/how-we-built-ebays-first-node-js-application/#.U11AwXFdXY8&quot;&gt;Node.js scales&lt;/a&gt; well.&lt;br /&gt;
When I joined the project, the team had a small working environment made a-la Node.js style: no clear definition of dependencies, a lot of nested callbacks to handle asynchronous behaviours, all presentation built with Jade, no commonly adopted patterns to organize the code logic, no unit tests (obvious as nested callbacks are nearly impossible to test!). I then rebooted the project with a better approach for the separation of concerns:&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;AMD as the format to define modules, with the &lt;a href=&quot;http://dojotoolkit.org/documentation/tutorials/1.9/node/&quot;&gt;Dojo loader to bootstrap the application&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://expressjs.com/&quot;&gt;Express&lt;/a&gt; to handle the RESTful entry points&lt;/li&gt;
&lt;li&gt;A layered structure to process the requests:&lt;/li&gt;
&lt;ul&gt;
&lt;li&gt;Resource: one class per entity, to gather data from the input streams, and forwarding them to the service layer;&lt;/li&gt;
&lt;li&gt;Service: one class per entity, possibly communicating with other services when data should be aggregated for many entities;&lt;/li&gt;
&lt;li&gt;DAO: one class per entity, controlling data from the file system, from MongoDB, from MySQL or from another service over HTTP.&lt;/li&gt;
&lt;/ul&gt;
&lt;li&gt;A set of classes modelling each entities; I imposed this layer to serve two needs: 1) allow some restrictions of entities (attributes can be declared &lt;i&gt;mandatory&lt;/i&gt; or &lt;i&gt;read-only&lt;/i&gt;, or need to match a regular expression) and 2) support non destructive partial updates.&lt;/li&gt;
&lt;li&gt;Unit tests to cover 100% of the implemented logic.&lt;/li&gt;
&lt;/ul&gt;
At this stage, the most complex classes are the base classes for the MongoDB and the MySQL DAOs (I judge their complexity by the tests that requires 3 times more code). But with the help of Promises, the code is elegant and compact ;)&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot; style=&quot;padding: 0 10px;&quot;&gt;/**
 * Select the identified resources, or all resources if not filter nor range are specified
 *
 * @Param {Object} filters bag of key/value pairs to be used as a filter the resources to be returned
 * @Param {Range} range limit the number of results returned
 * @Param {Object} order bag of key/value pairs to be used to order the results
 * @Return a Promise with a list of resources as the parameter of the onSuccess method
 *
 * @Throw error with code 204-NO CONTENT if the selection is empty, as a parameter of the onFailure method of the promise
 */
select: function (filters, range, order) {
    return all([this._select(filters, range, order), this._count(filters)]).then(function (responses) {
        range.total = responses[1];
        responses[0].range = range;
        return responses[0];
    });
},

// Helper forwarding the SELECT request to the MySql connection
_select: function (filters, range, order) {
    var query = this.getSelectQuery(filters, range, order),
        ModelClass = this.ModelClass;

    return this._getConnection().then(function (connection) {
        var dfd = new Deferred();

        connection.query(query, function (err, rows) {
            connection.release();
            if (err) {
                _forwardError(dfd, 500, &#39;Query to DB failed:&#39; + query, err);
                return;
            }

            var idx, limit = rows.length,
                entities = [];
            if (limit === 0) {
                _forwardError(dfd, 204, &#39;No entity match the given criteria&#39;, &#39;Query with no result: &#39; + query);
                return;
            }
            for (idx = 0; idx &amp;lt; limit; idx += 1) {
                entities.push(new ModelClass(rows[idx]));
            }
            dfd.resolve(entities);
        });

        return dfd.promise;
    });
},

// Helper forwarding the COUNT request to the MySql connection
_count: function (filters) {
    var query = this.getCountQuery(filters);

    return this._getConnection().then(function (connection) {
        var dfd = new Deferred();

        connection.query(query, function (err, rows) {
            connection.release();
            if (err) {
                _forwardError(dfd, 500, &#39;Query to DB failed:&#39; + query, err);
                return;
            }

            dfd.resolve(rows[0].total);
        });

        return dfd.promise;
    });
},&lt;/pre&gt;
&lt;div style=&quot;font-style: italic; text-align: center;&quot;&gt;
Code sample: the &lt;b&gt;select()&lt;/b&gt; method of the MySqlDao, and its two direct helpers.&lt;/div&gt;
Few comments on the code illustrated above:&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;Each helper wraps the asynchronicity with callbacks of the MySQL plugin into Promise (&lt;i&gt;via&lt;/i&gt; the &lt;code&gt;Deferred&lt;/code&gt; class);&lt;/li&gt;
&lt;li&gt;The main entry point relies on the Promise &lt;code&gt;all()&lt;/code&gt; method to convey the request result only when the responses from the two helpers are ready;&lt;/li&gt;
&lt;li&gt;The method &lt;code&gt;_getConnection()&lt;/code&gt; returns a Promise which is resolved with a connection from the MySQL pool (i.e. from &lt;code&gt;mysql.createPool().getConnection()&lt;/code&gt;);&lt;/li&gt;
&lt;li&gt;The method &lt;code&gt;_forwardError()&lt;/code&gt; is a simple helper logging the error and rejecting the Promise; at the highest level, express use the error code as the status for the HTTP response;&lt;/li&gt;
&lt;li&gt;The method &lt;code&gt;_select()&lt;/code&gt; converts each results into an instance of the specified model, providing transparently a support for field validation and partial updates;&lt;/li&gt;
&lt;li&gt;With the use of given &lt;code&gt;ModelClass&lt;/code&gt;, this &lt;code&gt;MySqlDao&lt;/code&gt; class acts like a Java and C# Generics.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;
The persistence layer&lt;/h1&gt;
&lt;div style=&quot;float: right; margin: 0 0 10px 10px;&quot;&gt;
&lt;img src=&quot;http://upload.wikimedia.org/wikipedia/en/thumb/e/eb/MongoDB_Logo.png/320px-MongoDB_Logo.png&quot; style=&quot;height: 25px; width: 75px;&quot; /&gt;&lt;/div&gt;
I&#39;m not a DBA and will never be one. Regarding the piece of code above, one of my colleague proposed to update the query for the &lt;code&gt;SELECT&lt;/code&gt; in order to get the record count at the same time. It would be repeated with each rows but it would save a round trip. At the end, I decided to keep the code as-is because it&#39;s consistent with the code of the MongoDB DAO. We&#39;ll measure the impact of the improvement later when the entire application is ready.&lt;br /&gt;
If I&#39;m not an expert, I always had to deal with databases: Oracle 10g, IBM DB2, MySQL, PostgreSQL for the relational databases and Google datastore and MongoDB for the Non-SQL ones. My current project relies on MongoDB where player&#39;s session information are stored and on MySQL which stores static information. I like working with MongoDB because of its ability to work with documents instead of rows of normalized values. It is very flexible, well aligned with entities used client-side. And MongoDB is highly scalable.&lt;br /&gt;
Once the DAOs have been correctly defined, implemented, and tested, dealing with any database at the service level is transparent. Developers can focus on the business logic while DBAs optimize the database settings and deployments.&lt;br /&gt;
&lt;h1&gt;
The continuous integration&lt;/h1&gt;
Java is an easy language to deal with. First, they are a lot of very good IDE, like &lt;a href=&quot;http://eclipse.org/&quot;&gt;Eclipse&lt;/a&gt; and &lt;a href=&quot;http://www.jetbrains.com/idea/&quot;&gt;IntelliJ&lt;/a&gt;. Then, they are plenty of test tools to help verifying the code behaves as expected&amp;amp;em;my favorites ones are JUnit, &lt;a href=&quot;https://code.google.com/p/mockito/&quot;&gt;Mockito&lt;/a&gt;, and &lt;a href=&quot;http://cobertura.github.io/cobertura/&quot;&gt;Cobertura&lt;/a&gt;. And finally Java applications can be remotely debugged, profiled, and even obfuscated.&lt;br /&gt;
&lt;div style=&quot;float: left; margin: 0 10px 10px 0;&quot;&gt;
&lt;img src=&quot;http://theintern.io/images/favicon.png&quot; style=&quot;height: 50px; width: 50px;&quot; /&gt;&lt;/div&gt;
In the past, I controlled the quality of my JavaScript code with JSUnit and JSCoverage. Now I recommend &lt;a href=&quot;http://theintern.io/&quot;&gt;Intern&lt;/a&gt; to run unit tests efficiently with Node.js and functional tests with Selenium. I really like Intern because it&#39;s AMD compliant, it produces coverage reports, and it mainly do organize my tests as I want! A small run of around 1,000 unit tests by Node.js takes around 5 seconds. The functional test suite with 20 green-path scenarios takes 20 seconds to run in Firefox and Chrome in parallel.&lt;br /&gt;
Here is a small nonetheless important point about Intern flexibility:&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;I want my tests to work on modules totally isolated one from another. To have them isolated, I inject mocks to replace the injected dependencies in the module to be tested.&lt;/li&gt;
&lt;li&gt;Intern suggested way requires:&lt;/li&gt;
&lt;ul&gt;
&lt;li&gt;Removing the module to be tested from the AMD cache, with &lt;code&gt;require.undef([mid]);&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;Replacing references of dependent classes by mock ones, with &lt;code&gt;require({map: { &#39;*&#39;: { [normal-mid]: [mock-mid] } });&lt;/code&gt;; &lt;/li&gt;
&lt;li&gt;Reloading the module to be tested that will now use the mock classes instead of the original ones;&lt;/li&gt;
&lt;li&gt;Calling and verifying the behaviour of the module.&lt;/li&gt;
&lt;/ul&gt;
&lt;li&gt;Currently, I prefer instrumenting the modules with the help of &lt;code&gt;dojo/has&lt;/code&gt; to be able to access private methods and replace on-the-fly dependent classes with mock ones. Each test injects the required mocks, and the &lt;code&gt;afterEach&lt;/code&gt; test method restore all original dependent classes.&lt;/li&gt;
&lt;li&gt;My Intern configuration file contains the definition used by &lt;code&gt;dojo/has&lt;/code&gt; to expose test-friendly methods, while my &lt;code&gt;index.html&lt;/code&gt; and &lt;code&gt;my app.profile.js&lt;/code&gt; (used by the Dojo build system) leave it &lt;code&gt;undefined&lt;/code&gt;. So these methods are not accessible from the browser, and not even defined in the built code.&lt;/li&gt;
&lt;li&gt;With the help of &lt;a href=&quot;http://stackoverflow.com/questions/22572008/how-to-mock-a-node-js-module-loaded-with-dojo-node/22572426#22572426&quot;&gt;Mockery&lt;/a&gt;, I can test everything, up to the classes controlling the access to MySQL, as illustrated above.&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;float: right; margin: 0 0 10px 10px;&quot;&gt;
&lt;img src=&quot;http://gruntjs.com/img/grunt-logo.svg&quot; style=&quot;height: 60px; width: 50px;&quot; /&gt;&lt;/div&gt;
In the Java world, &lt;code&gt;maven&lt;/code&gt; has replaced &lt;code&gt;ant&lt;/code&gt; as the configuration tool of choice. In the JavaScript world, developers have to rely on many tools:&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;Node.js, &lt;a href=&quot;https://www.npmjs.org/&quot;&gt;npm&lt;/a&gt;, and &lt;a href=&quot;http://bower.io/&quot;&gt;bower&lt;/a&gt; to manage the libraries required server-side (npm) and client-side (bower);&lt;/li&gt;
&lt;li&gt;Grunt to run administrative tasks like: building CSS file from Stylus ones, running the tests, compiling the code, deploying the built code, etc.&lt;/li&gt;
&lt;li&gt;Intern produces test and coverage reports for Jenkins and TeamCity CI tools.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;
The development environment&lt;/h1&gt;
&lt;div style=&quot;float: right; margin: 0 0 10px 10px;&quot;&gt;
&lt;img src=&quot;http://upload.wikimedia.org/wikipedia/commons/thumb/4/4c/Brackets_Icon.svg/200px-Brackets_Icon.svg.png&quot; style=&quot;height: 50px; width: 50px;&quot; /&gt;&lt;/div&gt;
My editor of choice is &lt;a href=&quot;http://brackets.io/&quot;&gt;Brackets&lt;/a&gt;, by Adobe. It&#39;s a very powerful tool, still being actively developed, so continuously better than before. It has a lot of &lt;a href=&quot;https://brackets-registry.aboutweb.com/&quot;&gt;extensions&lt;/a&gt;, like an &lt;a href=&quot;https://github.com/MiguelCastillo/Brackets-InteractiveLinter&quot;&gt;interactive linter&lt;/a&gt; and the &lt;a href=&quot;https://github.com/adobe-research/theseus&quot;&gt;Theseus debugger&lt;/a&gt;. And debugging and fixing extensions to fit your needs is very easy.&lt;br /&gt;
&lt;div style=&quot;float: left; margin: 0 10px 10px 0;&quot;&gt;
&lt;img src=&quot;http://upload.wikimedia.org/wikipedia/commons/thumb/8/87/Vagrant.png/197px-Vagrant.png&quot; style=&quot;height: 60px; width: 50px;&quot; /&gt;&lt;/div&gt;
MongoDB consumes as much as memory as possible. To avoid cluttering your development environment while keeping your database on hand, I suggest you use &lt;a href=&quot;http://vagrantup.com/&quot;&gt;vagrant&lt;/a&gt; to configure a virtual machine where MongoDB and your Node.js server run in isolation. Coupling a &lt;code&gt;Vagrantfile&lt;/code&gt; with a provisioning script allows all your collaborators to benefit from the same configuration.&lt;br /&gt;
When it&#39;s time to push on production environments, check if there isn&#39;t a Grunt extension that can help you sending the compiled code via FTP or SSH on a remote machine or on Amazon Web Service or on Google Cloud Engine.&lt;br /&gt;
I hope this helps, Dom</content><link rel='replies' type='application/atom+xml' href='http://domderrien.blogspot.com/feeds/596868830072563407/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://domderrien.blogspot.com/2014/05/the-javascript-technology-stack.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/168828253523263225/posts/default/596868830072563407'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/168828253523263225/posts/default/596868830072563407'/><link rel='alternate' type='text/html' href='http://domderrien.blogspot.com/2014/05/the-javascript-technology-stack.html' title='The JavaScript technology stack'/><author><name>Dom Derrien</name><uri>http://www.blogger.com/profile/15197441189507302313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total><georss:featurename>Montreal, QC, Canada</georss:featurename><georss:point>45.5086699 -73.553992499999993</georss:point><georss:box>45.1529059 -74.1994395 45.8644339 -72.908545499999988</georss:box></entry><entry><id>tag:blogger.com,1999:blog-168828253523263225.post-6403336553408845358</id><published>2014-03-23T17:33:00.001-04:00</published><updated>2014-04-27T15:13:57.054-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Community"/><category scheme="http://www.blogger.com/atom/ns#" term="Technology"/><title type='text'>Review of the book: Getting Started with Grunt</title><content type='html'>&lt;p style=&quot;border:1px solid #333; round-corner: 4px; padding: 4px;&quot;&gt;
&lt;b&gt;Disclaimer:&lt;/b&gt; I&#39;ve been offered a electronic copy of the book in exchange to this review. I have no contractual relationship with &lt;a href=&quot;http://www.packtpub.com/&quot;&gt;Packt Publishing&lt;/a&gt; nor the book author &lt;a href=&quot;https://twitter.com/jpillora&quot;&gt;Jamie Pillora&lt;/a&gt;.
&lt;/p&gt;
&lt;h1&gt;Context&lt;/h1&gt;
&lt;p&gt;
Since early 2000, I&#39;ve been on the Java technology stack for implementing three-tier applications (Web or mobile clients, J2EE Web server, Relational databases). I considered the move from make to &lt;a href=&quot;http://ant.apache.org/&quot;&gt;ant&lt;/a&gt; as a real improvement. My &lt;a href=&quot;https://github.com/DomDerrien/two-tiers-utils/blob/master/build/build.xml&quot;&gt;first public project&lt;/a&gt; on GitHub are still relying on ant.&lt;/p&gt;
&lt;p&gt;
&lt;img src=&quot;http://www.packtpub.com/sites/default/files/0628OS_Getting%20Started.jpg&quot; alt=&quot;Getting Started with Grunt: The JavaScript Task Runner&quot; title=&quot;Getting Started with Grunt: The JavaScript Task Runner&quot; style=&quot;float:right; border:2px solid #333;round-corner:4px;&quot;&gt;I was so happy with ant that I switched to &lt;a href=&quot;http://maven.apache.org/&quot;&gt;maven&lt;/a&gt; only around 2010. To me, the main benefit of maven is its management of the dependencies: from the maven repository, I can &lt;a href=&quot;https://github.com/DomDerrien/custom-portal/blob/master/pom.xml&quot;&gt;depend simply&lt;/a&gt; on many Java libraries like &lt;a href=&quot;https://developers.google.com/appengine/docs/java/&quot;&gt;Google App Engine&lt;/a&gt;, &lt;a href=&quot;http://www.jboss.org/resteasy&quot;&gt;RestEasy&lt;/a&gt; and &lt;a href=&quot;http://code.google.com/p/mockito/&quot;&gt;Mockito&lt;/a&gt;. I can also get resources for the Web client, like the &lt;a href=&quot;http://dojotoolkit.org/&quot;&gt;Dojo Toolkit&lt;/a&gt;. Over the years, I wrote few plugins to cover the same features I used to get from ant.
&lt;/p&gt;
&lt;p&gt;
Almost one, I heard about &lt;a href=&quot;http://gruntjs.com/&quot;&gt;Grunt&lt;/a&gt;, the JavaScript build tool. I liked that many plugins were also provided (the officially supported plugins with a name prefixed by &lt;code&gt;grunt-contrib-&lt;/code&gt;). Because I was happy with my maven environment, I then started to &lt;a href=&quot;https://github.com/DomDerrien/custom-portal/blob/master/Gruntfile.js&quot;&gt;use Grunt only for Stylus&lt;/a&gt; and the &lt;a href=&quot;https://github.com/gruntjs/grunt-contrib-watch&quot;&gt;grunt-contrib-watch&lt;/a&gt;.
&lt;/p&gt;
&lt;p&gt;
Recently, I joined the game company &lt;a href=&quot;http://ubisoft.com/&quot;&gt;Ubisoft&lt;/a&gt; to work on a project involving &lt;a href=&quot;http://nodejs.org/&quot;&gt;Node.js&lt;/a&gt; server-side. My switch to Grunt was immediate ;) I love the extensive set of plugins and the easiness of writing my own ones if needed. The pair &lt;a href=&quot;https://www.npmjs.org/&quot;&gt;npm&lt;/a&gt; / Grunt is rock solid: npm which manages the dependencies and Grunt runs the tasks.
&lt;/p&gt;
&lt;p&gt;
When Packt Publishing contacted me for the review of the book &lt;b&gt;&lt;a href=&quot;http://www.packtpub.com/getting-started-with-grunt-the-javascript-task-runner/book&quot;&gt;Getting Started with Grunt&lt;/a&gt;&lt;/b&gt;, I saw an occasion to consolidate my knowledge on Grunt, to compare what I know with someone else experience. And reviewing a published book is way more difficult and lengthy than working on drafts, a work I did one for the book &lt;a href=&quot;http://domderrien.blogspot.ca/2010/12/reviewed-book-google-app-engine-java.html&quot;&gt;Google App Engine and GWT&lt;/a&gt; ;)
&lt;/p&gt;
&lt;h1&gt;Book content&lt;/h1&gt;
&lt;p&gt;
As revealed by the title, the book targets new users of Grunt, or people evaluating the technology. If I compare to my own experience, Grunt power users won&#39;t learn much by reading the book.
&lt;/p&gt;
&lt;p&gt;
If the book does not explain how Grunt works, it however describes a lot of topics extensively:
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;i&gt;transpiling&lt;/i&gt; aspect: from CoffeeScript, from Stylus/Sass/Less;&lt;/li&gt;
&lt;li&gt;The code processing: verification with JSLint and JSHint, minification with Uglify;&lt;/li&gt;
&lt;li&gt;The code testing: with Mocha and PhantomJS; &lt;/li&gt;
&lt;li&gt;The deployment: assembling many files in one, sending it over FTP, publishing on Amazon S3;&lt;/li&gt;
&lt;li&gt;The customization: writing and publishing plugins.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Opinion&lt;/h1&gt;
&lt;p&gt;
This is book really targets new Grunt adopter. It helps understanding the basic tasks. It also describes how to setup a build environment for Web clients. I think exposing more grunt plugins, like &lt;code&gt;grunt-exec&lt;/code&gt; to run non JavaScript tool, could have set the book as a reference book...
&lt;/p&gt;
&lt;p&gt;
The author decided to focus on one type of application: a Web client based on &lt;a href=&quot;http://jade-lang.com/&quot;&gt;Jade&lt;/a&gt;, the HTML template engine. I think describing tasks for the application logic on a Node.js server, like &lt;code&gt;grunt-express&lt;/code&gt; or &lt;code&gt;grunt-nodemon&lt;/code&gt;, would have interest a wider audience. Grunt is a really versatile tool.
&lt;/p&gt;
&lt;p&gt;
I hope it helps,&lt;br /&gt;
A+, Dom
&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://domderrien.blogspot.com/feeds/6403336553408845358/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://domderrien.blogspot.com/2014/03/review-of-book-getting-started-with.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/168828253523263225/posts/default/6403336553408845358'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/168828253523263225/posts/default/6403336553408845358'/><link rel='alternate' type='text/html' href='http://domderrien.blogspot.com/2014/03/review-of-book-getting-started-with.html' title='Review of the book: Getting Started with Grunt'/><author><name>Dom Derrien</name><uri>http://www.blogger.com/profile/15197441189507302313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-168828253523263225.post-5043283801549327672</id><published>2014-01-31T22:16:00.000-05:00</published><updated>2014-04-27T15:28:51.078-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Experience"/><category scheme="http://www.blogger.com/atom/ns#" term="Technology"/><title type='text'>2013 Products I Can&#39;t Leave Without</title><content type='html'>&lt;p&gt;Having some spare time before starting a new job at Ubisoft in Montreal, I want to give a try of my old &lt;a href=&quot;http://domderrien.blogspot.ca/2009/01/2009-products-i-cant-leave-without.html&quot;&gt;2009 Products I Can&#39;t Leave Without&lt;/a&gt; post.
&lt;/p&gt;
&lt;p style=&quot;border:1px solid #000;padding:4px 10px;&quot;&gt;&lt;b&gt;Update April 27, 2014:&lt;/b&gt; &lt;a href=&quot;http://drop-dropbox.com/&quot;&gt;as many others&lt;/a&gt;, I have decided to stop using the Dropbox service. For now, I use &lt;a href=&quot;http://drive.google.com&quot;&gt;Google Drive&lt;/a&gt;  and &lt;a href=&quot;http://www.qnap.com/useng/index.php?lang=en-us&amp;sn=9000&quot;&gt;QNAP qsynch&lt;/a&gt;.&lt;/p&gt;
&lt;div style=&quot;float: left;&quot;&gt;
&lt;div style=&quot;background-color: black; border: 1px solid black; color: white; font-weight: bold; text-align: center;&quot;&gt;
2013&lt;/div&gt;
&lt;div style=&quot;background-color: grey; border: 1px solid black; padding: 0pt 10px;&quot;&gt;
Blogger&lt;br /&gt;
Chrome Canary&lt;br /&gt;
Dropbox&lt;br /&gt;
Eclipse&lt;br /&gt;
Feedly&lt;br /&gt;
Firefox&lt;br /&gt;
Gimp&lt;br /&gt;
Git&lt;br /&gt;
GMail&lt;br /&gt;
Google App Engine&lt;br /&gt;
Google Search&lt;br /&gt;
KeePassX&lt;br /&gt;
SourceTree&lt;br /&gt;
Unity3D&lt;br /&gt;
VirtualBox&lt;br /&gt;
YouTube&lt;/div&gt;
&lt;/div&gt;
&lt;div style=&quot;clear: both;&quot;&gt;
&lt;/div&gt;
&lt;br /&gt;
&lt;span style=&quot;font-size: large;&quot;&gt;Blogger&lt;/span&gt;&lt;br /&gt;
This is the tool used to publish this blog. I use WordPress in other projects. WP has definitely a larger feature set, without counting its amazing plugin list (commercial and free). However, Blogger is just fine for the type of edition I conduct here.&lt;br /&gt;
&lt;br /&gt;
&lt;div style=&quot;background-color: white; border: 1px solid black; font-size: smaller; padding: 5px 10px;&quot;&gt;
&lt;img src=&quot;http://www.crunchbase.com/assets/images/resized/0001/2809/12809v2-max-150x150.jpg&quot; style=&quot;float: right;&quot; /&gt;&lt;br /&gt;
&lt;table cellspacing=&quot;0&quot; style=&quot;border: 0; margin: 0; padding: 0;&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Company:&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;http://www.crunchbase.com/company/google&quot;&gt;Google&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Website:&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;http://www.google.com/&quot;&gt;blogger.com&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Launch Date:&lt;/td&gt;&lt;td&gt;August 18, 2007&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;br /&gt;
Blogger is a blog publishing platform formerly known as Pyra Labs before Google acquired it in February 2003. Blogger blogs are mostly hosted internally with the “dot Blogspot” domain but they can also be hosted externally on a user’s own server.&lt;br /&gt;
&lt;br /&gt;
Blogger provides bloggers with a WYSIWYG editor that allows them to drag-and-drop widgets, change fonts and edit page elements. Also, Feedburner’s feed management tools are tightly integrated with Blogger blogs due to Google’s recent acquisition.&lt;br /&gt;
&lt;br/&gt;
&lt;div style=&quot;background-color: lightgrey; float: right; padding: 0 10px;&quot;&gt;
Credits: &lt;a href=&quot;http://www.crunchbase.com/&quot;&gt;CrunchBase&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;br /&gt;
&lt;span style=&quot;font-size: large;&quot;&gt;Chrome Canary&lt;/span&gt;&lt;br /&gt;
Google declines the Chrome browser in 4 versions, and Chrome Canary is its most bleeding-edge version. I still use Firefox for most of the Internet browsing, but I now use Chrome for my development related tasks: Webapp debugging on the desktop, remote debugging on a tablet, device emulation (iPad, Nexus, etc.). As the vast majority of mobile browsers are WebKit based as Chrome, it&#39;s definitely a must-have development tool.&lt;br /&gt;
&lt;br /&gt;
&lt;div style=&quot;background-color: white; border: 1px solid black; font-size: smaller; min-height: 55px; padding: 5px 10px;&quot;&gt;
&lt;img src=&quot;https://www.google.com/intl/en/chrome/assets/common/images/chrome_canary_logo_2x.png&quot; style=&quot;width:162px;height:52px;float: right;&quot; /&gt;&lt;br /&gt;
&lt;table cellspacing=&quot;0&quot; style=&quot;border: 0; margin: 0; padding: 0;&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Company:&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;https://www.google.com/&quot;&gt;Google&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Website:&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;https://www.google.ca/intl/en/chrome/browser/canary.html/&quot;&gt;google.ca/intl/en/chrome/browser/canary.html&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;br/&gt;
Google Chrome is an based on the open source web browser Chromium which is based on Webkit. It was accidentally announced prematurely on September 1, 2008 and slated for release the following day. It premiered originally on Windows only, with Mac OS and Linux versions released in early 2010.&lt;br /&gt;
&lt;br/&gt;
&lt;div style=&quot;background-color: lightgrey; float: right; padding: 0 10px;&quot;&gt;
Credits: &lt;a href=&quot;http://www.crunchbase.com/&quot;&gt;CrunchBase&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;br /&gt;
&lt;span style=&quot;font-size: large;&quot;&gt;Eclipse&lt;/span&gt;&lt;br /&gt;
For an early adopter of IntelliJ IDEA by JetBrains, I had to use Eclipse (company&#39;s tool when working with IBM Rational, cheap when working with Compuware). I should recognize that it is going better and better (especially with the refactoring features and the JavaScript support) and it has more plugins than IntelliJ. It is also a platform for OSGi and for Rich Applications.&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://www.eclipse.org/&quot;&gt;Eclipse Website&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://draft.blogger.com/www.jetbrains.com/idea/&quot;&gt;IntelliJ IDEA Website&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.osgi.org/&quot;&gt;OSGi Alliance Website&lt;/a&gt; and &lt;a href=&quot;http://en.wikipedia.org/wiki/OSGi&quot;&gt;OSGi introduction&lt;/a&gt; on Wikipedia.&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;div style=&quot;background-color: white; border: 1px solid black; font-size: smaller; padding: 5px 10px;&quot;&gt;
&lt;img src=&quot;http://www.eclipse.org/eclipse.org-common/themes/Phoenix/images/eclipse_home_header.jpg&quot; style=&quot;float: right;&quot; /&gt;&lt;br /&gt;
&lt;table cellspacing=&quot;0&quot; style=&quot;border: 0; margin: 0; padding: 0;&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Company:&lt;/td&gt;&lt;td&gt;&lt;i title=&quot;Free and Libre Open Source Software&quot;&gt;FLOSS&lt;/i&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Website:&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;http://www.eclipse.org/&quot;&gt;eclipse.org&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;br/&gt;
Eclipse is a Java-based Integrated Development Environment (IDE). In addition to supporting Java (standard and J2EE), Eclipse supports other programming languages like C++ and Python thanks to plugins. It also offers extensions for developing on Android, BIRT, databases, etc.&lt;br/&gt;
&lt;br/&gt;
&lt;/div&gt;
&lt;br /&gt;
&lt;span style=&quot;font-size: large;&quot;&gt;Feedly&lt;/span&gt;&lt;br /&gt;
When Google Reader disappeared, I tried Feedly. It&#39;s not a great tool, but it does the job: I can continue to easily read the continuous new stream from the Internet ;)
&lt;br /&gt;
&lt;br /&gt;
&lt;div style=&quot;background-color: white; border: 1px solid black; font-size: smaller; padding: 5px 10px;&quot;&gt;
&lt;img src=&quot;http://s3.amazonaws.com/crunchbase_prod_assets/assets/images/resized/0024/8166/248166v2-max-250x250.png&quot; style=&quot;float: right;&quot; /&gt;&lt;br /&gt;
&lt;table cellspacing=&quot;0&quot; style=&quot;border: 0; margin: 0; padding: 0;&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Company:&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;http://www.crunchbase.com/product/feedly&quot;&gt;Feedly&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Website:&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;http://www.feedly.com/&quot;&gt;feedly.com&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Launch Date:&lt;/td&gt;&lt;td&gt;June 2008&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;br /&gt;
&lt;div style=&quot;background-color: lightgrey; float: right; padding: 0 10px;&quot;&gt;
Credits: &lt;a href=&quot;http://www.crunchbase.com/&quot;&gt;CrunchBase&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;br /&gt;
&lt;span style=&quot;font-size: large;&quot;&gt;Firefox&lt;/span&gt;&lt;br /&gt;
Having been a Web application developer for a long time, I adopted Firefox (then know as Firebird) in 2003. With the introduction of the Firebug extension (in 2005), it became with primary browser and it had never lost this status. Its early integration of Google search was also a serious advantage. These days, with the &lt;a href=&quot;https://addons.mozilla.org/en-US/firefox/addon/3780&quot;&gt;faviconize extension&lt;/a&gt; and Firefox ability to start with the previous configuration, my browser always starts with: iGoogle, GMail, Google Calendar.&lt;br /&gt;
&lt;br /&gt;
&lt;div style=&quot;background-color: white; border: 1px solid black; font-size: smaller; padding: 5px 10px;&quot;&gt;
&lt;img src=&quot;http://s3.amazonaws.com/crunchbase_prod_assets/assets/images/resized/0001/3109/13109v6-max-250x250.png&quot; style=&quot;float: right;&quot; /&gt;&lt;br /&gt;
&lt;table cellspacing=&quot;0&quot; style=&quot;border: 0; margin: 0; padding: 0;&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Company:&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;http://www.crunchbase.com/company/mozilla&quot;&gt;Mozilla&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Website:&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;http://getfirefox.com/&quot;&gt;getfirefox.com&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Launch Date:&lt;/td&gt;&lt;td&gt;November 9, 2004&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;br /&gt;
Firefox 4 Hits 100 Million Downloads After A Month (4/22/11).&lt;br /&gt;
&lt;br /&gt;
&lt;div style=&quot;background-color: lightgrey; float: right; padding: 0 10px;&quot;&gt;
Credits: &lt;a href=&quot;http://www.crunchbase.com/&quot;&gt;CrunchBase&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;br /&gt;
&lt;span style=&quot;font-size: large;&quot;&gt;Gimp&lt;/span&gt;&lt;br /&gt;
I never the budget and training for Adobe Photoshop. So I started using Gimp. If you can pass over its weird interface (too many windows, IMO), Gimp offer tons of features for Web application developers: to adjust pictures, to generate textures, to resize images, etc. And there are plenty of free tutorials on the Web.&lt;br /&gt;
&lt;br /&gt;
&lt;div style=&quot;background-color: white; border: 1px solid black; font-size: smaller; min-height: 45px; padding: 5px 10px;&quot;&gt;
&lt;img src=&quot;http://www.gimp.org/images/title.png&quot; style=&quot;float: right;&quot; /&gt;&lt;br /&gt;
&lt;table cellspacing=&quot;0&quot; style=&quot;border: 0; margin: 0; padding: 0;&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Company:&lt;/td&gt;&lt;td&gt;&lt;i title=&quot;Free and Libre Open Source Software&quot;&gt;FLOSS&lt;/i&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Website:&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;http://www.gimp.org/&quot;&gt;gimp.org&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;br /&gt;
My favorite video series on Photoshop, starting with the first episode: &lt;a href=&quot;http://www.youtube.com/watch?v=U_X5uR7VC4M&quot;&gt;You Suck at Photoshop #1: Distort, Warp, &amp;amp; Layer Effects&lt;/a&gt;.
&lt;br /&gt;&lt;br /&gt;
&lt;/div&gt;
&lt;br /&gt;
&lt;span style=&quot;font-size: large;&quot;&gt;Git&lt;/span&gt;&lt;br/&gt;
As a developer, I always want to put my code into a source control system. It is not just because I am afraid that my laptop crashes, then wasting hours of work. It is mainly because I want to keep track of the update history. At work, over the years, I used ClearCase, CVS, and Subversion. For my personal development, I used Subversion a lot and now I use Git.  &lt;br /&gt;
&lt;br /&gt;
&lt;div style=&quot;background-color: white; border: 1px solid black; font-size: smaller; min-height: 55px; padding: 5px 10px;&quot;&gt;
&lt;img src=&quot;http://git-scm.com/images/logo@2x.png&quot; style=&quot;float: right;width:110px;height:46px;&quot;/&gt;&lt;br/&gt;
&lt;table cellspacing=&quot;0&quot; style=&quot;border: 0; margin: 0; padding: 0;&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Company:&lt;/td&gt;&lt;td&gt;&lt;i title=&quot;Free and Libre Open Source Software&quot;&gt;FLOSS&lt;/i&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Website:&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;http://git-scm.com/&quot;&gt;git-scm.com&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;br /&gt;
Free hosting service of open-sources &lt;a href=&quot;http://www.github.com/&quot;&gt;Github&lt;/a&gt; - Charges applied for private hostings.&lt;br /&gt;
&lt;br /&gt;
&lt;/div&gt;
&lt;br /&gt;
&lt;span style=&quot;font-size: large;&quot;&gt;GMail&lt;/span&gt;&lt;br /&gt;
When I started working, I dealt with many machines and I hated having to start one just to look at a specific inbox. With GMail, my account is available anywhere. When I read &lt;a href=&quot;http://www.micropersuasion.com/2007/02/transform_gmail.html&quot;&gt;Turn Gmail Into Your Personal Nerve Center&lt;/a&gt;, I started to use GMail as my knowledge database.  &lt;br /&gt;
&lt;br /&gt;
&lt;div style=&quot;background-color: white; border: 1px solid black; font-size: smaller; padding: 5px 10px;&quot;&gt;
&lt;img src=&quot;http://s3.amazonaws.com/crunchbase_prod_assets/assets/images/resized/0001/2806/12806v97-max-150x150.png&quot; style=&quot;float: right;&quot; /&gt;&lt;br /&gt;
&lt;table cellspacing=&quot;0&quot; style=&quot;border: 0; margin: 0; padding: 0;&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Company:&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;http://www.crunchbase.com/company/google&quot;&gt;Google&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Website:&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;http://www.gmail.com/&quot;&gt;gmail.com&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Launch Date:&lt;/td&gt;&lt;td&gt;April 1, 2004&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;br /&gt;
Gmail, also known as Google Mail, is a free email service with innovative features like “conversation view” email threads, search-oriented interface and plenty of free storage (almost 7GB). Gmail opened in private beta mode in April 2004 by invite only. At first, invites were hard to come by and were spotted up for sell on auction sites like eBay. The email service is now open to everyone and is part of Google Apps. Paul Buchheit, an early employee at Google, is given credit for building the product.&lt;br /&gt;
&lt;br /&gt;
Another Gmail feature is the organization, tracking and recording users’ contact lists. For instance, if you start typing the letter C into the “To” field Gmail will bring up a list of every email address and contact name starting with the letter. This feature helps when you can’t quite remember a name. Plus, Gmail automatically adds and updates email addresses and names to your contact list when you write emails.&lt;br /&gt;
&lt;br /&gt;
&lt;div style=&quot;background-color: lightgrey; float: right; padding: 0 10px;&quot;&gt;
Credits: &lt;a href=&quot;http://www.crunchbase.com/&quot;&gt;CrunchBase&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;br /&gt;
&lt;span style=&quot;font-size: large;&quot;&gt;Google App Engine&lt;/span&gt;
&lt;br/&gt;
 My first job in Montréal, Canada, was with a small company named Steltor (bought few years later by Oracle). The core business was the development of a distributed calendar system (servers in cluster, native clients, web client, mobile client, etc.). Since then, I am used to tracking my work with an electronic calendar. Google Calendar and its ability to mix many agendas is excellent.  &lt;br /&gt;
&lt;br /&gt;
&lt;div style=&quot;background-color: white; border: 1px solid black; font-size: smaller; padding: 5px 10px;&quot;&gt;
&lt;img src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjYyJiYjBZNfrUrY1H_RXCT6gZeLZWfSsCEgPCHzreIoFP0Q-Sy6GeyqwxxRCRaXH3gddZ0yxFmAPAIsjx_YOPqdZSTKnzgl52x_EPe9xd4k_JIvbDBrVBPTtA3ONJsnRQRGGHEX6_kZopL/s1600/gae-logo-blue.png&quot; style=&quot;width:54;height:49px;float: right;&quot; /&gt;&lt;br /&gt;
&lt;table cellspacing=&quot;0&quot; style=&quot;border: 0; margin: 0; padding: 0;&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Company:&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;http://www.crunchbase.com/company/google&quot;&gt;Google&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Website:&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;http://developers.google.com/appengine&quot;&gt;developers.google.com/appengine&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Launch Date:&lt;/td&gt;&lt;td&gt;April 2008&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;br /&gt;
Google App Engine offers a full-stack, hosted, automatically scalable web application platform. The service allows developers to build applications in Python, Java (including other JVM based languages such as JRuby) which can then use other Google services such as the Datastore (built on BigTable) and XMPP. The service allows developers to create complete web application that run entirely on Google’s computing infrastructure and scale automatically as the application’s load changes over time. Google also provides an SDK for local development and site monitoring tools for measuring traffic and machine usage.
&lt;br /&gt;
&lt;br /&gt;
Google’s offering competes with Amazon’s Web Services suite, including EC2, S3, SQS, and SimpleDB.
&lt;br /&gt;
&lt;br /&gt;
&lt;div style=&quot;background-color: lightgrey; float: right; padding: 0 10px;&quot;&gt;
Credits: &lt;a href=&quot;http://www.crunchbase.com/&quot;&gt;CrunchBase&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;br /&gt;
&lt;span style=&quot;font-size: large;&quot;&gt;Google Search&lt;/span&gt;
&lt;br /&gt;
Google Search is an amazing tool: recently, I was trying to find a solution to a tough technical problem and I found it thanks to Google Search which pointed toward a blog post written the same day, just few hours before, in Europe! Incredible... When I give a conference into universities, I often say: “If I asked a question today and you have no clue about the response, that&#39;s fine. If you still have no clue tomorrow, you&#39;re in trouble...”   &lt;br /&gt;
&lt;br /&gt;
&lt;div style=&quot;background-color: white; border: 1px solid black; font-size: smaller; padding: 5px 10px;&quot;&gt;
&lt;img src=&quot;http://www.crunchbase.com/assets/images/resized/0002/9578/29578v7-max-150x150.jpg&quot; style=&quot;float: right;&quot; /&gt;&lt;br /&gt;
&lt;table cellspacing=&quot;0&quot; style=&quot;border: 0; margin: 0; padding: 0;&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Company:&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;http://www.crunchbase.com/company/google&quot;&gt;Google&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Website:&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;http://www.google.com/&quot;&gt;google.com&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Launch Date:&lt;/td&gt;&lt;td&gt;September 4, 1998&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;br /&gt;
Search is Google’s core product and is what got them an official transitive verb addition to the Merriam Webster for “google”. The product is known for its Internet-crawling Googlebots and its PageRank algorithm influenced heavily by linking.&lt;br /&gt;
&lt;br /&gt;
When users type keywords into the home page search box they are returned with relevant results that they can further refine. Google also has more specific search for blogs, images, news and video. Google will also return search results from your own computer files and emails via Google Desktop.&lt;br /&gt;
&lt;br/&gt;
&lt;div style=&quot;background-color: lightgrey; float: right; padding: 0 10px;&quot;&gt;
Credits: &lt;a href=&quot;http://www.crunchbase.com/&quot;&gt;CrunchBase&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;br /&gt;
&lt;span style=&quot;font-size: large;&quot;&gt;KeePassX&lt;/span&gt;
&lt;br /&gt;
KeePassX is an open source Password Safe, an multi-platform extension of KeePass. I use it in conjunction with Dropbox so my precious list of account coordinates are available on all my devices (desktops, tablets, and phones).  &lt;br /&gt;
&lt;br /&gt;
&lt;div style=&quot;background-color: white; border: 1px solid black; font-size: smaller; min-height: 80px; padding: 5px 10px;&quot;&gt;
&lt;img src=&quot;http://www.keepassx.org/images/kp_logo_main.png&quot; style=&quot;float: right;width:60px;height:60px;&quot; /&gt;&lt;br /&gt;
&lt;table cellspacing=&quot;0&quot; style=&quot;border: 0; margin: 0; padding: 0;&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Company:&lt;/td&gt;&lt;td&gt;&lt;i title=&quot;Free and Libre Open Source Software&quot;&gt;FLOSS&lt;/i&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Website:&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;http://keepassx.org/&quot;&gt;keepassx.org&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;br /&gt;
&lt;/div&gt;
&lt;br /&gt;
&lt;span style=&quot;font-size: large;&quot;&gt;SourceTree&lt;/span&gt; 
&lt;br /&gt;
As a developer, I much prefer using command line tools like maven, git, and other ant and Python scripts. Git is a really powerful tool but I worked with team members having some difficulties to deal with the branches, cherry-picking, and stashing for example. The freeware SourceTree offers a neat interface on the top of git.
I&#39;m more a command line user than a GUI one. Hoever&lt;br /&gt;
&lt;br /&gt;
&lt;div style=&quot;background-color: white; border: 1px solid black; font-size: smaller; padding: 5px 10px;&quot;&gt;
&lt;img src=&quot;https://www.atlassian.com/en/wac/software/sourcetree/overview/productLogo/imageBinary/soutcetree_logo_landing.png&quot; style=&quot;float: right;&quot; /&gt;&lt;br /&gt;
&lt;table cellspacing=&quot;0&quot; style=&quot;border: 0; margin: 0; padding: 0;&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Company:&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;http://www.crunchbase.com/company/atlassian&quot;&gt;Atlassian&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Website:&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;https://www.atlassian.com/software/sourcetree/overview&quot;&gt;atlassian.com/software/sourcetree/&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;br /&gt;
&lt;/div&gt;

&lt;br /&gt;
&lt;span style=&quot;font-size: large;&quot;&gt;Unity3D&lt;/span&gt; 
&lt;br /&gt;
For my own company AnotherSocialEconomy.com, I only developed native applications for Android. As a software architect at Electronic Arts, in the (now defunct) Play Mantis franchise lab in Montréal, I worked with Unity developers. I then learned its cross-platform capabilities, it&#39;s physics engine, and it&#39;s powerful C# library. Since then, I&#39;ve created few project with Unity and I think it&#39;s a powerful ecosystem.&lt;br /&gt;
&lt;br /&gt;
&lt;div style=&quot;background-color: white; border: 1px solid black; font-size: smaller; padding: 5px 10px;&quot;&gt;
&lt;img src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEggBS3ta22qhWALy6rcucQrHVPdw50AfQorxvMAKu60OS3zF0rw4IDZMTRFfWIQWUqfc5dPBde8aAn2a1pG9lO_mN_zkJ4CNmk6XcvS1afOLwStQZPvajfgIbNt5g_erRJaNASOf9sr-Oo_/s1600/unity-logo-white.png&quot; style=&quot;float: right;background-color:#000;&quot; /&gt;&lt;br /&gt;
&lt;table cellspacing=&quot;0&quot; style=&quot;border: 0; margin: 0; padding: 0;&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Company:&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;http://unity3d.com/company&quot;&gt;Unity Technologies&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Website:&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;http://unity3d.com/&quot;&gt;unity3d.com/&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;br /&gt;
&lt;/div&gt;
&lt;br /&gt;
&lt;span style=&quot;font-size: large;&quot;&gt;VirtualBox&lt;/span&gt;
&lt;BR/&gt;
Developing software requires sometimes specific configurations. Testing them requires always specific configurations (at least to replay always the same test cases every time the source control system, like Git, is updated). There are the famous VMWare products (Workstation, Player, ESX) and Microsoft VirtualPC. VirtualBox is an open source product provided by SUN Microsystems, and it has nice features while being powerful.  &lt;br /&gt;
&lt;br /&gt;
&lt;div style=&quot;background-color: white; border: 1px solid black; font-size: smaller; padding: 5px 10px;&quot;&gt;
&lt;img src=&quot;http://www.virtualbox.org/graphics/vbox_logo2_gradient.png&quot; style=&quot;float: right;with:100px;height:120px;&quot; /&gt;&lt;br /&gt;
&lt;table cellspacing=&quot;0&quot; style=&quot;border: 0; margin: 0; padding: 0;&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Company:&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;http://www.crunchbase.com/company/oracle&quot;&gt;Oracle&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Website:&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;http://www.virtualbox.org/&quot;&gt;virtualbox.org&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;br /&gt;
VirtualBox is a family of powerful x86 virtualization products for enterprise as well as home use. Not only is VirtualBox an extremely feature rich, high performance product for enterprise customers, it is also the only professional solution that is freely available as Open Source Software under the terms of the GNU General Public License (GPL).&lt;br /&gt;
&lt;br /&gt;
Presently, VirtualBox runs on Windows, Linux, Macintosh and OpenSolaris hosts and supports a large number of guest operating systems including but not limited to Windows (NT 4.0, 2000, XP, Server 2003, Vista), DOS/Windows 3.x, Linux (2.4 and 2.6), Solaris and OpenSolaris, and OpenBSD.&lt;BR/&gt;
&lt;BR/&gt;
&lt;/div&gt;
&lt;br /&gt;



&lt;span style=&quot;font-size: large;&quot;&gt;YouTube&lt;/span&gt; &lt;br/&gt;
YouTube is famous because of fun videos. But it also hosts technical videos.  &lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://research.google.com/video.html&quot;&gt;Tech Talk at Google&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.youtube.com/user/GoogleDevelopers&quot;&gt;Google Developer Channel&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;div style=&quot;background-color: white; border: 1px solid black; font-size: smaller; padding: 5px 10px;&quot;&gt;
&lt;img src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhmrlNjiVlzMyLaqwpGVt4r34MZ6WAqspuZZrdLaAeeEHJO3Yrv8GFSfclGGVtcpeFO-FO5K9GycZq4N_NG8_sXwjLvylOMO1elY43fy3BMeFhGr0SJCc63sx9bmXrUB-2IxJam2XsiLJyQ/s1600/youtube-logo.png&quot; style=&quot;float: right;&quot; /&gt;&lt;br /&gt;
&lt;table cellspacing=&quot;0&quot; style=&quot;border: 0; margin: 0; padding: 0;&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Company:&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;http://www.crunchbase.com/company/youtube&quot;&gt;Youtube&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Website:&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;http://www.youtube.com/&quot;&gt;youtube.com&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Launch Date:&lt;/td&gt;&lt;td&gt;December 11, 2005&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;br /&gt;
YouTube was founded in 2005 by Chad Hurley, Steve Chen and Jawed Karim, who were all early employees of PayPal. YouTube is the leader in online video, sharing original videos worldwide through a Web experience. YouTube allows people to easily upload and share video clips across the Internet through websites, mobile devices, blogs, and email.&lt;br /&gt;
&lt;br /&gt;
Everyone can watch videos on YouTube. People can see first-hand accounts of current events, find videos about their hobbies and interests, and discover the quirky and unusual. As more people capture special moments on video, YouTube is empowering them to become the broadcasters of tomorrow.&lt;br /&gt;
&lt;br /&gt;
In November 2006, within a year of its launch, YouTube was purchased by Google Inc. in one of the most talked-about acquisitions to date.&lt;br /&gt;
&lt;br/&gt;
&lt;div style=&quot;background-color: lightgrey; float: right; padding: 0 10px;&quot;&gt;
Credits: &lt;a href=&quot;http://www.crunchbase.com/&quot;&gt;CrunchBase&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;br /&gt;
I hope it helps,&lt;br /&gt;
A+, Dom</content><link rel='replies' type='application/atom+xml' href='http://domderrien.blogspot.com/feeds/5043283801549327672/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://domderrien.blogspot.com/2014/01/2013-products-i-cant-leave-without.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/168828253523263225/posts/default/5043283801549327672'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/168828253523263225/posts/default/5043283801549327672'/><link rel='alternate' type='text/html' href='http://domderrien.blogspot.com/2014/01/2013-products-i-cant-leave-without.html' title='2013 Products I Can&#39;t Leave Without'/><author><name>Dom Derrien</name><uri>http://www.blogger.com/profile/15197441189507302313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjYyJiYjBZNfrUrY1H_RXCT6gZeLZWfSsCEgPCHzreIoFP0Q-Sy6GeyqwxxRCRaXH3gddZ0yxFmAPAIsjx_YOPqdZSTKnzgl52x_EPe9xd4k_JIvbDBrVBPTtA3ONJsnRQRGGHEX6_kZopL/s72-c/gae-logo-blue.png" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-168828253523263225.post-6605231998330720220</id><published>2014-01-30T13:01:00.002-05:00</published><updated>2014-01-30T14:01:59.469-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Community"/><category scheme="http://www.blogger.com/atom/ns#" term="Technology"/><title type='text'>Singleton uniqueness issue in a C# generic class</title><content type='html'>&lt;p&gt;
I code in C# within the Unity environment (I really like the co-routine mechanism and the &lt;code&gt;yield return&lt;/code&gt; instruction).
&lt;/p&gt;
&lt;p&gt;
At one point, I developed a set of base classes to provide a default behavior to entities that must be exchanged on the wire and cached on the device. And I used generic/template classes to be able to generate instances of the final classes from within the methods of the super class. Information saved on the device are signed with a piece of information obtained from the server.
&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;public class LocalCache&amp;lt;T&amp;gt; where T : BaseModel {

    public static string signatureKey { get; set; }

    public T Get(long id) {
        // Get the JSON from PlayerPrefs
        // Verify its signature
        return (T) Activator.CreateInstance(typeof(T), new object[] { JSON });
    }

    public void Save(T entity) {
        // Get the JSON representing the data
        // Sign the JSON
        // Save the signed JSON into PlayerPrefs
    }

    public void Reset(long id) { ... }

    private string SignData(string data) { ... }
}&lt;/pre&gt;
&lt;p&gt;
In Java, there would be only one instance of the &lt;code&gt;signatureKey&lt;/code&gt; variable per JVM, whatever the number of class instances. In C#, I&#39;ve been surprised to find that this attribute is not unique!
&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;public class LocalCache&amp;lt;T&amp;gt; where T : BaseModel {

    // Other code as shown above...

    public static void TestDifferentInstanciations() {
        LocalCache&amp;lt;System.Int64&gt;.signatureKey = &quot;Set for Int64&quot;;

        UnityEngine.Debug.Log(&quot;Int16: &quot; + LocalCache&amp;lt;System.Int16&gt;.signatureKey);
        // =&gt; produces: &lt;b&gt;Int16:&lt;/b&gt;

        UnityEngine.Debug.Log(&quot;Int16: &quot; + LocalCache&amp;lt;System.Int64&gt;.signatureKey);
        // =&gt; produces: &lt;b&gt;Int64: Set for Int64&lt;/b&gt;

        UnityEngine.Debug.Log(&quot;string: &quot; + LocalCache&amp;lt;System.string&gt;.signatureKey);
        // =&gt; produces: &lt;b&gt;string:&lt;/b&gt;
    }
}&lt;/pre&gt;
&lt;p&gt;
My surprise comes from the fact that the concept of generic class only exist for the definitions. At runtime in C# only exist types and the type of &lt;code&gt;LocalCache&amp;lt;Int16&amp;gt;&lt;/code&gt; is different from the type of &lt;code&gt;LocalCache&amp;lt;Int64&amp;gt;&lt;/code&gt;. Then there are multiple copies of the &lt;code&gt;signatureKey&lt;/code&gt; attribute, exactly one per constructed types...
&lt;/p&gt;
&lt;p&gt;
The solution is to set the singleton into a separated non generic class!
&lt;/p&gt;
&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;internal static class StaticLocalCacheInfo {
    public static string signatureKey { get; set; }
}

public class LocalCache&amp;lt;T&amp;gt; where T : BaseModel {

    public static string signaturePartFromServer {
        get { return StaticLocalCacheInfo.signatureKey; }
        set { StaticLocalCacheInfo.signatureKey = value; }
    }

    // Rest of the business logic...

    public static void TestDifferentInstanciations() {
        LocalCache&amp;lt;System.Int64&gt;.signatureKey = &quot;Set for Int64&quot;;

        UnityEngine.Debug.Log(&quot;Int16: &quot; + LocalCache&amp;lt;System.Int16&gt;.signatureKey);
        // =&gt; produces: &lt;b&gt;Int16: Set for Int64&lt;/b&gt;

        UnityEngine.Debug.Log(&quot;Int16: &quot; + LocalCache&amp;lt;System.Int64&gt;.signatureKey);
        // =&gt; produces: &lt;b&gt;Int64: Set for Int64&lt;/b&gt;

        UnityEngine.Debug.Log(&quot;string: &quot; + LocalCache&amp;lt;System.string&gt;.signatureKey);
        // =&gt; produces: &lt;b&gt;string: Set for Int64&lt;/b&gt;
    }
}&lt;/pre&gt;
&lt;p&gt;
Note that I got the confirmation of the behavior on &lt;a href=&quot;http://forum.unity3d.com/threads/223675-Template-class-and-static-member-(C-)&quot;&gt;Unity forum&lt;/a&gt;.
&lt;/p&gt;
&lt;p&gt;
I hope it helps!&lt;br/&gt;
A+, Dom
&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://domderrien.blogspot.com/feeds/6605231998330720220/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://domderrien.blogspot.com/2014/01/singleton-uniqueness-issue-in-c-generic.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/168828253523263225/posts/default/6605231998330720220'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/168828253523263225/posts/default/6605231998330720220'/><link rel='alternate' type='text/html' href='http://domderrien.blogspot.com/2014/01/singleton-uniqueness-issue-in-c-generic.html' title='Singleton uniqueness issue in a C# generic class'/><author><name>Dom Derrien</name><uri>http://www.blogger.com/profile/15197441189507302313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-168828253523263225.post-3854348110061368080</id><published>2014-01-28T11:04:00.000-05:00</published><updated>2014-02-06T07:37:25.490-05:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Experience"/><category scheme="http://www.blogger.com/atom/ns#" term="Technology"/><title type='text'>Objectify and conflicting OnSave / OnLoad callbacks</title><content type='html'>&lt;p&gt;
Since the beginning, I&#39;ve been using &lt;a href=&quot;https://appengine.google.com&quot;&gt;Google App Engine&lt;/a&gt;. I tried first its Python implementation, then I &lt;a href=&quot;http://domderrien.blogspot.ca/2009/04/google-app-engine-meets-java.html&quot;&gt;moved to its Java implementation&lt;/a&gt;. The move to Java was dictated by the simplicity of the language and the largest set of tooling to code, &lt;a href=&quot;http://domderrien.blogspot.ca/2009/11/unit-tests-mock-objects-and-app-engine.html&quot;&gt;test&lt;/a&gt;, and debug the applications.
&lt;/p&gt;
&lt;p&gt;
My first implementation was using JDO as the ORM layer with my own servlet infrastructure implementing a REST interface. Nowadays, I use Objectify in place of JDO and &lt;a href=&quot;http://www.jboss.org/resteasy&quot;&gt;RestEasy&lt;/a&gt; (with &lt;a href=&quot;http://docs.jboss.org/resteasy/docs/3.0.6.Final/userguide/html_single/index.html#Guice1&quot;&gt;Guice&lt;/a&gt; and &lt;a href=&quot;http://docs.jboss.org/resteasy/docs/3.0.6.Final/userguide/html_single/index.html#Content_Marshalling_Providers&quot;&gt;JAX-RS&lt;/a&gt;).
&lt;/p&gt;
&lt;p&gt;
In &lt;a href=&quot;https://github.com/DomDerrien/custom-portal&quot;&gt;my latest project&lt;/a&gt;, I&#39;ve implemented two sets of entities:
&lt;ul&gt;
&lt;li&gt;One set which tracks with a creation date and a version number;&lt;/li&gt;
&lt;li&gt;One set which extends the first one and tracks the identifier of their owners.&lt;/li&gt;
&lt;/ul&gt;
&lt;/p&gt;
&lt;p&gt;
Both base classes implement an &lt;code&gt;@OnSave&lt;/code&gt; callback to control their core information. Here are a short version of the these classes.
&lt;/p&gt;
&lt;pre  class=&quot;prettyprint&quot;&gt;@Index public abstract class AbstractBase&amp;lt;T&amp;gt; {
    @Id private Long id;
    @Unindex protected Date creation;
    @Unindex protected Long version;

    // Accessors
    // ...

    @OnSave protected void prePersist() {
        if (creation == null) {
            creation = new DateTime().toDate();
            version = Long.valueOf(0L);
        }
        version ++;
    }
}&lt;/pre&gt;
&lt;br /&gt;
&lt;pre  class=&quot;prettyprint&quot;&gt;@Index public abstract class AbstractAuthBase&amp;lt;T&amp;gt; extends AbstractBase&amp;lt;T&amp;gt; {
    @Index private Long ownerId;

    // Accessors
    // ...

    @OnSave protected void prePersist() {
        if (ownerId == null || Long.valueOf(0L).equals(ownerId)) {
            throw new ClientErrorException(&quot;Field ownerId is missing&quot;);
        }
    }
}&lt;/pre&gt;
&lt;p&gt;
When I ran the implementation of the code above, I faced a weird behavior:
&lt;ul&gt;
&lt;li&gt;The entities of the &lt;code&gt;User&lt;/code&gt; class extending only &lt;code&gt;AbstractBase&lt;/code&gt; had their &lt;code&gt;creation&lt;/code&gt; and &lt;code&gt;version&lt;/code&gt; fields correctly set when persisted.&lt;/li&gt;
&lt;li&gt;The entities of the &lt;code&gt;Category&lt;/code&gt; class extending &lt;code&gt;AbstractAuthBase&lt;/code&gt; had none of these two fields set!&lt;/li&gt;
&lt;/ul&gt;
&lt;/p&gt;
&lt;p&gt;
It appears the issue comes from Objectify which was &lt;b&gt;only invoking the first method&lt;/b&gt;! And twice BTW...
&lt;/p&gt;
&lt;p&gt;
I looked then at the Objectify implementation, precisely at the methods &lt;code&gt;&lt;a href=&quot;http://code.google.com/p/objectify-appengine/source/browse/src/main/java/com/googlecode/objectify/impl/ConcreteEntityMetadata.java?r=44b4b7374237553a63122ef1ad3c147d2f36712f&quot;&gt;ConcreteEntityMetadata&amp;lt;T&amp;gt&lt;/a&gt;.&lt;a href=&quot;http://code.google.com/p/objectify-appengine/source/browse/src/main/java/com/googlecode/objectify/impl/ConcreteEntityMetadata.java?r=44b4b7374237553a63122ef1ad3c147d2f36712f#74&quot;&gt;processLifeCycleCallbacks()&lt;/a&gt;&lt;/code&gt; and &lt;code&gt;&lt;a href=http://code.google.com/p/objectify-appengine/source/browse/src/main/java/com/googlecode/objectify/impl/ConcreteEntityMetadata.java?r=44b4b7374237553a63122ef1ad3c147d2f36712f&quot;&gt;ConcreteEntityMetadata&amp;lt;T&amp;gt;&lt;/a&gt;.&lt;a href=&quot;http://code.google.com/p/objectify-appengine/source/browse/src/main/java/com/googlecode/objectify/impl/ConcreteEntityMetadata.java?r=44b4b7374237553a63122ef1ad3c147d2f36712f#160&quot;&gt;invokeLifeCycleCallbacks()&lt;/a&gt;&lt;/code&gt;. In the first method, you can see that the reference of the methods annotated &lt;code&gt;@OnSave&lt;/code&gt; and &lt;code&gt;@OnLoad&lt;/code&gt; are accumulated in two lists. In the second method, the given list is parsed and the method is applied to the current object.
&lt;/p&gt;
&lt;p&gt;
My issue arose because applying twice the method &lt;code&gt;prePersist()&lt;/code&gt; on a instance of a &lt;code&gt;Category&lt;/code&gt; class was always calling the method of the base class &lt;code&gt;AbstractAuthBase&amp;lt;T&amp;gt;&lt;/code&gt;! I fixed the issue by renaming the callbacks (one in &lt;code&gt;checkOwnerId()&lt;/code&gt; and the other in &lt;code&gt;setCreationAndVersion()&lt;/code&gt;).
&lt;/p&gt;
&lt;p&gt;
A+, Dom
&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://domderrien.blogspot.com/feeds/3854348110061368080/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://domderrien.blogspot.com/2014/01/objectify-and-conficting-onsave-onload.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/168828253523263225/posts/default/3854348110061368080'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/168828253523263225/posts/default/3854348110061368080'/><link rel='alternate' type='text/html' href='http://domderrien.blogspot.com/2014/01/objectify-and-conficting-onsave-onload.html' title='Objectify and conflicting OnSave / OnLoad callbacks'/><author><name>Dom Derrien</name><uri>http://www.blogger.com/profile/15197441189507302313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total><georss:featurename>Montreal, QC, Canada</georss:featurename><georss:point>45.5086699 -73.553992499999993</georss:point><georss:box>45.1529059 -74.1994395 45.8644339 -72.908545499999988</georss:box></entry><entry><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><category scheme="http://www.blogger.com/atom/ns#" term="Experience"/><category scheme="http://www.blogger.com/atom/ns#" term="Technology"/><title type='text'>SoapUI / REST / JSON / variable namespaces</title><content type='html'>As a developer of large Web applications, I&#39;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=&quot;http://junit.org/&quot;&gt;JUnit&lt;/a&gt; / &lt;a href=&quot;http://cobertura.sourceforge.net/&quot;&gt;Cobertura&lt;/a&gt; and &lt;a href=&quot;http://dojotoolkit.org/reference-guide/util/doh.html&quot; title=&quot;Dojo Objective Harness&quot;&gt;DOH&lt;/a&gt; / &lt;a href=&quot;http://siliconforks.com/jscoverage/&quot;&gt;JSCoverage&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;Functional and Integration tests with &lt;a href=&quot;http://seleniumhq.org/docs/03_webdriver.html&quot;&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=&quot;http://www.tradeinsight.com/&quot;&gt;TradeInsight&lt;/a&gt;, I was introduced to &lt;a href=&quot;http://www.soapui.org/&quot;&gt;SoapUI&lt;/a&gt; for the &lt;a href=&quot;http://www.soapui.org/Getting-Started/functional-testing.html&quot;&gt;functional tests&lt;/a&gt;. I like it&#39;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&#39;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=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right: auto; text-align: center;&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjYFPLP4YCVLZm7nV2K1TkkE-K68oijfgHQnpJpwq2GudK5RpIqFI5KtEyvpF6aa7HrVq3BK58lEURyxvu_I0iP2mILYxjWPZ4uyJEd-ZoIDFm34GFK1qT03aidV6V-h5jT-MMYc68-lz4e/s1600/soapui-1.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjYFPLP4YCVLZm7nV2K1TkkE-K68oijfgHQnpJpwq2GudK5RpIqFI5KtEyvpF6aa7HrVq3BK58lEURyxvu_I0iP2mILYxjWPZ4uyJEd-ZoIDFm34GFK1qT03aidV6V-h5jT-MMYc68-lz4e/s1600/soapui-1.png&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;JSON response produced by a REST service.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right: auto; text-align: center;&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiyN_BXwCXI6uofxI9TKH1SSBwV3QPnyAJAQH1keU2IqKSfCprq25xG_GKEGPeWF0jxl_VXNuRe18B7zLOkwGGsGvhgl00VE3GI3ClOZGGB3rUdDON2sAjRF-6Qt6Ya0cPwJHDWtvoWr4fr/s1600/soapui-2.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiyN_BXwCXI6uofxI9TKH1SSBwV3QPnyAJAQH1keU2IqKSfCprq25xG_GKEGPeWF0jxl_VXNuRe18B7zLOkwGGsGvhgl00VE3GI3ClOZGGB3rUdDON2sAjRF-6Qt6Ya0cPwJHDWtvoWr4fr/s1600/soapui-2.png&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&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=&quot;tr_bq&quot; style=&quot;font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;&quot;&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=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right: auto; text-align: center;&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi16ff34exSOkuIHwyvFgDyeMyGH10BzG2XkEDKFZoVtn2ktfL5q6sMC2jmfn3ysvicHlcrvVi_gi7XnfvpL4iGIDINWR4KfQazB0MP9ioTfoGOeDYNrwVqrL9YdrAq-AlyonOzFnyFIWUR/s1600/soapui-5.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi16ff34exSOkuIHwyvFgDyeMyGH10BzG2XkEDKFZoVtn2ktfL5q6sMC2jmfn3ysvicHlcrvVi_gi7XnfvpL4iGIDINWR4KfQazB0MP9ioTfoGOeDYNrwVqrL9YdrAq-AlyonOzFnyFIWUR/s1600/soapui-5.png&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;Simple XPath expression with the corresponding error message.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right: auto; text-align: center;&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjrW-GTkTGS4SRiWurv6ArxhpBkLv1e-k-GmQEsqQ2KKw-tZb4_aW8vEAFNEPPJYhJ-ogI1RorJ6VC7TUoieDHFm5eKuD8BkpbEm4OtHz73zfh1xKg5fdHEGKIF66J5I_U-TPrrNmioud_N/s1600/soapui-6.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjrW-GTkTGS4SRiWurv6ArxhpBkLv1e-k-GmQEsqQ2KKw-tZb4_aW8vEAFNEPPJYhJ-ogI1RorJ6VC7TUoieDHFm5eKuD8BkpbEm4OtHz73zfh1xKg5fdHEGKIF66J5I_U-TPrrNmioud_N/s1600/soapui-6.png&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&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 &#39;&lt;b&gt;*&lt;/b&gt;&#39;, which match any namespace. As all elements of the XML document are under the scope of the inferred namespace, it&#39;s important to prefix all nodes of the XPath expression with &#39;&lt;b&gt;*:&lt;/b&gt;&#39;, as illustrated by the following figure.&lt;br /&gt;
&lt;br /&gt;
&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right: auto; text-align: center;&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj8scD32r0bryL1eOH0WCBdBPmFCQY0cS0JpTRPqs3ZJUUD0gns47d_z8_btV-al46d60ott0heBWgYJuK5P7F1o5mkS5NHwVwEhyphenhyphen77Ny4nxnklKKSPauM3g9LtLVICHEMhbNJO7j4xVmtW/s1600/soapui-7.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj8scD32r0bryL1eOH0WCBdBPmFCQY0cS0JpTRPqs3ZJUUD0gns47d_z8_btV-al46d60ott0heBWgYJuK5P7F1o5mkS5NHwVwEhyphenhyphen77Ny4nxnklKKSPauM3g9LtLVICHEMhbNJO7j4xVmtW/s1600/soapui-7.png&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;Use of the &#39;*&#39; 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;</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='12 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/168828253523263225/posts/default/7487542450779400503'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/168828253523263225/posts/default/7487542450779400503'/><link rel='alternate' type='text/html' href='http://domderrien.blogspot.com/2012/02/soapui-rest-json-variable-namespaces.html' title='SoapUI / REST / JSON / variable namespaces'/><author><name>Dom Derrien</name><uri>http://www.blogger.com/profile/15197441189507302313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjYFPLP4YCVLZm7nV2K1TkkE-K68oijfgHQnpJpwq2GudK5RpIqFI5KtEyvpF6aa7HrVq3BK58lEURyxvu_I0iP2mILYxjWPZ4uyJEd-ZoIDFm34GFK1qT03aidV6V-h5jT-MMYc68-lz4e/s72-c/soapui-1.png" height="72" width="72"/><thr:total>12</thr:total></entry><entry><id>tag:blogger.com,1999:blog-168828253523263225.post-6018660260923303495</id><published>2011-06-27T21:47:00.000-04:00</published><updated>2014-03-15T11:50:58.546-04:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Entrepreneurship"/><category scheme="http://www.blogger.com/atom/ns#" term="Experience"/><title type='text'>Un nouveau chapître</title><content type='html'>Il y a quelques 18 moins, j&#39;ai choisi de devenir un &lt;a href=&quot;http://domderrien.blogspot.com/2010/02/ma-nouvelle-vie-en-tant-quentrepreneur.html&quot;&gt;entrepreneur à temps plein&lt;/a&gt;, consacrant mon temps et mes ressources financières au développement du service &lt;a href=&quot;http://anothersocialeconomy.com/&quot;&gt;AnotherSocialEconomy&lt;/a&gt;. Cela a été une expérience très &lt;a href=&quot;http://domderrien.blogspot.com/2011/04/state-of-anothersocialeconomy.html&quot;&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&#39;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&#39;abord les usagers, puis pour construire les services et faciliter leur développement. En ce sens, la méthodologie &lt;a href=&quot;http://theleanstartup.com/&quot;&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=&quot;http://domderrien.blogspot.com/2009/04/agile-scrum-is-hype-but-xp-is-more.html&quot;&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=&quot;http://stevenmilstein.com/&quot;&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&#39;entreprise.&lt;br /&gt;
&lt;br /&gt;
Mais il y a un mois, principalement à cause de l&#39;absence d&#39;une perspective de revenus stables dans un futur proche, je me suis mis à la recherche de contrats et de postes permanents. C&#39;est sûr que c&#39;est déchirant, mais l&#39;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;&quot;Failure to change is a vice!&quot;&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&#39;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&#39;il y a deux ans, MEI offrait principalement son service aux grands manufacturiers. Maintenant, dans une offre &lt;span style=&quot;border-bottom: 1px dotted grey;&quot; title=&quot;Software as a Service&quot;&gt;SAAS&lt;/span&gt;, MEI développe une nouvelle version pour les producteurs petits et intermédiaires, sous la marque &lt;a href=&quot;http://tradeinsight.com/&quot;&gt;TradeInsight&lt;/a&gt;. La synergie entre mon expérience (Java, JavaScript, cloud, mobile, Agile) et l&#39;équipe est indéniable.&lt;br /&gt;
&lt;br /&gt;
Au fait&amp;nbsp;: &lt;a href=&quot;http://www.tradeinsight.com/aboutus/careers&quot;&gt;MEI embauche&lt;/a&gt; encore&amp;nbsp;!&lt;br /&gt;
&lt;br /&gt;
L&#39;aventure d&#39;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&#39;informations quant aux partenariats possibles, veuillez prendre contact avec &lt;a href=&quot;http://draft.blogger.com/post-edit.g?blogID=168828253523263225&amp;amp;postID=6018660260923303495&quot;&gt;Steven&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
A+, Dom</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'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/168828253523263225/posts/default/6018660260923303495'/><link rel='alternate' type='text/html' href='http://domderrien.blogspot.com/2011/06/un-nouveau-chapitre.html' title='Un nouveau chapître'/><author><name>Dom Derrien</name><uri>http://www.blogger.com/profile/15197441189507302313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><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><category scheme="http://www.blogger.com/atom/ns#" term="Experience"/><category scheme="http://www.blogger.com/atom/ns#" term="Technology"/><title type='text'>OAuth authorization handling in a Android application</title><content type='html'>&lt;div style=&quot;background-color: lightgrey; border: 1 solid darkgrey; padding: 3px;&quot;&gt;Note : This post is part of the series &quot;Lessons learned as an independent developer&quot;. Please refer to the &lt;a href=&quot;http://domderrien.blogspot.com/2011/06/partage-dexperience-dans-le.html&quot;&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&#39;un développeur indépendant ». Au besoin, lisez mon&amp;nbsp;&lt;a href=&quot;http://domderrien.blogspot.com/2011/06/partage-dexperience-dans-le.html&quot;&gt;introduction&lt;/a&gt; pour plus d&#39;informations.&lt;/i&gt;&lt;/div&gt;&lt;br /&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-size: large;&quot;&gt;Context&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;a href=&quot;http://anothersocialeconomy.com/&quot;&gt;AnotherSocialEconomy&lt;/a&gt; APIs are based on standards as much as possible: &lt;a href=&quot;http://openid.net/&quot;&gt;OpenID&lt;/a&gt; and &lt;a href=&quot;http://oauth.net/&quot;&gt;OAuth&lt;/a&gt; for the authentication and authorization, HTTP-based REST interface (&lt;a href=&quot;http://www.nordsc.com/ext/classification_of_http_based_apis.html&quot; title=&quot;Classification of HTTP-based APIs&quot;&gt;1&lt;/a&gt;, &lt;a blog&quot;=&quot;&quot; fielding&#39;s=&quot;&quot; href=&quot;http://roy.gbiv.com/untangled/&quot; roy=&quot;&quot; title=&quot;&quot;&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=&quot;Apple-style-span&quot; style=&quot;font-size: large;&quot;&gt;OAuth provider&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhD-H8ZL9gereTEhCTykXFs80dj46p49qPZgDP84Uw1f-odyvw3mTj-5H0w9fnUftGTbf17AR2QZfiucWA_6-D-3oIfsJDghr4DnUOqLEzrsYuNFkNC2rQJMQdX4-1Iu04KxZvXAwLZSr0k/s1600/GAE-toLeft.png&quot; imageanchor=&quot;1&quot; style=&quot;clear: right; float: right; margin-bottom: 1em; margin-left: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhD-H8ZL9gereTEhCTykXFs80dj46p49qPZgDP84Uw1f-odyvw3mTj-5H0w9fnUftGTbf17AR2QZfiucWA_6-D-3oIfsJDghr4DnUOqLEzrsYuNFkNC2rQJMQdX4-1Iu04KxZvXAwLZSr0k/s1600/GAE-toLeft.png&quot; /&gt;&lt;/a&gt;&lt;/div&gt;There are many known &lt;a href=&quot;http://wiki.oauth.net/w/page/12238551/ServiceProviders&quot;&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&#39;t offer much flexibility if some debugging is required.&lt;br /&gt;
&lt;br /&gt;
For this experiment, I&#39;m going to use &lt;a href=&quot;http://code.google.com/&quot;&gt;Google App Engine&lt;/a&gt; &lt;a href=&quot;http://code.google.com/appengine/docs/java/overview.html&quot;&gt;Java&lt;/a&gt; and their &lt;a href=&quot;http://code.google.com/appengine/docs/java/oauth/overview.html&quot;&gt;OAuth&lt;/a&gt; support. For a complete walk-through, refer to &lt;a href=&quot;http://ikaisays.com/2011/05/26/setting-up-an-oauth-provider-on-google-app-engine/&quot;&gt;Ikai Lan&#39;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=&quot;Apple-style-span&quot; style=&quot;font-size: large;&quot;&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;&#39;Remember me&#39;&lt;/code&gt; option.&lt;br /&gt;
&lt;/li&gt;
&lt;/ul&gt;&lt;a href=&quot;http://draft.blogger.com/post-edit.g?blogID=168828253523263225&amp;amp;postID=1852953982359328834&quot; name=&quot;oauth-figure-1&quot;&gt;&lt;/a&gt;&lt;br /&gt;
&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right: auto; text-align: center;&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjKK6Pg19VA4NpxuzadMUOckwJs4nMUBqu-m1FiuYUZMvgE81MaIJ7bWwoP6Zvn5Q_HDuvKZJL1EZjOUDcRFCoeTYnjp8tjZmWSbKbEz1Vx3Uh9FbtQprx1yqrZR1Odi5edlDewFcNDSorz/s1600/android-oauth-real.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot; rel=&quot;lightbox&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;326&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjKK6Pg19VA4NpxuzadMUOckwJs4nMUBqu-m1FiuYUZMvgE81MaIJ7bWwoP6Zvn5Q_HDuvKZJL1EZjOUDcRFCoeTYnjp8tjZmWSbKbEz1Vx3Uh9FbtQprx1yqrZR1Odi5edlDewFcNDSorz/s400/android-oauth-real.png&quot; width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&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=&quot;http://anothersocialeconomy.appspot.com/widget/maps/stores.jsp&quot;&gt;list of registered stores&lt;/a&gt; in the case of &lt;a href=&quot;http://anothersocialeconomy.com/&quot;&gt;AnotherSocialEconomy.com&lt;/a&gt;, it can be friendlier to delay the authorization verification.&lt;br /&gt;
&lt;br /&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-size: large;&quot;&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=&quot;http://code.google.com/p/oauth-signpost/&quot;&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=&quot;color:black;&quot;&gt;.&lt;/span&gt;&lt;/blockquote&gt;Note that this library is also good to &lt;a href=&quot;https://github.com/kaeppler/signpost-examples/blob/master/OAuthTwitterExample/src/TwitterMain.java&quot;&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=&quot;http://draft.blogger.com/post-edit.g?blogID=168828253523263225&amp;amp;postID=1852953982359328834#oauth-figure-1&quot;&gt;Figure 1&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot; style=&quot;font-size:smaller;&quot;&gt;if (!justAuthenticated &amp;amp;&amp;amp; Preferences.get(Preferences.OAUTH_KEY, &quot;&quot;).length() == 0) {
    // Display the pane with the warning message and the sign in button
    setContentView(R.layout.main_noauth);

    // Update the &#39;Remember me&#39; 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 &#39;Keep me connected&#39; 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(&quot;&amp;lt;your_app_public_key&amp;gt;&quot;, &quot;&amp;lt;your_app_secret_key&amp;gt;&quot;);

            provider = new CommonsHttpOAuthProvider(
                    &quot;https://&amp;lt;your_app_id&amp;gt;.appspot.com/_ah/OAuthGetRequestToken&quot;,
                    &quot;https://&amp;lt;your_app_id&amp;gt;.appspot.com/_ah/OAuthAuthorizeToken&quot;,
                    &quot;https://&amp;lt;your_app_id&amp;gt;.appspot.com/_ah/OAuthGetAccessToken&quot;);
                    
            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, &quot;ase://oauthresponse&quot;);

                // 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(&quot;Dashboard no auth&quot;, &quot;Cannot initiate communication to get the request token\nException: &quot; + ex.getClass().getName() + &quot;\nMessage: &quot; + 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=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; style=&quot;margin-left: auto; margin-right: auto; text-align: center;&quot;&gt;&lt;tbody&gt;
&lt;tr&gt; &lt;td style=&quot;text-align: center;&quot; class=&quot;tr-caption-container&quot;&gt;&lt;a rel=&quot;lightbox&quot; href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhj_KlmILEDIw42vfHAD2cg6sF6giAwP1OuNv3eFsgZAfCdhGdYdIDEtTTPzW7meS-nGqL6zwMny2P0EiNDuaFEeIhbWLUVoy3ua-wqirGrBWOy0cdUlGHZ2vZM8mhyx9hPvFiWfc0zWEKd/s1600/oauth-android-1.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;400&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhj_KlmILEDIw42vfHAD2cg6sF6giAwP1OuNv3eFsgZAfCdhGdYdIDEtTTPzW7meS-nGqL6zwMny2P0EiNDuaFEeIhbWLUVoy3ua-wqirGrBWOy0cdUlGHZ2vZM8mhyx9hPvFiWfc0zWEKd/s400/oauth-android-1.png&quot; width=&quot;240&quot; /&gt;&lt;/a&gt;&lt;br /&gt;
&lt;span style=&quot;font-size:75%&quot;&gt;Figure 2: Pane displayed if application not yet authorized&lt;/span&gt;&lt;/td&gt; &lt;td style=&quot;text-align: center;&quot; class=&quot;tr-caption-container&quot;&gt;&lt;a rel=&quot;lightbox&quot; href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiqugRskUxX5FeMapLFNCvFP4fduG4meHNCJH5lOhOzWfI0lUnv6s3AZ9I6yeHM8UlhYqOBbswcbuJOuYGhT05Ym2-FqPys9CkIKky1kXQy2sasxQVC86IDdmDuNK1iGbp5U5vAdBDS3g11/s1600/oauth-android-2.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;400&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiqugRskUxX5FeMapLFNCvFP4fduG4meHNCJH5lOhOzWfI0lUnv6s3AZ9I6yeHM8UlhYqOBbswcbuJOuYGhT05Ym2-FqPys9CkIKky1kXQy2sasxQVC86IDdmDuNK1iGbp5U5vAdBDS3g11/s400/oauth-android-2.png&quot; width=&quot;240&quot; /&gt;&lt;/a&gt;&lt;br /&gt;
&lt;span style=&quot;font-size:75%&quot;&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=&quot;Apple-style-span&quot; style=&quot;font-size: large;&quot;&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=&quot;prettyprint&quot; style=&quot;font-size:smaller;&quot;&gt;&amp;lt;intent-filter&amp;gt;
    &amp;lt;action android:name=&quot;android.intent.action.VIEW&quot;/&amp;gt;
    &amp;lt;category android:name=&quot;android.intent.category.DEFAULT&quot; /&amp;gt;
    &amp;lt;category android:name=&quot;android.intent.category.BROWSABLE&quot;/&amp;gt;
    &amp;lt;data android:scheme=&quot;ase&quot; android:host=&quot;oauthresponse&quot;/&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=&quot;#oauth-figure-1&quot;&gt;Figure&amp;nbsp;1&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot; style=&quot;font-size:smaller;&quot;&gt;private boolean checkOAuthReturn(Intent intent) {
    boolean returnFromAuth = false;
    Uri uri = intent.getData();

    if (uri != null &amp;amp;&amp;amp; uri.toString().startsWith(&quot;ase://oauthresponse&quot;)) {
        // Step 4:
        // Get the request token from the Authentication log in page
        String code = uri.getQueryParameter(&quot;oauth_verifier&quot;);
            
        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(&quot;Dashboard no auth&quot;, &quot;Cannot complete communication to get the request token\nException: &quot; + ex.getClass().getName() + &quot;\nMessage: &quot; + 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=&quot;https://gist.github.com/1009626#file_dashboard.java&quot;&gt;gist on GitHub&lt;/a&gt;. This gist contains also a &lt;a href=&quot;https://gist.github.com/1009626#file_preferences.java&quot;&gt;wrapper of the &lt;code&gt;SharedPreferences&lt;/code&gt; class&lt;/a&gt;, the &lt;a href=&quot;https://gist.github.com/1009626#file_android_manifest.xml&quot;&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=&quot;https://gist.github.com/1009626#file_main_noauth.xml&quot;&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=&quot;Apple-style-span&quot; style=&quot;font-size: large;&quot;&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=&quot;prettyprint&quot; style=&quot;font-size:smaller;&quot;&gt;@Override
protected void onNewIntent(Intent intent) {
    checkOAuthReturn(intent);
}&lt;/pre&gt;&lt;br /&gt;
In this example, I&#39;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=&quot;prettyprint&quot; style=&quot;font-size:smaller;&quot;&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, &quot;&quot;).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</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'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/168828253523263225/posts/default/1852953982359328834'/><link rel='alternate' type='text/html' href='http://domderrien.blogspot.com/2011/06/oauth-authorization-handling-in-android.html' title='OAuth authorization handling in a Android application'/><author><name>Dom Derrien</name><uri>http://www.blogger.com/profile/15197441189507302313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhD-H8ZL9gereTEhCTykXFs80dj46p49qPZgDP84Uw1f-odyvw3mTj-5H0w9fnUftGTbf17AR2QZfiucWA_6-D-3oIfsJDghr4DnUOqLEzrsYuNFkNC2rQJMQdX4-1Iu04KxZvXAwLZSr0k/s72-c/GAE-toLeft.png" height="72" width="72"/><thr:total>0</thr:total></entry><entry><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><category scheme="http://www.blogger.com/atom/ns#" term="Experience"/><category scheme="http://www.blogger.com/atom/ns#" term="Technology"/><title type='text'>Partage d&#39;expérience dans le développement d&#39;applications Android</title><content type='html'>&lt;span style=&quot;font-size: large;&quot;&gt;Contexte&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;div style=&quot;padding:3px;border:1px solid darkgrey;background-color:lightgrey;&quot;&gt;Note : Cet article  est le premier d&#39;une série intitulée « Leçons d&#39;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=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiLWBBoIDahyesU1BNvL3nxFJU8SM-VwSm5bqgLDuI6e6Wrlh3L51DrY83AEbhVLhS-74qJG7i4X9Jd9fTqdetABg7iFSHXNtLpPMjmO6VWVzsGvCrsC84NGQO2HMi9Ur0CvfJvZUNEj45H/s1600/network-ASE-75.png&quot; imageanchor=&quot;1&quot; style=&quot;clear: right; float: right; margin-bottom: 1em; margin-left: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiLWBBoIDahyesU1BNvL3nxFJU8SM-VwSm5bqgLDuI6e6Wrlh3L51DrY83AEbhVLhS-74qJG7i4X9Jd9fTqdetABg7iFSHXNtLpPMjmO6VWVzsGvCrsC84NGQO2HMi9Ur0CvfJvZUNEj45H/s1600/network-ASE-75.png&quot; /&gt;&lt;/a&gt;&lt;/div&gt;Dans  le cadre de mon entreprise &lt;a href=&quot;http://anothersocialeconomy.com/&quot;&gt;AnotherSocialEconomy.com&lt;/a&gt;, j&#39;ai eu l&#39;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&#39;application cliente Android, depuis  l&#39;identification des usagers jusque l&#39;é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&#39;un produit ou service n&#39;ont qu&#39;à décrire  leur demande depuis l&#39;un des multiples points d&#39;entrée de l&#39;application  : une &lt;a href=&quot;http://anothersocialeconomy.com/Automobiles/index.html&quot;&gt;page&lt;/a&gt; Web faite pour AdWords, le &lt;a href=&quot;http://anothersocialeconomy.com/&quot;&gt;site&lt;/a&gt; de ASE ou affilié, la &lt;a href=&quot;http://apps.facebook.com/AnotherSocialEconomy&quot;&gt;page&lt;/a&gt;  de l&#39;application Facebook, un message direct depuis &lt;a href=&quot;http://twitter.com/&quot;&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&#39;a plus qu&#39;à 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&#39;ils recherchent en inversant le processus de recherche.&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-EgmW5Y-VmCudj2ucPtB_M1wvSmyEA2G0EWe3fRS0sHRQRkuzhzDBrYuYE2PQJDip0IcQmGvZTVStTQp4V6xRDttVfGolOmKRle-m5YRFqG-oK6J04fBbdmc8GpjXGxreo_06aZq9sPuy/s1600/s200_h_ae_gwt_java.png&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-EgmW5Y-VmCudj2ucPtB_M1wvSmyEA2G0EWe3fRS0sHRQRkuzhzDBrYuYE2PQJDip0IcQmGvZTVStTQp4V6xRDttVfGolOmKRle-m5YRFqG-oK6J04fBbdmc8GpjXGxreo_06aZq9sPuy/s1600/s200_h_ae_gwt_java.png&quot; /&gt;&lt;/a&gt;&lt;/div&gt;Le  moteur de ASE est présentement codé en Java et est hébergé sur  l&#39;infrastructure &lt;a href=&quot;http://code.google.com/appengine&quot;&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&#39;application serveur.&lt;br /&gt;
&lt;div style=&quot;clear: both;&quot;&gt;&lt;/div&gt;&lt;span style=&quot;font-size: large;&quot;&gt;La gestion des utilisateurs sur le serveur&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;http://openid.net/wordpress-content/themes/openid/images/logo_openid.png&quot; imageanchor=&quot;1&quot; style=&quot;clear: right; float: right; margin-bottom: 1em; margin-left: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;http://openid.net/wordpress-content/themes/openid/images/logo_openid.png&quot; /&gt;&lt;/a&gt;&lt;/div&gt;Depuis  le départ, le service des usagers de l&#39;application serveur repose sur  &lt;a href=&quot;http://openid.net/&quot;&gt;OpenID&lt;/a&gt;. Avec OpenID, l&#39;identification des utilisateurs est confiée à des  services tiers de confiance (Yahoo!, Google, AOL, etc.) sans que  l&#39;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&#39;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&#39;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&#39;il n&#39;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&#39;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&#39;il a s&#39;agit de développer une application Facebook,  les &lt;a href=&quot;http://developers.facebook.com/docs/authentication/&quot;&gt;mécanismes d&#39;identification de Facebook&lt;/a&gt;, &lt;a href=&quot;http://dev.twitter.com/pages/sign_in_with_twitter&quot;&gt;ceux de Twitter&lt;/a&gt; et de &lt;a href=&quot;http://msdn.microsoft.com/en-us/library/bb676641.aspx&quot;&gt;ceux de Microsoft  Live&lt;/a&gt; ont été intégrés à l&#39;application serveur. Des trois, le mécanisme  de Twitter est le plus standardisé (OAuth), donnant aussi accès aux  données de l&#39;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&#39;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&#39;identité de l&#39;utilisateur est confirmée par un  fournisseur OpenID, tant que cette identité reste associée à la session  Web, l&#39;envoi de données vers les navigateurs reste protégé.&lt;br /&gt;
&lt;br /&gt;
Par  contre, quand l&#39;application cliente est native (sur un ordinateur ou  sur un téléphone mobile), il n&#39;est pas possible de s&#39;appuyer sur un mode  de session Web robuste comme celui des navigateurs. Aussi une  application malicieuse pourrait intercepter l&#39;identifiant de session et  s&#39;en servir à l&#39;insu de l&#39;usager. Pour se prémunir contre cette attaque,  il est souhaitable d&#39;utiliser OAuth qui signe chaque échange entre  l&#39;application cliente et le serveur, rendant caduc l&#39;utilisation de  l&#39;identifiant de session Web.&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;font-size: large;&quot;&gt;L&#39;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&#39;opérateur téléphonique est changée, les données de l&#39;utilisateur  précédent ne sont plus accessibles. Chaque application à accès à son  propre espace de stockage protégé, mais l&#39;utilisateur peut réclamer cet  espace à tout instant. Ce n&#39;est donc pas une solution de stockage à long  terme.&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;http://oauth.net/images/oauth-logo.png&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;http://oauth.net/images/oauth-logo.png&quot; /&gt;&lt;/a&gt;&lt;/div&gt;Dans le modèle d&#39;authentification &lt;a href=&quot;http://oauth.net/&quot;&gt;OAuth&lt;/a&gt;, les  échanges de données sont signés par l&#39;application cliente grâce à un  jeton émis par l&#39;application serveur. Grâce à la signature,  l&#39;application serveur est assurée de l&#39;identité de l&#39;utilisateur à  chaque échange de données.&lt;br /&gt;
&lt;br /&gt;
Pour avoir un jeton, le protocole à observer par l&#39;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&#39;accès.&lt;/li&gt;
&lt;li&gt;Ce jeton est utilisé pour initier un appel vers une page d&#39;autorisation.&lt;/li&gt;
&lt;li&gt;L&#39;application  serveur présente alors une page d&#39;identification où l&#39;utilisateur doit,  s&#39;il n&#39;est pas déjà authentifié, entrer son identifiant et son mot de  passe, puis accepter que l&#39;application cliente accède aux données qui  sont gérées par l&#39;application serveur.&lt;/li&gt;
&lt;li&gt;L&#39;application serveur  retourne un second jeton attestant de l&#39;acceptation par l&#39;utilisateur de  l&#39;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&#39;application cliente de signer les  échanges de données de telle sorte que l&#39;application serveur les  associera à l&#39;utilisateur concerné.&lt;/li&gt;
&lt;li&gt;Souvent ces deux jetons ont  une grande durée de vie (pas d&#39;expiration dans le cas de Twitter), et  peuvent donc être sauvegardés par l&#39;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&#39;utilisateur peut révoquer ces deux jetons n&#39;importe  quand, ou qu&#39;ils peuvent expirer n&#39;importe quand (à cause d&#39;un  changement de stratégie du côté de l&#39;application serveur, par exemple)  aussi il faut être prêt à exécuter le processus pour obtenir deux  nouveaux jetons à n&#39;importe quel moment.&lt;/li&gt;
&lt;/ul&gt;Il est  important de noter que la sauvegarde des jetons d&#39;authentification doit  être très sécuritaire. Il n&#39;est pas acceptable de les sauvegarder dans un simple fichier texte situé &amp;nbsp;sur une carte d&#39;extension mémoire par exemple. Si le risque d&#39;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&#39;é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&#39;offre toujours pas de solution  bas niveau de sécurité maximale...&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;font-size: large;&quot;&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&#39;accent sur la  puissance du processeur central (&lt;a href=&quot;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/&quot;&gt;Qualcomm&lt;/a&gt;) et leur nombre (&lt;a href=&quot;http://androidandme.com/2011/05/news/nvidia-shows-off-the-power-of-kal-el-in-4-minutes-of-video-glory/&quot;&gt;NVidia&lt;/a&gt; vient  d&#39;annoncer un Tegra avec 4 cœurs), si l&#39;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&#39;ai eu des téléphones Nokia et Sony Ericsson capables de  rester en veille plus d&#39;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&#39;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&#39;ai utilisé la technique suivante :&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;L&#39;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&#39;était l&#39;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&#39;application client ouvrait une connexion HTTP pour transmettre en rafale ses données colligés.&lt;/li&gt;
&lt;li&gt;Une fois l&#39;ensemble de données rapatriés de chaque téléphone,  l&#39;application serveur établissaient des rapports de couverture pour l&#39;opérateur.&lt;/li&gt;
&lt;/ul&gt;Depuis la version 2.2, il existe le protocole AC2DM: &lt;i&gt;&lt;a href=&quot;http://code.google.com/android/c2dm/&quot;&gt;Android  Cloud to Device Messaging&lt;/a&gt;&lt;/i&gt;. Quand une application cliente configurée pour  AC2DM s&#39;initialise, elle doit s&#39;enregistrer auprès du serveur local  AC2DM et reçoit en retour un identifiant d&#39;enregistrement. C&#39;est la  responsabilité de l&#39;application cliente d&#39;envoyer cet identifiant à  l&#39;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&#39;approche du AC2DM est semblable à ma méthode d&#39;activation par  SMS. Il se peut même qu&#39;elle utilise en sous main cette technique ;) La  principale différence réside dans l&#39;aspect service : avec AC2DM,  l&#39;application cliente n&#39;a pas à rester active pour recevoir les  notifications, c&#39;est le serveur local de notifications qu&#39;il l&#39;activera  au besoin.&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;font-size: large;&quot;&gt;L&#39;application pour les consommateurs&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
L&#39;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&#39;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&#39;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&#39;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&#39;elles sont requises. Pour garder une structure proche du modèle de  données produit par l&#39;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=&quot;font-size: large;&quot;&gt;L&#39;application pour les détaillants&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
L&#39;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=&quot;Apple-style-span&quot; style=&quot;font-size: large;&quot;&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&#39;ai réalisées. Il y a plusieurs techniques qui ne sont pas évidentes, comme celle gérant l&#39;authentification avec OAuth, et j&#39;imagine que cela sera utile à bien des développeurs ;)&lt;br /&gt;
&lt;br /&gt;
A+, Dom</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'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/168828253523263225/posts/default/7112196750491780099'/><link rel='alternate' type='text/html' href='http://domderrien.blogspot.com/2011/06/partage-dexperience-dans-le.html' title='Partage d&#39;expérience dans le développement d&#39;applications Android'/><author><name>Dom Derrien</name><uri>http://www.blogger.com/profile/15197441189507302313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiLWBBoIDahyesU1BNvL3nxFJU8SM-VwSm5bqgLDuI6e6Wrlh3L51DrY83AEbhVLhS-74qJG7i4X9Jd9fTqdetABg7iFSHXNtLpPMjmO6VWVzsGvCrsC84NGQO2HMi9Ur0CvfJvZUNEj45H/s72-c/network-ASE-75.png" height="72" width="72"/><thr:total>0</thr:total></entry><entry><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><category scheme="http://www.blogger.com/atom/ns#" term="Experience"/><category scheme="http://www.blogger.com/atom/ns#" term="Technology"/><title type='text'>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&#39;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=&quot;font-size: large;&quot;&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=&quot;http://db.apache.org/jdo/jdo_v_jpa.html&quot;&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=&quot;prettyprint&quot;&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=&quot;prettyprint&quot;&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&#39;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=&quot;http://anothersocialeconomy.com/&quot;&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=&quot;http://code.google.com/appengine/docs/java/runtime.html#The_Request_Timer&quot;&gt;30 second limit&lt;/a&gt;, I decided to use tasks extensively (tasks can run for &lt;a href=&quot;http://code.google.com/appengine/docs/java/taskqueue/overview.html#Task_Execution&quot;&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=&quot;prettyprint&quot;&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(&quot;/_tasks/validateOpenDemand&quot;).
                param(Demand.KEY, demandKey.toString()).
                method(Method.GET)
        );&lt;/b&gt;
    }
    finally {
        pm.close();
    }
}&lt;/pre&gt;&lt;br /&gt;
&lt;span style=&quot;font-size: large;&quot;&gt;Issue&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
Until I activated the &lt;a href=&quot;http://code.google.com/appengine/docs/adminconsole/instances.html#Always_On&quot;&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=&quot;http://code.google.com/appengine/docs/java/taskqueue/overview.html#Task_Execution&quot;&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=&quot;font-size: large;&quot;&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=&quot;prettyprint&quot;&gt;// Create a task for that demand validation
        getQueue().add(
            withUrl(&quot;/_tasks/validateOpenDemand&quot;).
                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=&quot;http://code.google.com/appengine/docs/java/memcache/overview.html&quot;&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&#39;s no warranty that your entity won&#39;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=&quot;prettyprint&quot;&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(&quot;/_tasks/validateOpenDemand&quot;).
            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=&quot;http://code.google.com/appengine/docs/java/datastore/transactions.html&quot;&gt;transaction&lt;/a&gt;. When a task is scheduled within a transaction, it&#39;s really &lt;a href=&quot;http://code.google.com/appengine/docs/java/taskqueue/overview.html#Tasks_Within_Transactions&quot;&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=&quot;font-size: large;&quot;&gt;Conclusion&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
Now I understand the issue, I&#39;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=&quot;font-size: large;&quot;&gt;Notes:&lt;/span&gt;&lt;br /&gt;
* These days, I would start my application with &lt;a href=&quot;http://code.google.com/p/objectify-appengine/&quot;&gt;Objectify&lt;/a&gt;. This &lt;a href=&quot;http://borglin.net/gwt-project/?page_id=491&quot;&gt;blog post&lt;/a&gt; summarizes many arguments I agree on too in favor to Objectify.</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'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/168828253523263225/posts/default/6599301892451796655'/><link rel='alternate' type='text/html' href='http://domderrien.blogspot.com/2011/04/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>http://www.blogger.com/profile/15197441189507302313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><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><category scheme="http://www.blogger.com/atom/ns#" term="Entrepreneurship"/><category scheme="http://www.blogger.com/atom/ns#" term="Experience"/><title type='text'>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=&quot;http://stevenmilstein.com/2009/09/16/homage-to-techcrunch50-2009-its-organizers-and-participants/&quot;&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=&quot;http://apps.facebook.com/anothersocialeconomy/?ref=bookmarks&amp;amp;count=0&quot;&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=&quot;http://eztoff.com/&quot;&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=&quot;http://anothersocialeconomy.com/CarDealers/index.html&quot;&gt;used car market&lt;/a&gt;, under the name &lt;i&gt;&lt;a href=&quot;http://anothersocialeconomy.com/&quot;&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&#39;s very time consuming...&lt;/li&gt;
&lt;/ul&gt;&lt;div&gt;&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-size: large;&quot;&gt;What&#39;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=&quot;Apple-style-span&quot; style=&quot;font-size: large;&quot;&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&#39;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&#39;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&#39;s &lt;a href=&quot;http://www.ashmaurya.com/2010/02/customer-development-checklist-for-my-web-startup-part-1/&quot;&gt;blog&lt;/a&gt;, for example.&lt;br /&gt;
&lt;br /&gt;
&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-size: large;&quot;&gt;Technologies learned?&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhaeJ0HOPGVq4426EWvpliD-MyfMOglCDSMQzKhDtmfLOp0gcEf20uwQBGm78PiupslucrCxAkH-_cQ44tuFL1KFdx8igrQiMAPKHAEdTFYDD34nrqDWCN12BT5UAManT3BaJ_ZZGfL9oVL/s1600/GAE-toRight.png&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhaeJ0HOPGVq4426EWvpliD-MyfMOglCDSMQzKhDtmfLOp0gcEf20uwQBGm78PiupslucrCxAkH-_cQ44tuFL1KFdx8igrQiMAPKHAEdTFYDD34nrqDWCN12BT5UAManT3BaJ_ZZGfL9oVL/s1600/GAE-toRight.png&quot; /&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=&quot;http://code.google.com/appengine/docs/java/channel/overview.html&quot;&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=&quot;http://code.google.com/appengine/docs/java/tools/remoteapi.html&quot;&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=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgOuU5x77JeJs2x85HFvxxZyuQ6Rou8EEv7f6Sz9xVLWNeleFb0Bahxfe841n9oVDySYhV3aUNUtbZ2I5gXPaSRZ-c4yUXwAQeN9Z1bdFCifWhM8iUYrwJ5KYnLn90kL2ATLuOigxWP0MQp/s1600/Selection_009.png&quot; imageanchor=&quot;1&quot; style=&quot;clear: right; float: right; margin-bottom: 1em; margin-left: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgOuU5x77JeJs2x85HFvxxZyuQ6Rou8EEv7f6Sz9xVLWNeleFb0Bahxfe841n9oVDySYhV3aUNUtbZ2I5gXPaSRZ-c4yUXwAQeN9Z1bdFCifWhM8iUYrwJ5KYnLn90kL2ATLuOigxWP0MQp/s1600/Selection_009.png&quot; /&gt;&lt;/a&gt;&lt;/div&gt;Web console side, I&#39;ve started upgrading the code to &lt;a href=&quot;http://dojotoolkit.org/reference-guide/releasenotes/1.6.html&quot;&gt;Dojo 1.6&lt;/a&gt; and its new HTML5 compliant syntax. I don&#39;t use the AMD loader yet, but I&#39;m waiting for the one coming with 1.7. I have recently started to use &lt;a href=&quot;http://code.google.com/p/selenium/wiki/ArchitecturalOverview&quot;&gt;Selenium 2&lt;/a&gt; for my smoke tests and I really like it!&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgSwdseb0aTsqFjAd_xeUzD9C7B5QwxLGcJFnwbISzyu3LwvMlYBsCauZTOlRh_JhWj54binQDLh6z8rR3uBk0YVVEFKgFK89Zz5zi98zq_6qdq8o-J_FQYliZOUdXM3cw5AwbmZ4Tx3_WJ/s1600/Selection_010.png&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgSwdseb0aTsqFjAd_xeUzD9C7B5QwxLGcJFnwbISzyu3LwvMlYBsCauZTOlRh_JhWj54binQDLh6z8rR3uBk0YVVEFKgFK89Zz5zi98zq_6qdq8o-J_FQYliZOUdXM3cw5AwbmZ4Tx3_WJ/s1600/Selection_010.png&quot; /&gt;&lt;/a&gt;&lt;/div&gt;Mobile side, I wish to have more spare time to update &lt;a href=&quot;https://github.com/domderrien/dretailer/&quot;&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=&quot;http://android-developers.blogspot.com/2010/05/android-cloud-to-device-messaging.html&quot;&gt;C2DM&lt;/a&gt;) API. But I&#39;m also thinking of building application with the awesome&amp;nbsp;&lt;a href=&quot;http://davidwalsh.name/dojox-mobile&quot;&gt;dojox.mobile&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;http://upload.wikimedia.org/wikipedia/commons/f/fd/Adwords_logo.png&quot; imageanchor=&quot;1&quot; style=&quot;clear: right; float: right; margin-bottom: 1em; margin-left: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;http://upload.wikimedia.org/wikipedia/commons/f/fd/Adwords_logo.png&quot; /&gt;&lt;/a&gt;&lt;/div&gt;To develop our customer base in the used car experiment, we have created two &lt;a href=&quot;https://adwords.google.com/&quot;&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=&quot;http://www.sayu.co.uk/keyword-expansion-white-paper-1.html&quot;&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;</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'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/168828253523263225/posts/default/6833757880225685415'/><link rel='alternate' type='text/html' href='http://domderrien.blogspot.com/2011/04/state-of-anothersocialeconomy.html' title='State of the AnotherSocialEconomy Initiative'/><author><name>Dom Derrien</name><uri>http://www.blogger.com/profile/15197441189507302313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhaeJ0HOPGVq4426EWvpliD-MyfMOglCDSMQzKhDtmfLOp0gcEf20uwQBGm78PiupslucrCxAkH-_cQ44tuFL1KFdx8igrQiMAPKHAEdTFYDD34nrqDWCN12BT5UAManT3BaJ_ZZGfL9oVL/s72-c/GAE-toRight.png" height="72" width="72"/><thr:total>0</thr:total></entry><entry><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><category scheme="http://www.blogger.com/atom/ns#" term="Community"/><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="Technology"/><title type='text'>Wrapping up 2010, preparing 2011</title><content type='html'>&lt;span style=&quot;font-size: large;&quot;&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=&quot;http://www.compuware.com/&quot;&gt;Compuware&lt;/a&gt; and joined my partner Steven at &lt;a href=&quot;http://milstein-assoc.com/&quot;&gt;Milstein and Associates inc.&lt;/a&gt; end-of-January, to focus 150% of my time on &lt;a href=&quot;http://anothersocialeconomy.com/&quot;&gt;AnotherSocialEconomy (&lt;/a&gt;formely known as Twetailer).&lt;/li&gt;
&lt;li&gt;Adapted the &lt;a href=&quot;http://domderrien.blogspot.com/2010/03/httpdomderrien-preblogspotcom201003amaz.html&quot;&gt;Amazon Flexible Payment Service (FPS) library&lt;/a&gt; to the App Engine environment—freely available on &lt;a href=&quot;https://github.com/DomDerrien/amazon-fps-gaej/&quot;&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=&quot;http://eztoff.com/&quot;&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=&quot;https://github.com/DomDerrien/dretailer/&quot;&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=&quot;http://anothersocialeconomy.com/whats-with-widgets/&quot;&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=&quot;http://domderrien.blogspot.com/2010/12/reviewed-book-google-app-engine-java.html&quot;&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=&quot;https://github.com/DomDerrien/two-tiers-utils/&quot;&gt;github&lt;/a&gt; too.&lt;/li&gt;
&lt;li&gt;Developed a prototype of a &lt;a href=&quot;http://apps.facebook.com/anothersocialeconomy/&quot;&gt;Facebook application&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Augmented the AnotherSocialEconomy engine to support the used car dealers: buyers don&#39;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=&quot;http://www.mtlnewtech.com/&quot;&gt;Montreal NewTech&lt;/a&gt;, &lt;a href=&quot;http://www.android-montreal.com/&quot;&gt;Android Montreal&lt;/a&gt;, &lt;a href=&quot;http://www.meetup.com/Realite-Augmentee-Montreal-Augmented-Reality/&quot;&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=&quot;font-size: large;&quot;&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=&quot;font-size: large;&quot;&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=&quot;http://maps.google.com/maps?q=montreal,qc,canada&quot;&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=&quot;http://en.wikipedia.org/wiki/Data_mining&quot;&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=&quot;http://www.twilio.com/&quot;&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=&quot;http://code.google.com/appengine/&quot;&gt; App Engine&lt;/a&gt; and mobile applications on &lt;a href=&quot;http://www.android.com/&quot;&gt;Android&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;A+, Dom</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'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/168828253523263225/posts/default/5610405347014299744'/><link rel='alternate' type='text/html' href='http://domderrien.blogspot.com/2011/01/wrapping-up-2010-preparing-2011.html' title='Wrapping up 2010, preparing 2011'/><author><name>Dom Derrien</name><uri>http://www.blogger.com/profile/15197441189507302313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><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><category scheme="http://www.blogger.com/atom/ns#" term="Community"/><category scheme="http://www.blogger.com/atom/ns#" term="Experience"/><category scheme="http://www.blogger.com/atom/ns#" term="Technology"/><title type='text'>Reviewed book &#39;Google App Engine Java and GWT Application Development&#39; is out!</title><content type='html'>I know that&#39;s a pity not to post more regularly! It&#39;s just I&#39;m too busy with the developments for &lt;a href=&quot;http://anothersocialeconomy.com/&quot;&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&#39;ve been asked to review the draft of the book &lt;a href=&quot;https://www.packtpub.com/google-app-engine-java-and-gwt-application-development/book&quot;&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=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://www.packtpub.com/google-app-engine-java-and-gwt-application-development/book&quot; target=&quot;_blank&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-zx6pGD8rCFsFQno59xUG_uHM7JTRJ28CgsvCl_dMf-IBCcBpxYoCp3YZh9_tlsKFTM_22BC91T7Kj-nHFki4YT3fhZvmQFVyWUB2gudpASbXejdeJNSb4T4-oB-n0T93aepRAFMsbh8T/s1600/Selection_012.png&quot; title=&quot;The book &#39;Google App Engine Java and GWT Application Development&#39; on Packt Publishing website&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
Enjoy!&lt;br /&gt;
A+, Dom&lt;br /&gt;
&lt;br /&gt;
Note: I&#39;ve no incentive to sell the book, just the pleasure to share a good reference ;)</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'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/168828253523263225/posts/default/1743022830778412789'/><link rel='alternate' type='text/html' href='http://domderrien.blogspot.com/2010/12/reviewed-book-google-app-engine-java.html' title='Reviewed book &#39;Google App Engine Java and GWT Application Development&#39; is out!'/><author><name>Dom Derrien</name><uri>http://www.blogger.com/profile/15197441189507302313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-zx6pGD8rCFsFQno59xUG_uHM7JTRJ28CgsvCl_dMf-IBCcBpxYoCp3YZh9_tlsKFTM_22BC91T7Kj-nHFki4YT3fhZvmQFVyWUB2gudpASbXejdeJNSb4T4-oB-n0T93aepRAFMsbh8T/s72-c/Selection_012.png" height="72" width="72"/><thr:total>0</thr:total></entry><entry><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><category scheme="http://www.blogger.com/atom/ns#" term="Experience"/><category scheme="http://www.blogger.com/atom/ns#" term="Technology"/><title type='text'>Securing accounts on the Web</title><content type='html'>&lt;span style=&quot;font-size: large;&quot;&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=&quot;http://twitter.com/stevenmilstein/statuses/24054949446&quot;&gt;#1&lt;/a&gt; at 8:52 PM on Sept. 9: Just received 2 calls from friends wondering if I&#39;m being held at a London hotel. FYI, I&#39;m not.&lt;/li&gt;
&lt;li&gt;Tweet &lt;a href=&quot;http://twitter.com/stevenmilstein/statuses/24071815656&quot;&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&#39;ve got it back now but with no Contacts.&lt;/li&gt;
&lt;li&gt;Tweet &lt;a href=&quot;http://twitter.com/stevenmilstein/statuses/24126191330&quot;&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=&quot;http://bit.ly/czgYdg&quot;&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=&quot;http://en.wikipedia.org/wiki/Scam&quot;&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&#39;s password was not very strong, there&#39;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=&quot;font-size: large;&quot;&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&#39;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=&quot;http://www.westernunion.com/WUCOMWEB/staticMid.do?method=load&amp;amp;pagename=fraudScams&quot;&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=&quot;Apple-style-span&quot; style=&quot;font-size: xx-large;&quot;&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=&quot;http://www.microsoft.com/protect/fraud/passwords/create.aspx&quot;&gt;Strong Passwords | Microsoft Security&lt;/a&gt;—I&#39;m confident that they don&#39;t provide the &lt;a href=&quot;https://www.microsoft.com/protect/fraud/passwords/checker.aspx&quot;&gt;online password checker&lt;/a&gt; to enhance a grey&amp;nbsp;&lt;a href=&quot;http://en.wikipedia.org/wiki/Dictionary_attack&quot;&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&#39;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=&quot;http://keepass.info/&quot;&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=&quot;http://keepass.info/download.html&quot;&gt;download&lt;/a&gt; page.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.dropbox.com/referrals/NTE0NDA5MTk&quot;&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=&quot;http://keepass.info/help/base/pwgenerator.html&quot;&gt;password generator&lt;/a&gt; and Keepass &lt;a href=&quot;http://keepass.info/help/base/secedits.html&quot;&gt;secure edit controls&lt;/a&gt; makes the tool especially useful:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;It&#39;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&#39;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=&quot;font-size: large;&quot;&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&#39;ll see that some questions might weaken users more than offering a protection... These days, it&#39;s pretty simple to find the responses online!&lt;br /&gt;
&lt;br /&gt;
&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right: auto; text-align: center;&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjhyphenhyphenafyhwili0hlDcWfWCTHWzM8Q40u57ywjBHhMNbgmLdAKM9Lg_UnvAXZSfZuto6-c11NuXkgQH500O1gSr3WjjuuB58NdTJchBWzZgo822ECA6zMa2R2FqPV7GQ0WJouc6GCL_57a-f_/s1600/Apple+-+My+Apple+ID+-+Mozilla+Firefox_011.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjhyphenhyphenafyhwili0hlDcWfWCTHWzM8Q40u57ywjBHhMNbgmLdAKM9Lg_UnvAXZSfZuto6-c11NuXkgQH500O1gSr3WjjuuB58NdTJchBWzZgo822ECA6zMa2R2FqPV7GQ0WJouc6GCL_57a-f_/s1600/Apple+-+My+Apple+ID+-+Mozilla+Firefox_011.png&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&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&#39;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=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right: auto; text-align: center;&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhkLYgwt34wfGakaHjxBKagt5xQIcVKKrOBB819Efmv7qR2JPCqmRtu-czjVNVtW-qYhg0rJn-yZi_WzSCNeClgaeDgG95F5IA8Syf7DP2pomQCPDkExBqRA1_K6UTtwk7rPqrToPaLmfiy/s1600/Selection_012.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;226&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhkLYgwt34wfGakaHjxBKagt5xQIcVKKrOBB819Efmv7qR2JPCqmRtu-czjVNVtW-qYhg0rJn-yZi_WzSCNeClgaeDgG95F5IA8Syf7DP2pomQCPDkExBqRA1_K6UTtwk7rPqrToPaLmfiy/s400/Selection_012.png&quot; width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&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=&quot;Apple-style-span&quot;&gt;&lt;span class=&quot;Apple-style-span&quot; style=&quot;font-size: x-large;&quot;&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=&quot;http://googleonlinesecurity.blogspot.com/2010/09/moving-security-beyond-passwords.html&quot;&gt;Two-Step Authentication&lt;/a&gt; mechanism to log into Google services. This &lt;a href=&quot;http://en.wikipedia.org/wiki/One-time_password&quot;&gt;One-Time Password&lt;/a&gt; authentication is simpler than distributing a one-time password generator, &lt;a href=&quot;http://aws.amazon.com/mfa/&quot;&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</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'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/168828253523263225/posts/default/6197557307103404375'/><link rel='alternate' type='text/html' href='http://domderrien.blogspot.com/2010/09/securing-accounts-on-web.html' title='Securing accounts on the Web'/><author><name>Dom Derrien</name><uri>http://www.blogger.com/profile/15197441189507302313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjhyphenhyphenafyhwili0hlDcWfWCTHWzM8Q40u57ywjBHhMNbgmLdAKM9Lg_UnvAXZSfZuto6-c11NuXkgQH500O1gSr3WjjuuB58NdTJchBWzZgo822ECA6zMa2R2FqPV7GQ0WJouc6GCL_57a-f_/s72-c/Apple+-+My+Apple+ID+-+Mozilla+Firefox_011.png" height="72" width="72"/><thr:total>1</thr:total></entry><entry><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><title type='text'>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=&quot;http://dilbert.com/strips/comic/2010-09-13/&quot; title=&quot;Dilbert.com&quot;&gt;&lt;img alt=&quot;Dilbert.com&quot; border=&quot;0&quot; src=&quot;http://dilbert.com/dyn/str_strip/000000000/00000000/0000000/100000/00000/0000/100/100155/100155.strip.gif&quot; /&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=&quot;http://domderrien.blogspot.com/2008/12/manager-attitude.html&quot;&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&#39;s more related to the difference in each person&#39;s &lt;a href=&quot;http://www.acrwebsite.org/volumes/display.asp?id=5867&quot;&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=&quot;http://domderrien.blogspot.com/2010/05/work-around-general-laziness.html&quot;&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=&quot;http://edu.cyn.in/&quot;&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&#39;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</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'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/168828253523263225/posts/default/596900976299676303'/><link rel='alternate' type='text/html' href='http://domderrien.blogspot.com/2010/09/dilbert-social-media-age.html' title='Dilbert: Social Media age'/><author><name>Dom Derrien</name><uri>http://www.blogger.com/profile/15197441189507302313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><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><category scheme="http://www.blogger.com/atom/ns#" term="Entrepreneurship"/><category scheme="http://www.blogger.com/atom/ns#" term="Experience"/><title type='text'>Update business-side</title><content type='html'>It&#39;s been a long time since my last post. The summer went by so quickly. With my partner &lt;a href=&quot;http://stevenmilstein.com/&quot;&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=&quot;font-size: large;&quot;&gt;The product name&lt;/span&gt; &lt;br /&gt;
&lt;br /&gt;
The product name &lt;a href=&quot;http://twetailer.com/&quot;&gt;Twetailer&lt;/a&gt; is now &lt;a href=&quot;http://anothersocialeconomy.com/&quot;&gt;AnotherSocialEconomy.com&lt;/a&gt;. &lt;b&gt;Twetailer&lt;/b&gt; was chosen when &lt;a href=&quot;http://twitter.com/&quot;&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&#39;s blog post &lt;a href=&quot;http://stevenmilstein.com/2010/08/26/the-twouble-with-twetailer/&quot;&gt;The Twouble with Twetailer&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;font-size: large;&quot;&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&#39;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=&quot;http://ca.linkedin.com/pub/marc-bienstock/1/86a/860&quot;&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&#39;s an average of $200 lost.&lt;/li&gt;
&lt;/ul&gt;So end-of-July, we launched &lt;a href=&quot;http://eztoff.com/&quot;&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&#39;ll be cc&#39;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&#39;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&#39;s pain. With some visits to golf courses, we&#39;ve confirmed the courses&#39; pain too. The difficulty we have is that we&#39;re not &lt;i&gt;sales people&lt;/i&gt; and it&#39;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&#39;re going to tune the offer and be ready for the 2011 season.&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;font-size: large;&quot;&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&#39; 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&#39; response.&lt;/li&gt;
&lt;li&gt;We&#39;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&#39;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&#39;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&#39;s fine. We&#39;ll make some money if they make some, and they&#39;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=&quot;font-size: large;&quot;&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&#39;s also the Android application (project &lt;b&gt;&lt;a href=&quot;http://github.com/domderrien/dretailer/&quot;&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</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'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/168828253523263225/posts/default/7153701713028671877'/><link rel='alternate' type='text/html' href='http://domderrien.blogspot.com/2010/09/update-business-side.html' title='Update business-side'/><author><name>Dom Derrien</name><uri>http://www.blogger.com/profile/15197441189507302313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><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><category scheme="http://www.blogger.com/atom/ns#" term="Community"/><category scheme="http://www.blogger.com/atom/ns#" term="Technology"/><title type='text'>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=&quot;http://www.virtualbox.org/manual/ch08.html#idp14334672&quot;&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 &#39;Create and format hard disk partitions&#39;).&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 &#39;Extend Volume&#39; 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=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right: auto; text-align: center;&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiHlMXSGORtCoaIqgFdOwOTr5vbIm5Uj31jpkjqP5kl977tga6I2zIzp-L1O2MuTdzdmuVFwwiM22cD-siNdVuuyiuGvJpcrkxgRbcWHT9XtlNNq2vpNQJeSGGTJu20-BtsblLvJUCdVes7/s1600/ill.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiHlMXSGORtCoaIqgFdOwOTr5vbIm5Uj31jpkjqP5kl977tga6I2zIzp-L1O2MuTdzdmuVFwwiM22cD-siNdVuuyiuGvJpcrkxgRbcWHT9XtlNNq2vpNQJeSGGTJu20-BtsblLvJUCdVes7/s1600/ill.png&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;&#39;Extend Volume&#39; 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=&quot;http://forums.virtualbox.org/viewtopic.php?f=6&amp;amp;t=22422&quot;&gt;CloneVDI&lt;/a&gt;!&lt;br /&gt;
&lt;br /&gt;
Almost two months ago, I switched from Windows XP to &lt;a href=&quot;http://domderrien.blogspot.com/2010/06/joy-of-ubuntu.html&quot;&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=&quot;http://domderrien.blogspot.com/2010/03/ladies-and-gentlemen-here-is-twetailer.html&quot;&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&#39;t it?).&lt;br /&gt;
&lt;br /&gt;
To verify my test suites for Internet Explorer, I rely on &lt;a href=&quot;http://virtualbox.org/&quot;&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 &quot;Not enough space available&quot; warning :-( Instead of wasting another set of hours in a re-installation, I googled &lt;a href=&quot;http://www.google.com/search?hl=en&amp;amp;q=virtualbox+increase+vdi+size&quot;&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=&quot;http://forums.virtualbox.org/viewtopic.php?f=1&amp;amp;t=364&amp;amp;start=75&quot;&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=&quot;http://forums.virtualbox.org/viewtopic.php?f=6&amp;amp;t=22422&quot;&gt;CloneVDI&lt;/a&gt; tool. Enter name of source VDI and desired disk size into dialog box. Click &quot;Proceed&quot; 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=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right: auto; text-align: center;&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;409&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiYLFiyjL39ZbZCC3jIRvq2-NR_hvMoSdXAymkhC1wu6AcPMvrKyKiC_G_6sBOjTmWkhDvp2pPYjZvWvl7Ve6Seec4yWBed0J9_0jSVVPwi62JNcZjZHNCGZ4wI_sLg8RieVGhKTTe7tbg_/s640/Virtual+HD+Clone+and+Optimize+v2.02_004.png&quot; width=&quot;550&quot; /&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&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=&quot;http://forums.virtualbox.org/viewtopic.php?f=6&amp;amp;t=22422&quot;&gt;CloneVDI&lt;/a&gt; when it&#39;s time to allocate more disk space to a VirtualBox machine!&lt;br /&gt;
&lt;br /&gt;
A+, Dom</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'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/168828253523263225/posts/default/5251967564888404659'/><link rel='alternate' type='text/html' href='http://domderrien.blogspot.com/2010/07/how-to-resize-virtualbox-disk-images.html' title='How to resize VirtualBox disk images?'/><author><name>Dom Derrien</name><uri>http://www.blogger.com/profile/15197441189507302313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiHlMXSGORtCoaIqgFdOwOTr5vbIm5Uj31jpkjqP5kl977tga6I2zIzp-L1O2MuTdzdmuVFwwiM22cD-siNdVuuyiuGvJpcrkxgRbcWHT9XtlNNq2vpNQJeSGGTJu20-BtsblLvJUCdVes7/s72-c/ill.png" height="72" width="72"/><thr:total>3</thr:total></entry><entry><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><category scheme="http://www.blogger.com/atom/ns#" term="Community"/><category scheme="http://www.blogger.com/atom/ns#" term="Technology"/><title type='text'>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=&quot;http://en.wikipedia.org/wiki/Solaris_%28operating_system%29&quot; target=&quot;_blank&quot;&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=&quot;http://en.wikipedia.org/wiki/Mosaic_%28web_browser%29&quot; target=&quot;_blank&quot;&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=&quot;http://www.ubuntu.com/&quot; target=&quot;_blank&quot;&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 &quot;cooperative multitasking&quot; 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&#39;t know ;)&lt;br /&gt;
&lt;br /&gt;
To be honest, let&#39;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=&quot;http://www.knopper.net/knoppix/index-en.html&quot; target=&quot;_blank&quot;&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=&quot;http://www.compiz.org/&quot; target=&quot;_blank&quot;&gt;Compiz&lt;/a&gt; and its &lt;a href=&quot;http://wiki.compiz.org/Plugins/Cube&quot; target=_blank&quot;&gt;Cube plugin&lt;/a&gt;! It&#39;s just amazing.&lt;br /&gt;
&lt;br /&gt;
&lt;center&gt;&lt;object width=&quot;640&quot; height=&quot;385&quot;&gt;&lt;param name=&quot;movie&quot; value=&quot;http://www.youtube.com/v/WturcjzIjXg&amp;hl=en_US&amp;fs=1&amp;rel=0&quot;&gt;&lt;/param&gt;&lt;param name=&quot;allowFullScreen&quot; value=&quot;true&quot;&gt;&lt;/param&gt;&lt;param name=&quot;allowscriptaccess&quot; value=&quot;always&quot;&gt;&lt;/param&gt;&lt;embed src=&quot;http://www.youtube.com/v/WturcjzIjXg&amp;hl=en_US&amp;fs=1&amp;rel=0&quot; type=&quot;application/x-shockwave-flash&quot; allowscriptaccess=&quot;always&quot; allowfullscreen=&quot;true&quot; width=&quot;640&quot; height=&quot;385&quot;&gt;&lt;/embed&gt;&lt;/object&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;object width=&quot;480&quot; height=&quot;385&quot;&gt;&lt;param name=&quot;movie&quot; value=&quot;http://www.youtube.com/v/5baDknt6Z7w&amp;hl=en_US&amp;fs=1&amp;rel=0&quot;&gt;&lt;/param&gt;&lt;param name=&quot;allowFullScreen&quot; value=&quot;true&quot;&gt;&lt;/param&gt;&lt;param name=&quot;allowscriptaccess&quot; value=&quot;always&quot;&gt;&lt;/param&gt;&lt;embed src=&quot;http://www.youtube.com/v/5baDknt6Z7w&amp;hl=en_US&amp;fs=1&amp;rel=0&quot; type=&quot;application/x-shockwave-flash&quot; allowscriptaccess=&quot;always&quot; allowfullscreen=&quot;true&quot; width=&quot;480&quot; height=&quot;385&quot;&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=&quot;https://www.dropbox.com/downloading?os=lnx&quot; target=&quot;_blank&quot;&gt;Dropbox&lt;/a&gt;: has Linux/MacOS/Windows distributions.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://keepass.info/&quot; target=&quot;_blank&quot;&gt;Keepass&lt;/a&gt;: had to convert the database to 1.x format and now use &lt;a href=&quot;http://www.keepassx.org/downloads&quot; target=&quot;_blank&quot;&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=&quot;http://www.skype.com/intl/en-us/get-skype/on-your-computer/linux/&quot; target=&quot;_blank&quot;&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&#39;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</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'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/168828253523263225/posts/default/2911333838832619867'/><link rel='alternate' type='text/html' href='http://domderrien.blogspot.com/2010/06/joy-of-ubuntu.html' title='The joy of Ubuntu'/><author><name>Dom Derrien</name><uri>http://www.blogger.com/profile/15197441189507302313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><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><category scheme="http://www.blogger.com/atom/ns#" term="Community"/><category scheme="http://www.blogger.com/atom/ns#" term="Entrepreneurship"/><category scheme="http://www.blogger.com/atom/ns#" term="Experience"/><title type='text'>What motivates us?</title><content type='html'>One year and half ago, I wrote the blog post &lt;a href=&quot;http://domderrien.blogspot.com/2008/12/career-advice.html&quot;&gt;Career Advice &#39;08&lt;/a&gt;. The key idea was about encouraging people to develop their own expertise to be successful in their career. Don&#39;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=&quot;http://domderrien.blogspot.com/2010/05/work-around-general-laziness.html&quot;&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&#39;s &lt;a href=&quot;http://stevenmilstein.com/2010/04/19/kool-aid-being-served-here/&quot; target=&quot;_blank&quot;&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&#39;s illustration of &quot;What motivates us&quot;, for a talk given by the Royal Society for the encouragement of Arts, Manufactures and Commerce (&lt;a href=&quot;http://draft.blogger.com/thersa.org&quot; target=&quot;_blank&quot;&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=&quot;340&quot; width=&quot;560&quot;&gt;&lt;param name=&quot;movie&quot; value=&quot;http://www.youtube.com/v/u6XAPnuFjJc&amp;hl=en_US&amp;fs=1&amp;color1=0x5d1719&amp;color2=0xcd311b&quot;&gt;&lt;/param&gt;&lt;param name=&quot;allowFullScreen&quot; value=&quot;true&quot;&gt;&lt;/param&gt;&lt;param name=&quot;allowscriptaccess&quot; value=&quot;always&quot;&gt;&lt;/param&gt;&lt;embed src=&quot;http://www.youtube.com/v/u6XAPnuFjJc&amp;hl=en_US&amp;fs=1&amp;color1=0x5d1719&amp;color2=0xcd311b&quot; type=&quot;application/x-shockwave-flash&quot; allowscriptaccess=&quot;always&quot; allowfullscreen=&quot;true&quot; width=&quot;560&quot; height=&quot;340&quot;&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</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'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/168828253523263225/posts/default/1881803729057471072'/><link rel='alternate' type='text/html' href='http://domderrien.blogspot.com/2010/05/what-motivates-is.html' title='What motivates us?'/><author><name>Dom Derrien</name><uri>http://www.blogger.com/profile/15197441189507302313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><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><category scheme="http://www.blogger.com/atom/ns#" term="Entrepreneurship"/><category scheme="http://www.blogger.com/atom/ns#" term="Experience"/><title type='text'>Work around the general laziness ;)</title><content type='html'>This blog entry is primarily inspired by a rant from &lt;a href=&quot;http://thisweekinstartups.com/about-jason/&quot; target=&quot;_blank&quot;&gt;Jason Calacanis&lt;/a&gt;, during the episode #47 of &lt;a href=&quot;http://thisweekinstartups.com/&quot; target=&quot;_blank&quot;&gt;This Week in Startups&lt;/a&gt; (&lt;a href=&quot;http://www.youtube.com/watch?v=4XpQ7YVXv_0&amp;amp;autoplay=1&quot; target=&quot;_blank&quot;&gt;full show&lt;/a&gt;, or directly &lt;a href=&quot;http://www.youtube.com/watch?v=4XpQ7YVXv_0&amp;amp;autoplay=1#t=5m20s&quot; target=&quot;_blank&quot;&gt;starting near 5:20&lt;/a&gt;). During that show, Jason goes against the &lt;a href=&quot;http://en.wikipedia.org/wiki/Generation_Y&quot; target=&quot;_blank&quot;&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=&quot;http://www.bothsidesofthetable.com/about-2/&quot; target=&quot;_blank&quot;&gt;Mark Suster&lt;/a&gt;, the co-host of &lt;a href=&quot;http://thisweekin.com/thisweekin-venture-capital/&quot; target=&quot;_blank&quot;&gt;This Week in Venture&lt;/a&gt;&lt;a href=&quot;http://thisweekin.com/thisweekin-venture-capital/&quot; target=&quot;_blank&quot;&gt;Capital&lt;/a&gt;, with a series of posts culminating with &lt;a href=&quot;http://www.cloudave.com/link/the-long-term-value-of-loyalty&quot; target=&quot;_blank&quot;&gt;The Long-Term Value of Loyalty&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;font-size: large;&quot;&gt;What&#39;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&#39;t work that hard to get always more!&lt;br /&gt;
&lt;br /&gt;
In his &lt;a href=&quot;http://www.bothsidesofthetable.com/2010/04/22/never-hire-job-hoppers-never-they-make-terrible-employees/&quot; target=&quot;_blank&quot;&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=&quot;http://www.bothsidesofthetable.com/2010/04/25/job-hoppers-redux-an-employees-perspective/&quot; target=&quot;_blank&quot;&gt;second post&lt;/a&gt; by writing that “quitting a job because it&#39;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=&quot;font-size: large;&quot;&gt;Is laziness a syndrome limited to Generation Y?&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;div style=&quot;float:right;text-align:center;padding:0 0 10px 10px;font-size:smaller;&quot;&gt;&lt;img src=&quot;http://www.snorgtees.com/images/Laziness_Fullpic_1.gif&quot; title=&quot;Laziness Pays Off Now&quot; width=&quot;254&quot; height=&quot;204&quot;/&gt;&lt;br /&gt;
T-shirt &lt;a href=&quot;http://www.snorgtees.com/lazinesspaysoffnow-p-991.html?osCsid=6be25d378d6709b3ca66a9cc6630b693&quot; target=&quot;_blank&quot;&gt;Laziness Pays Off Now&lt;/a&gt; from &lt;a href=&quot;http://www.snorgtees.com/&quot; target=&quot;_blank&quot;&gt;&lt;b&gt;SNORG&lt;/b&gt;TEES&lt;/a&gt;&lt;/div&gt;Before becoming an &lt;a href=&quot;http://domderrien.blogspot.com/2010/03/ladies-and-gentlemen-here-is-twetailer.html&quot; target=&quot;_blank&quot;&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&#39;ve been involved in many non governmental organizations.&lt;br /&gt;
&lt;br /&gt;
Along my life, I&#39;ve always been curious, interested in learning new subjects, debating around them, and trying to implement the best ideas, especially when I&#39;m passionate. I&#39;ve been lucky to team up with great people (note that I learned a lot too from the failed partnerships) and I&#39;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&#39;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&#39;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&#39;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&#39;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=&quot;font-size: large;&quot;&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=&quot;font-weight: normal;&quot;&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&#39;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&#39;t get a tangible ROI, I think it&#39;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&#39;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&#39;s more important is that you trust that he&#39;s going to deliver as expected, or if he has some troubles that he&#39;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=&quot;font-size: large;&quot;&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=&quot;http://stevenmilstein.com/2010/04/19/kool-aid-being-served-here/&quot; target=&quot;_blank&quot;&gt;work for attribution&lt;/a&gt;. Because we&#39;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&#39;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&#39;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&#39;s very cool!&lt;br /&gt;
&lt;br /&gt;
A+, Dom</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'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/168828253523263225/posts/default/1672668254130890811'/><link rel='alternate' type='text/html' href='http://domderrien.blogspot.com/2010/05/work-around-general-laziness.html' title='Work around the general laziness ;)'/><author><name>Dom Derrien</name><uri>http://www.blogger.com/profile/15197441189507302313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><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><category scheme="http://www.blogger.com/atom/ns#" term="Community"/><category scheme="http://www.blogger.com/atom/ns#" term="Experience"/><category scheme="http://www.blogger.com/atom/ns#" term="Technology"/><title type='text'>Social Software, Cynapse, and Open Platforms</title><content type='html'>&lt;span style=&quot;font-size: large;&quot;&gt;What&#39;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&#39;s in, who&#39;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&#39;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=&quot;http://www.missiontolearn.com/wp-content/uploads/2009/08/community-300x199.jpg&quot; style=&quot;float: right; margin: 0pt 0pt 10px 10px;&quot; title=&quot;Collaboration&quot; /&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=&quot;http://www.flickr.com/&quot;&gt;flickr&lt;/a&gt; or &lt;a href=&quot;http://picasaweb.google.com/&quot;&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=&quot;http://www.cnet.com/&quot;&gt;cnet&lt;/a&gt; or &lt;a href=&quot;http://www.epinions.com/&quot;&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&#39;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=&quot;font-size: large;&quot;&gt;Why Cynapse?&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
When my partner &lt;a href=&quot;http://stevenmilstein.com/&quot; target=&quot;_blank&quot;&gt;Steven&lt;/a&gt; and I started to work on the &lt;a href=&quot;http://twetailer.com/&quot;&gt;&lt;span class=&quot;twetailer&quot;&gt;&lt;span class=&quot;twetailerBang&quot;&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=&quot;http://greenhouse.lotus.com/images/gh_icon.gif&quot; style=&quot;float: left; margin: 0pt 10px 10px 0;&quot; /&gt;As IBM-ers, we looked among the tremendous IBM products and we selected &lt;a href=&quot;http://greenhouse.lotus.com/&quot;&gt;Lotus GreenHouse&lt;/a&gt; which was still in &lt;a href=&quot;http://en.wikipedia.org/wiki/Software_release_life_cycle&quot;&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=&quot;109&quot; src=&quot;http://elgg.org/images/elgg.png&quot; style=&quot;float: right; margin: 0pt 0pt 10px 10px;&quot; width=&quot;200&quot; /&gt;We then decided to switch to &lt;a href=&quot;http://elgg.org/&quot;&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=&quot;http://stevenmilstein.com/2009/03/19/lessons-learned-from-social-content-20-circle-of-life-part-2/&quot;&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=&quot;http://en.wikipedia.org/wiki/Eating_one%27s_own_dog_food&quot;&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=&quot;http://milstein-assoc.com/&quot; target=&quot;_blank&quot;&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=&quot;http://edu.cyn.in/logo.jpg&quot; style=&quot;float: right; margin: 0pt 0pt 10px 10px;&quot; /&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=&quot;&quot; href=&quot;http://edu.cyn.in/%20target=&quot;&gt;&lt;span style=&quot;color: red;&quot;&gt;edu&lt;/span&gt;&lt;span style=&quot;color: #666666;&quot;&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=&quot;http://www.craigslist.org/&quot;&gt;craigslist&lt;/a&gt;, &lt;a href=&quot;http://www.cnewmark.com/2010/04/trust-and-reputation-systems-redistributing-power-and-influence.html&quot;&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=&quot;http://twetailer.com/&quot;&gt;&lt;span class=&quot;twetailer&quot;&gt;&lt;span class=&quot;twetailerBang&quot;&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&#39;s too early to advance any adoption rate ;)&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;font-size: large;&quot;&gt;Why Open Platforms?&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
As a developer, I&#39;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=&quot;http://randomfoo.net/oscon/2002/lessig/&quot; target=&quot;_blank&quot;&gt;&lt;code&gt;&lt;free culture=&quot;&quot;&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&#39;t be free as in “free beer” but they&#39;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=&quot;http://en.wikipedia.org/wiki/Database_dump&quot; target=&quot;_blank&quot;&gt;SQL dump for a database&lt;/a&gt;) and allow then to import them somewhere else, but that&#39;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=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgxtXm3cX4veWN7bATiGVYKWwLFcPA-XbS7F_U94zsUM6EKQf5SCppEv4__yxBYRm6aK3JaBglbHh1j7OuH9o8KIyKZEzRN_uHAL8AYaUZccVWTdJJ7gOUHiGQHU56v3OjbHiyfRrNhav-k/s1600/cynapse-community.png&quot; style=&quot;float: left; margin: 0pt 10px 10px 0pt;&quot; title=&quot;Cynapse Community&quot; /&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&#39; questions, by submitting patches) have also the side benefit of improving your reputation.&lt;br /&gt;
&lt;br /&gt;
A+, Dom</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'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/168828253523263225/posts/default/7163312978686602603'/><link rel='alternate' type='text/html' href='http://domderrien.blogspot.com/2010/04/social-software-cynapse-and-open.html' title='Social Software, Cynapse, and Open Platforms'/><author><name>Dom Derrien</name><uri>http://www.blogger.com/profile/15197441189507302313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgxtXm3cX4veWN7bATiGVYKWwLFcPA-XbS7F_U94zsUM6EKQf5SCppEv4__yxBYRm6aK3JaBglbHh1j7OuH9o8KIyKZEzRN_uHAL8AYaUZccVWTdJJ7gOUHiGQHU56v3OjbHiyfRrNhav-k/s72-c/cynapse-community.png" height="72" width="72"/><thr:total>2</thr:total></entry><entry><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><category scheme="http://www.blogger.com/atom/ns#" term="Community"/><category scheme="http://www.blogger.com/atom/ns#" term="Technology"/><title type='text'>Amazon FPS library for the Google App Engine environment</title><content type='html'>Here is a post on &lt;a href=&quot;http://aws.amazon.com/fps/&quot;&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=&quot;font-size: large;&quot;&gt;Amazon FPS as the payment platform&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;span class=&quot;twetailer&quot;&gt;&lt;span class=&quot;twetailerBang&quot;&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=&quot;http://en.wikipedia.org/wiki/Freemium&quot; target=&quot;_blank&quot;&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=&quot;twetailer&quot;&gt;&lt;span class=&quot;twetailerBang&quot;&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=&quot;http://domderrien.blogspot.com/2010/03/amazon-fps-library-for-google-app.html#amazonFPSNote1&quot;&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=&quot;twetailer&quot;&gt;&lt;span class=&quot;twetailerBang&quot;&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=&quot;twetailer&quot;&gt;&lt;span class=&quot;twetailerBang&quot;&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=&quot;http://www.amazonpaymentsblog.com/amazon_payments_blog/&quot;&gt;Amazon Payments&lt;/a&gt; blog on April 7, 2009: &lt;a href=&quot;http://www.amazonpaymentsblog.com/amazon_payments_blog/2009/04/our-next-webinar-monetize-your-innovation-with-amazon-fps-.html&quot; target=&quot;_blank&quot;&gt;Monetize your innovation with Amazon FPS&lt;/a&gt;. If you use the open-source tool &lt;a href=&quot;http://www.videolan.org/&quot; target=&quot;_blank&quot;&gt;VideoLAN VLC&lt;/a&gt;, you can load the ASX file directly from Akamai from &lt;a href=&quot;http://mfile.akamai.com/23543/wmv/citrixvar.download.akamai.com/23543/www/596/433/8935354780199596433/1-8935354780199596433-12310131507.asx&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;font-size: large;&quot;&gt;Amazon and the open-source model&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
Amazon FPS, as many others &lt;a href=&quot;http://en.wikipedia.org/wiki/Amazon_Web_Services&quot; target=&quot;_blank&quot;&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&#39; source and attached to them a very liberal license&lt;sup&gt;&lt;small&gt;&lt;a href=&quot;http://domderrien.blogspot.com/2010/03/amazon-fps-library-for-google-app.html#amazonFPSNote2&quot;&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&#39;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=&quot;font-size: large;&quot;&gt;Amazon FPS on Google App Engine platform&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
The &lt;span class=&quot;twetailer&quot;&gt;&lt;span class=&quot;twetailerBang&quot;&gt;!&lt;/span&gt;twetailer&lt;/span&gt; server-side logic is &lt;a href=&quot;http://domderrien.blogspot.com/2009/04/google-app-engine-meets-java.html&quot;&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=&quot;http://docs.amazonwebservices.com/AmazonFPS/latest/FPSAggregatedGuide/index.html?CBUI.html&quot; target=&quot;_blank&quot;&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=&quot;http://code.google.com/p/googleappengine/issues/detail?id=1556&quot; target=&quot;_blank&quot;&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=&quot;http://hc.apache.org/httpclient-3.x/&quot; target=&quot;_blank&quot;&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=&quot;http://developer.amazonwebservices.com/connect/thread.jspa?threadID=43352&amp;tstart=0&quot; target=&quot;_blank&quot;&gt;asked for advices&lt;/a&gt; on &lt;a href=&quot;http://developer.amazonwebservices.com/connect/forum.jspa?forumID=35&amp;start=0&quot; target=&quot;_blank&quot;&gt;AWS FPS forums&lt;/a&gt;. But without response, I have decided to go with my own wrapper of the &lt;a href=&quot;http://code.google.com/appengine/docs/java/urlfetch/overview.html&quot; target=&quot;_blank&quot;&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=&quot;font-size: large;&quot;&gt;Wrappers of Google URL Fetch for Amazon FPS&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
Following Amazon&#39;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=&quot;http://github.com&quot; target=&quot;_blank&quot;&gt;GitHub&lt;/a&gt;, in the repository &lt;a href=&quot;http://github.com/DomDerrien/amazon-fps-gaej&quot; target=&quot;_blank&quot;&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=&quot;http://github.com/DomDerrien/amazon-fps-gaej/blob/master/src/com/amazonaws/ipnreturnurlvalidation/SignatureUtilsForOutbound.java&quot; target=&quot;_blank&quot;&gt;com.amazonaws.ipnreturnurlvalidation.SignatureUtilsForOutbound&lt;/a&gt;&lt;/code&gt; and &lt;code&gt;&lt;a href=&quot;http://github.com/DomDerrien/amazon-fps-gaej/blob/master/src/com/amazonaws/fps/AmazonFPSClient.java&quot; target=&quot;_blank&quot;&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=&quot;http://github.com/DomDerrien/amazon-fps-gaej/blob/master/src/domderrien/wrapper/UrlFetch/UrlFetchConnectionManager.java&quot; target=&quot;_blank&quot;&gt;domderrien.wrapper.UrlFetch.UrlFetchConnectionManager&lt;/a&gt;&lt;/code&gt; and &lt;code&gt;&lt;a href=&quot;http://github.com/DomDerrien/amazon-fps-gaej/blob/master/src/domderrien/wrapper/UrlFetch/UrlFetchHttpConnection.java&quot; target=&quot;_blank&quot;&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=&quot;twetailer&quot;&gt;&lt;span class=&quot;twetailerBang&quot;&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=&quot;codeSnippetTitle&quot;&gt;&lt;code&gt;UrlFetchConnectionManager&lt;/code&gt; class definition&lt;/div&gt;&lt;pre class=&quot;prettyprint limitSnippetHeight&quot;&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(&quot;closeIdleConnections(long)&quot;);
 }

 public HttpConnection getConnection(HostConfiguration hostConfiguration) {
  throw new RuntimeException(&quot;getConnection(HostConfiguration)&quot;);
  // return null;
 }

 public HttpConnection getConnection(HostConfiguration hostConfiguration, long timeout) throws HttpException {
  throw new RuntimeException(&quot;getConnection(HostConfiguration, long)&quot;);
  // 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=&quot;codeSnippetTitle&quot;&gt;&lt;code&gt;UrlFetchConnection&lt;/code&gt; class definition&lt;/div&gt;&lt;pre class=&quot;prettyprint limitSnippetHeight&quot;&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 = &quot;: &quot;;
 private static final int SEPARATOR_LENGTH = SEPARATOR.length();
 private static final String NEW_LINE = &quot;\r\n&quot;;

 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(&quot;Content-Length: &quot;).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(&quot;;chunk size&quot;).append(NEW_LINE);
   buffer.append(new String(_response.getContent())).append(NEW_LINE);
   buffer.append(&quot;0;&quot;).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(&quot;assertNotOpen()&quot;);
 }

 @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(&quot;closeSocketAndStreams()&quot;);
 }

 @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(&quot;getHttpConnectionManager()&quot;);
 }

 @Override
 public InputStream getLastResponseInputStream() {
  throw new RuntimeException(&quot;getLastResponseInputStream()&quot;);
 }

 @Override
 public InetAddress getLocalAddress() {
  throw new RuntimeException(&quot;getLocalAddress()&quot;);
 }

 @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(&quot;getProxyHost()&quot;);
 }

 @Override
 public int getProxyPort() {
  throw new RuntimeException(&quot;getProxyPort()&quot;);
 }

 @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(&quot;getSendBufferSize()&quot;);
 }

 @Override
 protected Socket getSocket() {
  throw new RuntimeException(&quot;getSocket()&quot;);
 }

 @Override
 public int getSoTimeout() throws SocketException {
  throw new RuntimeException(&quot;getSoTimeout()&quot;);
 }

 @Override
 public String getVirtualHost() {
  throw new RuntimeException(&quot;getVirtualHost()&quot;);
 }

 @Override
 protected boolean isLocked() {
  throw new RuntimeException(&quot;isLocked()&quot;);
 }

 @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(&quot;isStale()&quot;);
 }

 @Override
 public boolean isStaleCheckingEnabled() {
  throw new RuntimeException(&quot;isStaleCheckingEnabled()&quot;);
 }

 @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(&quot;print(string): &quot; + data);
 }

 @Override
 public void printLine() throws IOException, IllegalStateException {
  throw new RuntimeException(&quot;printLine()&quot;);
 }

 @Override
 public void printLine(String data, String charset) throws IOException, IllegalStateException {
  throw new RuntimeException(&quot;printLine(string, String): &quot; + data + &quot; -- &quot; + charset);
 }

 @Override
 public void printLine(String data) throws IOException, IllegalStateException {
  throw new RuntimeException(&quot;printLine(string): &quot; + data);
 }

 @Override
 public String readLine() throws IOException, IllegalStateException {
  throw new RuntimeException(&quot;readLine()&quot;);
 }

 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 = &quot;HTTP/1.1 &quot; + responseCode;
   switch(responseCode) {
    case HttpStatus.SC_OK: line += &quot; OK&quot;; break;
    case HttpStatus.SC_BAD_REQUEST: line += &quot; BAD REQUEST&quot;; break;
    case HttpStatus.SC_UNAUTHORIZED: line += &quot; UNAUTHORIZED&quot;; break;
    case HttpStatus.SC_FORBIDDEN: line += &quot; FORBIDDEN&quot;; break;
    case HttpStatus.SC_NOT_FOUND: line += &quot; NOT FOUND&quot;; break;
    case HttpStatus.SC_INTERNAL_SERVER_ERROR: line += &quot; INTERNAL SERVER ERROR&quot;; break;
    case HttpStatus.SC_SERVICE_UNAVAILABLE: line += &quot; SERVICE UNAVAILABLE&quot;; break;
    default: line = &quot;HTTP/1.1 &quot; + HttpStatus.SC_BAD_REQUEST + &quot; BAD REQUEST&quot;;
   }
   waitForHttpStatus = false;
   return line;
  }
  throw new RuntimeException(&quot;readLine(String)&quot;);
 }

 @Override
 public void releaseConnection() {
  // Do nothing, connection closed automatically...
 }

 @Override
 public void setConnectionTimeout(int timeout) {
  throw new RuntimeException(&quot;setConnectionTimeout(int)&quot;);
 }

 @Override
 public void setHost(String host) throws IllegalStateException {
  throw new RuntimeException(&quot;setHost(String&quot;);
 }

 @Override
 public void setHttpConnectionManager(HttpConnectionManager httpConnectionManager) {
  throw new RuntimeException(&quot;setHttpConnectionManager(HttpConnectionManager&quot;);
 }

 @Override
 public void setLastResponseInputStream(InputStream inStream) {
  // Safe call, passed to inherited method
  super.setLastResponseInputStream(inStream);
 }

 @Override
 public void setLocalAddress(InetAddress localAddress) {
  throw new RuntimeException(&quot;setLocalAddress(InetAddress)&quot;);
 }

 @Override
 protected void setLocked(boolean locked) {
  // Safe call, passed to inherited method
  super.setLocked(locked);
 }

 @Override
 public void setParams(HttpConnectionParams params) {
  throw new RuntimeException(&quot;setParams(HttpConnectionParams)&quot;);
 }

 @Override
 public void setPort(int port) throws IllegalStateException {
  throw new RuntimeException(&quot;setPort(int)&quot;);
 }

 @Override
 public void setProtocol(Protocol protocol) {
  throw new RuntimeException(&quot;setProtocol(Protocol)&quot;);
 }

 @Override
 public void setProxyHost(String host) throws IllegalStateException {
  throw new RuntimeException(&quot;setProxyHost(String)&quot;);
 }

 @Override
 public void setProxyPort(int port) throws IllegalStateException {
  throw new RuntimeException(&quot;setProxyPort(int)&quot;);
 }

 @Override
 public void setSendBufferSize(int sendBufferSize) throws SocketException {
  throw new RuntimeException(&quot;setSendBufferSize(int)&quot;);
 }

 @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(&quot;setSoTimeout(int)&quot;);
 }

 @Override
 public void setStaleCheckingEnabled(boolean staleCheckEnabled) {
  throw new RuntimeException(&quot;setStaleCheckingEnabled(boolean)&quot;);
 }

 @Override
 public void setVirtualHost(String host) throws IllegalStateException {
  throw new RuntimeException(&quot;setVirtualHost(String)&quot;);
 }

 @Override
 public void shutdownOutput() {
  throw new RuntimeException(&quot;shutdownOutput()&quot;);
 }

 @Override
 public void tunnelCreated() throws IllegalStateException, IOException {
  throw new RuntimeException(&quot;tunnelCreated()&quot;);
 }

 @Override
 public void write(byte[] data, int offset, int length) throws IOException, IllegalStateException {
  throw new RuntimeException(&quot;write(byte[], int, int): &quot; + new String(data) + &quot;, &quot; + offset + &quot;, &quot; + length);
 }

 @Override
 public void write(byte[] data) throws IOException, IllegalStateException {
  throw new RuntimeException(&quot;write(byte[]): &quot; + 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(&quot;writeLine(byte[]): &quot; + 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=&quot;#&quot; name=&quot;amazonFPSNote1&quot;&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=&quot;#&quot; name=&quot;amazonFPSNote2&quot;&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;</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'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/168828253523263225/posts/default/1947501483083628645'/><link rel='alternate' type='text/html' href='http://domderrien.blogspot.com/2010/03/httpdomderrien-preblogspotcom201003amaz.html' title='Amazon FPS library for the Google App Engine environment'/><author><name>Dom Derrien</name><uri>http://www.blogger.com/profile/15197441189507302313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><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><category scheme="http://www.blogger.com/atom/ns#" term="Entrepreneurship"/><category scheme="http://www.blogger.com/atom/ns#" term="Experience"/><title type='text'>Ladies and gentlemen, here is !twetailer</title><content type='html'>On Tuesday March 9 evening, my partner &lt;a href=&quot;http://stevenmilstein.com/&quot; target=&quot;_blank&quot;&gt;Steven Milstein&lt;/a&gt; and I attended a &lt;a href=&quot;http://www.mtlnewtech.com/2010/03/03/lineup-for-the-newtech-demo-march-2010/&quot; target=&quot;_blank&quot;&gt;Montreal NewTech event&lt;/a&gt;. As three other companies, Steven pitched our project &lt;a href=&quot;http://twetailer.com/&quot; target=&quot;_blank&quot;&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=&quot;http://stevenmilstein.com/2010/03/10/my-first-pitch-to-more-than-person-over-the-age-of-12-what-a-rush/&quot; target=&quot;_blank&quot;&gt;Steven&#39;s blog post&lt;/a&gt; for the details.&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;font-size: large;&quot;&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=&quot;http://www.ibm.com/software/awdtools/portfolio/&quot; target=&quot;_blank&quot;&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=&quot;http://www.techcrunch50.com/&quot; target=&quot;_blank&quot;&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=&quot;http://calacanis.com/&quot; target=&quot;_blank&quot;&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=&quot;twetailer&quot;&gt;&lt;span class=&quot;twetailerBang&quot;&gt;!&lt;/span&gt;twetailer&lt;/span&gt; to broader audience!&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;font-size: large;&quot;&gt;Presentation&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;span class=&quot;twetailer&quot;&gt;&lt;span class=&quot;twetailerBang&quot;&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=&quot;twetailer&quot;&gt;&lt;span class=&quot;twetailerBang&quot;&gt;!&lt;/span&gt;twetailer&lt;/span&gt; ;) We made it months ago but it&#39;s still very accurate. Don&#39;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=&quot;405&quot; width=&quot;500&quot;&gt;&lt;param name=&quot;movie&quot; value=&quot;http://www.youtube.com/v/mWMUhxHjUCE&amp;hl=en_US&amp;fs=1&amp;color1=0x5d1719&amp;color2=0xcd311b&quot;&gt;&lt;/param&gt;&lt;param name=&quot;allowFullScreen&quot; value=&quot;true&quot;&gt;&lt;/param&gt;&lt;param name=&quot;allowscriptaccess&quot; value=&quot;always&quot;&gt;&lt;/param&gt;&lt;embed src=&quot;http://www.youtube.com/v/mWMUhxHjUCE&amp;hl=en_US&amp;fs=1&amp;color1=0x5d1719&amp;color2=0xcd311b&quot; type=&quot;application/x-shockwave-flash&quot; allowscriptaccess=&quot;always&quot; allowfullscreen=&quot;true&quot; width=&quot;500&quot; height=&quot;405&quot;&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=&quot;http://www.facebook.com/pages/Montreal-QC/solidwild/41234103682?ref=ts&quot;&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&#39;s possible you&#39;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&#39;s like advertising my business then! Cool.&lt;/li&gt;
&lt;/ul&gt;&lt;span style=&quot;font-size: large;&quot;&gt;What&#39;s next?&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
As mentioned by &lt;a href=&quot;http://domderrien.blogspot.com/2009/03/guy-kawasakis-keynote-in-montreal.html&quot;&gt;Guy Kawasaki in Montreal in March 2009&lt;/a&gt;, we have chosen to &quot;launch early and to correct progressively&quot;. 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=&quot;http://twetailer.cyn.in&quot; target=&quot;_blank&quot;&gt;twetailer.cyn.in&lt;/a&gt;, implemented with the excellent social software from &lt;a href=&quot;http://www.cynapse.com&quot; target=&quot;_blank&quot;&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=&quot;twetailer&quot;&gt;&lt;span class=&quot;twetailerBang&quot;&gt;!&lt;/span&gt;twetailer&lt;/span&gt; seems too tightly related to &lt;a href=&quot;http://twitter.com/&quot; target=&quot;_blank&quot;&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=&quot;twetailer&quot;&gt;&lt;span class=&quot;twetailerBang&quot;&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=&quot;twetailer&quot;&gt;&lt;span class=&quot;twetailerBang&quot;&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=&quot;twetailer&quot;&gt;&lt;span class=&quot;twetailerBang&quot;&gt;!&lt;/span&gt;twetailer&lt;/span&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;font-size: large;&quot;&gt;Call to contributors&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;span class=&quot;twetailer&quot;&gt;&lt;span class=&quot;twetailerBang&quot;&gt;!&lt;/span&gt;twetailer&lt;/span&gt; is a big project with a development still growing, so there&#39;s a lot of room for anyone to showcase their knowledge!&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;If you&#39;re a &lt;b&gt;developer&lt;/b&gt; with Java/JavaScript skills,&lt;/li&gt;
&lt;li&gt;If you&#39;re a &lt;b&gt;tester&lt;/b&gt; with automation experience,&lt;/li&gt;
&lt;li&gt;If you&#39;re a &lt;b&gt;UI designer &amp; Interactivity specialist&lt;/b&gt;,&lt;/li&gt;
&lt;li&gt;If you&#39;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&#39;re a &lt;b&gt;simple consumer&lt;/b&gt; in touch with a vibrant community,&lt;/li&gt;
&lt;li&gt;If you&#39;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&#39;t hesitate to contact Steven (&lt;a href=&quot;http://twitter.com/stevenmilstein&quot;&gt;Steven@Twitter&lt;/a&gt; or &lt;a href=&quot;mailto:steven.milstein@twetailer.com&quot;&gt;Steven@Twetailer.com&lt;/a&gt;) or myself (&lt;a href=&quot;http://twitter.com/domderrien&quot;&gt;Dom@Twitter&lt;/a&gt; or &lt;a href=&quot;mailto:dom.derrien@twetailer.com&quot;&gt;Dom@Twetailer.com&lt;/a&gt;) and we&#39;ll exchange on our community site at &lt;a href=&quot;http://twetailer.cyn.in&quot;&gt;twetailer.cyn.in&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
A+, Dom</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'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/168828253523263225/posts/default/405754375531762932'/><link rel='alternate' type='text/html' href='http://domderrien.blogspot.com/2010/03/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>http://www.blogger.com/profile/15197441189507302313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><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><category scheme="http://www.blogger.com/atom/ns#" term="Experience"/><title type='text'>Ma nouvelle vie en tant qu&#39;entrepreneur !</title><content type='html'>&lt;h2&gt;La démission&lt;/h2&gt;Il y a trois semaines, j&#39;ai démissionné de mon poste de Consultant technique pour la compagnie &lt;a href=&quot;http://www.compuware.com/&quot;&gt;Compuware&lt;/a&gt;, à Montréal. Plusieurs facteurs m&#39;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=&quot;http://www.compuware.com/about/management.asp#CZARNIK&quot;&gt;Paul Czarnik&lt;/a&gt; m&#39;assigne sur un projet en relation avec l&#39;intégration des outils de &lt;a href=&quot;http://www.gomez.com/&quot;&gt;Gomez&lt;/a&gt; (récemment achetée par Compuware) dans le produit phare &lt;a href=&quot;http://www.compuware.com/solutions/vantage.asp&quot;&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&#39;aide, dans la même veine de ce qui sera proposé aux organismes de Microfinance, comme &lt;a href=&quot;http://diku-dilenga.org/&quot;&gt;Diku Dilenga&lt;/a&gt; et &lt;a href=&quot;http://www.jamiibora.org/&quot;&gt;Jamii Bora&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;D&#39;un côté, il y avait un assignement plus prenant avec mon employeur, et de l&#39;autre, il y avait une demande d&#39;attention plus importante pour aborder une phase critique. J&#39;ai finalement opté pour la démission. Youpi, je suis maintenant indépendant&amp;nbsp;!&lt;br /&gt;
&lt;br /&gt;
Certains amis m&#39;ont demandé si cela avait un rapport avec mon âge (ils se souviennent de mon articule &lt;a href=&quot;http://domderrien.blogspot.com/2009/05/le-cap-des-40-ans.html&quot;&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&#39;est plus le cotoiement, même par Internet ou livre interposé, avec des gens inspirant comme Guy Kawasaki (&lt;a hreaf=&quot;http://domderrien.blogspot.com/2009/03/guy-kawasakis-keynote-in-montreal.html&quot; href=&quot;http://domderrien.blogspot.com/2009/03/guy-kawasakis-keynote-in-montreal.html&quot;&gt;GK&#39;s keynote in Montreal&lt;/a&gt;) qui me poussent à aller de l&#39;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&#39;Architecte logiciel pour IBM Rational, à Montréal, j&#39;avais eu le plaisir de travailler avec &lt;a href=&quot;http://stevenmilstein.com/&quot;&gt;Steven Milstein&lt;/a&gt; qui avait le rôle d&#39;Analyste d&#39;affaires.&lt;br /&gt;
&lt;br /&gt;
L&#39;idée originale du projet qui m&#39;occupe dorénavant est de Steven. Avec mon expérience, j&#39;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=&quot;http://git-scm.com/&quot;&gt;git&lt;/a&gt; et &lt;a href=&quot;http://github.com/&quot;&gt;GitHub&lt;/a&gt; pour la gestion des sources;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.pivotaltracker.com/&quot;&gt;Pivotal Tracker&lt;/a&gt; pour le suivi des tâches de développement;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://appengine.google.com/&quot;&gt;Google App Engine&lt;/a&gt; pour l&#39;infrastructure en production, initiallement avec la version Python et puis en version Java (voir &lt;a href=&quot;http://domderrien.blogspot.com/2009/04/google-app-engine-meets-java.html&quot;&gt;Google App Engine Meets Java&lt;/a&gt;);&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://eclipse.org/&quot;&gt;Eclipse&lt;/a&gt; et &lt;a href=&quot;http://ant.apache.org/&quot;&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=&quot;http://www.cynapse.com/cynin&quot;&gt;cyn.in&lt;/a&gt;, sélectionnée par Steven pour sa grande qualité et l&#39;évolution rapide de son développement.&lt;br /&gt;
&lt;br /&gt;
Dans l&#39;immédiat, je me concentre sur la mise au point de l&#39;outil, passant d&#39;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&#39;accord de non divulgation cependant. Idéalement, nous souhaitons avoir des partenariats pour avoir une masse critique et aller de l&#39;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&#39;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&#39;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&#39;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&#39;initiation des élèves. Le projet s&#39;appelle &lt;a href=&quot;http://edu.cyn.in/&quot;&gt;edu.cyn.in&lt;/a&gt;—voir le blog de &lt;a href=&quot;http://stevenmilstein.com/&quot;&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&#39;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&#39;organisme de microfinance Diku Dilenga continue, toujours pour le soutien technique, moins en tant qu&#39;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&#39;aider les microentrepreneurs et de d&#39;offrir une source de revenus aux dits organismes.&lt;br /&gt;
&lt;br /&gt;
À suivre donc,&lt;br /&gt;
A+, Dom</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'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/168828253523263225/posts/default/7597693722668436662'/><link rel='alternate' type='text/html' href='http://domderrien.blogspot.com/2010/02/ma-nouvelle-vie-en-tant-quentrepreneur.html' title='Ma nouvelle vie en tant qu&#39;entrepreneur !'/><author><name>Dom Derrien</name><uri>http://www.blogger.com/profile/15197441189507302313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></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.105995000000007 45.7858935 -73.172157</georss:box></entry><entry><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><category scheme="http://www.blogger.com/atom/ns#" term="Community"/><category scheme="http://www.blogger.com/atom/ns#" term="Technology"/><title type='text'>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=&quot;updateNotice&quot;&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=&quot;http://code.google.com/p/googleappengine/wiki/SdkForJavaReleaseNotes&quot;&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&#39;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=&quot;http://code.google.com/appengine/docs/java/datastore/dataclasses.html#Inheritance&quot;&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=&quot;http://groups.google.com/group/google-appengine-java/browse_thread/thread/fe334c9e461026fa/8f5872b052144c8d?#8f5872b052144c8d&quot;&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=&quot;http://domderrien.blogspot.com/2009/09/progress-update.html&quot;&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=&quot;font-size: larger;&quot;&gt;
Use case description&lt;/p&gt;
&lt;p&gt;
Let&#39;s consider a simple class organization implementing a common
J2EE pattern:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A &lt;span style=&quot;border-bottom: dotted 1px #666666;&quot;
  title=&quot;Data Transfer Object&quot;&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=&quot;border-bottom: dotted 1px #666666;&quot;
  title=&quot;Data Access Object&quot;&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=&quot;border-bottom: dotted 1px #666666;&quot;
  title=&quot;Representational State Transfer&quot;&gt;REST&lt;/span&gt; requests. The
Controller is an element of the implemented &lt;span
  style=&quot;border-bottom: dotted 1px #666666;&quot;
  title=&quot;Model-View-Controller pattern&quot;&gt;MVC pattern&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;center&gt;
&lt;div class=&quot;codeSnippetTitle&quot;&gt;
Use case illustration&lt;/div&gt;
&lt;object data=&quot;http://domderrien.github.com/diagrams/2009-11-20-illustration.svg&quot; height=&quot;580&quot;
 style=&quot;border: 2px solid transparent;&quot; title=&quot;Use case illustration&quot;
 type=&quot;image/svg+xml&quot; width=&quot;670&quot;&gt; &lt;img src=&quot;http://domderrien.github.com/diagrams/2009-11-20-illustration.png&quot;
  title=&quot;Use case illustration&quot; /&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=&quot;codeSnippetTitle&quot;&gt;
&lt;code&gt;Consumer&lt;/code&gt; DTO class
definition&lt;/div&gt;
&lt;pre class=&quot;prettyprint limitSnippetHeight&quot;&gt;@PersistenceCapable(identityType = IdentityType.APPLICATION, detachable=&quot;true&quot;)
@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=&quot;true&quot;&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=&quot;codeSnippetTitle&quot;&gt;
Excerpt from the &lt;code&gt;ConsumerOperations&lt;/code&gt;
DAO class definition&lt;/div&gt;
&lt;pre class=&quot;prettyprint&quot;&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=&quot;codeSnippetTitle&quot;&gt;
Excerpt from the abstract &lt;code&gt;BaseOperations&lt;/code&gt;
DAO class definition&lt;/div&gt;
&lt;pre class=&quot;prettyprint limitSnippetHeight&quot;&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&quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Response:
&lt;ul&gt;
&lt;li&gt;&lt;pre&gt;{key:43544, displayName:&quot;John&quot;, address:&quot;75, Queen, Montréal, Qc, Canada&quot;, 
locationKey:3245, location: {id:3245, postalCode:&quot;H3C2N6&quot;, countryCode:&quot;CA&quot;,
latitude:43.3, longitude:-73.4}, ...}&lt;/pre&gt;
&lt;/li&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/ul&gt;
&lt;div class=&quot;codeSnippetTitle&quot;&gt;
Excerpt from the &lt;code&gt;ConsumerRestlet&lt;/code&gt; Controller class definition&lt;/div&gt;
&lt;pre class=&quot;prettyprint&quot;&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=&quot;font-size: larger;&quot;&gt;
Simple mock&lt;/p&gt;
&lt;p&gt;
Now, it&#39;s time to test! To start slowly, let&#39;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&#39;s a call issued to get the identified &lt;code&gt;Consumer&lt;/code&gt; instance;&lt;/li&gt;
&lt;li&gt;There&#39;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&#39;t want to interfere with the App Engine infrastructure (the following chapter will address that aspect). So we&#39;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=&quot;http://github.com/DomDerrien/two-tiers-utils&quot;&gt;two-tiers-utils&lt;/a&gt;: &lt;code&gt;&lt;a href=&quot;http://github.com/DomDerrien/two-tiers-utils/blob/master/src/Java/javax/jdo/MockPersistenceManager.java&quot;
&gt;javax.jdo.MockPersistenceManager&lt;/a&gt;&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&quot;codeSnippetTitle&quot;&gt;
Custom part of the mock for the &lt;code&gt;PersistenceManager&lt;/code&gt; class&lt;/div&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;public class MockPersistenceManager implements PersistenceManager {
    private boolean closed = false; // To keep track of the &quot;closed&quot; 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=&quot;codeSnippetTitle&quot;&gt;
Three tests validating the behavior of the &lt;code&gt;ConsumerRestlet.getResource()&lt;/code&gt; method&lt;/div&gt;
&lt;pre class=&quot;prettyprint limitSnippetHeight&quot;&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(&quot;Expects only one call&quot;);
            }
            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(&quot;Done in purpose!&quot;);
        }
    };
    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=&quot;prettyprint limitSnippetHeight&quot;&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(&quot;Expects only one call&quot;);
            }
            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(&quot;Call not expected here!&quot;);
            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=&quot;prettyprint limitSnippetHeight&quot;&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(&quot;Expects only one call&quot;);
            }
            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=&quot;font-size: larger;&quot;&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&#39;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=&quot;http://code.google.com/appengine/docs/java/howto/unittesting.html&quot;
&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=&quot;http://blog.appenginefan.com/2009/05/jdo-and-unit-tests.html&quot;
&gt;JDO and unit tests&lt;/a&gt; from &lt;a href=&quot;http://blog.appenginefan.com/&quot;&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&#39;ve produced a &lt;code&gt;&lt;a href=&quot;http://github.com/DomDerrien/two-tiers-utils/blob/master/src/Java/com/google/apphosting/api/MockAppEngineEnvironment.java&quot;
&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=&quot;codeSnippetTitle&quot;&gt;
Three tests validating the behavior of the &lt;code&gt;ConsumerRestlet.getResource()&lt;/code&gt; method&lt;/div&gt;
&lt;pre class=&quot;prettyprint limitSnippetHeight&quot;&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 &quot;test&quot;;
        }
 
        public String getVersionId() {
          return &quot;1.0&quot;;
        }
 
        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 &quot;&quot;;
        }
 
        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 &quot;live&quot; request
            // See: http://groups.google.com/group/google-appengine-java/msg/8f5872b05214...
            out.put(&quot;com.google.appengine.server_url_key&quot;, &quot;http://localhost:8080&quot;);

            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(&quot;.&quot;)) {};
 
        // 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&#39;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(&quot;datastore_v3&quot;);
            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(&quot;Found a transaction nor commited neither rolled-back.&quot; +
                    &quot;Probably related to a missing PersistenceManager.close() call.&quot;);
        }
    }
 
    /**
     * 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(&quot;javax.jdo.PersistenceManagerFactoryClass&quot;,
                    &quot;org.datanucleus.store.appengine.jdo.DatastoreJDOPersistenceManagerFactory&quot;);
            newProperties.put(&quot;javax.jdo.option.ConnectionURL&quot;, &quot;appengine&quot;);
            newProperties.put(&quot;javax.jdo.option.NontransactionalRead&quot;, &quot;true&quot;);
            newProperties.put(&quot;javax.jdo.option.NontransactionalWrite&quot;, &quot;true&quot;);
            newProperties.put(&quot;javax.jdo.option.RetainValues&quot;, &quot;true&quot;);
            newProperties.put(&quot;datanucleus.appengine.autoCreateDatastoreTxns&quot;, &quot;true&quot;);
            newProperties.put(&quot;datanucleus.appengine.autoCreateDatastoreTxns&quot;, &quot;true&quot;);
            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&#39;s really great. &lt;div class=&quot;codeSnippetTitle&quot;&gt;
Excerpt of the &lt;code&gt;TestConsumerOperations&lt;/code&gt; class&lt;/div&gt;
&lt;pre class=&quot;prettyprint limitSnippetHeight&quot;&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 = &quot;unit@test.net&quot;;
        final String name = &quot;Mr Unit Test&quot;;
        Consumer newConsumer = new Consumer();
        newConsumer.setDisplayName(name);
        newConsumer.setEmail(email);
        assertNull(newConsumer.getId());
 
        // Verify there&#39;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&#39;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=&quot;font-size: larger;&quot;&gt;
Conclusion&lt;/p&gt;
&lt;p&gt;
As a big fan of &lt;span style=&quot;border-bottom: dotted 1px #666666;&quot; title=&quot;Test Driven Development&quot;&gt;TDD&lt;/span&gt;, I&#39;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&#39;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=&quot;http://github.com/DomDerrien/two-tiers-utils&quot;&gt;two-tiers-utils&lt;/a&gt; open-source project. In addition to mock classes for the App Engine environment,  you&#39;ll find:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Basic mock classes for the servlet (see &lt;code&gt;&lt;a href=&quot;http://github.com/DomDerrien/two-tiers-utils/tree/master/src/Java/javax/servlet/&quot;
&gt;javax.servlet&lt;/a&gt;&lt;/code&gt;) and &lt;code&gt;&lt;a href=&quot;http://github.com/DomDerrien/two-tiers-utils/tree/master/src/Java/javamocks/io/&quot;
&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=&quot;http://github.com/DomDerrien/two-tiers-utils/tree/master/src/Java/twitter4j&quot;
&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=&quot;http://github.com/DomDerrien/two-tiers-utils/tree/b8ef96bd09a684651cd4164c1fe6abf6c64ef419/src/Java/com/dyuproject&quot;
&gt;David Yu&#39;s Project&lt;/a&gt; which I use to allow users with OpenID credentials
to log in. Read the &lt;a href=&quot;http://code.google.com/p/dyuproject/issues/detail?id=16&quot;&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=&quot;http://domderrien.blogspot.com/2009/10/internationalization-and-my-two-tiers.html&quot;
&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=&quot;http://appengine.google.com/&quot;&gt;homepage&lt;/a&gt;
and the &lt;a href=&quot;http://code.google.com/appengine/&quot;&gt;SDK page&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;See my post on &lt;a
  href=&quot;http://domderrien.blogspot.com/2009/04/agile-scrum-is-hype-but-xp-is-more.html&quot;&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=&quot;http://www.ibm.com/developerworks/java/library/j-cq01316/index.html?ca=drs&quot;&gt;Don&#39;t
be fooled by the coverage report&lt;/a&gt; soon after I started using &lt;a
  href=&quot;http://cobertura.sourceforge.net/index.html&quot;&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=&quot;http://www.manning.com/massol/&quot;&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=&quot;http://www.manning.com/tahchiev/&quot;&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=&quot;http://www.manning-source.com/books/massol/massol_ch07.pdf&quot;&gt;chapter
7: Testing in isolation with mock objects&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;JDO stands for &lt;a href=&quot;http://java.sun.com/jdo/index.jsp&quot;&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=&quot;http://code.google.com/appengine/docs/java/gettingstarted/usingdatastore.html&quot;&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=&quot;http://groups.google.com/group/google-appengine-java/web/will-it-play-in-app-engine?pli=1&quot;
 &gt;Will it play in App Engine&lt;/a&gt;. For JDO related limitations, check the bottom of the
page &lt;a href=&quot;http://code.google.com/appengine/docs/java/datastore/usingjdo.html&quot;&gt;Usin JDO with App Engine&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&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'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/168828253523263225/posts/default/7433418658313596224'/><link rel='alternate' type='text/html' href='http://domderrien.blogspot.com/2009/11/unit-tests-mock-objects-and-app-engine.html' title='Unit tests, Mock objects, and App Engine'/><author><name>Dom Derrien</name><uri>http://www.blogger.com/profile/15197441189507302313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>4</thr:total></entry></feed>