Oh my god. It's full of code!

REST



Publicly Hosted Apex REST Class bug (maybe?)

I seem to have run across an odd bug. Custom Apex REST classes hosted via a Salesforce site will not work in a production version. It does work in sandbox and developer versions, so I am fairly convinced the approach is valid and my config is correct. This is a sample class.

@RestResource(urlMapping='/testPublicRest')
global class testPublicRest {
@HttpGet
global static String doGet() {
String name = RestContext.request.params.get('name');
return 'Hello '+name;
}

@isTest
global static void testRespondentPortal()
{
// set up the request object
System.RestContext.request = new RestRequest();
System.RestContext.response = new RestResponse();

//First lets try and create a contact.
RestContext.request.requestURI = '/testservice';
RestContext.request.params.put('name','test');
//send the request
testPublicRest.doGet();
}
}

Sandbox version sans namespace – Works
https://fpitesters.testbed.cs7.force.com/webServices/services/apexrest/testPublicRest?name=dan

Developer version with namespace – Works
https://xerointeractive-developer-edition.na9.force.com/partyForce/services/apexrest/XeroInteractive/testPublicRest?name=dan

Production version sans namespace – Fails
https://fpitesters.secure.force.com/webServices/services/apexrest/testPublicRest?name=dan

It fails saying that it cannot find a resource with that name.

<Errors>
<Error>
<errorCode>NOT_FOUND</errorCode>
<message>Could not find a match for URL /testPublicRest</message>
</Error>
</Errors>

If you attempt to access it via the non secure domain you will get an HTTPS required message, so the resource is at least being located. It throws this error, which makes sense.

<Errors>
<Error>
<errorCode>UNSUPPORTED_CLIENT</errorCode>
<message>HTTPS Required</message>
</Error>
</Errors>

