<?xml version="1.0" encoding="utf-8"?>
<feed xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xml:lang="en-us" xmlns="http://www.w3.org/2005/Atom">
  <title>Embracing the Cloud</title>
  <link rel="alternate" type="text/html" href="http://www.embracingthecloud.com/" />
  <link rel="self" href="http://www.embracingthecloud.com/SyndicationService.asmx/GetAtom" />
  <icon>favicon.ico</icon>
  <updated>2010-07-26T22:11:17.826-05:00</updated>
  <author>
    <name>Mike Leach</name>
  </author>
  <subtitle>public String CloudThoughts{ get; set;}</subtitle>
  <id>http://www.embracingthecloud.com/</id>
  <generator uri="http://dasblog.info/" version="2.3.9074.18820">DasBlog</generator>
  <entry>
    <title>Does The Cloud Need Standards?</title>
    <link rel="alternate" type="text/html" href="http://www.embracingthecloud.com/2010/07/27/DoesTheCloudNeedStandards.aspx" />
    <id>http://www.embracingthecloud.com/PermaLink,guid,498b3bc0-2794-4e2b-a1b0-dbb2175f94cf.aspx</id>
    <published>2010-07-26T21:58:53.716-05:00</published>
    <updated>2010-07-26T22:11:17.826-05:00</updated>
    <author>
      <name>Mike Leach</name>
    </author>
    <content type="xhtml">
      <div xmlns="http://www.w3.org/1999/xhtml">I missed the discussions on "<a href="http://www.google.com/search?q=oscon+2010+open+cloud">open
cloud</a>" and "cloud standards" at OSCON 2010, but indirectly got the gist of it.
Standards themselves aren't bad, but it's rarely a good idea to create a standard
for the sake of just creating a standard.<br /><br />
I've observed 3 fundamental perspectives and their likely motivators on the issue
of cloud standards:<br /><br />
1) Vendor: Invested $XXX million in software stack Y. Wants it proclaimed "standard"
(for obvious reasons)<br />
2) Developer: Wants "write once run everywhere" in the name of "portability" (doesn't
want to learn 5 different APIs)<br />
3) Consumer: Doesn't care. Wants it to "just work"<br /><br />
If you look across all cloud stacks today, what do you see that is common amongst
them and could even remotely be called a "standard"? I see TCP/IP running on Intel
x86 CPU architecture.<br /><br />
If someone told you 10 years ago "In order to democratize access to computing resources
and enable ubiquitous access to information we all need to standardize on Intel x86
architecture and deliver services over IP", several people would have scoffed at that.
It's incredible how far cloud computing has come as a "standard".<br /><br />
There is an art to minimizing standards in order to maximize adoption.<br /><br />
Look at electricity. For years there were debates over whether DC or AC power was
better. The US finally standardized on a plug with 120V AC at 60Hz as a "standard"
for electricity. 3 very simple data points that led to an explosion in adoption.<br /><br />
But <a href="http://en.wikipedia.org/wiki/Mains_power_around_the_world">look</a> at
the rest of the world. Different combinations of plugs, voltages, and frequencies
abound! Fortunately these standards are all "open" and simple enough to allow for
converters and transformers to fill the gaps. Fundamentally, the world really only
agreed to standardize on alternating current (AC) at a range of voltages between 110-240
Volts. But for consumers, it "just works".<br /><br />
That's the way cloud computing is evolving. Building on the Internet (TCP/IP), running
on servers (Intel x86), and bridging the gaps using a few basic tools (JSON, XML,
SOAP, REST, CSV).<br /><br />
Check out Nicholas Carr's book "<a href="http://www.nicholasgcarr.com/bigswitch/index.shtml">The
Big Switch</a>" for the back story on how industrial America moved towards standardized
and centralized power utilities and how that parallels what's going on in cloud computing.<br /><br />
The Internet is called "the web" for a reason. It's in reference to its inherently
chaotic data structure. Asserting standards to make sense of it won't help. It will
only inhibit adoption. The web and cloud computing must be allowed to organically
sprawl uninhibited.<br /><br />
Final thoughts:<br />
1) Vendors: Explain to customers which specific primitive cloud services you offer
(Windows/Linux, HTTP/SSL/SMTP/FTP) and preempt the discussion on integration by providing
APIs to raw computing resources.<br />
2) Developers: Sorry. The days of learning one language or standard are over. "Embrace
the cloud!" The next killer app exists at the convergence of several cloud services
(think mashups blending location, maps, social graph, consumer reviews, and merchant
offers... all in one).<br />
3) Customers: Sorry about the noise. We'll get this sorted out as soon as possible
and ensure the cloud "just works". Thank you for your patience :-)<br /><br /><br /><img width="0" height="0" src="http://www.embracingthecloud.com/aggbug.ashx?id=498b3bc0-2794-4e2b-a1b0-dbb2175f94cf" /></div>
    </content>
  </entry>
  <entry>
    <title>How Facebook Will Transform CRM and Contact Management</title>
    <link rel="alternate" type="text/html" href="http://www.embracingthecloud.com/2010/07/10/HowFacebookWillTransformCRMAndContactManagement.aspx" />
    <id>http://www.embracingthecloud.com/PermaLink,guid,edb98d3a-7966-41e7-b0df-b4f2aa656ab2.aspx</id>
    <published>2010-07-10T18:31:06.529-05:00</published>
    <updated>2010-07-11T00:13:29.076-05:00</updated>
    <category term="Facebook" label="Facebook" scheme="http://www.embracingthecloud.com/CategoryView,category,Facebook.aspx" />
    <category term="Salesforce" label="Salesforce" scheme="http://www.embracingthecloud.com/CategoryView,category,Salesforce.aspx" />
    <author>
      <name>Mike Leach</name>
    </author>
    <content type="xhtml">
      <div xmlns="http://www.w3.org/1999/xhtml">
        <p>
        </p>
        <img src="http://www.embracingthecloud.com/content/binary/logo_facebook.jpg" border="0" width="200px" />
        <br />
Facebook has nearly 500 million users (at the time of this writing) and are poised
to transform how customer relationships are acquired, cultivated, and supported.<br /><br />
Consider the evolution of Contact management over the past 50 years:<br /><ul><li>
Paper: Write names, addresses, birthdays, and other notes down on paper</li><li>
Rolodex: Everyone trades business cards and keeps a local paper copy</li><li>
PC Software: Rolodex moved to PC (Spreadsheets, Goldmine, Act!)</li><li>
Client / Server Software: Many people in same office share common contact database
(Siebel, MS CRM)</li><li>
Software as a Service: Contacts moved to Internet hosted server. Accessible from anywhere
(Salesforce.com)</li><li>
Crowd sourced: 3rd parties pooling contact information to improve data quality, keep
lists up to date (Plaxo, Jigsaw)</li><li>
Social Networking: Contact maintains singular identity. Always up to date. Control
of privacy and disclosure (LinkedIn, Facebook)<br /></li></ul>
The consumer now has more power than ever. Consumers now generate more information
about themselves than can be generated by 3rd parties. 
<br /><br />
Buying a list of leads that may be interested in buying camping gear will have no
where near the effectiveness of publishing an ad on Facebook targeted at consumers
with a self-identified interest in camping (See <a href="http://blog.facebook.com/blog.php?post=403570307130">The
Role of Advertising at Facebook</a>).<br /><br />
Consumers will increasingly defer to their friends recommendations on which restaurants
to visit, which shoes to buy, and which cars to drive.<br /><br />
CRM systems no longer contain the master records for Contact information.<br /><br />
Businesses must evolve past collecting and cleansing Contacts and instead collect
meta-information that refers to customer-managed online profiles, such as Facebook
and LinkedIn, for these resources are now truly authoritative. When there is a conflict
between CRM Contact information and an online social network profile, the social profile
will be master record.<br /><br />
Websites must evolve to become applications. Web forms soliciting contact information
will become a thing of the past. Consumers will not have the patience to do anything
more than a single click to identify interest in a product or service.<br /><br />
The video embedded in this blog post (and <a href="http://screencast.com/t/ZDY4MzJlZTc">available
here</a>) demonstrates a Cool Sites application integrated with Facebook Connect and
Salesforce CRM.<br /><object id="scPlayer" class="embeddedObject" type="application/x-shockwave-flash" data="http://content.screencast.com/users/CubicCompassVideo/folders/Jing/media/d8f421f4-4edc-4d06-8816-dcbfbdb7221e/jingswfplayer.swf" height="697" width="860"><param name="movie" value="http://content.screencast.com/users/CubicCompassVideo/folders/Jing/media/d8f421f4-4edc-4d06-8816-dcbfbdb7221e/jingswfplayer.swf" /><param name="quality" value="high" /><param name="bgcolor" value="#FFFFFF" /><param name="flashVars" value="thumb=http://content.screencast.com/users/CubicCompassVideo/folders/Jing/media/d8f421f4-4edc-4d06-8816-dcbfbdb7221e/FirstFrame.jpg&amp;containerwidth=860&amp;containerheight=697&amp;content=http://content.screencast.com/users/CubicCompassVideo/folders/Jing/media/d8f421f4-4edc-4d06-8816-dcbfbdb7221e/Facebook_Connect_Web_To_Lead_Form.swf&amp;blurover=false" /><param name="allowFullScreen" value="true" /><param name="scale" value="showall" /><param name="allowScriptAccess" value="always" /><param name="base" value="http://content.screencast.com/users/CubicCompassVideo/folders/Jing/media/d8f421f4-4edc-4d06-8816-dcbfbdb7221e/" /></object><img width="0" height="0" src="http://www.embracingthecloud.com/aggbug.ashx?id=edb98d3a-7966-41e7-b0df-b4f2aa656ab2" /></div>
    </content>
  </entry>
  <entry>
    <title>A Simple Trigger Template for Salesforce</title>
    <link rel="alternate" type="text/html" href="http://www.embracingthecloud.com/2010/07/08/ASimpleTriggerTemplateForSalesforce.aspx" />
    <id>http://www.embracingthecloud.com/PermaLink,guid,547dbff2-3b04-42f4-9284-d41aa4c069f7.aspx</id>
    <published>2010-07-08T16:16:12.796-05:00</published>
    <updated>2010-07-08T16:25:13.6056796-05:00</updated>
    <category term="Apex" label="Apex" scheme="http://www.embracingthecloud.com/CategoryView,category,Apex.aspx" />
    <category term="Salesforce" label="Salesforce" scheme="http://www.embracingthecloud.com/CategoryView,category,Salesforce.aspx" />
    <author>
      <name>Mike Leach</name>
    </author>
    <content type="html">&lt;table&gt;
