Download and launch an Elluminate session from the command line

by Patrick Connelly posted on January 07, 2013

We use Elluminate Live! for some of our meetings, and it has always bothered me that I have to launch the browser to use Elluminate especially for reoccurring meetings. So one afternoon I set out to fix this problem. I wrote the following script and have been using it for about a month now without any issues. You can set the parameters at the head of the script if you have a reoccurring meeting you want to use, or set the parameters at run time (or in an alias for multiple reoccurring meetings) if you need too.

The only prerequisite for this script is to have javaws installed an in your PATH.

#!/bin/bash

nick="NICKGOESHERE"
participant_password="PASSWORD"
leader_password="PASSWORD"
sid="SID"
output="/tmp/meeting.jnlp"
launch_on_download="true"

#############################################################################
####                   DO NOT EDIT PAST THIS POINT                       ####
#############################################################################

usage() {
  echo "usage: $0 [-n nick] [-p password] [-s sid] [-f file] [-l] [-j]"
	echo -e "\t-n The username"
	echo -e "\t-p The elluminate 'password'"
	echo -e "\t-s The elluminate 'sid'"
	echo -e "\t-l Use the leader password or the provided password is the leader password"
	echo -e "\t-f The output file [default ./meeting.jnlp]"
	echo -e "\t-j Launch the meeting file"
}

password=""
leader="false"

while getopts "hn:p:s:f:l" opt
do
	case $opt in
		n)
			echo "setting nick $OPTARG"
			nick=$OPTARG
			;;
		p)
			echo "setting password $OPTARG"
			password=$OPTARG
			;;
		l)
			echo "using leader password"
			leader="true"
			;;
		s)
			echo "setting sid $OPTARG"
			sid=$OPTARG
			;;
		f)
			echo "setting output $OPTARG"
			output=$OPTARG
			;;
		j)
			launch_on_download="true"
			;;
		h)
			usage
			exit
			;;
		?)
			usage
			exit
			;;
	esac
done

if [ ".$password" == "." ]
then
	if [ ".$leader" == ".true" ]
	then
		password=$leader_password
	else
		password=$participant_password
	fi
fi

if [ ".$output" == "." ]
then
	output_file="meeting.jnlp"
else
	output_file=$output
fi

post_data=""
url=""
curl_type=""
referer=""

if [ ".$leader" == ".true" ]
then
	#Leader needs to be a GET not a POST.  Plus some different headers
	url="https://sas.elluminate.com/site/external/launch/meeting.jnlp?sid=$sid&miuid=$password"
	curl_type="GET"
	referer="https://sas.elluminate.com/site/external/jwsdetect/meeting.jnlp?sid=$sid&miuid=$password"

	curl -s --insecure \
		-H 'Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' \
		-H 'Accept-Charset:ISO-8859-1,utf-8;q=0.7,*;q=0.3' \
		-H 'Accept-Encoding:gzip,deflate,sdch' \
		-H 'Accept-Language:en-US,en;q=0.8' \
		-H 'Connection:keep-alive' \
		-H 'DNT:1' \
		-H 'Host:sas.elluminate.com' \
		-H "Referer:$referer" \
		-H 'User-Agent:Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.94 Safari/537.4' \
		-X $curl_type \
		$url > $output_file
else
	post_data="username=$nick&sid=$sid&password=$password&submit.x=0&submit.y=0"
	url="https://sas.elluminate.com/site/external/launch/meeting.jnlp"
	curl_type="POST"
	referer="https://sas.elluminate.com/site/external/launch/meeting.jnlp?sid=$sid&password=$password"

	curl -s --insecure \
		-H 'Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' \
		-H 'Accept-Charset:ISO-8859-1,utf-8;q=0.7,*;q=0.3' \
		-H 'Accept-Encoding:gzip,deflate,sdch' \
		-H 'Accept-Language:en-US,en;q=0.8' \
		-H 'Cache-Control:max-age=0' \
		-H 'Connection:keep-alive' \
		-H 'Content-Type:application/x-www-form-urlencoded' \
		-H 'DNT:1' \
		-H 'Host:sas.elluminate.com' \
		-H 'Origin:https://sas.elluminate.com' \
		-H "Referer:$referer" \
		-H 'User-Agent:Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.94 Safari/537.4' \
		-d "$post_data" \
		-X $curl_type \
		$url > $output_file
