Dec 21, 2011

Failing Gracefully

No, this is not about how to design/build software which fails gracefully. This is a short commentary on my recent Did Not Finish (DNF) on the 300 Km Kolar Gold Fields (KGF) Brevet ride. 


For those not familiar with what a Brevet ride is: it is a self supported non competitive time bound bike ride. The 300 Km KGF Brevet ride would take us across 3 states and we had to cover 313 Km in 20 hours at an average speed of over 15 Kmph. I had previously done a 300 Km Brevet ride so was quite confident of completing this as well. 


But what a crazy ride it ended up to be. The ride started at 6.00 AM from MG Road metro station. It was great riding in the fog all the way up to Hoskote where we turned right towards Malur. This is where the state highways and village roads started. In addition to this had a consistent wind resistance for most of the ride. The weather was good and while there were bad stretches of roads, the tree cover on both sides of the road provided protection from the Sun. Rode through nice stretches but was always under the gun to keep moving - was averaging right around 15 Kmph including the breaks. Took only short breaks to eat and stretch. 


Reached Pernambut @ 3.00 PM but missed the SBI ATM which was the second control point. Went back in search of this and lost precious 30 minutes. Checked in to the control station just in the nick of time - had covered 143 Km in 9.5 hours. Reached Vaniambadi toll booth around 6.00 PM - had covered 182 Km in 12 hours. Took a rest break here and saw 7-8 riders pass by. While I was happy that I was not way behind the others till this point, I was worried about what lay ahead. I was averaging 15 Kmph at this point and the remaining 131 Km ride would be in cold, dark and include a tough 50 Km climbing ride from Krishnagiri to Hosur. This is where I decided to not ride in the dark but take a bus back to Bangalore from Krishnagiri instead. Reached Bargur around 8.00 PM and had dinner here. Thought I would try and get a bus right from here and waited for 30 minutes to get a bus but no luck. Again got into bike gear and rode to Krishnagiri and requested/argued with the conductors to take me & the bike to Bangalore. Thankfully had luck on the second try itself and after securing the bike on the top, deposited myself on an empty seat for a cold uncomfortable drive back to Bangalore. Reached at 00.30, got the bike down and rode home to reach around 1.20 to end a day of painful riding.


Fellow rider Parag later learnt about my DNF and enquired if I was okay. Informed him that I was okay but decided to quit as I did not want to ride in the night. My idea of a good bike ride is where you get to enjoy the ride and not just burn the Kms. Honestly I was in a pretty bad mood riding through the nice villages. Did not even exchange pleasantries with the village folks which I usually do on a ride. I guess no more Brevet rides for me... but hey! you never know :-). 


Couple of tech highlights of the ride:


Map My Ride: I developed an application using which a rider could mark one's location on a ride and anyone could track the progress of riders. I could not use the app very well as my iPhone did not seem to work very well but Devaraj marked his position throughout the ride - Thanks for checking out the app Devaraj. My thought now is that this app is better used by tourers and not in time bound events as time is of the essence in these rides and geolocation + marking location takes some time - I did end up wasting quite a bit of my time trying to locate myself and mark my location. You can check out the app here: Map My Ride. Suggestions for enhancements are welcome. 
Map My Ride - Mark Location Screen
Rider Progress - Devaraj & Shashi's Updates
Solar Charging Garmin: I had purchased a Solar Monkey solar charger on a recent trip to London but had never used it till now. I used this charger on this ride and am happy to report that my Garmin Forerunner 305, which usually runs out of battery in 13-14 hours, survived the entire ride - home to home - around 18 hours and still had some juice left in it. I think I can get it to work even more effectively if I am able to position the solar panel well. Check out the setup below. 
Solar Monkey & Solar Nut charging my Garmin Forerunner 305

The power of Community Development

Over the past few months the cloudspokes.com site was rebuilt on Heroku and Database.com. Code contributed by developers across 8 countries over 22 challenges was used to rebuild the site. Proud to say that I have been a part of this journey. Read more on the Cloudspokes Blog

Dec 6, 2011

Bangalore to Chennai Ride

It's been a while since I did much of regular cycling. Since Aug 2011, couple of overseas travels, unseasonal and (often unpredictable) Bangalore rains, the time I have been spending lately learning and using cool and exciting cloud technologies and finally plain laziness have kept me off the bike. So much so that it started showing on the waistline as well. 