&lt;tr&gt;
&lt;td valign="top"&gt;
&lt;img src="http://www.embracingthecloud.com/content/binary/Triggerbook-byPando.jpg" border="0" width="200px"&gt;
&lt;br /&gt;
&lt;i style="font-size: 11px"&gt;No, not this Trigger... keep reading...&lt;/i&gt; 
&lt;/td&gt;
&lt;td valign="top"&gt;
&lt;p&gt;
Trigger development (apologies to Roy Rogers' horse) is not done on a daily basis
by a typical Force.com Developer. 
&lt;/p&gt;
&lt;p&gt;
In my case, Trigger development is similar to using regular expressions (regex) in
that I often rely on documentation and previously developed code examples to refresh
my memory, do the coding, then put it aside for several weeks/months. 
&lt;/p&gt;
&lt;p&gt;
I decided to create a more fluent Trigger template to address the following challenges
and prevent me from repeatedly making the same mistakes: 
&lt;ul&gt;
&lt;li&gt;
Bulkification best practices not provisioned by the Trigger creation wizard 
&lt;li&gt;
Use of the 7 boolean context variables in code (isInsert, isBefore, etc...) greatly
impairs readability and long-term maintainability 
&lt;li&gt;
Trigger.old and Trigger.new collections are not available in certain contexts 
&lt;li&gt;
Asynchronous trigger support not natively built-in 
&lt;/ul&gt;
&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;p&gt;
The solution was to create a mega-Trigger that handles all events and delegates them
accordingly to an Apex trigger handler class. 
&lt;/p&gt;
&lt;p&gt;
You may want to customize this template to your own style. Here are some design considerations
and assumptions in this template: 
&lt;ul&gt;
&lt;li&gt;
Use of traditional event method names on the handler class (OnBeforeInsert, OnAfterInsert) 
&lt;li&gt;
Maps are used where they are most relevant 
&lt;li&gt;
Objects in map collections cannot be modified, however there is nothing in the compiler
to prevent you from trying. Remove them whenever not needed. 
&lt;li&gt;
Maps are most useful when triggers modify other records by IDs, so they're included
in update and delete triggers 
&lt;li&gt;
Encourage use of asynchronous trigger processing by providing pre-built @future methods. 
&lt;li&gt;
@future methods only support collections of native types. ID is preferred using this
style. 
&lt;li&gt;
Avoid use of before/after if not relevant. Example: OnUndelete is simpler than OnAfterUndelete
(before undelete is not supported) 
&lt;li&gt;
Provide boolean properties for determining trigger context (Is it a Trigger or VF/WebService
call?) 
&lt;li&gt;
There are no return values. Handler methods are assumed to assert validation rules
using addError() to prevent commit. 
&lt;/ul&gt;
&gt;
&lt;p&gt;
&lt;strong&gt;References:&lt;/strong&gt;
&lt;br /&gt;
&lt;a href="http://www.salesforce.com/us/developer/docs/pages/index_Left.htm#StartTopic=Content/apex_triggers.htm"&gt;Apex
Developers Guide - Triggers&lt;/a&gt;
&lt;br /&gt;
Steve Anderson - &lt;a href="http://gokubi.com/archives/two-interesting-ways-to-architect-apex-triggers"&gt;Two
interesting ways to architect Apex triggers&lt;/a&gt; 
&lt;/p&gt;
&lt;p&gt;
&lt;strong&gt;AccountTrigger.trigger&lt;/strong&gt; 
&lt;/p&gt;
&lt;pre class="brush: java;"&gt;
trigger AccountTrigger on Account (after delete, after insert, after undelete, 
after update, before delete, before insert, before update) {
	AccountTriggerHandler handler = new AccountTriggerHandler(Trigger.isExecuting, Trigger.size);
	
	if(Trigger.isInsert &amp;&amp; Trigger.isBefore){
		handler.OnBeforeInsert(Trigger.new);
	}
	else if(Trigger.isInsert &amp;&amp; Trigger.isAfter){
		handler.OnAfterInsert(Trigger.new);
		AccountTriggerHandler.OnAfterInsertAsync(Trigger.newMap.keySet());
	}
	
	else if(Trigger.isUpdate &amp;&amp; Trigger.isBefore){
		handler.OnBeforeUpdate(Trigger.old, Trigger.new, Trigger.newMap);
	}
	else if(Trigger.isUpdate &amp;&amp; Trigger.isAfter){
		handler.OnAfterUpdate(Trigger.old, Trigger.new, Trigger.newMap);
		AccountTriggerHandler.OnAfterUpdateAsync(Trigger.newMap.keySet());
	}
	
	else if(Trigger.isDelete &amp;&amp; Trigger.isBefore){
		handler.OnBeforeDelete(Trigger.old, Trigger.oldMap);
	}
	else if(Trigger.isDelete &amp;&amp; Trigger.isAfter){
		handler.OnAfterDelete(Trigger.old, Trigger.oldMap);
		AccountTriggerHandler.OnAfterDeleteAsync(Trigger.oldMap.keySet());
	}
	
	else if(Trigger.isUnDelete){
		handler.OnUndelete(Trigger.new);	
	}
}
&lt;/pre&gt;
&lt;p&gt;
&lt;strong&gt;AccountTriggerHandler.cls&lt;/strong&gt; 
&lt;/p&gt;
&lt;pre class="brush: java;"&gt; 
public with sharing class AccountTriggerHandler {
	private boolean m_isExecuting = false;
	private integer BatchSize = 0;
	
	public AccountTriggerHandler(boolean isExecuting, integer size){
		m_isExecuting = isExecuting;
		BatchSize = size;
	}
		
	public void OnBeforeInsert(Account[] newAccounts){
		//Example usage
		for(Account newAccount : newAccounts){
			if(newAccount.AnnualRevenue == null){
				newAccount.AnnualRevenue.addError('Missing annual revenue');
			}
		}
	}
	
	public void OnAfterInsert(Account[] newAccounts){
		
	}
	
	@future public static void OnAfterInsertAsync(Set&amp;lt;ID&amp;gt; newAccountIDs){
		//Example usage
		List&amp;lt;Account&amp;gt; newAccounts = [select Id, Name from Account where Id IN :newAccountIDs];
	}
	
	public void OnBeforeUpdate(Account[] oldAccounts, Account[] updatedAccounts, Map&amp;lt;ID, Account&amp;gt; accountMap){
		//Example Map usage
		Map&amp;lt;ID, Contact&amp;gt; contacts = new Map&amp;lt;ID, Contact&amp;gt;( [select Id, FirstName, LastName, Email from Contact where AccountId IN :accountMap.keySet()] );
	}
	
	public void OnAfterUpdate(Account[] oldAccounts, Account[] updatedAccounts, Map&amp;lt;ID, Account&amp;gt; accountMap){
		
	}
	
	@future public static void OnAfterUpdateAsync(Set&amp;lt;ID&amp;gt; updatedAccountIDs){
		List&amp;lt;Account&amp;gt; updatedAccounts = [select Id, Name from Account where Id IN :updatedAccountIDs];
	}
	
	public void OnBeforeDelete(Account[] accountsToDelete, Map&amp;lt;ID, Account&amp;gt; accountMap){
		
	}
	
	public void OnAfterDelete(Account[] deletedAccounts, Map&amp;lt;ID, Account&amp;gt; accountMap){
		
	}
	
	@future public static void OnAfterDeleteAsync(Set&amp;lt;ID&amp;gt; deletedAccountIDs){
		
	}
	
	public void OnUndelete(Account[] restoredAccounts){
		
	}
	
	public boolean IsTriggerContext{
		get{ return m_isExecuting;}
	}
	
	public boolean IsVisualforcePageContext{
		get{ return !IsTriggerContext;}
	}
	
	public boolean IsWebServiceContext{
		get{ return !IsTriggerContext;}
	}
	
	public boolean IsExecuteAnonymousContext{
		get{ return !IsTriggerContext;}
	}
}
&lt;/pre&gt;&lt;img width="0" height="0" src="http://www.embracingthecloud.com/aggbug.ashx?id=547dbff2-3b04-42f4-9284-d41aa4c069f7" /&gt;</content>
  </entry>
  <entry>
    <title>Gonna Buy 5 Copies for My Mother</title>
    <link rel="alternate" type="text/html" href="http://www.embracingthecloud.com/2010/06/29/GonnaBuy5CopiesForMyMother.aspx" />
    <id>http://www.embracingthecloud.com/PermaLink,guid,9e879018-3463-47b5-a58c-939939dd72b7.aspx</id>
    <published>2010-06-29T13:32:15.959625-05:00</published>
    <updated>2010-06-29T13:32:15.959625-05:00</updated>
    <category term="Salesforce" label="Salesforce" scheme="http://www.embracingthecloud.com/CategoryView,category,Salesforce.aspx" />
    <author>
      <name>Mike Leach</name>
    </author>
    <content type="xhtml">
      <div xmlns="http://www.w3.org/1999/xhtml">There are really only 2 tech blogs that
I read; <a href="http://techcrunch.com/">TechCrunch.com</a> and <a href="http://www.readwriteweb.com">ReadWriteWeb.com</a> (RWW).
They both provide balanced coverage of the consumer and enterprise markets while remaining
objective. You don't get the sense that site sponsors and advertisers are driving
the stories. I admire their integrity.<br /><br />
I've been particularly impressed with RWW's coverage on <a href="http://www.readwriteweb.com/cloud/">the
cloud</a> and the <a href="http://www.readwriteweb.com/archives/internet-of-things/">Internet
of Things</a>, so I was absolutely thrilled when <a href="http://twitter.com/alexwilliams">Alex
Williams</a> ran with a story yesterday about Chatter Bot titled "<a href="http://www.readwriteweb.com/cloud/2010/06/how-to-connect-an-office-build.php">How
to Connect an Office Building to an Activity Stream</a>". Check it out! (A <a href="http://www.lyricsg.com/112775/lyrics/drhookthemedicineshow/coveroftherollingstone.html">hint</a> on
the title of this blog :-) )<br /><p></p><a href="http://www.readwriteweb.com/cloud/2010/06/how-to-connect-an-office-build.php"><img src="http://www.embracingthecloud.com/content/binary/RWW_ChatterBot.png" border="0" /></a><img width="0" height="0" src="http://www.embracingthecloud.com/aggbug.ashx?id=9e879018-3463-47b5-a58c-939939dd72b7" /></div>
    </content>
  </entry>
  <entry>
    <title>Chatter Developer Challenge and Hackathon Roundup</title>
    <link rel="alternate" type="text/html" href="http://www.embracingthecloud.com/2010/06/25/ChatterDeveloperChallengeAndHackathonRoundup.aspx" />
    <id>http://www.embracingthecloud.com/PermaLink,guid,fbf88b23-9b49-477c-94a0-c05d5f759937.aspx</id>
    <published>2010-06-24T20:03:10.304-05:00</published>
    <updated>2010-06-24T20:05:32.523032-05:00</updated>
    <category term="Apex" label="Apex" scheme="http://www.embracingthecloud.com/CategoryView,category,Apex.aspx" />
    <category term="Salesforce" label="Salesforce" scheme="http://www.embracingthecloud.com/CategoryView,category,Salesforce.aspx" />
    <category term="Visualforce" label="Visualforce" scheme="http://www.embracingthecloud.com/CategoryView,category,Visualforce.aspx" />
    <author>
      <name>Mike Leach</name>
    </author>
    <content type="html">&lt;h1&gt;Chatter Developer Challenge / Hackathon 2010 Roundup
