<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/rss2full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" version="2.0" xml:base="http://www.esjewett.com">
<channel>
 <title>esjewett.com</title>
 <link>http://www.esjewett.com</link>
 <description />
 <language>en</language>
<feedburner:info uri="esjewett" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/rss+xml" href="http://www.esjewett.com/rss.xml" /><feedburner:emailServiceId>esjewett</feedburner:emailServiceId><feedburner:feedburnerHostname>http://feedburner.google.com</feedburner:feedburnerHostname><feedburner:feedFlare href="http://add.my.yahoo.com/rss?url=http%3A%2F%2Fwww.esjewett.com%2Frss.xml" src="http://us.i1.yimg.com/us.yimg.com/i/us/my/addtomyyahoo4.gif">Subscribe with My Yahoo!</feedburner:feedFlare><feedburner:feedFlare href="http://www.newsgator.com/ngs/subscriber/subext.aspx?url=http%3A%2F%2Fwww.esjewett.com%2Frss.xml" src="http://www.newsgator.com/images/ngsub1.gif">Subscribe with NewsGator</feedburner:feedFlare><feedburner:feedFlare href="http://feeds.my.aol.com/add.jsp?url=http%3A%2F%2Fwww.esjewett.com%2Frss.xml" src="http://o.aolcdn.com/favorites.my.aol.com/webmaster/ffclient/webroot/locale/en-US/images/myAOLButtonSmall.gif">Subscribe with My AOL</feedburner:feedFlare><feedburner:feedFlare href="http://www.bloglines.com/sub/http://www.esjewett.com/rss.xml" src="http://www.bloglines.com/images/sub_modern11.gif">Subscribe with Bloglines</feedburner:feedFlare><feedburner:feedFlare href="http://www.netvibes.com/subscribe.php?url=http%3A%2F%2Fwww.esjewett.com%2Frss.xml" src="http://www.netvibes.com/img/add2netvibes.gif">Subscribe with Netvibes</feedburner:feedFlare><feedburner:feedFlare href="http://fusion.google.com/add?feedurl=http%3A%2F%2Fwww.esjewett.com%2Frss.xml" src="http://buttons.googlesyndication.com/fusion/add.gif">Subscribe with Google</feedburner:feedFlare><feedburner:feedFlare href="http://www.pageflakes.com/subscribe.aspx?url=http%3A%2F%2Fwww.esjewett.com%2Frss.xml" src="http://www.pageflakes.com/ImageFile.ashx?instanceId=Static_4&amp;fileName=ATP_blu_91x17.gif">Subscribe with Pageflakes</feedburner:feedFlare><item>
 <title>OAuth signatures: why we need 'em</title>
 <link>http://feedproxy.google.com/~r/esjewett/~3/qUL7N51MO7k/oauth-signatures-why-we-need-em</link>
 <description>&lt;p&gt;Before I get started, let me say that this should be taken with a very large grain of salt. I am not a technical expert in these areas. I've only done a fair amount of reading and following along with standards efforts. Conclusions here should be questioned thoroughly and corrected as necessary, but since no one else seems to be willing to systematically look into the performance argument for signatures over SSL/TLS, I thought I'd just sit down for a couple of hours and pound it out. The following is the result of that work.&lt;/p&gt;
&lt;h2&gt;Resource access without SSL/TLS - Signatures vs. Bearer-tokens&lt;/h2&gt;
&lt;p&gt;The crux of the trade-off comes down to this: In the case of signed authorization information (with a nonce), a Man in the Middle (MITM) attacker owns the individual message. The MITM can do the following:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Decline to deliver the message&lt;/li&gt;
&lt;li&gt;Hold the message and deliver it later (subject to constraints on timestamp validity - so this is a limited attack)&lt;/li&gt;
&lt;li&gt;Deliver the message with altered contents (only if the contents themselves are not signed)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In the case of bearer-token authorization, a MITM owns the entire connection. The MITM can do the following:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Decline to deliver the message&lt;/li&gt;
&lt;li&gt;Hold the message and deliver it later&lt;/li&gt;
&lt;li&gt;Change the timestamp (if one is used at all)&lt;/li&gt;
&lt;li&gt;Change the contents of the message&lt;/li&gt;
&lt;li&gt;Create an arbitrary number of new messages with arbitrary contents using the bearer-token and deliver those (subject to bearer-token invalidation)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In my opinion, #5 makes bearer-tokens without SSL/TLS unusable in nearly all use cases. Any use case where spam is a threat, for example, is not an acceptable use case because of this attack because any MITM can send an arbitrary number of spam messages that the provider application will show as coming directly from the user. If you think Twitter direct-message spam is bad now...&lt;/p&gt;
&lt;p&gt;Because of this and other possible attacks (successfully impersonating a provider gets you the bearer-token, for example), I think that bearer-tokens without SSL/TLS are not worth considering further for the vast majority of use cases.&lt;/p&gt;
&lt;h2&gt;So what about SSL/TLS? Can't we just use that for everything and forget about signatures?&lt;/h2&gt;
&lt;p&gt;Yes, but there are tradeoffs.&lt;/p&gt;
&lt;p&gt;The most important and unavoidable tradeoff is performance. The performance tradeoff comes in two flavors:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Connection latency to bootstrap the connection (from the asymmetric/public-key encryption operations)&lt;/li&gt;
&lt;li&gt;Processing to encrypt requests (from the symmetric/private-key encryption operations)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Connection latency is incurred to the public-key cryptography operations that are used to facilitate the exchange of a shared secret, which can then be used in the much more efficient private/symmetric key cryptography used to encrypt the individual messages. Research (&lt;a href="http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.62.8589&amp;amp;rep=rep1&amp;amp;type=pdf"&gt;1 - pdf&lt;/a&gt;) indicates that, on the web server, SSL introduces approximately a 70% overhead over basic HTTP for small requests and that of this overhead almost 90% is incurred in the public key operations.&lt;/p&gt;
&lt;p&gt;As request size increases, the public key operation overhead remains constant while the private key operations to encrypt the request scales more or less linearly with request size. As a result, the actual act of encrypting the request becomes the limiting factor relatively quickly for large requests.&lt;/p&gt;
&lt;p&gt;Keep in mind, this is additional latency on the web server, not on the client. Other research (&lt;a href="http://iweb.tntech.edu/hexb/publications/https-STAR-03122003.pdf"&gt;2 - pdf&lt;/a&gt;) indicates that clients may be even more resource-bound for these types of operations, resulting in even more severe latency problems.&lt;/p&gt;
&lt;p&gt;It's important to note that these two types of performance impact become problematic under different use cases.&lt;/p&gt;
&lt;h2&gt;When SSL makes sense and when it doesn't&lt;/h2&gt;
&lt;p&gt;SSL has negative performance impacts which will result in real user experience performance impacts in certain situations. Service providers can use specialized hardware to significantly accelerate the necessary cryptography operations, but any client smaller than a medium-sized web application provider will not be able to afford the cost of such hardware. As such, these clients will suffer a user experience impact from the required use of SSL/TLS connections in some situations.&lt;/p&gt;
&lt;p&gt;What are these situations?&lt;/p&gt;
&lt;p&gt;Connection latency is an issue when new connections must be established for most operations. This is the case for intermittent API requests. Some use cases where this could be an issue (keeping in mind that even an extra fraction of a second of wait-time is a user-experience penalty that many applications may be unhappy paying):&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Display statistics in a rich/desktop app in response to a user request (Ego app on iPhone requesting statistics from Google Analytics, for instance)&lt;/li&gt;
&lt;li&gt;Check for new messages in a rich/desktop in response to a user request (Dashboard widget, for example)&lt;/li&gt;
&lt;li&gt;Request data within a web application from a little-used third-party provider in response to a user request (web app like youcalc.com with ability to connect to arbitrary OAuth-enabled datastores)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Meanwhile, encryption of the request becomes an issue for large request uploads and download especially. Some examples:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Video and media file access requests, including streaming&lt;/li&gt;
&lt;li&gt;Google Docs document list API upload/download requests&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In both of the situations outlined above, SSL/TLS imposes a performance overhead that should theoretically have user experience implications. As I've been thinking about this issue, the following matrix has proven helpful in visualizing the problem space and helping to show that SSL/TLS is a clear winner in only a limited (but admittedly very common) portion of the problem space.&lt;/p&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Sensitive to connection latency&lt;/th&gt;
&lt;th&gt;Not sensitive to connection latency&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Large requests&lt;/th&gt;
&lt;td&gt;SSL/TLS not a good option&lt;/td&gt;
&lt;td&gt;SSL/TLS may be an option&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;No large requests&lt;/th&gt;
&lt;td&gt;SSL/TLS may be an option&lt;/td&gt;
&lt;td&gt;SSL/TLS is a good option&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;h2&gt;How does signing address these use cases?&lt;/h2&gt;
&lt;p&gt;The answer to this question depends on the form that signing ends up taking. If only the authentication parameters are signed, then we are in a situation similar to OAuth 1.0a in which latency is minimal because the hash operation used in the signing is cheap and is executed on a very small amount of data. Further, performance is unrelated to request size because only the authentication parameters are signed and the length of these parameters are unrelated to the request payload.&lt;/p&gt;
&lt;p&gt;If full message signing is required, then we start to run into the encryption penalty, as effort to calculate the hash of the entire payload should scale more or less linearly with the size of the payload. However, connection latency should remain minimal for small requests.&lt;/p&gt;
&lt;p&gt;Of course, using signatures without transport-layer security brings with it a whole new matrix of known security considerations. As mentioned at the beginning of this little essay, from a security perspective, signing appears to be at least as good in all respects as bearer tokens sent in the clear.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;There are theoretical and currently deployed use-cases in which dispensing with SSL/TLS and signing at least the authentication parameters of a request makes sense. These are use cases in which SSL/TLS has an undesirable user experience impact and in which leaving the request payload in the clear does not have significant negative security impacts. For this reason, and because the signing approach is the predominant deployed OAuth method (even though SSL/TLS with a bearer token is an option), we should maintain a signature method in the current draft.&lt;/p&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.62.8589&amp;amp;rep=rep1&amp;amp;type=pdf"&gt;http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.62.8589&amp;amp;rep=rep1&amp;amp;type=pdf&lt;/a&gt; - Anatomy and Performance of SSL Processing - Li Zhao, Ravi Iyer, Srihari Makineni, Laxmi Bhuyan - p. 4 is the meat&lt;/li&gt;
&lt;li&gt;&lt;a href="http://iweb.tntech.edu/hexb/publications/https-STAR-03122003.pdf"&gt;http://iweb.tntech.edu/hexb/publications/https-STAR-03122003.pdf&lt;/a&gt; - A Performance Analysis of Secure HTTP Protocol - Xubin He&lt;/li&gt;
&lt;/ol&gt;