So when my parents and few other relatives decided to visit my cousin in Chennai, I figured why not ride to Chennai and return in bus along with them. Chennai appeared to be a good choice as I would be riding with loaded panniers and the route did not have many climbs. Thus the plan was born. 


The total distance to be covered was around 342 Km. My gameplan was as follows: 

  • Ride with minimal breaks
  • Start the ride early in the morning and end by early evening
  • Take rest during mid day when the Sun is at its scorching best
  • Ride as much as possible on Day 1 so that I reach Chennai early on Day 2 and spend more time with family. Vellore was the planned target for Day 1. 

Packed my two pannier bags as follows: 
  • Bag 1 - change of clothes at end of Day 1, fresh riding clothes for Day 2, two tubes and puncture kit. 
  • Bag 2 - Extra 1 litre bottle of water, 1/2 litre bottle of orange juice, sandwiches (packed by wife in the morning), air pump, tools, gatorade, torch, wet wipes, almonds and raisins. 
Day 1 - Saturday, 3/Dec/2011
Started from home at around 3.30 AM. Before long passed silk board and then stayed on the service lane all the way till past Electronic city. Stopped at the A2B before Krishnagiri at around 7.00 AM with the Garmin indicating 71 Km. Had a couple of sandwiches and a coffee and re started the ride around 7.30 AM. Saw Chiddu and Parag on the other side of the road returning from their Krishnagiri ride. Could wave and shout out only to Parag as Chiddu had passed me before I could react. We gave each other a thumbs up and moved on in our respective directions. Quite an uneventful ride till around 11.00 AM when the Sun started getting intense. The breeze which was against me was actually quite welcome due to the cooling effect. Passed by a town called Ambur which I later came to know is famous for leather and shoe works. Saw lots of small and large factories dealing with leather and shoes. Also passed by a small coir establishment - Could see some of the processing from road. It's a wonder how every part of the coconut plant is utilized in some form or other. I wanted to break for lunch only after completing half the distance to Chennai and also at a place where I could take some rest during the peak afternoon. Found a temple on the road side which had a resting area. Decided to take a break here for lunch. The Garmin showed 171 Km and the time was 12.25 PM. Washed up, had the remaining sandwiches and slept for some time after securing the bike. Left from here around 2.00 PM for the final leg of travel for the day. While the Sun still fell hot at 2.00 PM, post 3.00 PM, the heat was not that intense and was quite comfortable. Reached Vellore around 4.30 PM but decided to continue onto the next town Ranipet as there was still daylight and I was feeling strong. Reached Ranipet around 5.30 PM and found a hotel right at the edge of the town. The hotel was not so great and actually quite shady but I figured "will just be sleeping here and it is right on the highway" and so decided to stay there. Washed up, went out and had some food, got some water & bananas for next day. Got back to the hotel and after cleaning the bike and setting the alarm for the next day, went to sleep early at 9.00 PM. 

Day 2 - Sunday, 4/Dec/2011
Woke up around 3.45 AM and quickly got ready and hit the road at 4.18 AM. Was riding in a new area in dark so though I was using two sets of front and rear lights, was waiting for the dawn. The hints of daylight started around 5.45 AM but since the fog was rolling in, saw the first glimpse of Sun only around 7.00 AM. After passing Sriperumbudur, got off the main highway and took inside roads to head towards the Airport. I had another 30 odd Kms to cover. The roads were bad in stretches. Passed a glass recycling area - had never seen so much broken glass in one place. Crossed a stream of water on foot carrying the bike - contemplated removing my shoes but since it was only a short stretch to the destination I just walked through the water. Since it was a Sunday and feast day, saw several places selling pig meat - in fact at one place saw a whole pig head next to the meat - wow! Pretty soon I could see planes making their approach to landing - I was close to the destination. Covered the last few Kms quickly and with some directions from a traffic policeman arrived at my cousin's home in Nanganallur. Was received warmly by her family and thus ended my ride to Chennai. 