fi


if [ ".$launch_on_download" == ".true" ]
then
	javaws $output_file
fi

Auto watch a directory and rebuild the repository when an rpm is added/updated

by Patrick Connelly posted on December 19, 2012

Today I started setting up the repository for people to use to install the Solenopsis rpm. The problem is I want to be able to build the rpm (via Jenkins) and push it to a remote server and automatically have the repo rebuild when it sees an rpm updated or added to the directory.

To do this, I wrote a small python script that can be run and backgrounded. It sends a pushover notification and runs createrepo against the target directory.

import re
import httplib, urllib
from subprocess import call

ROOT_DIR = '/path/to/dir'

wm = pyinotify.WatchManager();
mask = pyinotify.IN_CLOSE_WRITE

def pushover(message):
        conn = httplib.HTTPSConnection("api.pushover.net:443")
        conn.request("POST", "/1/messages.json",
        urllib.urlencode({
                "token": "TOKEN",
                "user": "USER",
                "message": message,
        }), { "Content-type": "application/x-www-form-urlencoded" })
        conn.getresponse()

def gen_repo():
        pushover('rebuilding repo')
        call(["createrepo", ROOT_DIR])


class EventHandler(pyinotify.ProcessEvent):
        def process_IN_CLOSE_WRITE(self, event):
                if event.pathname.endswith('.rpm'):
                        gen_repo()

handler = EventHandler();
notifier = pyinotify.Notifier(wm, handler)
wdd = wm.add_watch(ROOT_DIR, mask, rec=True)

notifier.loop()

Nulling fields in Salesforce with SoapUI

by Patrick Connelly posted on August 26, 2012

The Problem

The other day I came across a problem where sending in a blank field to Salesforce via SOAP was not nulling out the field. Instead, the enterprise WSDL was treating this as if nothing was sent, and therefore not updating the field at all. This make sense. If you were to send a sparse data structure over with only fields you want to update, you wouldn’t want to either have to provide the current value of every field or have them all nulled out. So, how do you null out a field with SOAP via the enterprise (or partner) WSDL in Salesforce?

The Solution

This is input is formatted for SoapUI, it may differ depending on the client you are using to send the SOAP message. The key take away is fieldToNull urn.