Seems like I found a bug maybe? To test yourself just copy and paste the above code. Host it via a salesforce site. Access it in your sandbox it should work (remember to access it via https. To get to a REST service just include /services/apexrest/yourService at the end of your site url. Then try deploying it to prod and doing the same. It will most likely fail.

I’d love to hear any feedback/ideas on this, as it’s a fairly critical part of a framework I am developing. Thanks!

Also if you do have any info, make sure to post it on the stack exchange. That’s probably the best place for this kind of thing.
http://salesforce.stackexchange.com/questions/6122/custom-rest-service-https-error

UPDATE: Got it figured out. It was due to a permissions error on the guest account the site was using. Somehow an object for the services profile had an impossible permission setup (it had full read write modify all on an child object where it did not have read write modify all on the parent object (an opportunity)). So fixing the permissions and making sure the service had read/write to all objects and fields it required seems to have fixed this error. If you are getting this, make sure to check your object permissions and that everything the service needs is there, and that you don’t have some kind of weird setup issue like I did.


Angel IVR REST API wrapper for Salesforce Apex

Hey all,

Just a random post to help out any developers who may be trying to use the Angel IVR outbound calling features of their new REST API. This is a wrapper class that should do all the hard work for you. It handles all the HTTP traffic, batching, parsing of responses and serialization for ya. You’ll need to create a custom setting called Angel IVR Site and store your API token, API endpoint, subscriber Id in there (or just change the references to settings.whatever in the code to be hard coded. The test class shows creation of one of these objects, along with the expected fields names.

Here is the code. I’ll probably post up a sample app later and maybe even an installable package. I just wanted to get this out there before I forget, or get too lazy to do anything else with it.

/*Angel IVR API Wrapper
Description: Simple class for placing calls via the Angel IVR REST API. Also has experemental
implementations of the other API calls, including cancel, and request (gets job status)

See
https://www.socialtext.net/ivrwiki/outbound_rest_api_documentation
for API details.

Author: Daniel Llewellyn (Twitter: @Kenji776)
Date: 11/16/2012
*/

global class angelIVRWrapper
{
    class applicationException extends Exception {}

    public Angel_IVR_Site__c settings = Angel_IVR_Site__c.getValues('Prod');
    global static boolean isTest = false;

    //a single call item to place to angel. You will always pass a list of these. To make calls create a list
    //of these things (one for each person you wish to call if the call has variables unique to each person, or a single callitem with all the phone numbers included if
    //they are not unique). Then pass them into the campaignCall function along with the angel site you wish to use.
    global class callItem
    {
        public integer maxWaitTime = 30;
        public string phoneNumbers = '';
        public map<string,string> variables = new map<string,string>();
    }

    //a generic API response container. Will contain any error messages and status codes if something bombs.
    //otherwise it should contain a jobId you can later use for cancelling, checking status, etc. Also contains
    //a list of all the call items we attempted to place calls from, all the call items that where skipped (due to being invalid for some resaon)
    //and a list of the responses as provided by angel.
    global class callResponse
    {
        public string jobId = '';
        public string message = 'Calls Placed Successfully';
        public integer httpResponseCode = 200;
        public string httpResponse = 'ok';
        public boolean success = true;
        public list<callRequest> callRequestResponses = new list<callRequest>();
        public list<callItem> placedRequests = new list<callItem>();
        public list<callItem> skippedRequests = new list<callItem>();
    }

    //return object type from the API that contains details about a single call placed using the outbound API.
    global class callRequest
    {
        public string code = '';
        public string callStartTime = '';
        public string callEndTime = '';
        public string phonenumber='';
        public string phoneLineRequestID='';
        public string message = '';
    }    

    @RemoteAction
    global static list<callResponse> campaignCall(list<callitem> callItems, string angelSite)
    {
        angelIVRWrapper controller = new angelIVRWrapper();
        return controller.campaignCall(callItems , angelSite, true);
    }

    //wrapper for the campaignCall function of the Angel IVR. Pass it a list of call items, one for each person you wish to call.
    //it will return a call response object which should contain the job Id you can use for getting the status later.
    public list<callResponse> campaignCall(list<callitem> callItems, string angelSite, boolean allowPartial)
    {
        list<FeedItem> posts = new list<FeedItem>();
        set<string> phoneNumbers = new set<string>();

        //experemental idea for breaking a large number of call items into batches, delayed
        //by a number of seconds. This will likely have to use a recursive scheduled job or something.
        integer batchDelaySeconds = 600;

        list<callResponse> res = new list<callResponse>();
        map<string,string> params = new map<string,string>();
        params.put('allowPartial',string.valueOf(allowPartial));     

        //ask the broker to make a call to angel using the campaignCalls method, passing in the list of call items, and set the allowPartial 
        //url param as well.
        integer counter = 0;
        list<callItem> thisBatch = new list<callItem>();
        list<callItem> skippedRequests = new list<callItem>();

        //break the overall list into batches of 250, since that is the maximum amount you can place in one request.
        for(callItem callItem : callItems)
        {  
            counter++;
            if(callItem.variables.size() <= 50 && !phoneNumbers.contains(callItem.phoneNumbers))
            {              
                thisBatch.add(callItem); 
                phoneNumbers.add(callItem.phoneNumbers);

                if(callitem.variables.containsKey('ID'))
                {
                    FeedItem post = new FeedItem();
                    post.ParentId = callitem.variables.get('ID'); 
                    post.Body = 'Placed outbound call to this record using phone number '+callItem.phoneNumbers+' to Angel site ' + angelSite;    
                    posts.add(post);        
                }                       
            }
            //if this call item has too many variables, or we already have a call for this phone number in the queue, add the call item to our list
            //of skipped calls.
            else
            {
                skippedRequests.add(callItem);
            }

            if(thisBatch.size() == 250 || counter == callItems.size())
            {
                callResponse thisResponse = brokerRequest('POST','campaignCalls',thisBatch, angelSite, params);
                thisResponse.skippedRequests.addAll(skippedRequests);
                res.add(thisResponse);  
                thisBatch.clear();  
                skippedRequests.clear();    
            }
        }
        insert posts;
        return res;
    }

    //wrapper for the requests function of the Angel IVR. Pass it a job id and get back the current status of that job.
    public callResponse requests(string jobId)
    {        
        //ask the broker to make a call to angel using the campaignCalls method, passing in the list of call items, and set the allowPartial 
        //url param as well.  
        callResponse res = brokerRequest('GET','requests/job/'+jobId,null,null,null);        
        return res;
    } 

    //wrapper for the cancels function of the Angel IVR. Pass it a job id and that job will be cancelld if it is currently queued.
    public callResponse cancels(string jobId)
    {
        //ask the broker to make a call to angel using the campaignCalls method, passing in the list of call items, and set the allowPartial 
        //url param as well.  
        callResponse res = brokerRequest('GET','cancels/job/'+jobId,null,null,null);        
        return res;    
    }

    //handles the actual sending of http requests, handling of the response, formatting, etc.
    public callResponse brokerRequest(string httpVerb, string method, list<callitem> callitems, string angelSite, map<string,string> urlParams)
    {
        //create a call response object to pass back.
        callResponse callResponse = new callResponse();

        string requestURI;
        //create the endpoint URI with a bit of string concatination.
        if(angelSite !=null)
        {
            requestURI = settings.API_Endpoint__c+'/'+settings.Subscriber_ID__c+'/'+angelSite+'/'+method+'?apiKey='+settings.API_Key__c;
        }
        else
        {
            requestURI = settings.API_Endpoint__c+'/'+settings.Subscriber_ID__c+'/'+method+'?apiKey='+settings.API_Key__c;
        }    
        HttpRequest req = new HttpRequest();
        //setup the http request.
        req.setMethod(httpVerb);  
        req.setHeader('Content-Type','application/xml');     

        if(urlParams !=null)
        {
            for(string param : urlParams.keySet())
            {
                requestURI += '&'+param+'='+urlParams.get(param);
            }
        }
        req.setEndpoint(requestURI);

        if(callItems != null)
        {    
            //generating Angel XML using the serializer and set that as the request body.                  
            req.setBody(serializeCallItemAngelXml(callitems));
        }
        //send http request
        Http http = new Http();
        HttpResponse res = new HttpResponse();
        string responseBody;
        try
        {
            //some handling for if this is a test class or not. Can't make outbound calls in tests, so we need a mock response
            //if its a test.
            if(!isTest)
            {
                res = http.send(req); 
                responseBody = res.getBody();
            }
            else
            {
                responseBody = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?><outboundRequest jobID="0a14021a-1c-13b0b0b3e8a-7abc2f20-d09"><callRequest><requestDetails number="9522206974" phoneLineRequestID="200158136763"/><attempt callEndTime="" callStartTime="" code="queued"><message>queued</message></attempt></callRequest><timeCreated>2012-11-16T16:06:24.331-05:00</timeCreated></outboundRequest>';           
            }

            //if the http response doesn't have a 200 response code, an error happened, so we gotta toss an error, set the success to false, etc.
            //trying to parse whatever is in the body of the http request would probably cause an error as well, so we want to avoid doing that.

            if(res.getStatusCode() != 200 && !isTest)
            {
                throw new applicationException('Error during HTTP Request. Status Code:' + res.getStatusCode() + ' Status:' + res.getStatus() + ' Body Text: ' + res.getBody());
            }

            //if all has gone well until now, parse the results of the http request into a callResponse object and pass that back. This will
            //contain the job id and status of the call(s)
            callResponse = deserializeAngelOutboundRequestXML(responseBody);          
        }
        catch(exception e)
        {          
            callResponse.success = false;
            callResponse.message = e.getMessage() + ' on line: ' + e.getLineNumber() + '. Root cause: ' + e.getCause();   
            callResponse.httpResponseCode = res.getStatusCode();   
            callResponse.httpResponse = res.getStatus();

        }   
        callResponse.placedRequests.addAll(callItems);             
        return callResponse;           
    }

    //takes a list of callItem objects and turns them into valid AngelXML to send to the API. I wish
    //this was more dynamic (using some kind of reflection to iterate over the object properties, but whatever).
    //remember you can include variables in the callItem object to customize the information sent to Angel for each 
    //particular call.
    public static string serializeCallItemAngelXml(list<callitem> callitems)
    {
        string angelXML = '<callItems>';

        for(callitem thisCallItem : callItems)
        {
            angelXML += '<callItem>';
            angelXML += '<maxWaitTime>' + thisCallItem.maxWaitTime + '</maxWaitTime>';
            angelXML += '<phoneNumbers>' + thisCallItem.phoneNumbers + '</phoneNumbers>';
            for(string thisVar : thisCallItem.variables.keySet())
            {
                angelXML += '<variables><name>'+thisVar+'</name>';
                angelXML += '<value>'+thisCallItem.variables.get(thisVar)+'</value></variables>';
            }
            angelXML += '</callItem>';
        }

        angelXML += '</callItems>';
        return angelXML;
    }

    public static callResponse deserializeAngelOutboundRequestXML(string angelXMLResponse)
    {
        Xmlstreamreader reader = new Xmlstreamreader(angelXMLResponse);

        callResponse thisResponse = new callResponse();
        callRequest thisRequest;

        while (reader.hasNext()) 
        { 
            if(reader.getEventType() == XmlTag.START_ELEMENT && reader.getLocalName() == 'outboundRequest')
            {
                thisResponse.jobId = reader.getAttributeValue(null,'jobID'); 
            }  
            if(reader.getEventType() == XmlTag.START_ELEMENT && reader.getLocalName() == 'callRequest')
            {
                thisRequest = new callRequest();
            }            
            else if(reader.getEventType() == XmlTag.START_ELEMENT && reader.getLocalName() == 'requestDetails')
            {
                thisRequest.phonenumber = reader.getAttributeValue(null,'number');
                thisRequest.phoneLineRequestID = reader.getAttributeValue(null,'phoneLineRequestID');
            }
            else if(reader.getEventType() == XmlTag.START_ELEMENT && reader.getLocalName() == 'attempt')
            {
                thisRequest.callStarttime = reader.getAttributeValue(null,'callStartTime');
                thisRequest.callEndtime = reader.getAttributeValue(null,'callEndTime');
                thisRequest.code = reader.getAttributeValue(null,'code');
            }

            else if(reader.getEventType() == XmlTag.START_ELEMENT && reader.getLocalName() == 'message')
            {
                reader.next();
                thisRequest.message = getDecodedString(reader);
            }
            else if(reader.getEventType() == XmlTag.END_ELEMENT && reader.getLocalName() == 'attempt')
            {
                thisResponse.callRequestResponses.add(thisRequest);
            }            
            reader.next();
        }

        return thisResponse;
    }

    public static String getDecodedString(Xmlstreamreader reader)
    {
        return EncodingUtil.urlDecode(reader.getText(), 'UTF-8').trim();
    }

    @isTest
    public static void angelIVRWrapper()
    {
        isTest = true;

        //weird workaround to avoid mixed dml error, as seen here
        //http://boards.developerforce.com/t5/Apex-Code-Development/DML-not-allowed-on-user-in-test-context/m-p/98393
        User thisUser = [ select Id from User where Id = :UserInfo.getUserId() ];
            System.runAs ( thisUser ) {
            Angel_IVR_Site__c settings = new Angel_IVR_Site__c();
            settings.name = 'Prod';
            settings.Angel_Site_Id__c = 'test';
            settings.API_Endpoint__c = 'http://www.test.com';
            settings.API_Key__c = 'test api key';
            settings.Subscriber_ID__c = '402342';
            insert settings;
        }

        angelIVRWrapper controller = new angelIVRWrapper();
        list<angelIVRWrapper.callitem> callItems = new list<angelIVRWrapper.callitem>();
        Respondent__c testRespondent = testDataGenerator.createTestRespondent();

        angelIVRWrapper.callitem thisCallItem = new angelIVRWrapper.callItem();
        thisCallItem.phoneNumbers = '5555555555';
        thisCallItem.variables.put('RESPONDENT__R_NAME','Frank Jones');
        thisCallItem.variables.put('ID',testRespondent.id);

        callItems.add(thisCallItem);

        callResponse requestStatus = controller.requests(response[0].jobId);

        callResponse sendCancel = controller.cancels(response[0].jobId);

        list<callResponse> sendCalls = angelIVRWrapper.campaignCall(callItems, '200000124604');
    }
}

Access Salesforce REST API with Javascript

So this has been a bit of a holy grail kind of endeavor for me. A way to access Salesforce org data using only javascript. Salesforce has a million ways to get at it’s data an interact with it, but it’s always required some kind of server side technology to create the requests and deal with authentication. Not anymore! Now before I get too far, this is just proof of concept code I’d say. It works, but I only just got it working in the last few hours and have done very minimal testing, but the concepts are solid.

The approach is create a Salesforce Apex REST service that can perform all the basic CRUD operations. You then host that REST service on a salesforce.com site. By using JSONP and callbacks you can hit the service from any javascript program, send requests and get data back. This idea was put forth by Pat Patterson via his awesome post at

http://blogs.developerforce.com/developer-relations/2012/02/quick-tip-public-restful-web-services-on-force-com-sites.html

I just took the idea an expanded upon it a bit. So first off, here is the code.

/* RestProxy
Author: Daniel Llewellyn (Kenji776@gmail.com, @Kenji776)

Description: This is a rest class that can be made available to client applications via salesforce.com
sites which will allow them to perform basic CRUD operations against the org. Before this, there was no
way I know of for pure javascript applications to 'integrate' with Salesforce. You had to deal with oAuth or 
SOAP or whatever. Stuff Javascript can't really do. I needed a way to get data in and out of a site.com hosted site
which only gives you access to javascript. So I wrote this. YOu can perform queries, updates and deletes. Very basic right
now, but could be expanded on as required. Inspired by the awesome post at

http://blogs.developerforce.com/developer-relations/2012/02/quick-tip-public-restful-web-services-on-force-com-sites.html

*/

@RestResource(urlMapping='/respondentPortal/*')
global class respondentPortal 
{
    
    
    //simple wrapper response class. Makes it so all replies via this API have the same basic structure
    //including a boolean success flag, a message, any sObjects affected and some debugging params.
    global class restResponseWrapper
    {
        string message = 'run successful';
        boolean success = true;
        list<string> params = new list<string>();       
        list<sobject> sObjects = new list<sobject>();
        string requestURI = RestContext.request.requestURI;
    }
    
    class applicationException extends Exception {}
    
    //can run queries. This one doesn't except an /objectType/Id as the other methods do. This one just takes a query as specified in the
    //?query= param of the url. It will return all the objects in the sObjects property of the returned object.
    @HttpGet
    global static void doGet() 
    {                     
        restResponseWrapper thisResponse = new restResponseWrapper();
        
        //since all requests to this service will be get, since we are operating using JSONP, we first need to figure out what they actually want to do.
        thisResponse.params = getRestParams(thisResponse.requestURI);     
        string httpVerb = thisResponse.params[1].toLowerCase();

        try
        {      
              
            if(RestContext.request.params.get('authToken') == null)
            {
                throw new applicationException('No auth token provided. Please provide one with ?authToken= in the request url');
            }
            long authToken = getAuthToken();
            long providedToken = long.valueOf(RestContext.request.params.get('authToken'));
            
            //check the auth token, provide a 20 second range where it is valid (+10 seconds, -10 seconds)
            if(providedToken == null || providedToken < authToken - 10000 || providedToken > authToken + 10000)
            {
                throw new applicationException('Invalid auth token');
            }
                     
            if(httpVerb == 'get')
            {               
                String query = RestContext.request.params.get('query');
                thisResponse.sObjects = runQuery(EncodingUtil.urlDecode(query,'UTF-8'));  
                 
            }
            else if(httpVerb == 'post')
            {
                thisResponse.sObjects.add(saveSObject(thisResponse.params[2],null,RestContext.request.params));
            }
            else if(httpVerb == 'put')
            {
                thisResponse.sObjects.add(saveSObject(thisResponse.params[2],thisResponse.params[3],RestContext.request.params));
            } 
            else if(httpVerb == 'delete')
            {
                Schema.sObjectType objectDef = Schema.getGlobalDescribe().get(thisResponse.params[2]).getDescribe().getSObjectType();     
                sObject obj = objectDef.newSobject(thisResponse.params[3]);     
                database.delete(obj);  
            }   
            else
            {
                thisResponse.success = false;
                thisResponse.message = 'method ' + httpVerb + ' not implimented';                
            }          
        } 
        catch(exception e)
        {
            thisResponse.success = false;
            thisResponse.message = e.getMessage();
        }     
        

        RestContext.response.addHeader('Content-Type', 'text/plain');
        RestContext.response.responseBody = formatResponse(thisResponse);                    

    }

    
    
    //take a query string and run it. Yeah, I know SOQL injection possible here, but whatever. This
    //whole thing is inherently insecure. It's like worrying about wet floors on the titanic.
    public static list<sobject> runQuery(string queryString)
    {
        list<sobject> objs = new list<sobject>();
        objs = database.query(queryString);
        return objs;
    }
    
    //returns rest params (things after the main url, separated by /)
    //in an easy to use list. The name of the class should be element 0, the 
    //type of object should be element 1 and the id of the object should be element 2
    global static list<string> getRestParams(string url)
    {
        list<string> returnParams = new list<string>();
        integer endOfUrl = url.indexOf('?');
        if(endOfUrl == -1)
        {
            endOfUrl = url.length();
        }
        //clean up the url, make sure we are only dealing with the section we want to. After the host name, before the ? mark.
        //i've seen in come in with the full host name, and also just the relative portion, so we gotta make sure it's exactly the same
        //from here on in, or else the array it returns could get all messed up.
        if(url.indexOf('/apexrest/') > 0)
        {   
            url = url.substring(url.indexOf('/apexrest/')+10,endOfUrl);
        }  
        else
        {
            url = url.substring(1,endOfUrl);
        }            
        list<String> URLParams = url.split('/'); 
         
        for(integer i =0; i < 4; i++)
        {
            if(i<urlParams.size())
            {
                returnParams.add(EncodingUtil.urlDecode(urlParams[i], 'UTF-8'));
            }
            else
            {
                returnParams.add(' ');
            }
        }   
        
        return returnParams;           
    }
    
    
    //so for the record, I really dislike this method. I wish I could just take the pure json data and deserialize it into
    //an sobject type defined at runtime, but as far as I know, you cannot do that (at least I couldn't find the syntax for it).
    //so instead this iterates over the keys of the sobject known from the global describe, finds matching keys in the JSON data
    //and assignes the value from the JSON to the sObject.
    public static sObject saveSObject(string objectType, string recordid, Map<String, Object> sObjectData)
    {
        //create a generic sObject to contain the update values.
        sObject updateObj;      
                
        //get the describe object for the type of object passed in (its just passed in as a string from the URL)
        Schema.sObjectType objectDef = Schema.getGlobalDescribe().get(objectType).getDescribe().getSObjectType();
        
        //find all the fields for this object type
        Map<String, Schema.SobjectField> ObjectFieldsMap = objectDef.getDescribe().fields.getMap();
        
 
        //this method can handle updates or inserts. If a record ID was passed in, 
        //cast the object as the type represented by the ID. If not, just create a
        //new object of the type found in the object describe.
        if(recordId != null)
        {
            updateObj = objectDef.newSobject(recordid);
        }
        else
        {
            updateObj = objectDef.newSobject();
        }    
        // populate the object's fields by looping over all the params in the rest request.
        for (String key : sObjectData.keySet())
        {
            // only add params if they are valid field on the object
            if (ObjectFieldsMap.containsKey(key))
            {
                //figure out the type of this field so we can cast it to the correct type
                string fieldType = ObjectFieldsMap.get(key).getDescribe().getType().name().ToLowerCase();
                
                //since I don't know how to do, or if it's even possible to do dynamic casting we need a 
                //series of if statments to handle the casting to numeric types. I think all the others should
                //be fine if left as a string. Dates might explode, not sure.
                
                
                if(fieldType == 'currency' || fieldType == 'double' || fieldType == 'percent' || fieldType == 'decimal' )
                {
                    updateObj.put(key, decimal.valueOf(string.valueOf(sObjectData.get(key)).trim())); 
                }
                else if(fieldType == 'boolean')
                {
                    updateObj.put(key, Boolean.valueOf(sObjectData.get(key))); 
                }                   
                else if(fieldType == 'date')
                {
                    updateObj.put(key, date.valueOf(sObjectData.get(key))); 
                }                
                else
                {
                    updateObj.put(key, sObjectData.get(key));
                }
            }
            else
            {
                system.debug('Invalid field: '+ key + ' for object type ' + objectType);
            }
        }
        //update/insert the object
        upsert updateObj;
        
        //return the saved object.
        return updateObj;
        
    }
        
    //take one of those wrapper objects and format it by wrapping it in a callback if needed, and serializing 
    //the result into json.    
    public static blob formatResponse(restResponseWrapper responseData)
    {
        string response;
        String callback = RestContext.request.params.get('callback');
        if(callback != null)
        {
            response = callback + '(' + JSON.serialize(responseData) + ');';
        }    
        else
        {
            response = JSON.serialize(responseData);
        }
        return blob.valueOf(response);
    }
    
    //just put some random mathemtical expression here that will only be known by the client application and 
    //this class. I know it's not super secure, but it's better than nothing. Will at least stop super novice
    //script kiddies.
    public static long getAuthToken()
    {
        return DateTime.Now().getTime() - 42131 / 3 * 8;
    }
    
    @isTest
    global static void testRespondentPortal()
    {
        // set up the request object
        System.RestContext.request = new RestRequest();
        System.RestContext.response = new RestResponse();
        
        //First lets try and create a contact.
        RestContext.request.requestURI = '/respondentPortal/post/contact';
        RestContext.request.params.put('firstname','test');
        RestContext.request.params.put('lastname','guy');
        RestContext.request.params.put('authToken',string.valueOf(getAuthToken()));
        //send the request
        respondentPortal.doGet();
        respondentPortal.restResponseWrapper createResponse= (restResponseWrapper) JSON.deserialize(RestContext.response.responseBody.toString(),restResponseWrapper.class); 
        
       
        //make sure the create was successful       
        system.assertEquals(true,createResponse.success,createResponse.message);
              
        //Okay, now lets try and get that contact we created
        System.RestContext.request = new RestRequest();
        string query = 'select name, id from contact where id = \''+createResponse.sObjects[0].Id+'\'';
        RestContext.request.requestURI = '/respondentPortal/get';
        RestContext.request.params.put('query',EncodingUtil.urlEncode(query,'UTF-8'));
        RestContext.request.params.put('authToken',string.valueOf(getAuthToken()));
        respondentPortal.doGet();
        
        respondentPortal.restResponseWrapper queryResponse = (restResponseWrapper) JSON.deserialize(RestContext.response.responseBody.toString(),restResponseWrapper.class);        

        system.assertEquals(true,queryResponse.success,queryResponse.message);
        system.assertEquals(1,queryResponse.sObjects.size());
        
        //cool, now lets do an update on that contact
        System.RestContext.request = new RestRequest();
        RestContext.request.requestURI = '/respondentPortal/put/contact/'+createResponse.sObjects[0].Id;
        RestContext.request.params.put('firstname','frank');
        RestContext.request.params.put('authToken',string.valueOf(getAuthToken()));
        respondentPortal.doGet();
        respondentPortal.restResponseWrapper updateResponse= (restResponseWrapper) JSON.deserialize(RestContext.response.responseBody.toString(),restResponseWrapper.class);

        system.assertEquals(true,updateResponse.success,updateResponse.message);
        system.assertEquals(1,updateResponse.sObjects.size());   
        system.assertEquals('frank',updateResponse.sObjects[0].get('firstname'));
        
        //and finally we'll delete them 
        System.RestContext.request = new RestRequest();
        RestContext.request.requestURI = '/respondentPortal/delete/contact/'+createResponse.sObjects[0].Id;    
        RestContext.request.params.put('authToken',string.valueOf(getAuthToken()));
        respondentPortal.doGet();
        respondentPortal.restResponseWrapper deleteResponse= (restResponseWrapper) JSON.deserialize(RestContext.response.responseBody.toString(),restResponseWrapper.class); 
        system.assertEquals(true,deleteResponse.success,deleteResponse.message);  
        
        //now try to make a call without an auth token. it should bomb 
        System.RestContext.request = new RestRequest();
        RestContext.request.requestURI = '/respondentPortal/get';
        RestContext.request.params.put('query',EncodingUtil.urlEncode(query,'UTF-8'));
        respondentPortal.doGet();
        respondentPortal.restResponseWrapper noAuthTokenResponse= (restResponseWrapper) JSON.deserialize(RestContext.response.responseBody.toString(),restResponseWrapper.class);

        system.assertEquals(false,noAuthTokenResponse.success,noAuthTokenResponse.message);   


        //now try to make a call with a bad auth token. it should bomb 
        System.RestContext.request = new RestRequest();
        RestContext.request.requestURI = '/respondentPortal/get';
        RestContext.request.params.put('query',EncodingUtil.urlEncode(query,'UTF-8'));
        RestContext.request.params.put('authToken','12345678');
        respondentPortal.doGet();
        respondentPortal.restResponseWrapper badAuthTokenResponse= (restResponseWrapper) JSON.deserialize(RestContext.response.responseBody.toString(),restResponseWrapper.class);

        system.assertEquals(false,badAuthTokenResponse.success,badAuthTokenResponse.message); 
                
        //try to make a call to a non existing http verb   
        System.RestContext.request = new RestRequest();
        RestContext.request.requestURI = '/respondentPortal/herpDerp';
        RestContext.request.params.put('query',EncodingUtil.urlEncode(query,'UTF-8'));
        RestContext.request.params.put('authToken',string.valueOf(getAuthToken()));
        respondentPortal.doGet();
        respondentPortal.restResponseWrapper badVerbResponse= (restResponseWrapper) JSON.deserialize(RestContext.response.responseBody.toString(),restResponseWrapper.class);

        system.assertEquals(false,badVerbResponse.success,badVerbResponse.message); 
        
        blob jsonResponse = formatResponse(badVerbResponse);   
      
             
    }
}

And here is a basic test page to show you how to invoke it with javascript and handle the results, along with how to generate a matching auth token.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
<script src="core.js"></script>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Respondent Portal JS Library Test</title>

<script>

    // JavaScript Document
    var portal = new Object();
    
    portal.restEndPoint = 'https://yourendpoint.testbed.cs7.force.com/yoursite/services/apexrest/respondentPortal/';
    
    portal.runQuery = function(queryString,callback)
    {
        var jqxhr = $.getJSON(portal.restEndPoint+'get/?callback=?', {authToken:portal.generateAuthToken(),query: encodeURI(queryString)},  function(response) {
          console.log('queryRan');
          callback(response);
        })        
    }
    
    portal.generateAuthToken = function generateAuthToken()
    {
        return Math.round(new Date().getTime() - 42131 / 3 * 8);
    }
    
    portal.runQuery('select name, id from contact',function(result){
        console.log('Callback ran');
        console.log(result);
    });
</script>
</head>

<body>
</body>
</html>

The basic structure of a call being

https://yourorg.cs7.force.com/yoursite/services/apexrest/respondentPortal/%5BhttpVerb%5D/%5BobjectType%5D/%5Bid%5D?sObjectfield=value&othersObjectfield=otherValue
So to perform an update you might call.

https://yourorg.cs7.force.com/yoursite/services/apexrest/respondentPortal/put/contact/003M000000DSvWT?email=Kenji776@gmail.com

To do a create it would be

https://yourorg.cs7.force.com/yoursite/services/apexrest/respondentPortal/post/contact?firstname=Dan&lastname=Llewellyn

A delete would be

https://yourorg.cs7.force.com/yoursite/services/apexrest/respondentPortal/delete/contact/003M000000DSvWT

and finally a query.

https://yourorg.cs7.force.com/yoursite/services/apexrest/respondentPortal/get/?query=select id, name from contact

Also, a nice feature of the update and create, you don’t have to worry about only passing in valid sObject fields. It will use everything it can, and discard the rest. So if you arn’t sure if a field exists, or if you have rights to it, no worries. The program won’t exlode. It might not perfectly support dates or relationships yet though.

Any of those calls can accept a ?callback=functionName param which will cause the service to return the data in a callback which can be used for doing JSONP style transactions. EX

<script>
$.getJSON("https://yourorg.cs7.force.com/yoursite/services/apexrest/respondentPortal/get/?query=select id, name from contact&callback=?",
 function(data) {
    $('#data').html(data);
 });
</script>

Of course this service is a big ass security risk, so you’d likely want to at least add some kind of password to your requests, or something. Maybe a key that cycles daily that both your class and your javascript can calculate? I dunno. At least now you can get at your org data using javascript, which to me, is a big win.

EDIT: The new version of the code posted above includes a super basic authToken system. All it requires is that the client application and the REST service have a pre agreed upon method for generating a one time use key per request. I just used epoch time with some totally random changes to it, and then added a 10 second buffer. It’s pretty lame I know, but it’s better than nothing at least. Try and protect the formula you use to generate the key.

There ya go. Still a little rough around the edges, but should be enough to get you started.


While This is Awesome, There Must be a Better Way.

This is half an interesting post about how I solved a fairly complicated problem, and half me looking for a better way to do it. The problem I was solving in theory is fairly simple.

I want to import answers from an external survey software into our Salsforce org.

Pretty simple right? Standard import functionality. Nothing too complex. But now let’s build on this.

1) It should be automated. No manual triggering of the import.
2) It should be imported as it is created. Closer to real time than to ETL.
3) The place the data is coming from has no API. We only have direct ODBC access to it.
4) Since the process must be closer to real time, we should use a ‘push’ style mentality for import. The survey software should push the data into Salesforce as it is created, instead of Salesforce requesting it.
5) The survey software only gives me access to javascript to add functionality. No server side processing of any kind.
6) Solution should be as cloud based as possible. Ideally no code on our servers (we have a cold fusion server we use for a lot of data brokering, but we would like to get rid of it).