&lt;/h1&gt;
&lt;p&gt;
The &lt;a target="_blank" href="http://developer.force.com/chatter_developer_challenge"&gt;Chatter
Developer Challenge&lt;/a&gt; sponsored by Salesforce encouraged Developers to create a
wide variety of applications that demonstrate the new &lt;a target="_blank" href="http://www.salesforce.com/chatter/"&gt;Salesforce
Chatter&lt;/a&gt; API. 
&lt;/p&gt;
&lt;p&gt;
The challenge culminated in a Hackthon event on June 22nd 2010 at the San Jose Convention
Center where prizes &lt;a target="_blank" href="http://blog.sforce.com/sforce/2010/06/chatter-developer-challenge-winners-congratulations.html"&gt;were
awarded&lt;/a&gt; for various applications. 
&lt;/p&gt;
&lt;p&gt;
My entry, &lt;a target="_blank" href="http://developer.force.com/chatterdevchallenge/entry?id=087300000002lFbAAI"&gt;Chatter
Bot&lt;/a&gt;, demonstrated the use of Chatter within a Facility Management application
that captured physical world events and moved them to the cloud to produce Chatter
feed posts. 
&lt;/p&gt;
&lt;p&gt;
&lt;object width="480" height="385"&gt;
&lt;param name="movie" value="http://www.youtube.com/v/FzYLllR8X6A&amp;hl=en_US&amp;fs=1&amp;"&gt;&gt;
&lt;param name="allowFullScreen" value="true"&gt;&gt;
&lt;param name="allowscriptaccess" value="always"&gt;&gt;&lt;embed src="http://www.youtube.com/v/FzYLllR8X6A&amp;hl=en_US&amp;fs=1&amp;" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="480" height="385"&gt;&lt;/embed&gt;
&lt;/object&gt;
&lt;/p&gt;
&lt;p&gt;
Chatter Bot is a system comprised of 4 major components: 
&lt;ul&gt;
&lt;li&gt;
&lt;a target="_blank" href="http://www.arduino.cc/"&gt;Arduino board&lt;/a&gt; with motion and
light sensors (C/C++) 
&lt;li&gt;
Proxy Service (Java &lt;a target="_blank" href="http://processing.org/"&gt;Processing.org&lt;/a&gt; Environment) 
&lt;li&gt;
Salesforce Sites HTTP Listener (Visualforce/Apex) 
&lt;li&gt;
Facility Management App (Force.com database and web forms) 
&lt;/ul&gt;
(Source code to all components available at the bottom of this post)&lt;br /&gt;
&lt;img src="http://www.embracingthecloud.com/content/binary/Hackathon_ChatterSystemDiagram.png" /&gt; &gt;
&lt;p&gt;
I was elated to learn a few days before the hackathon that Chatter Bot had been selected
as a finalist and I was strongly encouraged to attend. So I packed up Chatter Bot
to take the 2 hour flight from Portland to San Jose. 
&lt;/p&gt;
&lt;p&gt;
It wasn't until I arrived at the airport that it suddenly dawned on me how much Chatter
Bot bares a striking resemblance to a poorly assembled explosive device. Apparently
the TSA agent handling the X-Ray machine thought so too and I was taken aside for
the full bomb sniffing and search routine. 
&lt;br /&gt;
&lt;img src="http://www.embracingthecloud.com/content/binary/Arduino_Chatter_Bot.jpg" /&gt; 
&lt;/p&gt;
&lt;p&gt;
It crossed my mind to add a bit levity to the situation by making some kind of remark,
but I quickly assessed that I was probably one misinterpreted comment away from being
whisked off in handcuffs to some TSA lockup room. Ironically, I had no problem with
security in San Jose coming back. They must be accustomed to these types of devices
in Silicon Valley. 
&lt;/p&gt;
&lt;p&gt;
Upon arriving in San Jose, I setup Chatter Bot and configured the San Jose Convention
Center (SJCC) as a Building Facility (Custom object) to be monitored.&lt;br /&gt;
&lt;img src="http://www.embracingthecloud.com/content/binary/Hackathon_Buildings.png" /&gt; 
&lt;/p&gt;
&lt;p&gt;
Several assets were created to represent some rooms within the SJCC.&lt;br /&gt;
&lt;img src="http://www.embracingthecloud.com/content/binary/Hackathon_Building_Detail.png" /&gt; 
&lt;/p&gt;
&lt;p&gt;
Finally, the Chatter Bot was associated with a particular room (Asset) through an
intersection object called AssetSensors that relates a device ID (usually a MAC address)
and an Asset.&lt;br /&gt;
&lt;img src="http://www.embracingthecloud.com/content/binary/Hackathon_AssetSensor_Master.png" /&gt; 
&lt;br /&gt;
&lt;img src="http://www.embracingthecloud.com/content/binary/Hackathon_AssetSensorDetail.png" /&gt; 
&lt;/p&gt;
&lt;p&gt;
Within minutes the motion and light sensors were communicating to the cloud via my
laptop and reporting on activity in the Hackathon room.&lt;br /&gt;
&lt;img src="http://www.embracingthecloud.com/content/binary/Hackathon_Asset_Room_J3.png" /&gt; 
&lt;/p&gt;
&lt;p&gt;
Given the high quality and functionality of fellow competitors apps, such as the very
cool &lt;a target="_blank" href="http://developer.force.com/chatterdevchallenge/entry?id=087300000002lGFAAY"&gt;Chatter
for Android&lt;/a&gt; app by &lt;a target="_blank" href="http://blog.jeffdouglas.com/"&gt;Jeff
Douglas&lt;/a&gt;, and observations from the public voting, I thought Chatter Bot might
be a little too "out of the box" to take a prize. It was a genuinely surreal and surprising
moment when I learned Chatter Bot received the &lt;a target="_blank" href="http://developer.force.com/chatterdevchallenge/entry?id=087300000002lGFAAY"&gt;grand
prize&lt;/a&gt;. 
&lt;/p&gt;
&lt;p&gt;
Thank you Salesforce for hosting such a great event and thank you to the coop-etition
for the encouraging exchange of ideas and feedback during the challenge! 
&lt;/p&gt;
&lt;h2&gt;Arduino Sensor
&lt;/h2&gt;
&lt;pre class="brush: java;"&gt;
/////////////////////////////
//VARS
//the time when the sensor outputs a low impulse
long unsigned int lowIn;