&lt;!--
&lt;rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/"&gt;
&lt;rdf:Description rdf:about="http://www.esjewett.com/blog/oauth-signatures-why-we-need-em" dc:identifier="http://www.esjewett.com/blog/oauth-signatures-why-we-need-em" dc:title="OAuth signatures: why we need &amp;#039;em" trackback:ping="http://www.esjewett.com/trackback/50" /&gt;
&lt;/rdf:RDF&gt;
--&gt;
&lt;div class="trackback-url"&gt;&lt;div class="box"&gt;

  &lt;h2&gt;Trackback URL for this post:&lt;/h2&gt;

  &lt;div class="content"&gt;http://www.esjewett.com/trackback/50&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/esjewett?a=qUL7N51MO7k:nk1DXJUgfyA:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/esjewett?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/esjewett?a=qUL7N51MO7k:nk1DXJUgfyA:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/esjewett?i=qUL7N51MO7k:nk1DXJUgfyA:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/esjewett?a=qUL7N51MO7k:nk1DXJUgfyA:SSaB9coO5pA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/esjewett?i=qUL7N51MO7k:nk1DXJUgfyA:SSaB9coO5pA" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/esjewett/~4/qUL7N51MO7k" height="1" width="1"/&gt;</description>
 <category domain="http://www.esjewett.com/tag/oauth">OAuth</category>
 <category domain="http://www.esjewett.com/tag/performance">performance</category>
 <category domain="http://www.esjewett.com/tag/security">security</category>
 <pubDate>Sat, 06 Mar 2010 23:25:21 +0000</pubDate>
 <dc:creator>esjewett</dc:creator>
 <guid isPermaLink="false">50 at http://www.esjewett.com</guid>
<feedburner:origLink>http://www.esjewett.com/blog/oauth-signatures-why-we-need-em</feedburner:origLink></item>
<item>
 <title>Why in-memory doesn't matter (and why it does)</title>
 <link>http://feedproxy.google.com/~r/esjewett/~3/aacEJzb08OU/why-in-memory-doesnt-matter-and-why-it-does</link>
 <description>&lt;p&gt;Well, that title was going to be perfect flame-bait, but then I went all moderate and decided to write a blog that actually matters. So here's the low-down:&lt;/p&gt;