Suddenly got harder eh? Right off the bat, point 3 means we are going to need to use some kind of middle-ware (in this case our Coldfusion webserver that can talk to the database). Salesforce has no method to talk directly to a database, as it shouldn’t. Everything is done with API’s these days, nobody directly queries databases anymore. At least they shouldn’t. So my first task was to write an API that Salesforce could access to get it’s data. So I wrote up a simple ColdFusion webservice that queries the survey database, formats the data in a reliable way, and returns a JSON array. While not ideal because it does use our server, at least it’s fairly transparent. It’s just a webservice call that could possibly be replaced in the future if the survey software does release an API in the future.

With the webservice up and running, now I need some Apex code to call out to it, get the data and handle it. So I wrote a nice little Apex method that uses a simple HTTP get with some URL params to invoke my ColdFusion webservice. Now it can get the JSON data. Using the new awesome JSON parser in winter 12 I am able to parse the data into a list of custom objects. Hooray.

So now I need a Salesforce object to store this data right? Actually I am going to want a few. I ended up creating 3 in total. The first object ‘survey’, is just a simple container object with pretty much no fields. The second object, ‘survey entry’ is just an object that will contain all the answer objects for a person in a given survey. It of course has a lookup to the ‘survey’ object, as well as a lookup to a contact, and some other info (when they took the survey, etc). The third object ‘survey answer’ is where the real juicy data is. It has the ID of the question, the text of the question, the persons answer, and a lookup to the survey entry.