//the amount of milliseconds the sensor has to be low 
//before we assume all motion has stopped
long unsigned int pause = 5000;

boolean lockLow = true;
boolean takeLowTime;  

int LDR_PIN = 2;    // the analog pin for reading the LDR (Light Dependent Resistor)
int PIR_PIN = 3;    // the digital pin connected to the PIR sensor's output
int LED_PIN = 13;

byte LIGHT_ON    = 1;
byte LIGHT_OFF   = 0;
byte previousLightState  = LIGHT_ON;
int lightLastChangeTimestamp = 0;
unsigned int LIGHT_ON_MINIMUM_THRESHOLD = 1015;
unsigned long lastListStateChange = 0; //Used to de-bounce borderline transitions.

// Messages
int SENSOR_MOTION = 1;
int SENSOR_LIGHT  = 2;

/////////////////////////////
//SETUP
void setup(){  
  //PIR initialization
  pinMode(PIR_PIN, INPUT);
  pinMode(LED_PIN, OUTPUT);
  digitalWrite(PIR_PIN, LOW);
  
  Serial.begin(9600);
  
  InitializeLED();
  InitializeLightSensor();
  InitializeMotionSensor();
}

////////////////////////////
//LOOP
void loop(){
  
  if(digitalRead(PIR_PIN) == HIGH){
    digitalWrite(LED_PIN, HIGH);   //the led visualizes the sensors output pin state
    if(lockLow){
      //makes sure we wait for a transition to LOW before any further output is made:
      lockLow = false;
      writeMeasure(SENSOR_MOTION, HIGH);
      delay(50);
      digitalWrite(LED_PIN, LOW);   //the led visualizes the sensors output pin state
    }
    takeLowTime = true;
  }

  if(digitalRead(PIR_PIN) == LOW){
    digitalWrite(LED_PIN, LOW);  //the led visualizes the sensors output pin state
    if(takeLowTime){
      lowIn = millis();          //save the time of the transition from high to LOW
      takeLowTime = false;       //make sure this is only done at the start of a LOW phase
    }
    
    //if the sensor is low for more than the given pause, 
    //we assume that no more motion is going to happen
    if(!lockLow &amp;&amp; millis() - lowIn &amp;gt; pause){
      //makes sure this block of code is only executed again after 
      //a new motion sequence has been detected
      lockLow = true;
      writeMeasure(SENSOR_MOTION, LOW);
      delay(50);
    }
  }
  
  ProcessLightSensor();
}

boolean InitializeLED(){
  Serial.println("INIT: Initializing LED (should see 3 blinks)... ");
  for(int i=0; i &amp;lt; 3; i++){
    digitalWrite(LED_PIN, HIGH);
    delay(500);
    digitalWrite(LED_PIN, LOW);
    delay(500);
  }
}

//the time we give the motion sensor to calibrate (10-60 secs according to the datasheet)
int calibrationTime = 10;

boolean InitializeMotionSensor(){
  //give the sensor some time to calibrate
  Serial.print("INIT: Calibrating motion sensor (this takes about ");
  Serial.print(calibrationTime);
  Serial.print(" seconds) ");
  for(int i = 0; i &amp;lt; calibrationTime; i++){
    Serial.print(".");
    delay(1000);
  }
  Serial.println(" done");
  Serial.println("INIT: SENSOR ACTIVE");
  delay(50);
}

boolean InitializeLightSensor(){
  Serial.print("INIT: Initializing light sensor. Light on threashold set to ");
  Serial.println(LIGHT_ON_MINIMUM_THRESHOLD);
  Serial.println("INIT: 20 samples follow...");
  for(int i = 0; i &amp;lt; 20; i++){
    int lightLevelValue = analogRead(LDR_PIN);
    Serial.print("INIT: ");
    Serial.println(lightLevelValue);
  }
}

boolean ProcessLightSensor(){
  byte currentState = previousLightState;
  int lightLevelValue = analogRead(LDR_PIN);  // returns value 0-1023. 0=max light. 1,023 means no light detected.
  
  if(lightLevelValue &amp;lt; LIGHT_ON_MINIMUM_THRESHOLD){
     currentState = LIGHT_ON;
  }
  else{
     currentState = LIGHT_OFF;
  }
  
  if(LightStateHasChanged(currentState) &amp;&amp; !LightStateIsBouncing() ){
    previousLightState = currentState; 
    
    if(currentState == LIGHT_ON){
      writeMeasure(SENSOR_LIGHT, HIGH);
    }
    else{
      writeMeasure(SENSOR_LIGHT, LOW);
    }
    
    delay(2000);
    lightLastChangeTimestamp = millis();
    
    return true;
  }
  else{
    return false; 
  }
}

boolean LightStateHasChanged(byte currentState){
   return currentState != previousLightState; 
}

//De-bounce LDR readings in case light switch is being quickly turned on/off
unsigned int MIN_TIME_BETWEEN_LIGHT_CHANGES = 5000;
boolean LightStateIsBouncing(){
   if(millis() - lightLastChangeTimestamp &amp;lt; MIN_TIME_BETWEEN_LIGHT_CHANGES){
      return true; 
   }
   else{
      return false; 
   }
}

byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; 
char deviceID[ ] = "007DEADBEEF0";
//Format MEASURE|version|DeviceID|Sensor Type|State (on/off)
void writeMeasure(int sensorType, int state){
  Serial.print("MEASURE|v1|");
  
  Serial.print(deviceID);
  Serial.print("|");
  
  if(sensorType == SENSOR_MOTION)
    Serial.print("motion|");
  else if(sensorType == SENSOR_LIGHT)
    Serial.print("light|");
  else
    Serial.print("unknown|");
  
  if(state == HIGH)
    Serial.print("on");
  else if(state == LOW)
    Serial.print("off");
  else
    Serial.print("unknown");
  
  Serial.println("");
}
&lt;/pre&gt;
&lt;h2&gt;Chatter Bot Proxy (Processing.org Environment)
&lt;/h2&gt;
&lt;pre class="brush: java;"&gt;
import processing.serial.*;

Serial port;
String buffer = "";

void setup()
{
    size(255,255);
    println(Serial.list());
    port = new Serial(this, "COM7", 9600);
}

void draw()
{
  if(port.available() &amp;gt; 0){
    int inByte = port.read();
    print( char(inByte) );
    if(inByte != 10){ //check newline
      buffer = buffer + char(inByte);
    }
    else{
       if(buffer.length() &amp;gt; 1){
          if(IsMeasurement(buffer)){
              postToForce(buffer);
          }
          buffer = "";
          port.clear();
       }
    }
  }
}

boolean IsMeasurement(String message){
  return message.indexOf("MEASURE") &amp;gt; -1;
}

void postToForce(String message){
  String[] results = null;
  try
  {
    URL url= new URL("http://listener-developer-edition.na7.force.com/api/measure?data=" + message);
    URLConnection connection = url.openConnection();

    connection.setRequestProperty("User-Agent",  "Mozilla/5.0 (Processing)" );
    connection.setRequestProperty("Accept",  "text/plain,text/html,application/xhtml+xml,application/xml" );
    connection.setRequestProperty("Accept-Language",  "en-us,en" );
    connection.setRequestProperty("Accept-Charset",  "utf-8" );
    connection.setRequestProperty("Keep-Alive",  "300" );
    connection.setRequestProperty("Connection",  "keep-alive" );
    
    results = loadStrings(connection.getInputStream());  
  }
  catch (Exception e) // MalformedURL, IO
  {
    e.printStackTrace();
  }

  if (results != null)
  {
    for(int i=0; i &amp;lt; results.length; i++){
      println( results[i] );
    }
  }
}
&lt;/pre&gt;
&lt;h2&gt;Visualforce Site Chatter Listener
&lt;/h2&gt;
&lt;pre class="brush: java;"&gt;
&amp;lt;apex:page controller="measureController" action="{!processRequest}" 
contentType="text/plain; charset=utf-8" showHeader="false" 
standardStylesheets="false" sidebar="false"&amp;gt;
{!Result}
&amp;lt;/apex:page&amp;gt;
&lt;/pre&gt;
&lt;h2&gt;Controller
&lt;/h2&gt;
&lt;pre class="brush: java;"&gt;
public with sharing class measureController {
	public void processRequest(){
    	if(Data != null){
    		system.debug('data= ' + Data);
    	}
    	
    	CreateFeedPosts();
    }
    
    private void CreateFeedPosts(){
    	if(AssetDeviceBindings.size() == 0)
    		return;
    	
    	for(AssetSensor__c binding : AssetDeviceBindings){
	    	FeedPost newFeedPost = new FeedPost();
	    	newFeedPost.parentId = binding.Asset__c;
			newFeedPost.Type = 'TextPost';
	        newFeedPost.Body = FeedPostMessage();
	        insert newFeedPost;
    	}
    }
    
    private string FeedPostMessage(){
    	if(AssetDeviceBindings.size() == 0)
    		return '';
    	
    	if(SensorType == 'motion'){
    		if(State == 'on')
    			return 'Motion detected';
    		else
    			return 'Motion stopped';
    	}
    	else if(SensorType == 'light'){
    		return 'Lights turned ' + State;
    	}
    	else
    		return 'Unknown sensor event';
    }
    
    private List&amp;lt;AssetSensor__c&amp;gt; m_assetSensor = null;
    public List&amp;lt;AssetSensor__c&amp;gt; AssetDeviceBindings{
    	get{
    		if(m_assetSensor == null){
    			m_assetSensor = new List&amp;lt;AssetSensor__c&amp;gt;();
    			if(DeviceID != null){
    				m_assetSensor = [select Id, Name, Asset__c, DeviceID__c from AssetSensor__c where DeviceID__c=:DeviceID limit 500];
    			}
    		}
    		return m_assetSensor;
    	}
    }
    
    private integer EXPECTED_MESSAGE_PARTS = 5;
    private integer DATA_MESSAGE_TYPE = 0;
    private integer DATA_VERSION	= 1;
    private integer DATA_DEVICEID	= 2;
    private integer DATA_SENSOR_TYPE= 3;
    private integer DATA_STATE		= 4;
    
    private List&amp;lt;string&amp;gt; m_dataParts = null;
    public List&amp;lt;string&amp;gt; DataParts{
    	get{
    		if(m_dataParts == null &amp;&amp; Data != null){
    			m_dataParts = Data.split('\\|');
    		}
    		return m_dataParts;
    	}
    }
    
    public string Version{
    	get{
    		if(Data != null &amp;&amp; DataParts.size() &amp;gt;= EXPECTED_MESSAGE_PARTS){
    			return DataParts[DATA_VERSION];
    		}
    		else
    			return null;
    	}
    }
    
    public string DeviceID{
    	get{
    		if(Data != null &amp;&amp; DataParts.size() &amp;gt;= EXPECTED_MESSAGE_PARTS){
    			return DataParts[DATA_DEVICEID];
    		}
    		else
    			return null;
    	}
    }
    
    public string SensorType{
    	get{
    		if(Data != null &amp;&amp; DataParts.size() &amp;gt;= EXPECTED_MESSAGE_PARTS){
    			return DataParts[DATA_SENSOR_TYPE];
    		}
    		else
    			return null;
    	}
    }
    
    public string State{
    	get{
    		if(Data != null &amp;&amp; DataParts.size() &amp;gt;= EXPECTED_MESSAGE_PARTS){
    			return DataParts[DATA_STATE];
    		}
    		else
    			return null;
    	}
    } 
    
    private string m_data = null;
    public string Data{
    	get{
    		if(m_data == null &amp;&amp; ApexPages.currentPage().getParameters().get('data') != null){
    			m_data = ApexPages.currentPage().getParameters().get('data');
    		}
    		return m_data;
    	}
    }
    
    private string m_result = '';
    public String Result{
    	get{
    		return 'ok';
    	}
    }
    
    public static testMethod void tests(){
    	Asset testAsset = new Asset();
    	testAsset.Name = 'Test Asset';
    	testAsset.AccountID = [select Id from Account order by CreatedDate desc limit 1].Id;
    	insert testAsset;
    	
    	AssetSensor__c binding = new AssetSensor__c();
    	binding.Name = 'Test Binding';
    	binding.DeviceID__c = '007DEADBEEF9';
    	binding.Asset__c = testAsset.Id;
    	insert binding;
    	
    	measureController controller = new measureController();
    	controller.processRequest();
    	system.assert(controller.Data == null);
    	system.assert(controller.DataParts == null);
    	system.assert(controller.Version == null);
    	system.assert(controller.DeviceID == null);
    	system.assert(controller.SensorType == null);
    	system.assert(controller.State == null);
    	
    	string TEST_MEASURE = 'MEASURE|v1|007DEADBEEF9|motion|on';
    	ApexPages.currentPage().getParameters().put('data', TEST_MEASURE);
    	controller = new measureController();
    	controller.processRequest();
    	system.assert(controller.Data == TEST_MEASURE);
    	system.assert(controller.DataParts != null);
    	system.assert(controller.DataParts.size() == 5);
    	system.assert(controller.Version == 'v1');
    	system.assert(controller.DeviceID == '007DEADBEEF9');
    	system.assert(controller.SensorType == 'motion');
    	system.assert(controller.State == 'on');
    	
    	system.assert(controller.AssetDeviceBindings != null);
    	system.assert(controller.AssetDeviceBindings.size() == 1);
    	system.assertEquals('007DEADBEEF9', controller.AssetDeviceBindings[0].DeviceID__c);
    	system.assertEquals(testAsset.Id, controller.AssetDeviceBindings[0].Asset__c);
    	
    	system.assert(controller.Result == 'ok');
    }
}
&lt;/pre&gt;&lt;img width="0" height="0" src="http://www.embracingthecloud.com/aggbug.ashx?id=fbf88b23-9b49-477c-94a0-c05d5f759937" /&gt;</content>
  </entry>
  <entry>
    <title>Announcing Cool Sites for AppExchange</title>
    <link rel="alternate" type="text/html" href="http://www.embracingthecloud.com/2010/06/21/AnnouncingCoolSitesForAppExchange.aspx" />
    <id>http://www.embracingthecloud.com/PermaLink,guid,9b6a96ef-4655-4649-813d-17a1125db1bc.aspx</id>
    <published>2010-06-21T17:43:39.015-05:00</published>
    <updated>2010-06-21T17:46:51.703125-05:00</updated>
    <category term="Salesforce" label="Salesforce" scheme="http://www.embracingthecloud.com/CategoryView,category,Salesforce.aspx" />
    <author>
      <name>Mike Leach</name>
    </author>
    <content type="xhtml">
      <div xmlns="http://www.w3.org/1999/xhtml">I'm very happy to announce that <a href="https://sites.secure.force.com/appexchange/listingDetail?listingId=a0N30000003HfaKEAS">Cool
