Salesforce REST API Demo from Cloudstock

December 17th, 2010

This is the demo that I put together for the Cloudstock Hackathon and I tried to throw in as many partner services as possible. I finally ended up with five so it was dubbed the “Kitchen Sink” demo. I thought some people may find it useful as it shows how to use the Force.com REST API in conjunction with OAuth2 using the Spring MVC framework. Pat Patterson put together a great¬†Getting Started with the Force.com REST API article but my app is slightly different and IMHO easier, since it uses the Spring Framework.

The app is a external-facing recruiting site that advertises the open Appirio positions. Please remember that this is a demo and I put most of the code into a couple of controllers to make it easier to show. The code can definitely be refactored in certain places.

Here’s how the app uses the partner services:

Partner How Used?
Store pdf job descriptions on Box for download by applicants.
Use Force.com as the datastore for open jobs. Access to Force.com using OAuth2 and the REST API.
Send a job to a friend via SMS.
Application built using Spring STS, Spring Roo and Spring MVC. The application runs locally on tc Server.
Store metrics for jobs on MongoHQ.

Here’s a video of the application so you can see it in action plus some explanation of the Spring code. The controller code for the OAuth functionality and interacting with the Job records is following the jump.

It may be easier to watch this at Youtube with a larger picture. Just double-click the video.

OAuthController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
package com.appirio.jobs.web;
 
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
 
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.methods.PostMethod;
import org.json.JSONException;
import org.json.JSONObject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.WebRequest;
 
/**
 * @author Jeff Douglas (jeff@appirio.com)
 */
@RequestMapping("/oauth/**")
@Controller
public class OAuthController {
 
  private static final String ACCESS_TOKEN = "ACCESS_TOKEN";
  private static final String INSTANCE_URL = "INSTANCE_URL";
 
  private String clientId = null;
  private String clientSecret = null;
  private String redirectUri = null;
  private String environment = null;
  private String authUrl = null;
  private String tokenUrl = null;
  private String accessToken = null;
 
  private void init() {
 
      redirectUri = "http://blog.jeffdouglas.com//AppirioCareers/oauth/_callback";
      environment = "https://na5.salesforce.com";
      // client id and secret from Force.com Remote Access
      clientId = "YOUR-CLIENT-ID";
      clientSecret = "YOUR-CLIENT-SECRET";
 
        try {
      authUrl = environment + "/services/oauth2/authorize?response_type=code&client_id=" 
        + clientId + "&redirect_uri=" + URLEncoder.encode(redirectUri, "UTF-8");
    } catch (UnsupportedEncodingException e1) {
      // TODO Auto-generated catch block
      e1.printStackTrace();
    }
 
        tokenUrl = environment + "/services/oauth2/token";
 
  }
 
  /* Start the OAuth process if no session with the access token is found. 
   * If a session exists, the redirect the user to the /job/list page. */
  @RequestMapping(value = "/oauth/start", method = RequestMethod.GET)
  public String startOauth(WebRequest req) {
 
    init();
    // check for an access token in the servlet session
    accessToken = (String) req.getAttribute(ACCESS_TOKEN, RequestAttributes.SCOPE_SESSION);
 
    if (accessToken != null) {
      System.out.println("Session found!! Access token: "+accessToken);
      return "forward:/job/list";
    } else {
      System.out.println("No session... need to auth.");        
      return "redirect:" + authUrl;
    }
 
  }
 
  /* OAuth callback from Force.com after authrozing the application.  */
  @RequestMapping(value = "/oauth/_callback", method = RequestMethod.GET)
  public String endOauth(WebRequest req) {
 
    init();
    String accessToken = null;
    String instanceUrl = null;
    String code = req.getParameter("code");
    HttpClient http = new HttpClient();
    PostMethod post = new PostMethod(tokenUrl);
    post.addParameter("client_secret", clientSecret);
    post.addParameter("redirect_uri", redirectUri);
    post.addParameter("grant_type", "authorization_code");
    post.addParameter("code", code);
    post.addParameter("client_id", clientId);
 
    try {
      JSONObject json = null;
      http.executeMethod(post);
      String respBody = post.getResponseBodyAsString();
      System.out.println("post response: " + respBody);
      try {
        json = new JSONObject(respBody);
        accessToken = json.getString("access_token");
        instanceUrl = json.getString("instance_url");
      } catch (JSONException e) {
        e.printStackTrace();
      }
      System.out.println("found access token: "+accessToken);
      System.out.println("found instance url: "+instanceUrl);
    } catch (IOException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    } finally {
      post.releaseConnection();
    }
 
    System.out.println("Setting Access token: "+accessToken);
    System.out.println("Setting Instance Url: "+instanceUrl);
 
    /* Set the token and url to the session so other servlets can access it. */
    req.setAttribute(ACCESS_TOKEN, accessToken, RequestAttributes.SCOPE_SESSION);
    req.setAttribute(INSTANCE_URL, instanceUrl, RequestAttributes.SCOPE_SESSION);
 
    return "forward:/job/list";
  }
 
