Oh my god. It's full of code!

Invoke Apex class via workflow rule

Since I started working with Apex, one thing I have wanted to be able to do is invoke a class via workflow rules instead of having to write triggers. The idea came to a head on a recent project in which users NEEDED to be able to configure when and how an Apex class was triggered without the need for coding. I wracked my brain for quite a while until I came up with a workable solution.

The easiest way to let a user control when something happens is by use of workflow rules. Workflow rules are basically ‘if then’ statements in which you define a condition, and an action. They are done using the GUI and have some fairly flexible and powerful features. This is where I decided to start my quest XD

Workflow rules give you three different actions you can take, each offered a different possibility of how I might use it invoke my Apex class, but in the end, only one survived. Those three actions being;

  • Send an outbound Message
  • Send an email alert
  • Perform a field update

My first thought was to use the outbound messaging functionality of workflow rules. Outbound messages encode user defined data in a SOAP (simple object access protocol) envelope in the HTTP request header, and pass it off to a target page (endpoint). The target page then will investigate the headers, extract the SOAP content (which is just XML) and do whatever it needs to do. So I thought, alright cool I’ll send my outbound message to a visualforce page which can then open up the contents of the message, find the information needed and use a web service called by the VF page to actually invoke the class. I began writing my code and everything was going well until I hit one crucial snag. Apex/Visualforce in it’s current incarnation Apex has NO way to access the HTTP request headers (you know, that part where the SOAP data is). So you can certainly send that data to the visualforce page with your workflow rule, but the page has no way to read the data. So if you just need to call an apex class but not pass any data to it, this method will work. It did not however work for because I needed to pass data to my class.

I wasn’t quite ready to give up on outbound messaging yet, because it just felt so right. It seemed like it had the answer I wanted so I kept thinking. It lead me to another idea. What if instead of calling the visual force page directly from the workflow rule, I call another page I control, on another server. Write a up a quick listener in ColdFusion that CAN get the SOAP data from the header, then just call a listener in my org that expects to receive data in the URL instead. So for example, my application sends text messages, so it needs a phone number and a message. The outbound message calls my ColdFusion ‘webservice’. The webservice takes the data it gets, parses the XML and extracts the phone number and message. It then calls a visualforce page hosted on a Salesforce site, and hands it that same data, except as URL arguments. Apex can get data from URL arguments, so this could work. The problem with this is that any data you want to include in your message MUST be on the object that triggered the rule. Outbound messages can’t spider relationships. Also, I wanted to package and possibly sell this application, so that means I’d have to somehow know which Salesforce site and page to call back with my URL arguments so now I need to let users register callback handlers and it just got really complicated. Too much so. I finally decided the outbound messaging idea was dead. If you have no need to package your app, and don’t mind your application not totally living in the cloud, this is also a workable solution.

So now I was stuck. How could I use my remaining 2 possibilities. Field updates pretty much seemed unworkable right from the get go. No way they would be flexible enough. So that left me with email alerts. I began thinking about the email to case functionality and the email services.

‘What if’ I thought ‘I were to create an email listener that would read the contents of emails and use those to call my trigger?’. The idea of creating an email service at first was a bit off putting, it seemed too clunky, but the more I thought the more it seemed workable. Also I had no other ideas and a looming deadline, so I went for it.