Sites</a> was released this weekend. Cool Sites provides a <a href="http://www.getcoolsites.com/page#Gallery">gallery</a> of
pre-built page templates, plugins, and sites built on Salesforce Sites.<br /><br />
Basic web content management tools and workflows for creating navigation menus and
web pages are included. Check it out! <a href="http://www.getcoolsites.com/" target="_blank">http://www.getcoolsites.com</a><br /><br /><a href="http://www.getcoolsites.com/" target="_blank"><br /></a><a href="http://www.getcoolsites.com/" target="_blank"><img src="http://www.embracingthecloud.com/content/binary/CoolLogo.gif" border="0" /></a><img width="0" height="0" src="http://www.embracingthecloud.com/aggbug.ashx?id=9b6a96ef-4655-4649-813d-17a1125db1bc" /></div>
    </content>
  </entry>
  <entry>
    <title>Jazz - A Software Development Methodology</title>
    <link rel="alternate" type="text/html" href="http://www.embracingthecloud.com/2010/06/13/JazzASoftwareDevelopmentMethodology.aspx" />
    <id>http://www.embracingthecloud.com/PermaLink,guid,7e0b3209-d24c-4ba1-9d22-ff184f41b6d8.aspx</id>
    <published>2010-06-13T15:04:29.987-05:00</published>
    <updated>2010-06-13T15:46:13.690875-05:00</updated>
    <category term="Jazz" label="Jazz" scheme="http://www.embracingthecloud.com/CategoryView,category,Jazz.aspx" />
    <author>
      <name>Mike Leach</name>
    </author>
    <content type="xhtml">
      <div xmlns="http://www.w3.org/1999/xhtml">
        <h1>Jazz - A Software Development Methodology
</h1>
        <img src="http://www.embracingthecloud.com/content/binary/jazz.jpg" border="0" width="289" height="223" />
        <br />
        <b>What</b>
        <br />
Jazz is a software development methodology for the ongoing creation and improvisation
around several small apps distributed on open Internet platforms (the "cloud").<br /><br /><br /><b>Why</b><br />
Contemporary tools, languages, and distribution platforms of the 21st century make
it feasible for software to be created, delivered, and performed similarly to Jazz
music of the 20th century.<br /><br />
Software, like music, may be consumed repeatedly throughout the day by audiences.<br /><br />
Software has become ubiquitous and essential to people's everyday lives.<br /><br />
Audiences increasingly are demanding to know more about the personalities and teams
behind their favorite software applications.<br /><br /><br /><b>What (Apps)</b><br />
Jazz combos seek to build a repertoire of several small apps, as opposed to developing
and supporting one large app. Jazz is most suitable for Developers distributing applications
through online stores (such as the Apple App Store, Google Marketplace, or Salesforce
AppExchange).<br /><br />
The repertoire should consist of at least 10 apps.<br /><br />
Not all apps are for commercial distribution (such as contributions to open source
frameworks and proof of concept apps used for marketing purposes). 
<br /><br />
Only 1 in 10 apps within the repertoire will achieve any significant amount of notoriety
or market adoption.<br /><br /><br /><b>Who (The Combo)</b><br />
Apps are initially composed by a single person, which are then rehearsed and performed
by combos of up to 5-7 people. Apps are occasionally composed by 2 collaborators.<br /><br />
Apps are consumed by audiences (customers).<br /><br /><br /><b>Where (Spaces)</b><br />
Jazz is composed, rehearsed, and performed in 3 distinct "spaces".<br /><br />
Composition and practice are done in solitude (home, coffee shop, the beach) but always
connected (anywhere with Internet access)<br /><br />
Rehearsals are done with a group about once per week in public meeting places<br /><br />
Performances are ongoing. They occur both virtually and physically. All audience interactions
are performances (this includes app store listings, webinars, support, and customizations)<br /><br /><br /><b>When</b><br />
Jazz is not a 9 to 5 job. However, ongoing performances are typically supported by
someone during working hours.<br /><br />
Jazz Developers dedicate at least 3 days per week to uninterrupted work (composition
and improvisation) and at least 1 day for group rehearsals (meetings) and audience
interactions (support and review of customer feedback).<br /><br />
Compositions may take 1 day or 1 year to write. It should take no more than 2 years
to accumulate a repertoire of 10 apps.<br /><br />
Team members are often given "the floor" during performances to lead and improvise
within a composition. Improvisations are short creative cycles within the overall
structure of a composition.<br /><br /><br /><b>How</b><br />
Each member of a Jazz combo owns and maintains their own instruments (tools) that
best support their function within the combo.<br /><br />
Academic mastery of instrument and language are assumed before confidence in composition
and improvisation can be achieved.<br /><br />
Jazz combos are unified in language presented to audiences, but individually use languages
specific to their instrument or function.<br /><br />
Jazz encourages the sharing, remixing, and reuse of ideas through use of <a href="http://creativecommons.org/">creative
commons</a> licenses.<br /><br />
All individuals are given public recognition for their contribution to an app.<br /><br />
Performers may practice up to 10 hours for a 1 hour performance (10:1 ratio of effort,
unit tests behind released products).<br /><br />
The best performances are achieved through mastery of instrument, confidence in skills,
and relaxation of mind.<br /><br />
The result is more important than the means. Don't over think the process to achieving
a good performance.<br /><br />
Improvisations often rely on audience feedback to determine the path of a performance.<br /><br />
The Jazz combo may dynamically adjust composition structure during a performance through
subtle communication to support an improvisation that may lead to new discoveries
or increased levels of audience feedback and participation.<img width="0" height="0" src="http://www.embracingthecloud.com/aggbug.ashx?id=7e0b3209-d24c-4ba1-9d22-ff184f41b6d8" /></div>
    </content>
  </entry>
  <entry>
    <title>Chatter Bot for Salesforce</title>
    <link rel="alternate" type="text/html" href="http://www.embracingthecloud.com/2010/06/07/ChatterBotForSalesforce.aspx" />
    <id>http://www.embracingthecloud.com/PermaLink,guid,fefa196d-33e1-42c3-af68-859b73c36e0a.aspx</id>
    <published>2010-06-07T11:57:05.15725-05:00</published>
    <updated>2010-06-07T11:57:05.15725-05:00</updated>
    <category term="Salesforce" label="Salesforce" scheme="http://www.embracingthecloud.com/CategoryView,category,Salesforce.aspx" />
    <author>
      <name>Mike Leach</name>
    </author>
    <content type="html">&lt;p&gt;
