Picture1 I recently received a very interesting resume for a position at Salesforce.com. What caught my attention was that instead of the traditional resume structure (summary/objective, employment history, languages/platforms etc.), the resume started with two pages of blurbs from the applicant's LinkedIn recommendations. That's as good an example as any of the importance of social networks in general and the power of the world's largest professional networking site – LinkedIn – in particular. Apparently the market agrees.

Wouldn't it be cool if you could harness the power of someones professional network from within Chatter? What if certain status updates in Chatter could automatically be broadcasted to the user's LinkedIn network (check out the attached screenshot for a simple example)? That's one example of integrating Force.com with another Cloud based application, and I recently hosted a webinar to discuss the unique characteristics and patterns of such Cloud to Cloud integrations. You can watch a recording of the webinar here. One of the integration patterns that I discussed during the webinar was Outbound integrations – i.e. when Force.com initiates the integration with an outbound callout to the external cloud application/platform. The Chatter–>LinkedIn synchronization use case was one of the demos that I did to help illustrate that pattern and I've since posted the code to GitHub. You can jump to this point in the recording if you're only interested in the LinkedIn demo (go ahead, I won't take it personally!).

The Chatter–>LinkedIn integration uses Apex HTTP callouts and I wanted to highlight and discuss some of the code here. (Note: I would recommend watching the recording first to get a better sense of the high-level integration architecture). The starting point of the integration is the 'SyncLinkedInChatterStatus' trigger on the User object. This trigger looks for the special '#linkedin' hashtag in each Chatter status update and if found, invokes the 'updateStatus' method of the 'UpdateLinkedInUserStatus' class. Why delegate the actual LinkedIn sync to another class (other than having a nice modular code structure)? Because any Apex callout from a trigger context has to be made asynchronously using the special @future annotation. Lets take a closer look at that code

public with sharing class UpdateLinkedInUserStatus {
@future(callout=true)
public static void updateStatus(List userIds, List statusUpdates)
{
for (Integer i = 0; i< userIds.size(); i++)
{
LinkedIn l = new LinkedIn(userIds.get(i));
LinkedIn.Share s = new LinkedIn.Share();
s.comment = statusUpdates.get(i);
s.visibility = 'anyone';
try
{
if (Limits.getLimitCallouts() > Limits.getCallouts())
{
l.updateUserShare(s);
}
}
catch(Exception e){}
}
}
}

As you can see, the actual logic for invoking the LinkedIn APIs in encapsulated in the appropriately named 'LinkedIn' class. Lets now take a look at where the real action is – the 'LinkedIn' class.

public with sharing virtual class LinkedIn
{
public static final String LINKED_IN_API_URL = 'http://api.linkedin.com/v1';
public static final String LINKED_OAUTH_SERVICE_NAME = 'LinkedIn';
private String sfdcUserId;
private OAuth oa;
public LinkedIn(String userId)
{
sfdcUserId = userId;
}
public String updateUserShare(Share s)
{
HttpRequest req = createRequest('/people/~/shares','POST', s.toXML());
HttpResponse resp = executeLinkedInRequest(req);
return getResponseBody(req, resp);
}
protected virtual HttpRequest createRequest(String path, String method, DOM.Document request)
{
HttpRequest req = new HttpRequest();
req.setEndpoint(LINKED_IN_API_URL  + path);
req.setMethod(method == null ? 'GET' : method);
req.setHeader('Content-Type', 'text/xml');
req.setTimeout(60000);
if (request != null)
req.setBodyDocument(request);
if (oa == null)
{
oa = new OAuth();
if (!oa.setService(LINKED_OAUTH_SERVICE_NAME, sfdcUserId))
{
System.debug('Didnt work:'+oa.message);
throw new AuthenticationException(oa.message);
}
}
oa.sign(req);
return req;
}
protected virtual HttpResponse executeLinkedInRequest(HttpRequest req)
{
HttpResponse res = new Http().send(req);
if (req.getMethod() == 'POST' && res.getStatusCode() != 201)
{
System.debug('OAuth header:'+res.getHeader('oauth_problem'));
throw new TwitterApiException (res.getBody());
}
else if (req.getMethod() == 'GET' && res.getStatusCode() != 200)
throw new TwitterApiException (res.getBody());
return res;
}
protected virtual String getResponseBody(HttpRequest req, HttpResponse resp )
{
return resp.getBody();
}
.....
}

Lets start with the 'updateUserShare' method that is invoked to update the user's LinkedIn status ('Share' is the LinkedIn terminology for when users share updates with their network, analogous to a Chatter user's status update). The method simply invokes the LinkedIn REST API by calling 3 separate methods in sequence – the 'createRequest' method to create an HTTP request, the 'executeLinkedInRequest' method to execute the HTTP request and finally the 'getResponseBody' method to parse the response from LinkedIn. The reason for breaking out an Apex HTTP callout into these 3 steps is two fold. One, it makes the code more modular and reusable (we can easily add support for additional LinkedIn API calls by reusing the same 3 methods and simply changing the combination of URL, HTTP method and request body). Also, it's a good strategy for testing Apex callouts. As explained during the webinar, testing Apex callouts requires special considerations and breaking your callout into 3 separate steps is one of those strategies. The 'LinkedIn' class uses a more advanced version of that basic testing design pattern that also requires the class/methods to be defined as Virtual/Protected. For a blast from the past, check out this post from the esteemed DevAngel that explains this particular pattern in more detail. As you'll see from that post, this pattern was first implemented by the equally esteemed Simon Fell in the TwitterForce toolkit and I simply reused (ok, ok – stole) the pattern for my LinkedIn integration. 