Lets start with a simple example of updating a field via the Enterprise WSDL

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="urn:enterprise.soap.sforce.com" xmlns:urn1="urn:sobject.enterprise.soap.sforce.com">
   <soapenv:Header>
      <urn:SessionHeader>
         <urn:sessionId>${#Project#sessionid}</urn:sessionId>
      </urn:SessionHeader>
   </soapenv:Header>
   <soapenv:Body>
      <urn:update>
         <!--Zero or more repetitions:-->
         <urn:sObjects xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Contact">
               <urn:Id>${#Project#contactid}</urn:Id>
               <urn:CustomField__c>Yay! Data</urn:CustomField__c>
         </urn:sObjects>
      </urn:update>
   </soapenv:Body>
</soapenv:Envelope>

This will set the CustomField__c on the Contact to “Yay! Data”

To null out this field we simply send the field name as part of the fieldsToNull list

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="urn:enterprise.soap.sforce.com" xmlns:urn1="urn:sobject.enterprise.soap.sforce.com">
   <soapenv:Header>
      <urn:SessionHeader>
         <urn:sessionId>${#Project#sessionid}</urn:sessionId>
      </urn:SessionHeader>
   </soapenv:Header>
   <soapenv:Body>
      <urn:update>
         <!--Zero or more repetitions:-->
         <urn:sObjects xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Contact">
               <urn:Id>${#Project#contactid}</urn:Id>
               <urn:fieldsToNull>CustomField__c</urn:fieldsToNull>
         </urn:sObjects>
      </urn:update>
   </soapenv:Body>
</soapenv:Envelope>

Dynamic dependent picklists in Salesforce

by Patrick Connelly posted on July 09, 2012

One thing that comes up a lot in the in the #salesforce IRC channel is doing dynamic Visual Force driven off of picklists. So, let’s buckle up and get to it.

Data Model

In this simple example we are going to make an extension to the case page. On this page we are going to us a custom Product/Version object to display on the page. The product list well be determined on the start/end date of the product. And the version will be driven by the currently selected product. Product

  • Name – The name of the product
  • Currently_Supported__c – Formula based on StartDate__c and EndDate__c (Integer version of a boolean)
  • StartDate__c – The date the product should be shown (Rollup min from the version)
  • EndDate__c – The date the product should be hidden (Rollup max from the version)

Version

  • Name – The name of the version
  • Currently_Supported__c – Formula based on StartDate__c and EndDate__c (Integer version of a boolean)
  • StartDate__c – The date the version should be shown
  • EndDate__c – The date the version should be hidden
  • Product__c – The product the version is related to

Apex Controller

public with sharing class ProductUtils {
    static public List<Product__c> getAllProducts(Boolean includeEOL) {
        //This is done since the formula field cannot return a boolean
        Integer currentlySupported = (includeEOL) ? 0 : 1;

        return [
            select Name
            from Product__c
            where Currently_Supported__c >= :currentlySupported
            order by Name
        ];
    }

    public static List<Product__c> getAllProducts() {
        return getAllProducts(false);
    }

    public static List<Version__c> getAllVersions(Id productId, Boolean includeEOL) {
        Integer currentlySupported = (includeEOL) ? 0 : 1;

        return [
            select Name,
                Product__c
            from Version__c
            where Currently_Supported__c >= :currentlySupported and
                Product__c = :productId
            order by Name
        ];
    }

    public static List<Version__c> getAllVersions(Id productId) {
        return getAllVersions(productId, false);
    }
}

These methods are simple util methods to get product information and version information

global with sharing class CaseEdit_ControllerExtension {
    private final Id recordId;
    private final Case record;
    private final ApexPages.StandardController controller;

    public Case_ControllerExtension(ApexPages.StandardController stdController) {
        this.controller = stdController;
        this.recordId = this.controller.getId();
        this.record = [
            select Product__c,
                Subject,
                Version__c
            from Case
            where Id = :this.recordId
            limit 1
        ];
    }

    public List<SelectOption> getProductList() {
        List<SelectOption> products = new List<SelectOption>();
        products.add(new SelectOption('', '--None--'));

        for (Product__c p: ProductUtils.getAllProducts()) {
            products.add(new SelectOption(p.Id, p.Name));
        }

        return products;
    }

    public Id getProduct() {
        return this.record.Product__c;
    }

    public void setProduct(Id productId) {
        this.record.Product__c = productId;
    }

    public List<SelectOption> getVersionList() {
        List<SelectOption> versions = new List<SelectOption>();
        versions.add(new SelectOption('', '--None--'));

        if (record.Product__c != null) {
            for (Version__c v: ProductUtils.getAllVersions(getProduct())) {
                versions.add(new SelectOption(v.Id, v.Name));
            }
        }

        return versions;
    }

    public Id getVersion() {
        return this.record.Version__c;
    }

    public void setVersion(Id versionId) {
        this.record.Version__c = versionId;
    }

    public PageReference doSave() {
        Case c = (Case) controller.getRecord();
        c.Product__c = this.record.Product__c;
        c.Version__c = this.record.Version__c;

        upsert c;

        return new PageReference('/'+c.Id);
    }
}

The controller has the getters and setters for the product and version. But most importantly the getters for productList and versionList. The versionList is triggered off the record’s product. The other part of this is that for whatever reason (I couldn’t find a good one) is the getRecord does not include the changes made to the Product__c and Version__c field, so you’ll need to set them by hand in the doSave method.

One thing to note is since this is all done in the controller extension and since get and set use the Id, the select list will have the correct thing set when editing an existing record.

Visual Force Page

<apex:page standardController="Case" extensions="CaseEdit_ControllerExtension" title="Case Edit" tabStyle="Case">
    <apex:form id="form">
        <apex:pageBlock title="Case Edit">
            <apex:pageBlockButtons>
                <apex:commandButton action="{!doSave}" value="Save" />
                <apex:commandButton action="{!cancel}" value="Cancel" />
            </apex:pageBlockButtons>
            <apex:pageBlockSection title="Case Information">
                <apex:inputField value="{!Case.Summary}" required="true" />
                <apex:pageBlockSectionItem>
                    <apex:outputLabel for="productList" value="{!$ObjectType.Case.fields.Product__c.label}" />
                    <apex:actionRegion>
                        <apex:selectList value="{!product}" title="Product" size="1" id="products">
                            <apex:selectOptions value="{!productList}" />
                            <apex:actionSupport event="onchange" rerender="versions" />
                        </apex:selectList>
                    </apex:actionRegion>
                </apex:pageBlockSectionItem>
                <apex:pageBlockSectionItem>
                    <apex:outputLabel for="versions" value="{!$ObjectType.Case.fields.Version__c.label}" />
                    <apex:actionRegion>
                        <apex:selectList value="{!version}" title="Version" size="1" id="versions">
                            <apex:selectOptions value="{!versionList}" />
                        </apex:selectList>
                    </apex:actionRegion>
                </apex:pageBlockSectionItem>
            </apex:pageBlockSection>
        </apex:pageBlock>
    </apex:form>
</apex:page>

The key parts of this is that the actionRegion surrounds both the item changing (product) and the dependent item (version). If you had a third picklist you wanted to trigger on you could add another actionSupport item and tell it to rerender that third list.

Conclusion

Dependent picklists are not very hard to do as long as you remember the actionRegion around both the source and target, and making sure to get the data from the picklist prior to upserting your record.


Swapping two files with a quick bash function

by Patrick Connelly posted on July 03, 2012

One thing I find myself doing a lot is swapping two files in bash. I thought about making this into a bash script then I realized 1) that’s over kill and 2) not as portable as I want. So, if you add this to your .bashrc then re-source it, you’ll be able to run the command swap to switch two files

function swap() {
  TMP_NAME="TMP_$RANDOM"
  mv "$1" "/tmp/$TMP_NAME" && mv "$2" "$1" && mv "/tmp/$TMP_NAME" "$2"
}

Scheduled actions in Salesforce with Apex

by Patrick Connelly posted on May 26, 2012

Scheduled actions in Apex are great to use when you need to have a section of code run at a particular time in the future and Time-Based workflows will not work. In the example below I’ll talk about how to schedule code to run at the first of every month, in addition talk about some constructs you can use to make your life easier when you have to redeploy/change this code

Schedulable

The key part of the apex class is that it must implement Schedulable and it must have an excecute method.

global class scheduledMonthly implements Schedulable {
    /**
    * Builds up all of the new Objects
    *
    * @param sc The schedulable context
    */
    global void execute(SchedulableContext sc) {
        //Code goes here
    }
}

Now, we can fill out the class. Below is an example where we iterate through all of the accounts of a specific record type and insert a new list of MyObject based on that account. This could be as complex as you want.

global class scheduledMonthly implements Schedulable {
    /**
    * Builds up all of the new Objects
    *
    * @param sc The schedulable context
    */
    global void execute(SchedulableContext sc) {
        RecordType rt = [
            select Id
            from RecordType
            where DeveloperName = 'Recipient'
        ];

        List<MyObject__c> objectList = new List<MyObject__c>();

        //Get all of the accounts of type 'Recipient'
        for (Account account: [
            select Id
            from Account
            where RecordTypeId = :rt.Id
        ]) {
            objectList.add(new MyObject__c(
                Account__c = account.Id
            ));
        }

        if (!objectList.isEmpty()) {
            insert objectList;
        }
    }
}

To schedule this class we could call it from any number of places, such as a VisualForce page, a Trigger or the Developer Console. Since scheduled classes cannot be pushed out or changed when there are jobs in the queue we want to add a helper method to schedule this job. In our instance it will only be ran once a month, so we include our CRON_EXP as a static variable (for easy use) and to reduce the change of mis-scheduling.

/**
* To schedule the monthly reconciliation:
*    NOTE: It should run at midnight on the first of every month on it's own, but if you make
*    changes and need to requeue run the command below from the developer's console
*
*    scheduledMonthly.scheduleIt();
*/

global class scheduledMonthly implements Schedulable {
    public static String CRON_EXP = '0 0 0 1 * ? *';

    /**
    * Static method used to schedule the default reconciliation
    *
    * @return The jobId from the scheduled run
    */
    global static String scheduleIt() {
        scheduledMonthly sm = new scheduledMonthly();
        return System.schedule('Monthly Reconciliation', CRON_EXP, sm);
    }

    /**
    * Builds up all of the new Monthly Reconciliations and Distributions
    *
    * @param sc The schedulable context
    */
    global void execute(SchedulableContext sc) {
        RecordType rt = [
            select Id
            from RecordType
            where DeveloperName = 'Recipient'
        ];

        List<MyObject__c> objectList = new List<MyObject__c>();

        //Get all of the accounts of type 'Recipient'
        for (Account account: [
            select Id
            from Account
            where RecordTypeId = :rt.Id
        ]) {
            objectList.add(new MyObject__c(
                Account__c = account.Id
            ));
        }

        if (!objectList.isEmpty()) {
            insert objectList;
        }
    }
}

To reset our schedule all we have to do is use type the following into the Developer console.

scheduledMonthly.scheduleIt();

And by looking in Setup → Monitoring → Scheduled Jobs we can see that our scheduledMonthly class is there.

Cron Syntax

There are seven fields for Salesforce’s cron syntax, unlike *nix’s 5 fields.

  • Seconds [0-59]
  • Minutes [0-59]
  • Hours [0-23]
  • Day of month [1-31]
  • Month [1-12 or JAN-DEC]
  • Day of week [1-7 or SUN-SAT]
  • Year [1970-2099]

There are also some special characters you can use:

  • , used to delimit values [hours, day of month, month, day of week, year]
  • - used to specify a range [hours, day of month, month, day of week, year]
  • * used to specify all values [hours, day of month, month, day of week, year]
  • ? used to specify no specific value [day of month, day of week]
  • / used to specify increments [hours, day of month, month, day of week, year]
  • L used to specify the end of a range [day of month, day of week]
  • W used to specify the nearest weekday [day of month]
  • # used to specify the nth day of the month [day of week]

Testing

Testing for scheduled apex may seem confusing but it’s very straight forward. Like @future calls, scheduled apex will not fire until after the _Test.stopTest()_has been run. In the test below we test the following:

  • The Cron Expression is the same
  • The job has not been triggered yet
  • The scheduled date is correct
  • No MyObjects were created
  • One object was created after the scheduled job was ran
@isTest
class scheduledMonthlyTest {
    public static RecordType fetchRecordType(String name) {
        return [
            select Id
            from RecordType
            where DeveloperName = :name
        ];
    }

    public static Account getAccount(RecordType rt) {
        return getAccount(rt, '_unittest_account_: 001');
    }

    public static Account getAccount(RecordType rt, String name) {
        return new Account(
            Name = name,
            RecordTypeId = rt.Id
        );
    }

    public static CronTrigger fetchCronTrigger(String jobId) {
        return [
            select CronExpression,
                TimesTriggered,
                NextFireTime
            from CronTrigger
            where Id = :jobId
        ];
    }
    public static Map<Id, List<MyObject__c>> fetchMyObjects(List<Account> accts) {
        Map<Id, List<MyObject__c>> result = new Map<Id, List<MyObject__c>>();

        for (Account a: accts) {
            result.put(a.Id, new List<MyObject__c>());
        }

        for (MyObject__c mo: [
            select Account__c
            from MyObject__c
            where Account__c in :result.keySet()
        ]) {
            result.get(mo.Account__c).add(mo);
        }
    }

    static testMethod void testScheduledMonthly() {
        RecordType rt = fetchRecordType('Recipient');

        Account testAccount = getAccount(rt);
        insert testAccount;

        Test.startTest();

        String jobId = System.schedule(
            '_unittest_scheduled_: 001',
            scheduledMonthly.CRON_EXP,
            new scheduledMonthly()
        );

        CronTrigger ct = fetchCronTrigger(jobId);

        System.assertEquals(
            scheduledMonthly.CRON_EXP,
            ct.CronExpression,
            'Did not get the same Cron Expression back'
        );
        System.assertEquals(
            0,
            ct.TimesTriggered,
            'The job has been run and should not have'
        );

        DateTime today = DateTime.now();
        String dateString = '' +
            today.year() + '-' +
            today.addMonths(1).month() +
            '-01 00:00:00';
        System.assertEquals(
            String.valueOf(DateTime.valueOf(dateString)),
            String.valueOf(ct.NextFireTime),
            'Did not get the right fire date'
        );

        List<MyObject__c> myObjs = fetchMyObjects(new List<Account>{testAccount}).get(testAccount.Id);
        System.assert(
            myObjs.isEmpty(),
            'Should have gotten no objects back'
        );

        Test.stopTest();

        myObjs = fetchMyObj(new List<Account>{testAccount}).get(testAccount.Id);
        System.assert(
            1,
            myObjs.size(),
            'Did not get the right number of objects back'
        );
    }
}

Limitations / Notes

There are three big limitations / notes about scheduled Apex

  • It will be put on the queue at the given time, it is not guaranteed to run at that time.
  • You can only have 25 classes scheduled at a time
  • You cannot use getContent or getContentAsPDFPageReference

Salesforce and soapUI – Using the default query method

by Patrick Connelly posted on April 13, 2012

In a previous post I discussed how to test Salesforce webservices with soapUI. In this post I will show how to use the “default” methods inside the enterprise WSDL.

Logging In

First we need to login to Salesforce and get our session Id. Under the SoapBinding list, expand login and choose Show Request Editor. After opening the request editor we need to remove the extra headers we don’t need, and fill in our username and password.

Logging In

Then press the “play” button to send the request

Login request play button

Now in the resulting XML we can pull out our session Id

Session Id

And we can pull out our server Url

Server Url

Adding the new endpoint

If we create a new query request and remove the unneeded headers and insert our session Id and run the request you get the following error:

UNKNOWN_EXCEPTION: Destination URL not reset. The URL returned from login must be set in the SforceService

To fix this issue we need to add a new end point to our SOAP request. Using the server Url obtained during login we can add it to our request

New end point

And now we can rerun our new request with the correct endpoint

New end point

Conclusion

Unlike custom webservices which include the Salesforce endpoint as part of the WSDL the standard Salesforce enterprise WSDL only has the test or login url included. Because of this, we need to use the returned server url to set our end point.


Reducing Salesforce SOQL queries by using static variables

by Patrick Connelly posted on April 04, 2012

The more moving pieces you have with triggers and classes the more you want to reduce the number of SOQL queries. One way to do this is to have Utility classes that do a lot of the heavy lifting. The problem with this is that you don’t want to call a utility method that does a query every time, because if you call it from different triggers you’ll end up with multiple calls. This is where overloading static variables can come in.

The Problem

Lets say you have a trigger on a Contact that needs information from our mostly static MyObject__c and then the Contact trigger then updates a Case. The Case trigger also need information from the MyObject__c. Normally this would require two SOQL queries even if it was in a utility class. We can use some of the built-in functionality in Apex to overload a static variable.

The Class

public with sharing class MyObjectUtils {
     public static List<MyObject__c> myObjectList {
          get {
               if (myObjectList == null) {
                    myObjectList = [
                         select Name
                         from MyObject__c
                    ];
               }

               return myObjectList;
          }
          set;
     }

     public static Map<String, MyObject__c> myObjectMap {
          get {
               if (myObjectMap == null) {
                    myObjectMap = new Map<String, MyObject__c>();
                    for (MyObject__c obj: myObjectList) {
                         myObjectMap.put(obj.Name, obj);
                    }
               }

               return myObjectMap;
          }
          set;
     }
}

The Implementation

In the trigger where we want to use the MyObject instance we can do the following

MyObject__c obj = MyObjectUtils.myObjectMap.get('Foo');

Now, the next time the map or list are used in the same execution we will have it “cached” and will not have to make an additional query.


Classifying Triggers in Salesforce

by Patrick Connelly posted on February 13, 2012

Anyone that has ever had multiple triggers on objects in Salesforce knows that it can be very painful to manage them. Because of the way Salesforce chooses to run the triggers your code can be run in a non-deterministic order. In addition to this, having to sort through multiple files to find the one piece of code you are looking to update can be painful.

To combat this, you can take your triggers and condense them down into a single trigger and a single class. Inside this class you would have a method containing each of your individual triggers.

Preface

In the examples below we will be creating a trigger on the MyObject__c to do awesomeness. In the example we do not cover the case of undelete.

The Trigger

The trigger is quite simple, all it does is call the a static method of the class with the correct parameters.

trigger MyObject on MyObject__c (before insert, before update, before delete, after insert, after update, after delete) {
     MyObjectTrigger.processTrigger(Trigger.oldMap, Trigger.new, Trigger.isBefore);
}

The Class

This is where the meat of the functionality exists. The constructor sets up the maps and lists as well as the booleans. Inside your doAwesomeness method you can check to the booleans isUpdate, isDelete, isInsert to make your routing determination. If you do not want the method to run, just return out of it and the execution will stop.

public class MyObjectTrigger {
     private final Map<Id, MyObject__c> oldMap;
     private final Map<Id, MyObject__c> newMap;
     private final List<MyObject__c> newObjs;
     private final Boolean isInsert;
     private final Boolean isUpdate;
     private final Boolean isDelete;
     private final Boolean isBulk;

     /**
     * The constructor
     *
     * @param xoldMap The old map from the trigger
     * @param xnewObj The list of new objects from the trigger
     * @param isBefore If the trigger is in before or after
     */
     public MyObjectTrigger(Map<Id, MyObject__c> xoldMap, List<MyObject__c> xnewObjs, Boolean isBefore) {
          oldMap = xoldMap;
          newObjs = xnewObjs;

          if (!isBefore && newObjs != null) {
               newMap = new Map<Id, MyObject__c>(newObjs);
          }

          isDelete = (((newObjs == null || newObjs.isEmpty()) && isBefore) || ((newMap == null || newMap.isEmpty()) && !isBefore));
          isUpdate = ! (isDelete || oldMap == null || oldMap.isEmpty());
          isInsert = ! (isDelete || isUpdate);
          isBulk = (((!isDelete) && (newObjs.size() > 1)) || ((isDelete) && (oldMap.size() > 1)));
     }

     public void doAwesomeness() {
          //Do stuff
     }

    /**
    * Method to initiate trigger logic
    *
    * @param oldMap The old map from the trigger
    * @param newObj The list of new objects from the trigger
    * @param isBefore If the trigger is in before or after
    */
     public static void processTrigger(Map<Id, MyObject__c> oldMap, List<MyObject__c> newObj, Boolean isBefore) {
          final MyObjectTrigger myTrigger = new MyObjectTrigger(oldMap, newObj, isBefore);

          if (isBefore) {
               myTrigger.doAwesomeness();
          }
     }
}

Conclusion

We’ve been using this method for almost a year now and it works really well. If you need data to persist between methods this way works wonderfully. Just create a global variable and set it up in your constructor. This will save you SOQL calls and if done correctly could save you DML operations


Vim and Salesforce development

by Patrick Connelly posted on February 09, 2012

Since I switched over to using vim as my primary mode of Salesforce development, I’ve been asked several times how I’ve configured vim. Well, it’s about time I show the man behind the curtain. My primary vim config file is quite large now, but I’ve condensed it down to the parts that I think are most pertinent to Salesforce development.

Highlighting

Now vim is great for editing but where I think it shines the most is in highlighting. I’ve taken the standard java vim file and have modified it for use with the Apex language. To use it, download the apex.vim file and place it in your syntax directory for vim.

Local

To make it work for just your user you can place the file in ~/.vim/syntax/ but you will need to also symlink (or copy) the html.vim file into that directory.

ln -s /usr/share/vim/vim73/syntax/html.vim ~/.vim/syntax/html.vim

Global

To make it work for all users on the system you can just place the file into your global syntax directory. For fedora and probably most unix systems it is /usr/share/vim/vim73/syntax/

.vimrc

Now that we’ve got highlighting installed, lets get vim to use it. First we need to create our backup and swap directories. We do this to keep extra files out of our Salesforce directory so that we don’t try to deploy them (or commit them to our repo).

mkdir ~/.bak ~/.swp

Now we can add / replace the following options into our .vimrc file

You might need to edit the values for tabstop, shiftwidth and softtabstop for your coding standards but 4 is what we use.