Here's a fun video put together for my &lt;a target=_blank href="http://developer.force.com/chatter_developer_challenge"&gt;Salesforce
Chatter Developer Challenge&lt;/a&gt; entry.
&lt;/p&gt;
&lt;object width="480" height="385"&gt;
&lt;param name="movie" value="http://www.youtube.com/v/FzYLllR8X6A&amp;hl=en_US&amp;fs=1&amp;"&gt;&gt;
&lt;param name="allowFullScreen" value="true"&gt;&gt;
&lt;param name="allowscriptaccess" value="always"&gt;&gt;&lt;embed src="http://www.youtube.com/v/FzYLllR8X6A&amp;hl=en_US&amp;fs=1&amp;" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="480" height="385"&gt;&lt;/embed&gt;
&lt;/object&gt;&lt;img width="0" height="0" src="http://www.embracingthecloud.com/aggbug.ashx?id=fefa196d-33e1-42c3-af68-859b73c36e0a" /&gt;</content>
  </entry>
  <entry>
    <title>A Simple RESTful Interface Using Salesforce Sites, Visualforce, and Apex</title>
    <link rel="alternate" type="text/html" href="http://www.embracingthecloud.com/2010/05/28/ASimpleRESTfulInterfaceUsingSalesforceSitesVisualforceAndApex.aspx" />
    <id>http://www.embracingthecloud.com/PermaLink,guid,6ca59b34-f896-48a2-b1bf-33ffbe96b4ec.aspx</id>
    <published>2010-05-28T14:34:08.282-05:00</published>
    <updated>2010-05-28T16:58:55.797875-05:00</updated>
    <category term="Apex" label="Apex" scheme="http://www.embracingthecloud.com/CategoryView,category,Apex.aspx" />
    <category term="Salesforce" label="Salesforce" scheme="http://www.embracingthecloud.com/CategoryView,category,Salesforce.aspx" />
    <author>
      <name>Mike Leach</name>
    </author>
    <content type="html">A true REST interface with full support for HTTP Verbs, status codes, and URIs is currently not available on the Salesforce.com platform. However, a simple REST-like interface for getting objects can be developed using Salesforce Sites, Visualforce, and Apex.&lt;br&gt;
&lt;br&gt;
This example uses a free Developer Edition with a Site named 'api' that uses only
2 Visualforce pages named 'rest' and 'error'. The rest page accepts a single parameter
named 'soql', executes the SOQL query, and returns a JSON formatted response.&lt;br&gt;
&lt;br&gt;
&lt;p&gt;
&lt;/p&gt;
&lt;img src="http://www.embracingthecloud.com/content/binary/rest_create_site.png" border="0"&gt;
&lt;br&gt;
&lt;br&gt;
The error page is also used to generically handle all 40x and 50x errors.&lt;br&gt;
&lt;br&gt;
&lt;img src="http://www.embracingthecloud.com/content/binary/rest_error_handler.png" border="0"&gt;
&lt;br&gt;
&lt;br&gt;
The body of the error page returns a simple JSON message that the api is unavailable.&lt;br&gt;
&lt;pre class="brush: java;"&gt;
&amp;lt;apex:page contenttype="application/x-JavaScript; charset=utf-8" 
showheader="false" standardstylesheets="false" sidebar="false"&amp;gt;
{"status": 500, "error": "api currently not available"}
&amp;lt;/apex:page&amp;gt;
&lt;/pre&gt;
&lt;p&gt;
The rest Visualforce page (full source at bottom of this post) accepts a SOQL parameter
and returns JSON results. To get a list of all Leads with their First and Last names,
you'd use the SOQL &lt;pre&gt;
select Id, FirstName, LastName from Lead
&lt;/pre&gt;
and pass this query to the REST interface in a GET format such as &lt;a target=_blank href="http://cubic-compass-developer-edition.na7.force.com/api?soql=select%20Id,%20FirstName,%20LastName%20from%20Lead"&gt;(example
here)&lt;/a&gt; &lt;pre&gt;
http://cubic-compass-developer-edition.na7.force.com/api?soql=select%20Id,%20FirstName,%20LastName%20from%20Lead
&lt;/pre&gt;
&gt;
&lt;p&gt;
Note that the rest page is defined as the default handler for the site named 'api',
so it's not required in the URL.&lt;br /&gt;
This simple interface supports any flavor of SOQL, including the WHERE and LIMIT keywords,
so you can pass queries like &lt;pre&gt;
select Id, FirstName, LastName from Lead where LastName='Smith' limit 20
&lt;/pre&gt;
REST interfaces often assume the unique ID of an object is the last portion of the
URL request. This can similarly be achieved with a query like &lt;a target=_blank href="http://cubic-compass-developer-edition.na7.force.com/api?soql=select%20Id,%20FirstName,%20LastName%20from%20Lead%20where%20Id='00QA00000019xkpMAA'%20limit%201"&gt;(example
here)&lt;/a&gt; &lt;pre&gt;
select Id, FirstName, LastName from Lead where Id='00QA00000019xkpMAA' limit 1
&lt;/pre&gt;
&gt;
&lt;p&gt;
All of these example queries will only return the Id field by default. To fix this,
update the Sites Public Access Settings and grant Read access to the Lead object.&lt;br&gt;
&lt;/p&gt;
&lt;img src="http://www.embracingthecloud.com/content/binary/rest_access_settings.png" border="0"&gt;
&lt;br&gt;
&lt;br&gt;
&lt;br&gt;
&lt;br&gt;
&lt;img src="http://www.embracingthecloud.com/content/binary/rest_object_permissions.png" border="0"&gt; 
&lt;p&gt;
The new &lt;a target=_blank href="http://blog.sforce.com/sforce/2010/05/url-rewriting-for-your-customizing-your-site-urls.html"&gt;URL
rewriting feature&lt;/a&gt; in Summer 10 provides &lt;strike&gt;some additional options&lt;/strike&gt; the
necessary means to implementing a RESTful interface with full support for object URIs
and linking. 
&lt;/p&gt;
&lt;strong&gt;Visualforce Source Code for rest.page&lt;/strong&gt; &lt;pre class="brush: java;"&gt;
&amp;lt;apex:page controller="RESTController" action="{!processRequest}" 
contentType="application/x-JavaScript; charset=utf-8" showHeader="false" 
standardStylesheets="false" sidebar="false"&amp;gt;
{!JSONResult}
&amp;lt;/apex:page&amp;gt;
&lt;/pre&gt;
&lt;strong&gt;Apex Source Code for RESTController.cls&lt;/strong&gt; &lt;pre class="brush: java;"&gt;
public with sharing class RESTController {
	public void processRequest(){
		validateRequest();		
    	if( HasError )
    		return;
    	
    	//Add support for other types of verbs here
    	processGetQuery();
    }
    
    static final string ERROR_MISSING_SOQL_PARAM = 'Bad Request. Missing soql parameter';
    static final string ERROR_SOBJECT_MISSING	 = 'Bad Request. Could not parse SObject name from SOQL';
    static final string ERROR_FROM_MISSING		 = 'Bad request. SOQL missing FROM keyword';
    public void validateRequest(){
    	if(Query == null){
    		errorResponse(400, ERROR_MISSING_SOQL_PARAM);
    	}
    	else if(sObjectName == null){
    		//Force a get of object name property.
    		//Detailed error response should already be logged by sObjectName parser
    	}
    }
    
    public boolean HasError = False;
    private void errorResponse(integer errorCode, string errorMessage){
    	JSONResponse.putOpt('status', new JSONObject.value(errorCode));
    	JSONResponse.putOpt('error', new JSONObject.value(errorMessage));
    	HasError = True;
    }
        
    public void processGetQuery(){
    	Map&amp;lt;String, Schema.SObjectField&amp;gt; fieldMap = Schema.getGlobalDescribe().get(SObjectName).getDescribe().fields.getMap();
    	List&amp;lt;JSONObject.value&amp;gt; objectValues = new List&amp;lt;JSONObject.value&amp;gt;();
    	List&amp;lt;sObject&amp;gt; resultList = Database.query(Query);
 		
    	for(sObject obj : resultList){
    		JSONObject json = new JSONObject();
    		json.putOpt('id', new JSONObject.value( obj.Id ));
    		for(SObjectField field : fieldMap.values() ){
    			try{
    				string f = field.getDescribe().getName();
    				string v = String.valueOf( obj.get(field) );
    				json.putOpt(f, new JSONObject.value( v ));
    			}
    			catch(Exception ex){
    				//Ignore. Field not included in query
    			}
    		}
			objectValues.add(new JSONObject.value(json));
    	}
    	JSONResponse.putOpt('status', new JSONObject.value(200));
    	JSONResponse.putOpt('records', new JSONObject.value(objectValues));
    }
    
    private string m_query = null;
    public string Query{
    	get{
    		if(m_query == null &amp;&amp; ApexPages.currentPage().getParameters().get('soql') != null){
    			m_query = ApexPages.currentPage().getParameters().get('soql');
    		}
    		return m_query;
    	}
    }

	static final string SOQL_FROM_TOKEN = 'from ';    
    private string m_sObject = null;
    public string sObjectName{
    	get{
    		if(m_sObject == null &amp;&amp; Query != null){
    			string soql = Query.toLowerCase();
    			
    			integer sObjectStartToken = soql.indexOf(SOQL_FROM_TOKEN);
    			if(sObjectStartToken == -1){
    				errorResponse(400, ERROR_FROM_MISSING);
    				return null;
    			}
    			sObjectStartToken += SOQL_FROM_TOKEN.length(); 
    			
    			integer sObjectEndToken = soql.indexOf(' ', sObjectStartToken);
    			if(sObjectEndToken == -1)
    				sObjectEndToken = soql.length();
    			
    			m_sObject = Query.substring(sObjectStartToken, sObjectEndToken);
    			m_sObject = m_sObject.trim();
    			system.debug('m_sObject = ' + m_sObject);
    		}
    		return m_sObject;
    	}
    }
    
    private JsonObject m_jsonResponse = null;
    public JSONObject JSONResponse{
    	get{
    		if(m_jsonResponse == null)
    			m_jsonResponse = new JSONObject();
    		return m_jsonResponse;
		}
		set{ m_jsonResponse = value;}
	}
    
	public String getJSONResult() {
    	return JSONResponse.valueToString();
	}
	
	public static testMethod void unitTests(){
		RESTController controller = new RESTController();
		controller.processRequest();
		system.assertEquals(True, controller.HasError);
		system.assertEquals(True, controller.JSONResponse.has('status'));
		system.assertEquals(400, controller.JSONResponse.getValue('status').num);
		system.assertEquals(True, controller.JSONResponse.has('error'));
		system.assertEquals(ERROR_MISSING_SOQL_PARAM, controller.JSONResponse.getValue('error').str);
		
		controller = new RESTController();
		ApexPages.currentPage().getParameters().put('soql', 'select Id fro Lead');
		controller.processRequest();
		system.assertEquals(True, controller.HasError);
		system.assertEquals(True, controller.JSONResponse.has('status'));
		system.assertEquals(400, controller.JSONResponse.getValue('status').num);
		system.assertEquals(ERROR_FROM_MISSING, controller.JSONResponse.getValue('error').str);
		
		controller = new RESTController();
		ApexPages.currentPage().getParameters().put('soql', 'select Id from Lead');
		controller.processRequest();
		system.assertEquals(False, controller.HasError);
		system.assertEquals('Lead', controller.sObjectName);
		
		Lead testLead = new Lead(FirstName = 'test', LastName = 'lead', Company='Bedrock', Email='fred@flintstone.com');
        insert testLead;
        
        controller = new RESTController();
		ApexPages.currentPage().getParameters().put('soql', 'select Id from Lead where email=\'fred@flintstone.com\'');
		controller.processRequest();
		system.assertEquals(False, controller.HasError);
		system.assertEquals('Lead', controller.sObjectName);
		system.assertEquals(True, controller.JSONResponse.has('records'));
	}
}
&lt;/pre&gt;&lt;img width="0" height="0" src="http://www.embracingthecloud.com/aggbug.ashx?id=6ca59b34-f896-48a2-b1bf-33ffbe96b4ec" /&gt;</content>
  </entry>
  <entry>
    <title>Google I/O 2010 Conference Roundup</title>
    <link rel="alternate" type="text/html" href="http://www.embracingthecloud.com/2010/05/23/GoogleIO2010ConferenceRoundup.aspx" />
    <id>http://www.embracingthecloud.com/PermaLink,guid,1b1c709f-42cf-4483-82fc-a2ebb1624877.aspx</id>
    <published>2010-05-22T19:02:22.45-05:00</published>
    <updated>2010-05-22T19:04:07.27825-05:00</updated>
    <category term="Google" label="Google" scheme="http://www.embracingthecloud.com/CategoryView,category,Google.aspx" />
    <author>
      <name>Mike Leach</name>
    </author>
    <content type="xhtml">
      <div xmlns="http://www.w3.org/1999/xhtml">The Google I/O Developer Conference this