So now I modified my Apex class a bit to create the survey answers objects, and relate them to the proper ‘survey entry’ (and create one if one does not exist for this person in this survey yet). Boom, so now all the ‘hard’ work is done. I have a class that can be called that will import the survey data for a person into Salesforce. But wait, how do I actually call this thing? I don’t want this on a scheduler, I want it to get called when new data is available. So I need to make this class itself into a webservice of some kind.

I have a bit of experience with Apex REST so I decided that would be a fitting way to handle this. This class only needs the ID of the survey, and the person for whom it needs to import data. That information is easily included in the URL or in POST fields, so I quickly modified my class to be an Apex REST service. Now it was ready to begin being accessed from the outside world. The question now is, how do I invoke the service itself?

First I used the apigee app console to make sure it was working as required. Apigee handles the oAuth and lets you specify params so testing your Apex REST service couldn’t be easier. Once I had verified that it worked, I needed some method to allow the survey software to invoke it. Problem is of course, if you remember that the survey software only supports JavaScript. JavaScript is still subject to that cross domain security policy BS. Normally you could use the script injection technique to make a callout to a different domain, but I need to set headers and such in the request, as well as making it a post request, so that wasn’t going to fly. On top of that I have would have no idea how to let JavaScript start using oAuth or get a valid session ID. So here is where things get a little murky.

