Oh my god. It's full of code!

Posts tagged “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');
    }
}

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.


Salesforce REST API musings

So this post is going to be more questions, thoughts, and things to check on more than any useful data. Salesforce Apex REST services is currently in pilot, meaning you can get it enabled for your organization. What it does is allows you to take an Apex class and turn it into a RESTful…. thingy. So you tell your class what URL to be responsible for, put in methods that handle the various HTTP verbs (GET, POST, PUT, DELETE). Now each of these verbs essentially maps to a different CRUD operation (create = post, read = get, update = put, delete = delete). Of course you can also pass in query params by using the ? and & symbols. Now then, it seems to me, that the easiest design at least to get started would be to create 4 very generic methods and have them figure out what they are supposed to do by looking at the URL structure and query params.

Say for example you created your Apex class and told it to listen for anything in the /myservice/ sub folder. You’d mark your apex class like this

RestResource(urlMapping=’/myservice/*’)

now to access it, you’d simple go to

https://%5Byourinstance%5D.salesforce.com/services/apexrest/myservice

If you were to visit that page in your browser (ignoring the authentication error you’d get) you’d hit the method marked with @HttpGet because your browser by default sends GET requests. Following me so far? Now say you wanted to write a rest class that would work with accounts and contacts. Now first you’d say, ok I’ll need at least 8 methods then (4 methods per object X 2 objects). But wait, really you’ll more or less be recreating the same methods for both. Sure the functionality might change a bit based on the object type and optional query data, but not so dramatic as to require completely separate methods. For basic CRUD operations it feels like maybe you could make your methods generic enough to cover all sObjects.

Right now you might be like ‘uhh what the hell are you talking about?’ but here, I’ll try and give an example. Straight from the developer blog over at Salesforce they have a sample that looks like this

@HttpGet
global static Case getCaseRecord(RestRequest req, RestResponse res) 
{
    String caseNum = req.requestURI.substring(req.requestURI.lastIndexOf('/')+1);
    Case c = CustomCaseMgmt.getCase(caseNum);
    return c;
}

Makes enough sense right? You make a get request you get a case back. But what if I don’t want a case, what if I want a contact. You need another method for that (which leads me to the question how does the REST api know which one to call in a situation, can there even be multiple? AHHH MY BRAIN!). Anywho of course my novice programmer brain says ‘wait, lets just make a nice abstract method that will work for any sObject!’. So I end up with something like

@HttpGet
global static list<sObject> getRecord(RestRequest req, RestResponse res)
{
    //No idea if this is the right way to get url arguments. Seems clunky and wrong but its from the sample code
    string objectType = req.requestURI.substring(req.requestURI.lastIndexOf('/')+1);
    string objectId = req.requestURI.substring(req.requestURI.lastIndexOf('/')+2);
    
    string queryString = 'select name, id from ' + objectType + ' where id = \''+objectId+'\''; 
    list<sObject> objects = Database.query(queryString);
    
    return objects;    
}

See, now I have no idea if this is a good idea or not. You call the URL and in the arguments you tell it the type of object you want, and the ID of the object as well. Later I would certainly build on this to allow the user to specify a limit, offset, and what fields they want, and maybe even allow them to write custom where statements if they were familiar with SOQL. Point being, I can save myself writing multiple methods if I have 1 method that changes it’s behavior based on the data in the URL query string. Writing very dynamic methods might be a bit harder up front, but think of the re-usability! Also you get off writing a lot less test code, which is always a win in my book. Hell if I/you did this right we could have a downloadable class that pretty much instantly enables any org total REST functionality for all their objects!

So ideally you’d be able to do something like

https://%5Byourinstance%5D.salesforce.com/services/apexrest/myservice/contact/003000000000000

and if that was a get request it would hit the get method, see that you and that you want the contact with id of 003000000000000. Of course you would be able to replace contact with any sObject and it should still work. Then you could maybe make your method cooler and make the Id an optional parameter and let developers just enter a query in your url like

https://%5Byourinstance%5D.salesforce.com/services/apexrest/myservice/contact/?query=lastname like ‘jones%’ and createdDate > LAST_YEAR&limit=50

To only find people names jones who where created this year. Doing something like that would make your API super powerful but still fairly intuitive for developers who are used to working with the force.com platform. Of if you didn’t want to support full SOQL queries you could have some keywords like

https://%5Byourinstance%5D.salesforce.com/services/apexrest/myservice/contact/?lastname=jones&createdDate=THIS_YEAR&limit=50

but of course that is less flexible and requires more coding in your method to handle the special keywords, and tends to remove the ability to do LIKE, greater than, less than, not equal to, etc (unless you build in some more crazyness). Anyway, you see where I am going with this?

Of course it only gets harder when you start dealing with creates and updates (theoretically deletes should be just as easy, but some syntax issues might make it hard, I dunno). So I have no idea if I should continue down this road of attempting to create generic REST methods or not. I’d really like to start walking this path putting my best foot forward, but I’m not sure what that is. This is the part where the intelligent developer community comes out and tells me I’m a total n00b and how to do it right (and it turns out its some insanely complicated horrific mess that seems easy to them but makes me want to cry. That would be my luck).

Anyway, take this all with a grain of salt. I just learned about the REST API last week, and while I’m starting to write code, it doesn’t even work in my dev org yet. So hell if I know. I feel like I’m stumbling in the dark here but I know where I want to be. If you have any insight on this approach, feel free to leave comments or whatever.


Salesforce outbound messages and visualforce

So I’ve been working on a project recently. A crazy one. One I hope to be able to package and sell on the app exchange.

What I am doing, is attempting to create a Salesforce application that will be packagable and sellable in the appexchange market. The challenge I am facing is allowing an end user to select when an apex class will be invoked, and pass their own data to it. I don’t know if a user will want to call a class when a contact is created, or a lead is updated, or an opportunity is deleted. The application is simply used for sending text (SMS) messages, based on criteria the user sets. The good news is that my application is pretty much done, save for one step. The bad news, that one step may be impossible, at least how I am trying to accomplish it.

My first thought on how to accomplish this was use a workflow rule (since workflow rules are easy enough for even regular end users to create) to send an outbound message to a visualforce page that comes with the app. The visualforce page would receive the SOAP data (which lives in the http request data), parse it out and send the data contained within to an apex class, which does the rest of the hard work of actually sending the message. The problem here is that, as far as I know, visualforce/apex have no way to access the data contained within the http request. Hence the SOAP data that is passed is useless, it is lost.

The workflow as I see it

The key thing is here, is that I simply need an easy way for users to select when the apex class will be called, and what data they will pass to it (it only needs the phone number and the message). I don’t really care how it happens as long as it is easy for the user to set up.

If this will not work, I only have two other ideas. One is to make the workflow rule send an email to a designated email address that has a bot monitoring the inbox, which checks the mailbox every few seconds, reads the emails, extracts the message data (the email would probably have XML in the body) then with that information call the apex method that sends the message. The other approach is to have the outbound messages go to a webservice that my company controls on premise, which COULD access the SOAP data in http request, parse it out, then call a visualforce page associated with the target account and pass the message and phone in the URL as arguments.

I have just emailed a Salesforce engineer that I met recently and maybe he’ll be able to come up with a way to doing what I want to accomplish. Otherwise, if you have any awesome ideas, feel free to leave them in the comments. I’ll give a free copy of the app to anyone who can solve this last problem.