I decided the emails would contain a very simple XML structure would could be parsed, the data extracted and then used to call my apex class. The workflow rule simple calls the email template that has the XML structure and sends it to the email address that is listening. So I created the email service (really quite easy) and a simple Apex class to handle the data. After a bit of tweaking (there were some restrictions about who was allowed to actually email the service and it was rejecting my workflow user for a while without me knowing it.), it worked like a charm. Here is a the email service listenener that saved the day.

    global Messaging.InboundEmailResult handleInboundEmail(Messaging.InboundEmail email, Messaging.InboundEnvelope envelope) 
    {
        // declare the result variable
        Messaging.InboundEmailResult result = new Messaging.InboundEmailresult();
        
        // declare a string variable to catch our custom debugging messages
        String myErr;
        result.success = true;
        
        try
        {          
            // extract the email body : either the htmlBody or the plainTextBody
            String messageRecipPhone = '';
            String messageBody = '';  
            String messageContent = '';     

            messageBody = email.plainTextBody;
            messageBody = messageBody.trim();

            //Extract just the XML portion of the message
            messageBody = messageBody.substring(messageBody.indexOf('<?xml version="1.0"?>'),messageBody.indexOf('</emailData>')+12);

                        
            messageRecipPhone = readXMLelement(messageBody,'phoneNumber');
            messageRecipPhone = messageRecipPhone.replaceAll('\\D','');        
            
            messageContent = readXMLelement(messageBody,'message');
            
            if(messageContent.length() > 155)
            {
                messageContent = messageContent.substring(0,155);
            }
                                             
            Spartan_SMS_Message__c message = new Spartan_SMS_Message__c(Recipient_Phone_Number__c = messageRecipPhone, Message_Body__c=messageContent);
            insert message;
            
        }
        
        catch(exception e)
        {     
            Spartan_Error_Log__c log = new Spartan_Error_Log__c();           
            log.trace__c = e.getTypeName() + '\n' + e.getCause() + '\n' + e.getMessage();
            insert log;
        }

        return result;
    }

    public static String readXMLelement(String xml, String element)
    {
        String elementValue = 'NOT FOUND'; 
        
        try
        {
	        Xmlstreamreader reader = new Xmlstreamreader(xml);
	        while (reader.hasNext()) 
	        {
	            if (reader.getEventType() == XmlTag.START_ELEMENT && reader.getLocalName() == element)
	            {
	                System.debug('Found SID');
	                reader.next();
	                elementValue = getDecodedString(reader);
	            }         
	            reader.next();
	        }
	        return elementValue;
        }
        catch(exception e)
        {
			Spartan_Error_Log__c log = new Spartan_Error_Log__c();
            log.trace__c = e.getTypeName() + '\n' + e.getCause() + '\n' + e.getMessage() + '\n' + xml;
            insert log;
                    	
        	string err;
        	err = e.getTypeName() + '\n' + e.getCause() + '\n' + e.getMessage() + '\n' + xml;
        	return err;
        }
    }

You might look at that and say ‘but Dan, you didn’t actually invoke another Apex method at all in your code, you just created a record!’. Yes, very observant, but I have a trigger attached to that kind of record that does what I want. Also I could have easily invoked a class there if I wanted to. Also for your parsing pleasure I included a very simple XML parser that can get the value of any attribute when given valid XML and an attribute name. The function is also smart enough to just extract and parse the XML chunk of the email. You can put whatever crap you want before and after, and it won’t care, it will just happily do it’s job.

Also, since debugging email services and the like can be a pain, i created an object just for logging. If anything goes wrong, or I want an easily reviewable log of what happened, I can just create and insert a log record with whatever data I please. I highly recommend doing the same if you are going to be writing an app with any kind of complexity that produces output you might not otherwise get to see.

So there you have it, the best way I’ve found to allow users to choose when an apex class will be invoked and with what data. You simply create a workflow rule that sends an email containing XML data to an email service you have configured that will parse and use the data. If you have any questions feel free to ask, I’m happy to explain but I just didn’t want to drag this out any longer than I had to.

7 responses

  1. shiv

    Is it possible that specific section on standard visual force page should be display when a specific criteria meet ? For example i have three section on my standard page section1, section 2, section3………my section1 has a pick list with values Sec-2 and Sec-3….now i select Sec-2 in picklist than only section2 should be display…

    April 18, 2012 at 1:51 am

  2. Mike Leach

    Your Outbound Message thought process looks correct, and ultimately is the correct solution. By checking the “Send SessionID” checkbox when creating an OM, your listener could then call back to Salesforce using the sessionID to run Apex, query, execute DML, and any number of Web Service API calls.

    September 27, 2012 at 10:52 pm

  3. Anonymous

    i want to call specific apex class method from workflow rule

    January 12, 2015 at 12:11 pm

  4. Checkout this outbound message approach using heroku
    http://www.embracingthecloud.com/2013/01/01/SalesforceCallingApexFromWorkflows.aspx

    January 12, 2015 at 3:32 pm

  5. Great idea. What do you think the performance impact is using this method versus a trigger to execute a class based on a status change? I was thinking about using a WF and leveraging something like this because it seems I can make it more configurable down the road and potentially create any easy to configure method versus forcing the dev team to force more and more triggers as similar reqs are needed.

    April 21, 2015 at 8:14 pm

    • Granted because you are replying on email servers, which are never going to be as fast as something like a direct call to a REST resource, there is a bit of performance loss. Thankfully Salesforce email servers are VERY fast, so I usually have not had to wait more than a few seconds for the entire cycle to complete. Also, this solution was done quite some time ago, I wouldn’t be surprised if there are better solutions these days (especially if Apex has been updated to access HTTP request headers, then the outbound messaging is viable).

      April 21, 2015 at 9:55 pm

Leave a reply to shiv Cancel reply