past week (May 19-20) was a highly anticipated event for both Google web and mobile
Developers alike.<br /><br />
With the exception of Chrome OS, all of the technologies and products on my <a href="http://www.embracingthecloud.com/2010/05/17/HeadingToGoogleIO2010DeveloperConference.aspx">watch
list</a> unveiled new and exciting features that re-affirmed my belief that Google
is indeed one of the leading cloud platforms to be embraced by all cloud Developers.<br /><br />
The marriage between the Android mobile OS and Google services is simply outstanding.
The new Web Store works seamlessly with mobile devices and actions taken on either
the desktop or mobile are immediately reflected on the other device.<br /><br />
Google handed out 2 mobile phones to attendees, a Motorola Droid and a <a href="http://now.sprint.com/evo/">HTC
EVO 4G</a>, and I was amazed at the ease and speed of syncing both devices with my
existing Google Premier Apps account.<br /><br />
The dilemma I faced was whether to write mobile apps natively for Android apps or
use HTML5. Geolocation, games, and apps requiring haptic response will most definitely
be better served running natively on Android. General web sites and marketing centric
apps written in HTML5 will enjoy the benefits of running cross platform (iPhone, Blackberry,
Android) but will lack high end graphics and GPS support (although SVG and Geo Javascript
extensions could address both of these in HTML5).<br /><br />
Developing for Google in previous years has been something of a hobby for many Developers,
since there were few monetization opportunities outside AdWords and AdSense. Google
unveiled several new monetization platforms for both desktop and mobile developers
that will make Android very attractive to existing iPhone developers (plus Google
doesn't discriminate apps uploaded to their Marketplace).<br /><br />
With 100K Android phone activations per day it's becoming very easy to do the quick
math and trust that Google Android is a viable and profitable distribution vehicle.<br /><br />
A considerable amount of time was spent on Google Business during the day 1 keynote.
The <a href="http://adtmag.com/articles/2010/05/21/vmware-partners-with-google-on-cloud-with-spring.aspx">Google/VMWare
partnership</a> mirrored closely what was announced for <a href="http://www.vmforce.com">VMForce</a> just
a couple weeks ago, although I think Salesforce did a much better job of helping Developers
understand what was being offered via SpringSource. Google is serious about the business
market and wants Java apps written on their platform.<br /><br />
I think the most underrated business feature is <a href="http://www.google.com/google-d-s/scripts/scripts.html">Google
Apps Script</a>. This is a free extension to Google Premier Apps that enables complex
workflows between spreadsheets, web forms, email, and calendars. I would highly advise
SMBs to use Apps Script for their business needs while larger Enterprises would probably
benefit more from the SpringSource business platform.<br /><br />
All demonstration hiccups aside, the <a href="http://www.google.com/tv/">Google TV</a> announcement
was interesting from a consumer perspective. It was quite amazing to see Google's
CEO on stage with the other CEO's associated with the launch (Sony, Best Buy, Intel,
Logitech, and Adobe). That was some serious firepower and, if anything, really reaffirmed
that Google is pursuing a stratified and open platform in contrast to Apple's closed
platform.<br /><br />
On a technical note, I enjoyed spending some time with the Chrome development team
during their open "Office Hours". The conversation with them inspired me to download
the <a href="http://code.google.com/p/v8/">V8 Javascript engine</a> and brush up on
my C++ skills in order to contribute some JSON parsing enhancements I requested.<br /><br />
Most of what I was hoping for in HTML5 support was either previewed at the conference
or is scheduled for delivery this year. The V8 engine is in the Android 2.2 Froyo
release and we can expect 2010 to be a milestone year for evolving browser technology.<br /><br />
The unedited YouTube video below is a 10 minute walkthrough of the conference floor
with both Hardware and Software vendors showing their wares (check out the Salesforce
booth around minute 3).<br /><br /><p><object height="344" width="425"><param name="movie" value="http://www.youtube.com/v/cBwK-eLnFlg&amp;hl=en&amp;fs=1" /><param name="allowFullScreen" value="true" /><param name="allowscriptaccess" value="always" /><embed src="http://www.youtube.com/v/cBwK-eLnFlg&amp;hl=en&amp;fs=1" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" height="344" width="425"></embed></object></p><img width="0" height="0" src="http://www.embracingthecloud.com/aggbug.ashx?id=1b1c709f-42cf-4483-82fc-a2ebb1624877" /></div>
    </content>
  </entry>
</feed>