  @RequestMapping
  public String index() {
    return "oauth/index";
  }
}

JobController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
package com.appirio.jobs.web;
 
import java.io.IOException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
 
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.httpclient.methods.GetMethod; 
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.ModelAndView;
 
import com.appirio.jobs.domain.Job;
import com.appirio.jobs.domain.SmsMessage;
import com.mongodb.BasicDBObject;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.DBCursor;
import com.mongodb.Mongo;
import com.mongodb.MongoException;
import com.twilio.sdk.TwilioRestClient;
import com.twilio.sdk.TwilioRestException;
import com.twilio.sdk.TwilioRestResponse;
 
/**
 * @author Jeff Douglas (jeff@appirio.com)
 */
 
@RequestMapping("/job/**")
@Controller
public class JobController {
 
  private static final String ACCESS_TOKEN = "ACCESS_TOKEN";
  private static final String INSTANCE_URL = "INSTANCE_URL";
  private static Mongo m;
  private static DB db;
  private static DBCollection coll;
  private static DBCursor cur;
  private ArrayList<Job> jobs = new ArrayList<Job>();
 
 
    @RequestMapping(value="/job/list", method=RequestMethod.GET)
    public ModelAndView list(WebRequest req) {
 
      // if the job collection is empty then fetch jobs from Force.com
      if (jobs.isEmpty()) {
 
        // fetch the access token and url from the servlet session
        String accessToken = (String) req.getAttribute(ACCESS_TOKEN, RequestAttributes.SCOPE_SESSION);
        String instanceUrl = (String) req.getAttribute(INSTANCE_URL, RequestAttributes.SCOPE_SESSION);
 
        System.out.println("Access token: "+accessToken);
        System.out.println("Instance Url: "+instanceUrl);
 
        jobs = new ArrayList<Job>();
      HttpClient httpclient = new HttpClient();
          GetMethod gm = new GetMethod(instanceUrl + "/services/data/v20.0/query");
          //set the token in the header
          gm.setRequestHeader("Authorization", "OAuth "+accessToken);
          //set the SOQL as a query param
          NameValuePair[] params = new NameValuePair[1];
          //no need to url encode here...it will cause your query to fail
          params[0] = new NameValuePair("q","Select Id, Name, Job_Title__c, Location__c, " +
              "Duties__c, Skills__c, Salary__c, Box_Url__c from Job__c Order by Job_Title__c");
          gm.setQueryString(params);
 
          String respBody = "";
 
          try {
        httpclient.executeMethod(gm);
        respBody = gm.getResponseBodyAsString();
            System.out.println("response body: " + respBody);
      } catch (HttpException e1) {
        // TODO Auto-generated catch block
        e1.printStackTrace();
      } catch (IOException e2) {
        // TODO Auto-generated catch block
        e2.printStackTrace();
      } finally {
              gm.releaseConnection();
          }
 
          try {
              JSONObject json = new JSONObject(respBody);                
              JSONArray results = json.getJSONArray("records");
 
             for(int i = 0; i < results.length(); i++) {
               // add the json payload to a Job object
               Job job = new Job(results.getJSONObject(i).getString("Id"), 
            results.getJSONObject(i).getString("Name"),
            results.getJSONObject(i).getString("Job_Title__c"),
            results.getJSONObject(i).getString("Location__c"),
            results.getJSONObject(i).getString("Duties__c"),
            results.getJSONObject(i).getString("Skills__c"),
            results.getJSONObject(i).getString("Salary__c"),
            results.getJSONObject(i).getString("Box_Url__c"));
 
               // add the job to the collection
               jobs.add(job);
             }
 
 
          } catch (JSONException e) {
              e.printStackTrace();
          }
 
          System.out.println("jobs found: "+jobs.size());
 
      }
 
      ModelAndView mav = new ModelAndView("job/list");
      mav.addObject("jobs", jobs);
      return mav;
    }
 
    @RequestMapping(value="/job/{id}/display", method=RequestMethod.GET)
    public ModelAndView display(@PathVariable String id, Model model) {
      Job job = getJobById(id);
      incrementCount(job.getName(),"views");
      ModelAndView mav = new ModelAndView("job/display");
      mav.addObject(getJobById(id));
      return mav;
    }
 