&lt;p&gt;There's a lot of talk lately about in-memory and how it's the awesome. This is especially true in the SAP-o-sphere, primarily due to SAP's marketing might getting thrown behind &lt;a href="http://www.sdn.sap.com/irj/sdn/nw-bwa"&gt;Business Warehouse Accelerator (BWA)&lt;/a&gt; and the &lt;a href="http://www.informationweek.com/news/services/saas/showArticle.jhtml?articleID=218900184"&gt;in-memory analytics&lt;/a&gt; baked into &lt;a href="http://www.sap.com/sme/solutions/businessmanagement/businessbydesign/index.epx"&gt;Business ByDesign&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I'm here today to throw some cold water on that pronouncement. Yes, in-memory is a great idea in a lot of situations, but it has its downsides, and it won't address a lot of the issues that people are saying it addresses. In the SAP space, I blame some of the marketing around BWA. In the rest of the internet, I'm not sure if this is even an issue.     &lt;/p&gt;
&lt;p&gt;Since I've actually done a fair amount of thinking about these issues (and as a result I &lt;a href="http://twitter.com/esjewett/status/6581020927"&gt;troll&lt;a/&gt; &lt;a href="http://twitter.com/esjewett/status/6465987159"&gt;people&lt;/a&gt; on Twitter about it), I thought maybe it'd be helpful if I wrote it down.&lt;/a/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;So let's get down to brass tacks:&lt;/p&gt;
&lt;h3&gt;How in-memory helps&lt;/h3&gt;
&lt;p&gt;In short: it speeds everything up.&lt;/p&gt;
&lt;p&gt;How much? Well, let's do the math:&lt;/p&gt;
&lt;p&gt;Your high-end server hard drive has a seek time of around &lt;a href="http://en.wikipedia.org/wiki/Hard_disk_drive#Seek_time"&gt;2 ms&lt;/a&gt;. That's 2*10^-3 seconds (thanks &lt;a href="http://www.google.com/search?q=2ms+in+seconds"&gt;Google&lt;/a&gt;). Yes, I'm ignoring rotational latency to keep it simple.&lt;/p&gt;
&lt;p&gt;Meanwhile, fast RAM has a &lt;a href="http://en.wikipedia.org/wiki/Memory_latency"&gt;latency measured in nanoseconds&lt;/a&gt;. Let's say 10ns to keep it simple. That's 10^-8 seconds.&lt;/p&gt;
&lt;p&gt;So, if I remember my arithmetic (and I don't), RAM is about 2*10^5, or 200,000 times faster than hard disk access.&lt;/p&gt;
&lt;p&gt;Keep in mind that RAM is actually faster because the CPU-memory interface usually supports faster transfer rates than the disk-CPU interface. But then, hard disks are actually faster because there are ways to drastically improve overall access performance and transfer rates (RAID, iSCSI? - not really my area). Point is, RAM helps your data access go a lot faster.&lt;/p&gt;
&lt;h3&gt;But ... er ... wait a second (or several thousand)&lt;/h3&gt;
&lt;p&gt;So here I am thinking, "Well, we're all fine and dandy then. I just put my job in RAM and it goes somewhere between 100,000 and 1,000,000 times as fast. Awesome!".               &lt;/p&gt;
&lt;p&gt;But then I remember that RAM isn't a viable backing store for some applications, like ERPs (no matter what &lt;a href="http://www.informationweek.com/news/software/erp/showArticle.jhtml?articleID=217400833"&gt;Hasso Plattner seems to be saying&lt;/a&gt;) or any other application where you can't lose data, period. Yes it can act as a cache, but your writes (at least) are going to have to be transactional and will be constrained by the speed of your actual backing store, which will probably be a database on disk.                                                           &lt;/p&gt;
&lt;p&gt;And then I see actual benchmarks meant to reflect the real world like &lt;a href="http://www.xbitlabs.com/articles/storage/display/ssd-iram_6.html"&gt;this&lt;/a&gt;. For those who won't click the link, the numbers are a bit hard to read, but I'm seeing RAM doing about 10,000 database operations in the amount of time it takes a hard disk store to do about 100. That's only a 100x speedup.&lt;/p&gt;
&lt;p&gt;Ok, now I'm back down to earth and I'm thinking, "I just put my job in RAM and I'll get maybe a 50-100x speedup but at the cost of significant volatility". (I'm also thinking that SAP's &lt;a href="http://www.sdn.sap.com/irj/sdn/nw-bwa?rid=/library/uuid/3604c604-0901-0010-f0aa-b37378495537"&gt;claimed performance improvements of 10x - 100x&lt;/a&gt; sound just about like what we'd expect.)&lt;/p&gt;
&lt;p&gt;This is still really really good. It makes some things possible that were not possible before and it makes some things easy that used to be hard.&lt;/p&gt;
&lt;h3&gt;And finally, why in-memory doesn't matter&lt;/h3&gt;
&lt;p&gt;But really, what is the proportion of well-optimized workloads in the world? How often are people going to use in-memory as an excuse to be lazy about solving the actual underlying problems? In my experience, a lot. Already we are hearing things along the lines of, "The massive BW query on a DSO is slow? Throw the DSO into the BWA index." [Editor's note: A DSO is essentially a flat table. Also, the current version of BWA doesn't support direct indexing of DSOs, but it probably will soon, along with directly indexing ERP tables.]&lt;/p&gt;
&lt;p&gt;Now's the part where we who know what we're doing tear these people to shreds and tell them to implement a real Information Lifecycle Management system and build a Inmon-approved data warehouse using their BW system (BW makes it relatively easy). Then that complex query on a flat table that used to take two days of runtime will run in 30 seconds.&lt;/p&gt;
&lt;p&gt;Well, that would be one approach, but frankly most people and companies don't have the time or the organizational maturity in their IT function to pull this off. And in this world, where people have neither the time nor the business process for this sort of thing, then it starts to make sense to spend money on it, and something like BWA is a great thing in this context.&lt;/p&gt;
&lt;p&gt;But it's not great because it's in-memory. It's great because it takes your data - that data you haven't had the time to properly build into a datawarehouse with a layered and scalable architecture, highly optimized ROLAP stores, painstakingly configured caching, and carefully crafted delta processes - and it compresses it, partitions it, and denormalizes it (where appropriate). Then, as the icing on the cake, it caches the heck out of it in memory.&lt;/p&gt;
&lt;p&gt;Let's be clear: BW already has in-memory capabilities. Livecache is used with APO, and the OLAP cache resides in memory. The reason BWA matters is not that it is in-memory. It matters because it does the hard work for you behind the scenes, and partially because of this it is able to use architectural paradigms like column-based stores, compression, and partitioning that deliver performance improvements for certain types of queries regardless of the backing store.&lt;/p&gt;
&lt;p&gt;In-memory is great, and fast, and should be used. But in most ways that are really important, it doesn't matter all that much.&lt;/p&gt;

&lt;!--
&lt;rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/"&gt;
&lt;rdf:Description rdf:about="http://www.esjewett.com/blog/why-in-memory-doesnt-matter-and-why-it-does" dc:identifier="http://www.esjewett.com/blog/why-in-memory-doesnt-matter-and-why-it-does" dc:title="Why in-memory doesn&amp;#039;t matter (and why it does)" trackback:ping="http://www.esjewett.com/trackback/48" /&gt;
&lt;/rdf:RDF&gt;
--&gt;
&lt;div class="trackback-url"&gt;&lt;div class="box"&gt;

  &lt;h2&gt;Trackback URL for this post:&lt;/h2&gt;

  &lt;div class="content"&gt;http://www.esjewett.com/trackback/48&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/esjewett?a=aacEJzb08OU:7kbnZpOJWeM:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/esjewett?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/esjewett?a=aacEJzb08OU:7kbnZpOJWeM:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/esjewett?i=aacEJzb08OU:7kbnZpOJWeM:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/esjewett?a=aacEJzb08OU:7kbnZpOJWeM:SSaB9coO5pA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/esjewett?i=aacEJzb08OU:7kbnZpOJWeM:SSaB9coO5pA" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/esjewett/~4/aacEJzb08OU" height="1" width="1"/&gt;</description>
 <category domain="http://www.esjewett.com/tag/architecture">architecture</category>
 <category domain="http://www.esjewett.com/tag/bwa">bwa</category>
 <category domain="http://www.esjewett.com/tag/enterprise">enterprise</category>
 <category domain="http://www.esjewett.com/tag/in-memory">in-memory</category>
 <category domain="http://www.esjewett.com/tag/sap">sap</category>
 <pubDate>Sun, 13 Dec 2009 01:54:15 +0000</pubDate>
 <dc:creator>esjewett</dc:creator>
 <guid isPermaLink="false">48 at http://www.esjewett.com</guid>
<feedburner:origLink>http://www.esjewett.com/blog/why-in-memory-doesnt-matter-and-why-it-does</feedburner:origLink></item>
<item>
 <title>What's the deal with JAX-RS and Lift?</title>
 <link>http://feedproxy.google.com/~r/esjewett/~3/SIBkS-kK2QQ/whats-the-deal-with-jax-rs-and-lift</link>
 <description>&lt;p&gt;There has been some talk lately (from the Java performance maven &lt;a href="http://twitter.com/kohlerm"&gt;@kohlerm&lt;/a&gt; and others) about JAX-RS in the context of the API of a Lift application - specifically &lt;a href="http://incubator.apache.org/esme/"&gt;ESME&lt;/a&gt;. &lt;a href="http://jcp.org/en/jsr/detail?id=311"&gt;JAX-RS&lt;/a&gt; is a Java annotation framework for programming RESTful web services (APIs for the non-enterprisey out there). &lt;/p&gt;
&lt;p&gt;The real goal of the talk about JAX-RS with regards to ESME (as I see it, and I'm not the only point of view on this by a long shot), is to create an API that is as RESTful as reasonably possible, and which (for most resources) is indistinguishable from a JAX-RS implementation from the perspective of a client consuming the API.&lt;/p&gt;
&lt;p&gt;It appears at first glance that the easiest way to achieve the goal of indistinguishability from a JAX-RS implementation is to do a JAX-RS implementation. I'm not convinced it is so straightforward.&lt;/p&gt;
&lt;p&gt;Here are the requirements for a platform for implementing an HTTP API for ESME, in my view:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Access to ESME instance objects as instantiated in the Lift application (preferably we are not using the database as the way that the API and the application communicate, especially since ESME doesn't necessarily even have a DB in my understanding)&lt;/li&gt;
&lt;li&gt;Respect the ESME security architecture (meaning that the API must act as a specific user)&lt;/li&gt;
&lt;li&gt;Support delta/streaming collections over HTTP in addition to REST-only resources and collections&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Meanwhile, I've found a few instances of people attempting to use JAX-RS in the context of a Lift application. Unfortunately, I don't think they actually meet these requirements.                                                &lt;/p&gt;
&lt;p&gt;Here (&lt;a href="http://blogs.sun.com/sandoz/entry/using_scala_s_closures_with"&gt;http://blogs.sun.com/sandoz/entry/using_scala_s_closures_with&lt;/a&gt;) we have a discussion of how we can implement JAX-RS provider classes in Scala instead of in Java. This is, admittedly very attractive, but I'm not seeing a way to import the entire Lift application context into these provider classes. Or rather, enough of the context to satisfy my requirements 1 &amp;amp; 2 above.&lt;/p&gt;
&lt;p&gt;I had a fleeting moment of hope when I saw &lt;a href="http://github.com/jstrachan/liftweb/tree/master/lift-jersey"&gt;lift-jersey&lt;/a&gt; and &lt;a href="http://groups.google.com/group/liftweb/browse_thread/thread/dba9a11251aa5067"&gt;this thread&lt;/a&gt; where we have James Strachan writing a Lift module that appears to allow us to use the Lift templating language within a JAX-RS implementation in Scala. However, it looks to me like this only allows using the Lift templating language, and by and large replaces the Lift request-handling stack with JAX-RS. This is sort of like what we want to do, but not really, and I think we're going to have the same struggles with integrating the Lift application itself that we would have had with the first example. &lt;/p&gt;
&lt;p&gt;Where we'll really start running into trouble, even if we can satisfy requirements 1 &amp;amp; 2, is in requirement 3. In ESME, we some resource collections that need to be provided in a "delta" or "streaming" format to our clients, but also provide more orthodox RESTful HTTP interfaces. JAX-RS doesn't appear to be very friendly to the delta/streaming problem-space, so we will actually be forced to implement the streaming parts of these resources in Lift/Scala directly. This means that we can't just cordon off a portion of the URI-space of the ESME application to be served by Jersey or another JAX-RS container, outside of the context of the Lift application. The two containers would need to be very closely intertwined in the URL-space of the ESME application. In other words, we'd need to pattern-match before the request even hits Jersey and JAX-RS, which kind of defeats the purpose.&lt;/p&gt;
&lt;p&gt;So, that little exploration was relatively fruitless, but maybe this blog will attract some comment setting me straight on how to do this.&lt;/p&gt;
&lt;p&gt;Meanwhile, I had the thought "What's so great about annotations anyway?" My first impression is that there is nothing so great about annotations. First impressions are usually wrong, but it got me thinking along the lines that what JAX-RS does with annotations is suspiciously similar to what the Lift request dispatcher does with pattern matching. Here, let's loosely borrow an example from the &lt;a href="//github.com/jstrachan/liftweb/blob/master/lift-jersey-test/src/main/scala/net/liftweb/jersey/test/resources/ResourceReturningTemplateView.scala"&gt;lift-jersey Github page&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;import javax.ws.rs.{Produces, Path, GET}&lt;br /&gt;
/**&lt;br /&gt;
 * @version $Revision: 1.1 $&lt;br /&gt;
 */&lt;br /&gt;
@Path("/resourceReturningTemplateView")&lt;br /&gt;
class ResourceReturningTemplateView{&lt;br /&gt;
&amp;nbsp;&amp;nbsp;@GET&lt;br /&gt;
&amp;nbsp;&amp;nbsp;def view() = &amp;lt;xml_container&amp;gt;Some Text&amp;lt;/xml_container&amp;gt;&lt;br /&gt;
}&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;So, all this says is that when we receive a GET request to the path "/resourceReturningTemplateView" in our application, return the results of the view() method, which in this case is a string of XML.&lt;/p&gt;
&lt;p&gt;What does this look like in a native Lift? Well, ignoring the other Lift incantations that need to be done (which are really just one line in Boot.scala, a class definition, and an implicit function that converts from a NodeSeq or Elem to a LiftResponse), all that's required is something like&lt;/p&gt;
&lt;p&gt;&lt;code&gt;def dispatch: LiftRules.DispatchPF = {&lt;br /&gt;
&amp;nbsp;&amp;nbsp;case Req("resourceReturningTemplateView" :: Nil, _, GetRequest) =&amp;gt;&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;xml_container&amp;gt;Some Text&amp;lt;/xml_container&amp;gt;&lt;br /&gt;
}&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Lift request matching appears to be pretty powerful, so I started looking at whether it can cover the use cases of JAX-RS. The upshot is that I think it does pretty much everything we need, so I'd prefer to stick with the Lift pattern matching over JAX-RS annotations for the time being. Some things may be a bit more complicated in Lift, like the @Consumes annotation and the Allow header value, or direct handling of form fields as specified in the @FormParam annotation. However, I think these are all doable and just require good patterns be developed.&lt;/p&gt;
&lt;p&gt;Final thought: I'm pretty sure that JAX-RS isn't really an API for RESTful web services. It's an API for pattern-matching HTTP requests in a language (Java) that doesn't have native pattern-matching.&lt;/p&gt;
&lt;p&gt;Final disclaimer: No actual code was harmed, or tested for that matter, in the writing of this blog. In other words, that code up there probably doesn't work, and that's nobody's fault but my own.&lt;/p&gt;

&lt;!--
&lt;rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/"&gt;
&lt;rdf:Description rdf:about="http://www.esjewett.com/blog/whats-the-deal-with-jax-rs-and-lift" dc:identifier="http://www.esjewett.com/blog/whats-the-deal-with-jax-rs-and-lift" dc:title="What&amp;#039;s the deal with JAX-RS and Lift?" trackback:ping="http://www.esjewett.com/trackback/47" /&gt;
&lt;/rdf:RDF&gt;
--&gt;
&lt;div class="trackback-url"&gt;&lt;div class="box"&gt;

  &lt;h2&gt;Trackback URL for this post:&lt;/h2&gt;

  &lt;div class="content"&gt;http://www.esjewett.com/trackback/47&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/esjewett?a=SIBkS-kK2QQ:jfpxINqbJNM:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/esjewett?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/esjewett?a=SIBkS-kK2QQ:jfpxINqbJNM:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/esjewett?i=SIBkS-kK2QQ:jfpxINqbJNM:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/esjewett?a=SIBkS-kK2QQ:jfpxINqbJNM:SSaB9coO5pA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/esjewett?i=SIBkS-kK2QQ:jfpxINqbJNM:SSaB9coO5pA" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/esjewett/~4/SIBkS-kK2QQ" height="1" width="1"/&gt;</description>
 <category domain="http://www.esjewett.com/tag/api">api</category>
 <category domain="http://www.esjewett.com/tag/esme">esme</category>
 <category domain="http://www.esjewett.com/tag/jax-rs">jax-rs</category>
 <category domain="http://www.esjewett.com/tag/lift">lift</category>
 <category domain="http://www.esjewett.com/tag/programming">programming</category>
 <category domain="http://www.esjewett.com/tag/scala">scala</category>
 <pubDate>Mon, 30 Nov 2009 01:54:55 +0000</pubDate>
 <dc:creator>esjewett</dc:creator>
 <guid isPermaLink="false">47 at http://www.esjewett.com</guid>
<feedburner:origLink>http://www.esjewett.com/blog/whats-the-deal-with-jax-rs-and-lift</feedburner:origLink></item>
<item>
 <title>Statistical misunderstandings and Google Book Search</title>
 <link>http://feedproxy.google.com/~r/esjewett/~3/d3jDHpdgyk0/statistical-misunderstandings-and-google-book-search</link>
 <description>&lt;p&gt;Well, there I was, chatting away with (hopefully still) a friend about configuring a Twitter search widget for a blog and up comes the Google Books meta-data topic, in the form of a link to &lt;a href="http://languagelog.ldc.upenn.edu/nll/?p=1701"&gt;Google Books: A Metadata Train Wreck&lt;/a&gt;. This is the kind of article that really gets at me. Which is to say that it is an article holding a position that I mostly agree with in principle, but which does the position the disservice of making pretty questionable arguments.&lt;/p&gt;
&lt;p&gt;So with that, I'll lay out two (or 5) things that really get my goat when people start talking about large data sets and meta-data.&lt;/p&gt;
&lt;h3&gt;The misunderstanding about the whole point&lt;/h3&gt;
&lt;p&gt;There seems to be a misunderstanding between the scholarly community and Google about what Google Books actually is. I think it is this misunderstanding that leads to claims like the one about "miscategorization" of translators as authors. It seems clear to me that the Google Books team made a conscious decision to put translators and authors into the same search field. As such, it's not an error so much as it is a semantic disagreement. I'm willing to bet that Google maintains author and translator metadata separately on the backend and just concatenates the fields for the purposes of searching.&lt;/p&gt;
&lt;p&gt;In brainstorming, I came up with a few things Google Book Search and the whole library digitalization project might be, from Google's perspective:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A vehicle for advertising and referral revenue for Google.&lt;/li&gt;
&lt;li&gt;A project to create a dataset that Google can use to train its translation and semantic knowledge engines.&lt;/li&gt;
&lt;li&gt;A dumb idea that will never help Google and will eventually be abandoned.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;It's possible that Google really conceives the project as at least partially having the goal of enabling scholarship, but I think #2 is probably the real goal, having watched how Google works for about a decade now. If I'm right about this, then Google Book Search is just a cover; a way of making the data gathered from libraries publicly available. If #1 is right, then having the author and translator in the same field is exactly the right thing to do, since lots of people will mistakenly search for the translator instead of the author.&lt;/p&gt;
&lt;p&gt;Would it be good for Google to provide a scholarly interface to Google Books metadata that splits out translator and author? Yes. Does Google have a responsibility to provide this interface? I don't see how.&lt;/p&gt;
&lt;h3&gt;The misleading or useless appeals to statistics and data&lt;/h3&gt;
&lt;p&gt;The second point is perhaps the more important one: There is often a misunderstanding or ignorance of statistical methods and the type of weird stuff that happens within large data-sets. I see this occasionally when the scholarly/library community blogs about Google Books, but I see it in a lot of other places too. This (I assume) ignorance results (again, I assume) in a belief that appeal to common sense or personal experience is a valid argumentative technique when large data sets are in play. From experience with large data sets, I have a tendency to believe that common sense and personal experience are worse than useless when large datasets are involved. In any case, this belief results in two major problems and we see examples of both in the above-linked blog.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Statistical problem #1:&lt;/b&gt; Claims about the number of errors in a particular set of meta-data without any comparison to existing meta-data sets that might give us a baseline of the number of errors we may expect. For example, if there are 2 errors of type X per 1000 records in Google Books, that doesn't tell me anything unless I also know that there are 0.3 errors of type X per 1000 records in the UC library system. That tells me Google Books has some catching up to do. On the other hand, maybe there are 5 errors per 1000 in the UC library system, in which case Google Books is doing pretty good. The blog (and most of these sorts of blogs) fails to give any useful metric for me to compare against.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Statistical problem #2:&lt;/b&gt; The sample size is usually left out. If there are 300 errors of type X in Google Books and only 100 errors of type X in my local public library ... well that probably means that Google books is 100x better than my local public library because it's got a lot more books in it. But I can't tell because no one bothers to say how many books are in Google Books and how many are in my local public library.&lt;/p&gt;
&lt;p&gt;In summary: A metric like "572 errors of type X" is useless because I don't know the sample size or have anything to compare to. "2 errors of type X per 1000 records in Google Books as opposed to 0.4 errors of type X per 1000 records in the Harvard University library system" on the other hand is incredibly useful as it provides a basis for comparison and understanding.&lt;/p&gt;
&lt;h3&gt;To be completely clear&lt;/h3&gt;
&lt;p&gt;Again, I'm sympathetic to the desire for a great digitally accessible and searchable book repository. I'm sympathetic to the underlying concern about the devaluation of knowledge and education that drives a lot of these arguments (for example, &lt;a href="http://lisagoldresearch.wordpress.com/2009/09/05/when-i-look-at-books-i-see-an-outdated-technology-like-scrolls-before-books/"&gt;this blog&lt;/a&gt;, which said friend later forwarded). But arguments made in an undisciplined and sometimes misleading manner actually do more to hinder the cause than to further it, at least in my mind.&lt;/p&gt;

&lt;!--
&lt;rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/"&gt;
&lt;rdf:Description rdf:about="http://www.esjewett.com/blog/statistical-misunderstandings-and-google-book-search" dc:identifier="http://www.esjewett.com/blog/statistical-misunderstandings-and-google-book-search" dc:title="Statistical misunderstandings and Google Book Search" trackback:ping="http://www.esjewett.com/trackback/46" /&gt;
&lt;/rdf:RDF&gt;
--&gt;
&lt;div class="trackback-url"&gt;&lt;div class="box"&gt;

  &lt;h2&gt;Trackback URL for this post:&lt;/h2&gt;

  &lt;div class="content"&gt;http://www.esjewett.com/trackback/46&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/esjewett?a=d3jDHpdgyk0:DTTUyZYlY70:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/esjewett?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/esjewett?a=d3jDHpdgyk0:DTTUyZYlY70:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/esjewett?i=d3jDHpdgyk0:DTTUyZYlY70:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/esjewett?a=d3jDHpdgyk0:DTTUyZYlY70:SSaB9coO5pA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/esjewett?i=d3jDHpdgyk0:DTTUyZYlY70:SSaB9coO5pA" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/esjewett/~4/d3jDHpdgyk0" height="1" width="1"/&gt;</description>
 <category domain="http://www.esjewett.com/tag/argument">argument</category>
 <category domain="http://www.esjewett.com/tag/books">books</category>
 <category domain="http://www.esjewett.com/tag/data">data</category>
 <category domain="http://www.esjewett.com/tag/google">google</category>
 <category domain="http://www.esjewett.com/tag/statistics">statistics</category>
 <pubDate>Sun, 06 Sep 2009 22:56:42 +0000</pubDate>
 <dc:creator>esjewett</dc:creator>
 <guid isPermaLink="false">46 at http://www.esjewett.com</guid>
<feedburner:origLink>http://www.esjewett.com/blog/statistical-misunderstandings-and-google-book-search</feedburner:origLink></item>
<item>
 <title>The infrastructure coop and the web</title>
 <link>http://feedproxy.google.com/~r/esjewett/~3/s2ipYoDxhCs/the-infrastructure-coop-and-the-web</link>
 <description>&lt;p&gt;(In which I take a moment to meander on a topic about which I know very little.)&lt;/p&gt;
&lt;p&gt;In Doc Searls' recent writing on &lt;a href="http://blogs.law.harvard.edu/doc/2009/08/12/the-ultimate-alignment/" title="&amp;quot;The Ultimate Alignment&amp;quot;"&gt;"The Ultimate Alignment"&lt;/a&gt; he writes on the disconnect between the interests of the companies that are building the web and the people who are using it. To summarize, companies have (or are supposed to have) their investor's best interests in mind rather than the best interests of their customers and users.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Theoretically, our efficient market system is supposed to align these two groups of interests. Or at least mediate between them. In theory, theory and practice are the same. In practice they tend to differ and one group of interests has a tendency to be subverted. When it comes to situations like the recent Facebook acquisition of Friendfeed or cable and telecom companies colluding with the recording industry to restrict peer-to-peer connections, the disconnect between the interests of users and investors comes into stark relief, and we see that users can be left out in the cold when interests conflict.&lt;/p&gt;
&lt;p&gt;Customers have generally been getting the short end of the stick, or the &lt;a href="http://www.theregister.co.uk/2008/03/17/bt_phorm_lies/" title="pointy end"&gt;pointy end&lt;/a&gt; if they're especially unlucky. This can, and probably should be attributed to a lack of customer organization and a resultant lack of bargaining power; something that is starting to be addressed with varying success by the social web, and which Searls sees culminating in a real capability for direct customer negotiation, which he calls Vendor Relationship Management.&lt;/p&gt;
&lt;p&gt;A somewhat alternative approach &lt;a href="http://www.scripting.com/stories/2009/08/11/alignTheInterestsOf1UsersA.html" title="proposed by Dave Winer"&gt;proposed by Dave Winer&lt;/a&gt;, which Searls writes about in the above linked article and to which he appears sympathetic, is the tale in which the customers seize the reins of power and re/self-organize companies into a sane and impeccably moral engine of economic progress, for the good of all. [Ed. Sometimes he has a hard time keeping the lid on the sarcasm, especially in entries written on airplanes. Must be the altitude.]&lt;/p&gt;
&lt;p&gt;Despite the sarcasm, I'm incredibly sympathetic to this idea, so after all of that throat clearing, I'd like to think about how it would work. What Searls and Winer are suggesting is a customer-owned coop.&lt;/p&gt;
&lt;h4&gt;Coops are cooperatives&lt;/h4&gt;
&lt;p&gt;My understanding of a customer-owned coop is that it is a business which is wholly owned and capitalized by its customers and the purpose of which is to provide a specific service rather than to turn a profit. If additional capital beyond what is required to deliver the service is generated, that capital is returned to the customer/owners. If an existing customer would like to become an owner then they can invest the requisite amount in the coop that is required to receive an equal share. Usually there is some ownership incentive provided, such as the ability to make use of the coop's services, or a discount on those services.&lt;/p&gt;
&lt;p&gt;There are lots of kinds of coops, from housing and utility coops with services available only to owners to grocery stores and book stores which operate as commercial entities and often provide only slim discounts to owners. Public companies and governments are arguably coops, though they are not usually acknowledged as such and are usually not strictly customer/user-owned. (Though this is usually fairly close to the truth in the case of democratically elected governments with an electoral process that is relatively uninfluenced by monetary concerns ... and we know there are plenty of those around. [Ed. This sarcasm is getting a tad out of control, isn't it?])&lt;/p&gt;
&lt;p&gt;Coops are often formed to establish some vital piece of infrastructure that a community feels is not adequately provided by existing commercial or governmental entities. A community that is lacking a quality grocery store will sometimes establish one as a coop. A university community that feels its access to academic books is restricted or provided at far too high a price by a commercial book store might establish a coop bookstore, effectively buying itself a distribution channel that did not exist before. Same goes for utilities.&lt;/p&gt;
&lt;p&gt;In this way of thinking, coops are usually focused on infrastructure, with a special focus on distribution infrastructure during the 20th century. Distribution of food, books, natural gas, or electricity.&lt;/p&gt;
&lt;h4&gt;Open source is a bit like a coop ... and how&lt;/h4&gt;
&lt;p&gt;Open source software development is like a coop. The buy-in is contribution, of code, testing, bug-wrangling, documentation, publicity, or community process. The ownership privileges are varied. Contributions of code usually buy the contributor partial ownership of the code-base and the accompanying control, depending on the license - a privilege that cannot be purchased in any other way. Same goes for contributions of documentation &lt;i&gt;vis a vis&lt;/i&gt; the body of documentation. All other contributions buy a less well defined voice in the community process.&lt;/p&gt;
&lt;p&gt;Open source projects often resemble coops in another manner in that they are usually established to fill a need that a community feels is not adequately met (or is not met at an appropriate price) by existing commercial offerings. Some of the most successful open source projects have been and continue to be focused on infrastructure.&lt;/p&gt;
&lt;h4&gt;Open source as barter coop and the problem therein&lt;/h4&gt;
&lt;p&gt;This ground has been pretty heavily tread before, but the above description is of a coop established via barter, rather than exchange of more abstract monetary instruments and the signing of binding legal contracts. Importantly open source projects are only open to ownership by individuals who are prepared to deal in a particular instrument of barter. Open source is a &lt;a href="http://catb.org/~esr/writings/cathedral-bazaar/cathedral-bazaar/" title="bazaar"&gt;bazaar&lt;/a&gt;, but it is a bazaar that is only accessible to those who can pay the entrance fee in a particular currency. It is a bazaar, frequented only by merchants and large distributors, not by individual shoppers. It has the best prices and the best goods, but good luck getting to them.&lt;/p&gt;
&lt;p&gt;And therein, of course, is a problem with the open source cooperative model. The strength of many cooperatives is that any interested user can become an owner (and often does), and this is the model that Winer and Searls would like to see, it seems. But in the open source world this is not the case. In meat-space cooperatives, and bazaars for that matter, the answer is money. Anyone with money can buy a share in a coop grocery store. You don't necessarily need to be able to contribute broccoli or organic lamb to participate.&amp;nbsp;&lt;/p&gt;
&lt;h4&gt;Coops as cooperatives (and the trouble with staying that way)&lt;/h4&gt;
&lt;p&gt;Often coops seem to work best when they are providing services to a local community. This may be partially the case because it reduces the interest in joining a coop for reasons other than taking advantage of the primary service that a coop provides. This local focus can be used as a hedge against the possibility of a group of disinterested investors taking over the coop and turning it into a profit-making operation, divorced from a direct interest in providing a service to its customers and instead focused on providing a service to its owners (now two different groups of people). &lt;i&gt;Viola&lt;/i&gt;! Modern commercial entity.&lt;/p&gt;
&lt;p&gt;For this reason I very much doubt that founding a new company and immediately IPOing as Winer suggests would not accomplish the goal of creating a company that is truly aligned with its users and customers to provide a service they desire or require. Some guard or incentive must exist, as is the case with coops, to keep the coop owned by its users and customers, yet still open to membership from new and interested users and customers.&lt;/p&gt;
&lt;p&gt;Open source projects don't have the local angle going for them, but truthfully there is precious little to gain from participating in an open source project if ones goal is not to improve the project. Ego and a bullet on the resume perhaps. Through narrow focus and severely restricted ownership, these projects make sure that all owners are user/customers, even if they cannot guarantee (and in fact do not aspire to a reality) that all users are owners.&lt;/p&gt;
&lt;h4&gt;And so we come to the point&lt;/h4&gt;
&lt;p&gt;We have open source projects, which are fairly successful at making sure that they are guided primarily by input from user/customers but which can do a fairly bad job of making sure that all users/customers are well-served. We have commercial entities which do a good job of serving their owners but a bad job of making sure that their owners are also their customers, resulting in divergent interests. We have local coops, which tend to do a pretty good job of both but don't tend to scale well past a local community.&lt;/p&gt;
&lt;p&gt;And then we're talking about essentially a distributed infrastructure coop that shares some of the best features of local coops and commercial entities. What structural features could a web-infrastructure cooperative boast that would meet the following necessary requirements?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Any user/customer can (and often does) become an owner&lt;/li&gt;
&lt;li&gt;Only user/customers become owners (with the possible exception of a small minority ownership by other interested parties)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I don't know the answer to this question. In fact, a part of me doubts that there is an answer and that we will be better served attempting to scale community processes. But it seems it is the crux of the problem of the dis-alignment in web infrastructure.&lt;/p&gt;

&lt;!--
&lt;rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/"&gt;
&lt;rdf:Description rdf:about="http://www.esjewett.com/blog/the-infrastructure-coop-and-the-web" dc:identifier="http://www.esjewett.com/blog/the-infrastructure-coop-and-the-web" dc:title="The infrastructure coop and the web" trackback:ping="http://www.esjewett.com/trackback/45" /&gt;
&lt;/rdf:RDF&gt;
--&gt;
&lt;div class="trackback-url"&gt;&lt;div class="box"&gt;

  &lt;h2&gt;Trackback URL for this post:&lt;/h2&gt;

  &lt;div class="content"&gt;http://www.esjewett.com/trackback/45&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/esjewett?a=s2ipYoDxhCs:SFGp5WUctHo:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/esjewett?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/esjewett?a=s2ipYoDxhCs:SFGp5WUctHo:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/esjewett?i=s2ipYoDxhCs:SFGp5WUctHo:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/esjewett?a=s2ipYoDxhCs:SFGp5WUctHo:SSaB9coO5pA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/esjewett?i=s2ipYoDxhCs:SFGp5WUctHo:SSaB9coO5pA" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/esjewett/~4/s2ipYoDxhCs" height="1" width="1"/&gt;</description>
 <category domain="http://www.esjewett.com/tag/business">business</category>
 <category domain="http://www.esjewett.com/tag/coop">coop</category>
 <category domain="http://www.esjewett.com/tag/dave-winer">dave winer</category>
 <category domain="http://www.esjewett.com/tag/doc-searls">doc searls</category>
 <category domain="http://www.esjewett.com/tag/infrastructure">infrastructure</category>
 <category domain="http://www.esjewett.com/tag/software">software</category>
 <category domain="http://www.esjewett.com/tag/web">web</category>
 <pubDate>Sat, 15 Aug 2009 15:23:17 +0000</pubDate>
 <dc:creator>esjewett</dc:creator>
 <guid isPermaLink="false">45 at http://www.esjewett.com</guid>
<feedburner:origLink>http://www.esjewett.com/blog/the-infrastructure-coop-and-the-web</feedburner:origLink></item>
<item>
 <title>Make a webhook out of anything</title>
 <link>http://feedproxy.google.com/~r/esjewett/~3/YazP0j6dbdY/make-a-webhook-out-of-anything</link>
 <description>&lt;p&gt;(or ... "How I learned to stop worrying about doing it right, and just make the damn thing work")&lt;/p&gt;
&lt;p&gt;I've had this problem for a while:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;I use a great service called &lt;a href="http://www.instapaper.com" title="Instapaper"&gt;Instapaper&lt;/a&gt; (try it, seriously) for keeping track of my reading list. Which is great. But I want stuff to happen to certain items once I'm done reading something in Instapaper. They should be posted to Twitter, stored in Evernote, or squirreled away in &lt;a href="http://www.diigo.com" title="Diigo"&gt;Diigo&lt;/a&gt;, &lt;a href="http://www.delicious.com" title="del.icio.us"&gt;del.icio.us&lt;/a&gt;, or &lt;a href="http://www.pinboard.in" title="Pinboard"&gt;Pinboard&lt;/a&gt;.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;This just isn't very achievable. While Instapaper is totally awesome, it does not provide automatic posting to all (or any) of these sites. It doesn't even provide &lt;a href="http://blog.webhooks.org/about/" title="Webhooks"&gt;Webhooks&lt;/a&gt;, which might provide the ... hook ... to allow for this sort of posting via some service like Yahoo! Query Language (&lt;a href="http://developer.yahoo.com/yql/" title="YQL"&gt;YQL&lt;/a&gt;) or &lt;a href="http://www.tarpipe.com/" title="Tarpipe"&gt;Tarpipe&lt;/a&gt;. What Instapaper provides is an RSS feed of shared items.&lt;/p&gt;
&lt;p&gt;I've &lt;a href="http://www.esjewett.com/blog/tarpipe-with-yql" title="previously reviewed"&gt;previously reviewed&lt;/a&gt; how to use YQL to post to Tarpipe, which solves part of the problem. It is possible to use this technique to consume a feed in Yahoo! Pipes and then post each item to Tarpipe. But this isn't really what I want to do, because it will post each item to Tarpipe each time the feed is read. Which is going to result in a lot of duplicate Tweets, Evernote notes, or whatever else I'm having Tarpipe do.&lt;/p&gt;
&lt;p&gt;What we need is a Yahoo! Pipe that will only call the special Tarpipe (or other) YQL when there is a &lt;b&gt;new&lt;/b&gt; item in the feed. Pipes isn't very good at this, but in steps Google Reader. Pretty much all that Google Reader does is query a feed occasionally and keep track of when a new item appears.&lt;/p&gt;
&lt;p&gt;My strategy (and it works, bless Google and Yahoo!'s hearts) is to use Google Reader to check a Pipe and cache the items it has seen in a publicly accessible label. This is a little circular, but the very Pipe that Google Reader is checking pulls the feed I want to webhookify and compares the contents of the feed to the contents of the Google Reader label. If the Pipe sees any items in the feed that aren't yet stored in the Google Reader label, it does its magic on only those items.&lt;/p&gt;
&lt;p&gt;I've made the Pipe that does this public at &lt;a href="http://pipes.yahoo.com/esjewett/feed_to_webhook_using_google_reader" title="http://pipes.yahoo.com/esjewett/feed_to_webhook_using_google_reader"&gt;http://pipes.yahoo.com/esjewett/feed_to_webhook_using_google_reader&lt;/a&gt; but getting it working take a little doing, which is described below with screenshots. I'll demonstrate using a public feed (&lt;a href="http://daringfireball.net/" title="http://daringfireball.net/feeds/articles"&gt;Daring Fireball&lt;/a&gt;'s main article feed, because just about every single article is worth bookmarking), but you could use it on any feed that Yahoo! Pipes can access. I use it on my Instapaper starred items feed, among other things.&lt;/p&gt;
&lt;h4&gt;Step 1&lt;/h4&gt;
&lt;p&gt;Determine your Google Reader User ID by selecting your Shared Items feed in Google Reader:&lt;/p&gt;
&lt;p&gt;&lt;img src="http://www.esjewett.com/files/2009-08-07_1727.png" /&gt;&lt;/p&gt;
&lt;p&gt;Once you've selected this feed, take note of the URL in the address bar of your browser. It includes a string that is your Google Reader User ID. The ID contains &lt;b&gt;only numbers&lt;/b&gt;. That "F" in front of it is not part of the ID and the "%" after it is not part of the ID. Copy this ID down somewhere as you'll need it later.&lt;/p&gt;
&lt;p&gt;&lt;img src="http://www.esjewett.com/files/2009-08-07_1736.png" /&gt;&lt;/p&gt;
&lt;h4&gt;Step 2&lt;/h4&gt;
&lt;p&gt;Create your Pipe by cloning &lt;a href="http://pipes.yahoo.com/esjewett/feed_to_webhook_using_google_reader" title="http://pipes.yahoo.com/esjewett/feed_to_webhook_using_google_reader"&gt;http://pipes.yahoo.com/esjewett/feed_to_webhook_using_google_reader&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="http://www.esjewett.com/files/2009-08-07_1723.png" /&gt;&lt;/p&gt;
&lt;h4&gt;Step 3&lt;/h4&gt;
&lt;p&gt;Populate the user input fields of the Yahoo! Pipe with the feed you want to use ("http://daringfireball.net/feeds/articles" in our case), the Google Reader User ID from step 1, and the label you are going to make publicly accessible in Google Reader in a later step. The label you choose is important. It needs to be a label that is used for only this purpose and only this feed. Make it unique and call it something that will remind you of its purpose. I'll call mine "Daring Fireball Articles".&lt;/p&gt;
&lt;p&gt;Once you've done all this, click the "Run Pipe" button.&lt;/p&gt;
&lt;p&gt;&lt;img src="http://www.esjewett.com/files/2009-08-07_1738.png" /&gt;&lt;/p&gt;
&lt;h4&gt;Step 4&lt;/h4&gt;
&lt;p&gt;You should at this point get a list of the latest Daring Fireball articles, or whatever is in the feed that you've chosen to use. Click the button to add the Pipe results to Google Reader.&lt;/p&gt;
&lt;p&gt;&lt;img src="http://www.esjewett.com/files/2009-08-07_1740.png" /&gt;&lt;/p&gt;
&lt;p&gt;Click the "Add to Google Reader" button.&lt;/p&gt;
&lt;p&gt;&lt;img src="http://www.esjewett.com/files/2009-08-07_1743.png" /&gt;&lt;/p&gt;
&lt;h4&gt;Step 5&lt;/h4&gt;
&lt;p&gt;Now you should be in Google Reader staring at your newly subscribed feed. Click the "Feed settings" button.&lt;/p&gt;
&lt;p&gt;&lt;img src="http://www.esjewett.com/files/2009-08-07_1744.png" /&gt;&lt;/p&gt;
&lt;p&gt;Then choose "New Folder..." from the bottom of the list of option, and name the folder whatever you put in as the "Google Reader Label" above. In our case, it is "Daring Fireball Articles".&lt;/p&gt;
&lt;p&gt;&lt;img src="http://www.esjewett.com/files/2009-08-07_1746.png" /&gt;&lt;/p&gt;
&lt;p&gt;You should see the feed on the left sidebar, under the folder you just created.&lt;/p&gt;
&lt;p&gt;&lt;img src="http://www.esjewett.com/files/2009-08-07_1748.png" /&gt;&lt;/p&gt;
&lt;h4&gt;Step 6&lt;/h4&gt;
&lt;p&gt;Now we have all the infrastructure in place to actually do something with this feed. But we have not yet defined the action that our webhook Pipe will execute. So we need to tweak this pipe slightly. Go back to the pipe. You'll find your cloned version (And you did clone it didn't you?) at &lt;a href="http://pipes.yahoo.com/pipes/person.info" title="http://pipes.yahoo.com/pipes/person.info"&gt;http://pipes.yahoo.com/pipes/person.info&lt;/a&gt;, at the top.&lt;/p&gt;
&lt;p&gt;Edit the pipe.&lt;/p&gt;
&lt;p&gt;&lt;img src="http://www.esjewett.com/files/2009-08-07_1752.png" /&gt;&lt;/p&gt;
&lt;p&gt;At the lower-right corner of the edit screen is a loop operator with no module or pipe defined.&lt;/p&gt;
&lt;p&gt;&lt;img src="http://www.esjewett.com/files/2009-08-07_1755.png" /&gt;&lt;/p&gt;
&lt;p&gt;Drag any pipe or valid module into this loop. This action will be called exactly once for every new item in the feed you have just defined. If you want to post to Tarpipe, I recommend taking a look at the Pipe &lt;a href="http://pipes.yahoo.com/esjewett/post_to_tarpipe_1_0_api" title="http://pipes.yahoo.com/esjewett/post_to_tarpipe_1_0_api"&gt;http://pipes.yahoo.com/esjewett/post_to_tarpipe_1_0_api&lt;/a&gt; (you'll have to clone it as well), which will post each item to tarpipe, using fields you specify as the title and body of the post. But you could call an arbitrary pipe that makes a call to a web service or even YQL.&lt;/p&gt;
&lt;p&gt;I am using this Pipe to call a Tarpipe that posts ever Daring Fireball article into Evernote automatically (the Tarpipe workflow key is fake, so don't get any ideas :-)&lt;/p&gt;
&lt;p&gt;&lt;img src="http://www.esjewett.com/files/2009-08-07_1805.png" /&gt;&lt;/p&gt;
&lt;p&gt;That's it.&lt;/p&gt;
&lt;p&gt;Using this method of setup the pipe will not process existing entries in the feed, but it will process any new entries through the pipe you have assigned to the loop.&lt;/p&gt;
&lt;p&gt;One limitation of this particular pipe is that it will not work reliably for feeds that are updated often. This is simply because Google Reader doesn't poll often enough. I have observed that Google Reader polls this feed every 4-8 hours. If more than 8 items are added between polls, older items will not be picked up by Google Reader and will not be processed by the pipe.&lt;/p&gt;
&lt;p&gt;Ok, that's it. For real this time.&lt;/p&gt;

&lt;!--
&lt;rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/"&gt;
&lt;rdf:Description rdf:about="http://www.esjewett.com/blog/make-a-webhook-out-of-anything" dc:identifier="http://www.esjewett.com/blog/make-a-webhook-out-of-anything" dc:title="Make a webhook out of anything" trackback:ping="http://www.esjewett.com/trackback/44" /&gt;
&lt;/rdf:RDF&gt;
--&gt;
&lt;div class="trackback-url"&gt;&lt;div class="box"&gt;

  &lt;h2&gt;Trackback URL for this post:&lt;/h2&gt;

  &lt;div class="content"&gt;http://www.esjewett.com/trackback/44&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/esjewett?a=YazP0j6dbdY:5czCBBE7MdM:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/esjewett?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/esjewett?a=YazP0j6dbdY:5czCBBE7MdM:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/esjewett?i=YazP0j6dbdY:5czCBBE7MdM:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/esjewett?a=YazP0j6dbdY:5czCBBE7MdM:SSaB9coO5pA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/esjewett?i=YazP0j6dbdY:5czCBBE7MdM:SSaB9coO5pA" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/esjewett/~4/YazP0j6dbdY" height="1" width="1"/&gt;</description>
 <category domain="http://www.esjewett.com/tag/google">google</category>
 <category domain="http://www.esjewett.com/tag/pipes">pipes</category>
 <category domain="http://www.esjewett.com/tag/reader">reader</category>
 <category domain="http://www.esjewett.com/tag/tarpipe">tarpipe</category>
 <category domain="http://www.esjewett.com/tag/webhooks">webhooks</category>
 <category domain="http://www.esjewett.com/tag/yahoo">yahoo</category>
 <pubDate>Fri, 07 Aug 2009 23:28:24 +0000</pubDate>
 <dc:creator>esjewett</dc:creator>
 <guid isPermaLink="false">44 at http://www.esjewett.com</guid>
<feedburner:origLink>http://www.esjewett.com/blog/make-a-webhook-out-of-anything</feedburner:origLink></item>
<item>
 <title>Why use OAuth in clients? (OAuth FAQ Part 3)</title>
 <link>http://feedproxy.google.com/~r/esjewett/~3/NyJY3r_wFHM/why-use-oauth-in-clients-oauth-faq-part-3</link>
 <description>&lt;p&gt;There is an ongoing meme that OAuth provides little or no security benefit in client applications. I believe that this assertion is incorrect.&lt;/p&gt;
&lt;p&gt;Client applications are applications that run on my computer or phone and so are implicitly "trusted" by me. (I use quotes because I think that the assumption of user trust for client applications is wrong, but that is neither here nor there.)&lt;/p&gt;
&lt;p&gt;I already addressed this somewhat obliquely in my &lt;a href="http://www.esjewett.com/blog/oauth-qa-part-1"&gt;OAuth FAQ Part 1&lt;/a&gt; and &lt;a href="http://www.esjewett.com/blog/oauth-qa-part-2"&gt;Part 2&lt;/a&gt;, but it bears repeating, which I just did on the OAuth Google Group &lt;a href="http://groups.google.com/group/oauth/browse_thread/thread/19df79b192295d97"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To quote myself:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;
One major security benefit of using OAuth for client apps is that the client is only provided with an access token for the service and not the user's password.&lt;/p&gt;
&lt;p&gt;If the access token and consumer secret are compromised, then either can be revoked, either by the user (in the case of the access token) or by the provider (in the case of the consumer secret). In most provider implementations a request authorized with an access token is not allowed to update certain aspects of the user's account, such as the password. &lt;/p&gt;
&lt;p&gt;If the client requires the user to input their password (for example, Google's ClientLogin protocol) and the client becomes compromised, then the password is exposed, allowing full access to the user's account. Game over. &lt;/p&gt;
&lt;p&gt;In my mind, this is *the* reason I want clients I use to use OAuth rather than a username/password login scheme.&lt;/p&gt;
&lt;p&gt;I would be happy with another token-based login scheme as well, but OAuth is a perfectly good, publicly reviewed standard and I see no reason why a provider should cook up a bespoke token-based authorization scheme when OAuth is available and works for clients.
&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;In other words, developers should use OAuth in their phone and desktop applications and users should demand it. I do.&lt;/p&gt;

&lt;!--
&lt;rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/"&gt;
&lt;rdf:Description rdf:about="http://www.esjewett.com/blog/why-use-oauth-in-clients-oauth-faq-part-3" dc:identifier="http://www.esjewett.com/blog/why-use-oauth-in-clients-oauth-faq-part-3" dc:title="Why use OAuth in clients? (OAuth FAQ Part 3)" trackback:ping="http://www.esjewett.com/trackback/43" /&gt;
&lt;/rdf:RDF&gt;
--&gt;
&lt;div class="trackback-url"&gt;&lt;div class="box"&gt;

  &lt;h2&gt;Trackback URL for this post:&lt;/h2&gt;

  &lt;div class="content"&gt;http://www.esjewett.com/trackback/43&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/esjewett?a=NyJY3r_wFHM:3LU1x-EtMDM:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/esjewett?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/esjewett?a=NyJY3r_wFHM:3LU1x-EtMDM:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/esjewett?i=NyJY3r_wFHM:3LU1x-EtMDM:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/esjewett?a=NyJY3r_wFHM:3LU1x-EtMDM:SSaB9coO5pA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/esjewett?i=NyJY3r_wFHM:3LU1x-EtMDM:SSaB9coO5pA" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/esjewett/~4/NyJY3r_wFHM" height="1" width="1"/&gt;</description>
 <category domain="http://www.esjewett.com/tag/api">api</category>
 <category domain="http://www.esjewett.com/tag/oauth">OAuth</category>
 <category domain="http://www.esjewett.com/tag/security">security</category>
 <pubDate>Fri, 24 Jul 2009 16:20:34 +0000</pubDate>
 <dc:creator>esjewett</dc:creator>
 <guid isPermaLink="false">43 at http://www.esjewett.com</guid>
<feedburner:origLink>http://www.esjewett.com/blog/why-use-oauth-in-clients-oauth-faq-part-3</feedburner:origLink></item>
<item>
 <title>Tarpipe with YQL</title>
 <link>http://feedproxy.google.com/~r/esjewett/~3/4UfJx9OotQc/tarpipe-with-yql</link>
 <description>&lt;p&gt;I've been complaining for months that there is apparently no way to get a Yahoo! Pipes -&gt; Tarpipe connection. What I'm really looking to do is drive a Tarpipe workflow using an RSS feed, so when a new item shows up in the RSS or Atom feed it will kick off a Tarpipe workflow for that item.&lt;/p&gt;
&lt;p&gt;Unfortunately Tarpipe doesn't natively support feeds to kick off a workflow. It allows for the use of email or a specifically formatted request to one of its two APIs. Yahoo! Pipes meanwhile can consume RSS feeds second to none, but it is limited in the after-effects that it can trigger. Pipes does provide a web service operator that can send a request to an arbitrary URL, but the person building the pipe has very little control over the format of the request, so this is not a satisfactory way to interact with arbitrary APIs, and it doesn't work with Tarpipe's API.&lt;/p&gt;
&lt;p&gt;For a couple of months I've been wondering if the relatively new &lt;a href="http://developer.yahoo.com/yql/"&gt;Yahoo! Query Language (YQL)&lt;/a&gt; might be the missing link. In perusing the &lt;a href="http://developer.yahoo.com/yql/guide/"&gt;YQL documentation&lt;/a&gt; yesterday I noticed that YQL now supports INSERT, UPDATE, and DELETE operations in addition to its SELECT operation. Given the built in ability to make state-changing requests to APIs specified in YQL's Open Data Table (ODT) format, I decided to give it a shot.&lt;/p&gt;
&lt;p&gt;I'll be darned, but it works.&lt;/p&gt;
&lt;p&gt;I threw up an Open Data Table definition for the Tarpipe 1.0 API on Github, based on the provided YQL examples and the very understandable YQL documentation. Open Data Table definitions are XML files that specify the possible operations on a "table" as well as optional Javascript to be executed when the operation is called.&lt;/p&gt;
&lt;p&gt;The complete Open Data Table for the Tarpipe 1.0 API (with the exception of the "image" field) is now &lt;a href="http://github.com/esjewett/yql-tables/blob/13784d27bd53a110381128e4f9066c64ebb4218c/tarpipe/tarpipe.process_1_0.xml"&gt;available&lt;/a&gt; on my Github fork of &lt;a href="http://github.com/spullara/"&gt;spullara's&lt;/a&gt; fork of the &lt;a href="http://github.com/yql/yql-tables/tree/master"&gt;yql-tables&lt;/a&gt; repository. Whew. Gotta love Github for creating a simple interface to a tangle of forks.&lt;/p&gt;
&lt;p&gt;The stable raw XML file is &lt;a href="http://www.esjewett.com/files/tarpipe.process_1_0.xml"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This definition specifies an ODT that responds to a query of the format&lt;br /&gt;
&lt;code&gt;&lt;br /&gt;
INSERT into tarpipe.process_1_0 (workflow_key,title,body) VALUES ("workflow_key_here","This is my title","This is the body")&lt;br /&gt;
&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Given a valid Tarpipe workflow key, which is found in the definition of a Tarpipe workflow based on a REST receptor, this call will kick off the workflow with the provided title and body parameters.&lt;/p&gt;
&lt;p&gt;Since this ODT is not currently included in the standard environment, we also have to specify the ODT definition to use, so the complete query we need to send to YQL looks like&lt;br /&gt;
&lt;code&gt;&lt;br /&gt;
USE "http://www.esjewett.com/files/tarpipe.process_1_0.xml"; INSERT into tarpipe.process_1_0 (workflow_key,title,body) VALUES ("workflow_key_here","This is my title","This is the body")&lt;br /&gt;
&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;And that's it. I (or rather Github) have already provided the hosted ODT definition, so all you need to do is run the YQL query above in order to kick off a Tarpipe workflow. The query can be run in &lt;a href="http://developer.yahoo.com/yql/guide/how_to_run.html"&gt;any of the manners allowed by the YQL system&lt;/a&gt;, including the console available &lt;a href="http://developer.yahoo.com/yql/console/"&gt;here&lt;/a&gt;. Simply replace the text 'workflow_key_here' (keep the double-quotes) with your Tarpipe workflow key and you should be good to go.&lt;/p&gt;
&lt;p&gt;Next up, I'm hoping we'll be able to go through how to use this techinque to wire Yahoo! Pipes directly into Tarpipe workflows. I'm not quite there yet as it's tough to get Pipes to ignore old entries, but I think we can figure something out...&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;UPDATE (7 Aug, 2009)&lt;/strong&gt; - Due to an obscure incompatibility between Ruby on Rails versions higher than 2.1 and the Yahoo! infrastructure, the github.com versions of the open data table XML files no longer work. I am now hosting the XML file necessary for this blog at &lt;a href="http://www.esjewett.com/files/tarpipe.process_1_0.xml"&gt;http://www.esjewett.com/files/tarpipe.process_1_0.xml&lt;/a&gt;. I've changed the code and links above to point to this file.&lt;/p&gt;

&lt;!--
&lt;rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/"&gt;
&lt;rdf:Description rdf:about="http://www.esjewett.com/blog/tarpipe-with-yql" dc:identifier="http://www.esjewett.com/blog/tarpipe-with-yql" dc:title="Tarpipe with YQL" trackback:ping="http://www.esjewett.com/trackback/42" /&gt;
&lt;/rdf:RDF&gt;
--&gt;
&lt;div class="trackback-url"&gt;&lt;div class="box"&gt;

  &lt;h2&gt;Trackback URL for this post:&lt;/h2&gt;

  &lt;div class="content"&gt;http://www.esjewett.com/trackback/42&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/esjewett?a=4UfJx9OotQc:eKsP_rdb4cA:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/esjewett?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/esjewett?a=4UfJx9OotQc:eKsP_rdb4cA:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/esjewett?i=4UfJx9OotQc:eKsP_rdb4cA:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/esjewett?a=4UfJx9OotQc:eKsP_rdb4cA:SSaB9coO5pA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/esjewett?i=4UfJx9OotQc:eKsP_rdb4cA:SSaB9coO5pA" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/esjewett/~4/4UfJx9OotQc" height="1" width="1"/&gt;</description>
 <category domain="http://www.esjewett.com/tag/soa">soa</category>
 <category domain="http://www.esjewett.com/tag/tarpipe">tarpipe</category>
 <category domain="http://www.esjewett.com/tag/yahoo">yahoo</category>
 <category domain="http://www.esjewett.com/tag/yql">yql</category>
 <pubDate>Tue, 21 Jul 2009 00:42:51 +0000</pubDate>
 <dc:creator>esjewett</dc:creator>
 <guid isPermaLink="false">42 at http://www.esjewett.com</guid>
<feedburner:origLink>http://www.esjewett.com/blog/tarpipe-with-yql</feedburner:origLink></item>
<item>
 <title>Introducing jsglue</title>
 <link>http://feedproxy.google.com/~r/esjewett/~3/LYr5cl___0Q/introducing-jsglue</link>
 <description>&lt;p&gt;Last weekend I pushed out the very first version of something I'm calling jsglue to Github. It now lives here: &lt;a title="http://github.com/esjewett/jsglue/tree/master" href="http://github.com/esjewett/jsglue/tree/master"&gt;http://github.com/esjewett/jsglue/tree/master&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;jsglue is in essence a framework for implementing web-connective applications a la Yahoo! Pipes and Tarpipe. Currently it is at best a compliment to those programs and at worst totally useless. In the future I would like to see it or something like it become an alternative to these tools, for a few reasons that I'll eventually get into in later posts.&lt;/p&gt;
&lt;p&gt;jsglue does three things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It allows you to register a handler to a path.
&lt;ul&gt;
&lt;li&gt;The handler consists of a path and two pieces of javascript - one that constructs a response to a request sent to that path, and one that constructs one request (and in the future multiple requests, optionally) to another URL.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;It accepts HTTP requests to paths with registered handlers.
&lt;ul&gt;
&lt;li&gt;When this happens, it creates a response using the handler javascript for this purpose, and it adds a job to a stack that will be processed later.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;It provides a program that can be run periodically to process the stack of jobs that has built up, sending off new requests as specified by the javascript in the handler associated with each job.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That's it.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Why do I care?&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;Well, hopefully that will become clear of its own accord. But the key is that the full contents of the original request are exposed to the javascript processing script that constructs the new request. As such, you can do pretty much any kind of processing you like within this handler code, which is user-defined.&lt;/p&gt;
&lt;p&gt;So why do you care? Let me count the ways:&lt;/p&gt;
&lt;p&gt;1. Receive a request in JSON and spit it back out multi-part form-encoded (in fact, right now this is pretty much the only thing you can do). Ever tried to connect up Yahoo! Pipes with Tarpipe? It doesn't work. With this, it can.&lt;/p&gt;
&lt;p&gt;2. &lt;a title="Webhooks" href="http://timothyfitz.wordpress.com/2009/02/09/what-webhooks-are-and-why-you-should-care/"&gt;Webhooks&lt;/a&gt; are great. Webhooks are the facility to have a web application issue an HTTP request to an arbitrary URL when some event happens in the web application. That sounds boring, but it's actually awesome. Webhooks are great, except that no one speaks the same language so every webhook-based solution is bespoke. Bespoke is great in a suit or a coffee mug, but it's bad in web infrastructure. Yahoo! Pipes can't understand webhook calls. Tarpipe usually can't understand them. Most other web applications can't understand them. There needs to be some sort of middle-person.&lt;/p&gt;
&lt;p&gt;3. If I'm going to run a ton of my personal data through some middleman web application, you should have the option of running that web application yourself. I'm not saying you will, but I think it would be nice if you could.&lt;/p&gt;
&lt;p&gt;Okay, 3 ways is enough for now. We'll get to more later.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Why do you *not* care?&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;Well, there are lots of reasons for that too.&lt;/p&gt;
&lt;p&gt;1. This is dorky. No, there is not a UI. No, it doesn't do much of interest. It's an infrastructure prototype more than anything else. The idea is really that we need infrastructure for building applications that can do this sort of thing. I don't have a lot of time to spare, so I'm willing to just put a framework out there, and maybe a REST-only web-application if I can get the components running on a hosting service (harder than it sounds). I'll leave it to someone else to put the UI on top of it. I'm not convinced that the "pipe" metaphor is correct (I'm partial to "tubes" myself), but I don't have a better idea, so someone else will have to have that idea.&lt;/p&gt;
&lt;p&gt;2. This code sucks. Yes it does. I urge you to fork it, improve it, or throw up your hands in disgust and start over. I just want something that does this. I don't really care if it's written by me.&lt;/p&gt;
&lt;p&gt;3. There's no way this execution model will fly on a public site, and no one is going to run this on their own server. This is sort a feature of this design that allows your users to execute arbitrary javascript on your server. As such, I think this will primarily find use on private servers, or as a back-end engine for a public site where the inputs are carefully cleansed. Not a recipe for ultra-popularity, I'll grant. But that's not really the point either.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;So what's it made out of?&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;Currently, there are only four main ingredients:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a title="Ruby" href="http://www.ruby-lang.org/"&gt;Ruby&lt;/a&gt; is the implementation language. It's role in jsglue is to serve as duct-tape for the other components.
&lt;/li&gt;
&lt;li&gt;&lt;a title="Datamapper" href="http://datamapper.org/"&gt;Datamapper&lt;/a&gt; is the database interface, allowing you to use pretty much any supported database (I'm using SQLite at the moment).
&lt;/li&gt;
&lt;li&gt;&lt;a title="Sinatra" href="http://www.sinatrarb.com/"&gt;Sinatra&lt;/a&gt; for the HTTP web-service interfaces. These interfaces are pretty much a direct mapping onto the database. (REST-ful? Maybe.) (Incidentally, how is it that a minuscule Ruby web-framework beats out FRANK SINATRA in the Google rankings?)
&lt;/li&gt;
&lt;li&gt;&lt;a title="Johnson" href="http://github.com/jbarnette/johnson/tree/master"&gt;Johnson&lt;/a&gt; for the Javascript processing.
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That's it. It's a couple hundred lines of code. I haven't really counted, or put it on Ohloh.com for that matter, but it can't be more than that. It's got some unit tests. It's going to be changing quickly as I make it more multi-purpose.&lt;/p&gt;
&lt;p&gt;I'll document and post examples as they become available.&lt;/p&gt;

&lt;!--
&lt;rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/"&gt;
&lt;rdf:Description rdf:about="http://www.esjewett.com/blog/introducing-jsglue" dc:identifier="http://www.esjewett.com/blog/introducing-jsglue" dc:title="Introducing jsglue" trackback:ping="http://www.esjewett.com/trackback/41" /&gt;
&lt;/rdf:RDF&gt;
--&gt;
&lt;div class="trackback-url"&gt;&lt;div class="box"&gt;

  &lt;h2&gt;Trackback URL for this post:&lt;/h2&gt;

  &lt;div class="content"&gt;http://www.esjewett.com/trackback/41&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/esjewett?a=LYr5cl___0Q:lQv34DRXxFs:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/esjewett?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/esjewett?a=LYr5cl___0Q:lQv34DRXxFs:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/esjewett?i=LYr5cl___0Q:lQv34DRXxFs:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/esjewett?a=LYr5cl___0Q:lQv34DRXxFs:SSaB9coO5pA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/esjewett?i=LYr5cl___0Q:lQv34DRXxFs:SSaB9coO5pA" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/esjewett/~4/LYr5cl___0Q" height="1" width="1"/&gt;</description>
 <category domain="http://www.esjewett.com/tag/javascript">javascript</category>
 <category domain="http://www.esjewett.com/tag/johnson">johnson</category>
 <category domain="http://www.esjewett.com/tag/jsglue">jsglue</category>
 <category domain="http://www.esjewett.com/tag/pipes">pipes</category>
 <category domain="http://www.esjewett.com/tag/ruby">ruby</category>
 <category domain="http://www.esjewett.com/tag/sinatra">sinatra</category>
 <category domain="http://www.esjewett.com/tag/tarpipe">tarpipe</category>
 <pubDate>Wed, 06 May 2009 23:03:01 +0000</pubDate>
 <dc:creator>esjewett</dc:creator>
 <guid isPermaLink="false">41 at http://www.esjewett.com</guid>
<feedburner:origLink>http://www.esjewett.com/blog/introducing-jsglue</feedburner:origLink></item>
<item>
 <title>A tour of testing with an SAP focus (in the end)</title>
 <link>http://feedproxy.google.com/~r/esjewett/~3/GuNMY0dU6AQ/a-tour-of-testing-with-an-sap-focus-in-the-end</link>
 <description>&lt;p&gt;As might have been assumed from the my post on &lt;a href="http://www.esjewett.com/blog/automated-testing-in-sap-systems"&gt;automated testing in SAP systems&lt;/a&gt; a couple months ago, I've been delving into testing in the SAP landscape. I'm beginning to put together a series of presentations and workshops on the subject, the first of which I was delighted to deliver last week.&lt;/p&gt;
&lt;p&gt;Being the first in the series, this presentation focuses on an overview of the leading edge of the field. It would be nice to be able to emulate the sort of testing techniques we can use in Ruby in SAP BI and EPM application development. Nice, but not necessarily realistic. I can dream!&lt;/p&gt;
&lt;div style="width:425px;text-align:left" id="__ss_1352251"&gt;&lt;a style="font:14px Helvetica,Arial,Sans-serif;display:block;margin:12px 0 3px 0;text-decoration:underline;" href="http://www.slideshare.net/esjewett/testing-sap-modern-methodology?type=powerpoint" title="Testing Sap: Modern Methodology"&gt;Testing Sap: Modern Methodology&lt;/a&gt;
&lt;object style="margin:0px" width="425" height="355"&gt;&lt;param name="movie" value="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=testingsap-modernmethodology-forpublicdistribution-090427112037-phpapp01&amp;rel=0&amp;stripped_title=testing-sap-modern-methodology" /&gt;&lt;param name="allowFullScreen" value="true" /&gt;&lt;param name="allowScriptAccess" value="always" /&gt;&lt;embed src="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=testingsap-modernmethodology-forpublicdistribution-090427112037-phpapp01&amp;rel=0&amp;stripped_title=testing-sap-modern-methodology" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="355"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;div style="font-size:11px;font-family:tahoma,arial;height:26px;padding-top:2px;"&gt;View more &lt;a style="text-decoration:underline;" href="http://www.slideshare.net/"&gt;presentations&lt;/a&gt; from &lt;a style="text-decoration:underline;" href="http://www.slideshare.net/esjewett"&gt;Ethan Jewett&lt;/a&gt;.&lt;/div&gt;
&lt;/div&gt;

&lt;!--
&lt;rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/"&gt;
&lt;rdf:Description rdf:about="http://www.esjewett.com/blog/a-tour-of-testing-with-an-sap-focus-in-the-end" dc:identifier="http://www.esjewett.com/blog/a-tour-of-testing-with-an-sap-focus-in-the-end" dc:title="A tour of testing with an SAP focus (in the end)" trackback:ping="http://www.esjewett.com/trackback/40" /&gt;
&lt;/rdf:RDF&gt;
--&gt;
&lt;div class="trackback-url"&gt;&lt;div class="box"&gt;

  &lt;h2&gt;Trackback URL for this post:&lt;/h2&gt;

  &lt;div class="content"&gt;http://www.esjewett.com/trackback/40&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/esjewett?a=GuNMY0dU6AQ:HoaoVVEWIVA:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/esjewett?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/esjewett?a=GuNMY0dU6AQ:HoaoVVEWIVA:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/esjewett?i=GuNMY0dU6AQ:HoaoVVEWIVA:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/esjewett?a=GuNMY0dU6AQ:HoaoVVEWIVA:SSaB9coO5pA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/esjewett?i=GuNMY0dU6AQ:HoaoVVEWIVA:SSaB9coO5pA" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/esjewett/~4/GuNMY0dU6AQ" height="1" width="1"/&gt;</description>
 <category domain="http://www.esjewett.com/tag/abap">abap</category>
 <category domain="http://www.esjewett.com/tag/agile">agile</category>
 <category domain="http://www.esjewett.com/tag/bdd">bdd</category>
 <category domain="http://www.esjewett.com/tag/cucumber">cucumber</category>
 <category domain="http://www.esjewett.com/tag/enterprise">enterprise</category>
 <category domain="http://www.esjewett.com/tag/open">open</category>
 <category domain="http://www.esjewett.com/tag/rspec">rspec</category>
 <category domain="http://www.esjewett.com/tag/ruby">ruby</category>
 <category domain="http://www.esjewett.com/tag/sap">sap</category>
 <category domain="http://www.esjewett.com/tag/tdd">tdd</category>
 <category domain="http://www.esjewett.com/tag/testing">testing</category>
 <pubDate>Tue, 28 Apr 2009 02:43:16 +0000</pubDate>
 <dc:creator>esjewett</dc:creator>
 <guid isPermaLink="false">40 at http://www.esjewett.com</guid>
<feedburner:origLink>http://www.esjewett.com/blog/a-tour-of-testing-with-an-sap-focus-in-the-end</feedburner:origLink></item>
</channel>
</rss>