How could I allow a javascript only application invoke my Apex REST service? Looks like I would again have to turn to my ColdFusion middleware box. I wrote another webservice which can invoke the desired Apex method from ColdFusion. You can call Apex REST services using a session ID instead of having to deal with oAuth so I went that route. I already have integration between Salesforce and ColdFusion through use of the awesome CFC library provided at RIA Forge (I actually helped contribute a bit to that). So I just wrote up what basically amounts to be a wrapper. You can invoke it from a simple get request and it will wrap the request with the required headers (authorization, content-length, content-type) and send it off to Salesforce. ColdFusion web services have the awesome feature of being able to be called via a URL, instead of having to use a WSDL or whatever. Come to think of it, they are almost a forerunner for REST, but I digress.

So now I have a URL that when called (with some arguments/params) will invoke my Apex REST service that goes and gets the survey data and imports it. So now I still need to get the survey software to call this URL. Here I use the classic script injection technique to make my cross domain request (because the survey software and my ColdFusion box live on different domains) and it much to my surprise it all worked. If you are curious, the code to do that looks like this.


function loadJSON(url)
{
var headID = document.getElementsByTagName("head")[0];
var newScript = document.createElement('script');
newScript.type = 'text/javascript';
newScript.src = url;
headID.appendChild(newScript);
}
var survey = '12345';
var token = 'mkwcgvixvxskchn';
var contact = '003GASDFADFAFDAS';
newUrl = 'http://XXXXXXXXXXXXX/webservice.cfc?method=importData&survey='+survey+'&token&='+token+'&contact='+contact;
loadJSON(newUrl);