Some Gyan
While the whole ride was quite uneventful - no punctures or breakdowns, no accidents or altercations, no wrong turns, no running out of water - the ride itself made me come to a few conclusions:
  • While it may sound like a cliché, it is true that the longest of journeys begin with that first step or in this case the first pedal. Till the time I came out of my home on Saturday morning, I was not sure whether I would start or no. But I just told myself "Lets go for it. You can always stop and return.". 
  • While touring, don't aim for speed and higher gears. Keep at lower gears and keep going. I was myself surprised that though I rode for long stretches in 2 x 3, 2 x 4 & 2 x 5 combinations, I still had an overall riding average of over 20 Kmph. 
  • Be prepared - food, drink and tools. Though I didn't have to use the tools, it was calming to know I had the tools to do basic fixes and puncture repair if I needed to. I always made sure I had a reserve of water with me and would stop to buy water before I ran out of it. 
  • It is difficult to keep riding on and on. On this route, the pedalling is not difficult as it is a fairly flat route with the few uphills being only the ramps to bypass the towns and cross the occasional stream and railway tracks. The catch is how do you motivate yourself to keep going. I just kept at it visualizing the bed at the end of the day, the nice food I would have at the end of the day and the get together with the family members after so many days. Visualizing a nice end or a goal is always a good motivation to keep you going - whether it is cycling or something you are aiming for in life. I have myself found this technique work for me many a time. 
Statistics
  • Day 1 - 238 Km - 11 Hours 20 Minutes riding time, 14 Hours elapsed time
  • Day 2 - 105 Km - 5 Hours 30 Minutes riding time, 6 Hours 10 Minutes elapsed time
  • Intake:
    • Food - 6 2-slice Potato sandwiches, 7 Bananas, 1 Orange, 3 Idlis, 1 Vada, handful of almonds and raisins
    • Fluids - 6 Litres of water, 1 small coke, 1/2 litre orange juice and 6 packs of Gatorade 
I did not take too many pictures on this trip to not lose time but here are a few. Not sure how many of you have seen this type of grasshoppers. They used to be a regular sight in U.P. where I grew up but haven't seen any here in Karnataka, Andhra or Maharashtra. 



Oct 3, 2011

Google+ to Salesforce Chatter Integration

This article describes my entry to the cloudspokes.com Google+ to Salesforce Chatter integration challenge. It covers using OAuth2 to connect to Google+ from Salesforce Apex, making call to the Google+ API to retrieve the Google+ posts to be posted to Chatter and then posting them to Chatter. 

Requirements