You'll also notice the use of an 'OAuth' class to sign the request before sending it off to LinkedIn. As is true for most modern cloud applications, LinkedIn supports the OAuth authentication protocol and I used the excellent OAuth 1.0 Apex client library to implement the authentication. Note that there are a couple of preliminary, one-time setup steps before you can use that OAuth Apex client library. For the sake of brevity I didn't cover those steps in the webinar (or in this post) and so let me know if you need additional details on setting up the OAuth Apex client in Force.com.

The 'LinkedIn' class included in this sample currently only supports the LinkedIn Share API that allows a user to share updates with his/her professional network. It can however be easily enhanced to support additional LinkedIn API features (like the Profile API, the Connections API etc.). So if your LinkedIn use case requires a different integration scenario, feel free to use this sample as a starting point. Let me know if you have any questions or comments.

And yes, we did hire the applicant.

tagged Bookmark the permalink. Trackbacks are closed, but you can post a comment.
  • Rhonda Ross

    There’s a post out on IdeaExchange for this – vote it up if you’d like this to become standard functionality in a future release.
    Allow hashtag to include a chatter post to Twitter, FB, LinkedIn
    https://sites.secure.force.com/success/ideaView?c=09a30000000D9xtAAC&id=087300000007f0gAAA

  • Vinay

    wonderful article.
    RChilli is brining lot of value added features where you can insert profiles directly from linkedin or other items while browing them.
    you can connect with them and see when it will be released
    thanks

  • cbernal

    I get this error: Invalid type Share at line 11 on LinkedIn class. Anyone knows why I get this error? I’m new in salesforce.
    Thanks.

  • http://twitter.com/CeiBernal Christian

    I’m using the code on GitHub, but I get this exception when i click on the link to authorize Salesforce to access my LinkedIn account: caused by: System.QueryException: List has no rows for assignment to SObject. The exception is on this query (OAuth class, line 36):

    service = [SELECT request_token_url__c, access_token_url__c, consumer_key__c,consumer_secret__c,
    authorization_url__c,
    (select token__c, secret__c, isAccess__c
    FROM tokens__r
    WHERE owner__c=:UserInfo.getUserId() )
    FROM OAuthService__c
    WHERE name = :serviceName];

    I think that is because I made the objects relying me on the querys and probably they’re wrong, Im new
    in salesforce and I did not understand the ‘.object’ files, so can you please tell me the fields type
    for each object (OAuthService__c and OAuth_Token__c) and the relationships type.
    Thanks in advance.

    • Anonymous

      Christian – in order for the OAuth to work, you need to add a new record to the ‘OAuth Service’ custom object with the name ‘LinkedIn’. You will then need to add the OAuth credentials (Consumer Key, Consumer Secret etc.) from your LinkedIn app to that record. Details on how to configure OAuth in your LinkedIn App can be found here -
      http://developer.linkedin.com/documents/authentication.

      Hope this helps…

      • http://twitter.com/CeiBernal Christian

        Thank u, now I’ve authorized with Linkedin, but my status updates on chatter aren’t posted on Linkedin, I have the same code except the SyncLinkedInChatterStatus trigger because it gets an error with the current status field of the user. I found that this field is deprecated
        in API version 25.0

        So i replaced that trigger by the trigger below.

        trigger SyncLinkedInChatterStatus on FeedItem (before insert, before update) {

        String defaultLinkedInKeyword = ‘#linkedin’;

        List userIds = new List();
        List statusUpdates = new List();

        For(Integer i=0; i 0)
        UpdateLinkedInUserStatus.updateStatus(userIds, statusUpdates);
        }

        I believe that it makes the same but in the cloud to coud integration with force.com you mentioned that the trigger is on the user and not on the feeditem object and i don’t know if here is my problem.
        Thanks.

  • Mike

    Sandeep – Can you share the additional details on setting up the OAuth Apex client in Force.com. I am looking to integration using OAuth2.0

    • Anonymous

      Mike – the OAuth library that I used in the sample (http://developer.force.com/codeshare/projectpage?id=a0630000008MQYMAA4) unfortunately only supports OAuth 1. I’m not aware of any OAuth 2.0 Apex library, but that just means that there is an opportunity for someone to create it :)

      • Mike

        Even for OAuth 1.0 library, when using OAuth class variable, it gives an error. I guess the class needs to be defined as global, but as it is a managed package I cannot edit the class defn.
        Also, this library being in Beta, can I use it in a Production Org?
        Thanks for your guidance !

        • Anonymous

          Mike – just use the OAuth code from the GitHub repo where I posted this LinkedIn example – https://github.com/sbhanot-sfdc/LinkedInForce. No Managed package to worry about that way.

          You have to first create a new record in the ‘OAuth Services’ object (name it ‘LinkedIn’). Set the OAuth params in that record to the values that you get from LinkedIn (Consumer Key, Consumer Secret, Request and Access token URLS etc.). Once that is done, you can see how I use the OAuth library in the LinkedIn.cls class to authenticate with LinkedIn.

          Hope this helps.