So in the end, this is the process I came up with.

1) User takes online survey from 3rd party site (lets call it survey.com)
2) survey.com invokes javascript which calls the ColdFusion webservice (which includes survey id and person id in the request)
3) ColdFusion receives the request, and ‘wraps’ it with the needed authorization information to make a valid HTTP request.
4) Salesforce custom Apex REST class receives the request (with survey id and person id still included)
5) Salesforce sends request BACK to a different ColdFusion webservice, which requests the actual question/answer data.
6) ColdFusion receives request. Queries survey database and encodes database info as an array of JSON encoded objects.
7) JSON encoded data is returned to the calling Apex, where it is parsed into custom objects and committed to the database.

Seems kind of obtuse eh? Yet I can’t think of any way to make it leaner. I really would like to eliminate the 2nd and 3rd step and just have the survey software invoke the Apex REST directly somehow, or at least make the call totally cloud based. I suppose I could host a visualforce page that does the same thing, and have the JavaScript call that…

So anyway, here you can see an interesting case study of integrating a lot of different crap using some fairly new methods and techniques. I am totally open to suggestions on how to refine this. Right now there are just so many points of failure that it makes me nervous but again it seems to be about the best I can do. Thoughts and feedback welcome in the comments.


Cloudspokes Scorecard REST API Entry

Well I just submitted my first attempt at an Apex RESP API. Overall I think it turned out pretty okay. I learned a lot, and I think I even created a cool new reusable class for converting XML into an updatable list of sObjects. Check out the video and leave any feedback!

Challenge Video