Though the title says Google+ to Salesforce Chatter integration, the integration is initiated in reverse. From Salesforce end, we should be able to pull the user's Google+ posts targeted to be sent to the user's Chatter account using a specific hashtag (#ch). 

The Moving Parts
  1. Google OAuth2 - Authentication and Authorization to Google+ APIs. 
  2. Google+ API - Provides access to the user's Google+ posts. 
  3. JSON Parser by Ron Hess - Is used to parse the JSON returned by the Google OAuth2 for server-side web applications and the Google+ APIs. 
  4. Visualforce page and it's controller - Implements the integration logic and allows the user to fetch his Google+ posts and insert the same to Salesforce Chatter. 
Setting Up Google APIs Access
  • Navigate to Goole APIs Console. Create a new project, e.g. API Project. 
  • Navigate to Services tab of Google APIs Console and turn on the Google+ API service. 
  • Navigate to API Access tab of Google APIs Console. Click on Create an OAuth 2.0 client ID
  • Enter 'G+ Chatter Integration' in product name field. This information will be shown to users when your application requests access to their private data using your new client ID. Click Next. 
  • Select Web Application on the next screen. Click on more options link next to Authorized Redirect URIs and enter https:///apex/GooglePlusToChatter in Authorized Redirect URIs text box and https:// in Authorized JavaScript Origins text box. Replace  with your salesforce instance url. Click on Create client ID. Note the client ID and Client Secret. These will be required to setup the Apex connectivity to Google+ in later steps. 
  • See the pictures below for visuals of the above steps. 

Picture 1 - Initiate creation of client ID

    Picture 2 - Create Client ID Step 1
    Picture 3 - Create Client ID Step 2
Picture 4 - View of newly created Client ID
Google+ APIs
Google+ APIs were made available in Sep 2011. The initial set provides read access only. 


The three APIs provided in the initial set are: 

  • People:get - Get a person's profile - https://www.googleapis.com/plus/v1/people/{userId}
  • Activities:list - List all of the activities in the specified collection for a particular user. We will be using this API for our purpose - https://www.googleapis.com/plus/v1/people/{userId}/activities/{collection}
  • Activities: get - Get an activity - https://www.googleapis.com/plus/v1/activities/{activityId}

The userId referred to in the first two bullets above is not the google id used to login. It is an id which uniquely identifies each resource in Google+. You can find your userId by going to your google+ profile. The numeric text following plus.google.com/ is your userId. The picture below shows my Google+ profile with my userId enclosed in the blue rectangle. 
Picture 5 - Getting userId from Google+ profile
The Process
The overall integration works in the following manner: 
  1. The application passes the client id, redirect URI and requested scope of access to Google OAuth2 URL. 
  2. If the user is not logged into Google, user is presented with the authentication screen. Once the user logs in, the user is presented with a screen to authorize your application to access your data. If the user were already logged into Google, only the authorization screen is presented. 
  3. If the user declines to authorize the access, Google returns an access denied message. 
  4. If the user authorizes the access, Google returns and authorization code to the application. 
  5. The application then passes the code, client id, client secret and redirect URI to the Google OAuth2 token URL. 
  6. Google responds with an access token and a refresh token. 
  7. The application uses the access token and the Google userId to send an HTTP get request to Google+ activities list URL. 
  8. Google+ responds with the last 20 Google+ posts in JSON format. 
  9. Application uses JSONObject.cls to parse the response and find all Google+ posts with hashtag #ch which have not been posted to Chatter. 
  10. Application posts selected Google+ posts to Chatter as user's current status. 

Implementation Details


Integration Settings: The following data elements need to be stored in force.com Custom Settings: Google_Plus_to_Chatter_Settings__c under name 'MySettings':
  • Client ID
  • Client Secret
  • Google Plus User
  • Last Published
Picture 6 - Storing integration settings in Custom Settings
Constants used in the integration  
INTEGRATION_SETTINGS_KEY = 'MySettings';  
GOOGLE_OAUTH_URL_CODE = 'https://accounts.google.com/o/oauth2/auth';  
GOOGLE_OAUTH_URL_TOKEN = 'https://accounts.google.com/o/oauth2/token';  
GOOGLE_API_ACCESS_SCOPE = 'https://www.googleapis.com/auth/plus.me';  
GOOGLE_PLUS_REDIRECT_URI_SUFFIX = '/apex/GooglePlusToChatter';  
GOOGLE_PLUS_POSTINGS_URL = 'https://www.googleapis.com/plus/v1/people/';  

URL to Retrieve Authorization Code
 String url = GOOGLE_OAUTH_URL_CODE  
  + '?client_id=' + integrationSettings.Client_Id__c  
  + '&redirect_uri=' + getInstance() + GOOGLE_PLUS_REDIRECT_URI_SUFFIX   
  + '&scope=' + GOOGLE_API_ACCESS_SCOPE  
  + '&response_type=code'; //Retrieving code  


Code to Retrieve Access Token
 //Using the code, client_id and client_secret, get the access token.   
 HttpRequest req = new HttpRequest();  
 req.setEndpoint(GOOGLE_OAUTH_URL_TOKEN);  
 String postData = 'code=' + EncodingUtil.urlEncode(code, 'UTF-8');  
 postData = postData + '&client_id=' + EncodingUtil.urlEncode(integrationSettings.Client_Id__c, 'UTF-8');  
 postData = postData + '&client_secret=' + EncodingUtil.urlEncode(integrationSettings.Client_Secret__c, 'UTF-8');  
 postData = postData + '&redirect_uri=' + EncodingUtil.urlEncode(getInstance() + GOOGLE_PLUS_REDIRECT_URI_SUFFIX, 'UTF-8');  
 postData = postData + '&grant_type=' + EncodingUtil.urlEncode('authorization_code', 'UTF-8');  

Code to Invoke Google+ API to get Activities
 HttpRequest req = new HttpRequest();  
 req.setEndpoint(GOOGLE_PLUS_POSTINGS_URL   
  + integrationSettings.Google_Plus_User__c   
  + '/activities/public'   
  + '?access_token=' + EncodingUtil.urlEncode(accessToken, 'UTF-8'));  
        req.setMethod('GET');  

Code to parse JSON returned by Google
 String json = res.getBody().replace('\n', '');  
 JSONObject j = new JSONObject( json );  
 totalPostsRetrieved = j.getValue('items').values.size();  
 //Iterate through the post and select the chatter posts  
 for (Integer i = 0; i < totalPostsRetrieved; i++) {  
      //Use this post only if the post timestamp is after the last published timestamp and  
      //if it contains the #ch hashtag  
      datetime googlePlusPostTimestamp = convertGoogleTimestampToDatetime(j.getValue('items').values[i].obj.getString('published'));  
      String googlePlusPost = j.getValue('items').values[i].obj.getValue('object').obj.getString('content');  
      if (googlePlusPostTimestamp > integrationSettings.Last_Published__c && googlePlusPost.contains('#ch')) {  
           postsForChatter.add(googlePlusPost);  
      }  
 }  
 chatterPosts = postsForChatter.size();  
 //Update last published timestamp  
 integrationSettings.Last_Published__c = publishedDatetime;  
 try {  
   upsert(integrationSettings);  
 } catch(System.DMLException e) {  
   ApexPages.Message pageMsg = new ApexPages.Message(ApexPages.Severity.ERROR, 'Error updating last published timestamp: ' + e);  
   ApexPages.addMessage(pageMsg);  
 }  


Code to Post to Chatter
 //On first time page load, 'code' parameter is not set.   
 //'code' parameter is set by Google OAuth 2 on redirection back to this page.    
 if (parms.containsKey('code')) {  
   String accessToken = getAccessToken(parms.get('code'));  
   if (accessToken != null) {       
     List<String> postsForChatter = getGooglePlusPostsForChatter(accessToken);     
     for (String post : postsForChatter) {  
         User user = [select id, CurrentStatus from User where id = :UserInfo.getUserId()];  
         user.CurrentStatus = post;  
         update user;  
     }     
   }  
 }  


Application In Action
Picture 7 - Google+ Before posting new entry
Picture 8 - Chatter Before posting new entry
Picture 9 - Page to invoke Chatter integration

Picture 10 - Added New Post on Google+
Picture 11 - Google Authorization Challenge
Picture 12 - Shows 1 post to Chatter and updated Last Published Date

Picture 13 - Shows Google+ post made to Chatter
Improvement Areas
The following enhancements to this integration are possible:

  1. JSON Parser gives too many scripts exception if the number of posts retrieved is high. This needs to be addressed. 
  2. The retrieval of posts can be automated via a scheduled job. 
  3. The code currently requests new access tokens each time. It does not reuse existing token and on failure retrieve a new access token using refresh token. This can be implemented to make the code more in line with the Google OAuth2 design. 

References

I would appreciate feedback from the readers. I will be posting a short video of the application in action in a few days. For additional information and actual code, please get in touch with me.

      Sep 15, 2011

      Flying in the Cloud

      The last six weeks has been a whirlwind tour of new technology platforms on the cloud. Salesforce, Google Analytics, MailChimp, Amazon SES & Heroku... whew! 

      In the process learnt Salesforce configuration & setup, Apex and visualforce basics, attended Dreamforce 2011 conference @ San Francisco and got my Salesforce developer certification at the conference. Made three crowdsourcing challenge submissions on cloudspokes.com and won one so far. The empowerment of developing on the cloud based platforms as opposed to traditional environments feels good.

      The Dreamforce conference is the first large technology conference I attended in over three years. The energy at the conference was just plain awesome. The learning and exposure to salesforce and related technologies appeared to be better than similar technology conferences.
      The day before the start of Dreamforce 2011 @ Moscone Center
      The last day of the conference - still busy!

      As more and more cloud platforms are made available, the development community needs to understand the nuances of development on the cloud. Things which come to mind are responsive & stable internet connection, platform enforced limitations, platform integration capabilities, source code management, continuous integration, enforced testing and error handling. Add to this the socialization of enterprise applications and mobile client platforms outnumbering the traditional browser based clients, we have at our hands a heady concoction of technologies sure to challenge and satisfy even the geekiest of us all. In summary, I feel transported back 10 years. The difference: Java then and Cloud now.

      So let's Fly in the Clouds - चलो बादलों में उडें|