    @RequestMapping(value="/job/{id}/sms", method=RequestMethod.GET)
    public ModelAndView message(@PathVariable String id, Model model) {
      ModelAndView mav = new ModelAndView("job/sms"); 
      SmsMessage sms = new SmsMessage();
      sms.setPhone("9412274843");
      sms.setMessage("Check out this AWESOME job with Appirio!");
      mav.addObject("smsMessage", sms);
      mav.addObject(getJobById(id));
      return mav;
    }
 
    @RequestMapping(value="/job/{id}/print", method=RequestMethod.GET)
    public String print(@PathVariable String id, Model model) {
      Job job = getJobById(id);
      incrementCount(job.getName(),"downloads");
      System.out.println(job.getBoxUrl());
      return "redirect:"+job.getBoxUrl();
    }
 
    @RequestMapping(value = "/job/{id}/smsSend", method = RequestMethod.POST)
    public ModelAndView smsSubmit(@PathVariable String id, @ModelAttribute SmsMessage sms, Model model) {
 
      Job job = getJobById(id);
      sendSms(job, sms.getPhone(), sms.getMessage());
      incrementCount(job.getName(),"messages");
 
      ModelAndView mav = new ModelAndView("job/smsConfirm"); 
      mav.addObject("phone", sms.getPhone());
      mav.addObject("message", sms.getMessage());
      mav.addObject(job);
      return mav;
 
    }
 
  private void incrementCount(String name, String type) {
 
    try {
      m = new Mongo("flame.mongohq.com", 27065);
      db = m.getDB("AppirioCareers");
      char[] password = { '4','+','r','E','o','x','x','x','x','x'};
      boolean auth = db.authenticate("jeffdonthemic", password);
      System.out.println("Mongo auth?: "+auth);
      coll = db.getCollection("jobs");  
    }
    catch (UnknownHostException ex) {
      ex.printStackTrace();
    }
    catch (MongoException ex) {
      ex.printStackTrace();
    }
 
    cur = coll.find(new BasicDBObject("name", name));  
    while (cur.hasNext()) {
      BasicDBObject doc = (BasicDBObject)cur.next();
      if (type.equals("views"))
        doc.put("views", (Integer)doc.get("views")+1);
      else if (type.equals("messages"))
        doc.put("messages", (Integer)doc.get("messages")+1);
      else
        doc.put("downloads", (Integer)doc.get("downloads")+1);
      coll.update( new BasicDBObject("name", name), doc );
    }
 
  }
 
    private void sendSms(Job job, String phone, String message) {
 
        String AccountSid = "YOUR-ACCOUNT-ID";
        String AuthToken = "YOUR-AUTH-TOKEN";
        String ApiVersion = "2010-04-01";
 
        TwilioRestClient client = new TwilioRestClient(AccountSid, AuthToken, null);
 
        String msg = "\n"+message+"\n"+job.getJobTitle()+"\nhttp://appirio.com/careers";
 
        System.out.println("size: "+msg.length());
 
        //build map of post parameters 
        Map<String,String> params = new HashMap<String,String>();
        params.put("From", "14155992671");
        params.put("To", phone);
        params.put("Body", msg);
        TwilioRestResponse response;
        try {
            response = client.request("/"+ApiVersion+"/Accounts/"+AccountSid+"/SMS/Messages", "POST", params);
 
            if(response.isError())
                System.out.println("Error making outgoing call: "+response.getHttpStatus()+"\n"+response.getResponseText());
            else {
                System.out.println(response.getResponseText());
 
            }
        } catch (TwilioRestException e) {
            e.printStackTrace();
        }
 
    }
 
    private Job getJobById(String id) {
      Job job = null;
      for (Job j : jobs) {
        if (j.getId().equals(id)) {
          job = j;
          break;
        }
      }
      return job;
    }
 
    @RequestMapping
    public String index() {
        return "job/index";
    }
 
}

 

VN:F [1.9.22_1171]
Rating: 9.3/10 (3 votes cast)
VN:F [1.9.22_1171]
Rating: 0 (from 0 votes)
Salesforce REST API Demo from Cloudstock, 9.3 out of 10 based on 3 ratings

Categories: Code Sample, Innovation, Java, Salesforce

Leave a comment

Comments Feed2 Comments

  1. Shaun

    Is there a way to get a download link or a link to this on your git hub? Also is this App Engine compatible and if not can it be? Thanks!

    VA:F [1.9.22_1171]
    Rating: 0.0/5 (0 votes cast)
    VA:F [1.9.22_1171]
    Rating: 0 (from 0 votes)
  2. Jeff Douglas

    Shaun, you can fork the code here. I think the Spring stuff is not compatible with GAE.

    VN:F [1.9.22_1171]
    Rating: 0.0/5 (0 votes cast)
    VN:F [1.9.22_1171]
    Rating: 0 (from 0 votes)

Leave a comment

Feed

http://blog.jeffdouglas.com / Salesforce REST API Demo from Cloudstock