My laptop is 10 times faster

This Salesforce blog post Apex Runtime Update for Developer Edition Orgs describes a significant change in how Developer Edition orgs execute Apex code, and this 3rd party blog post by Alex Berg Salesforce’s Apex Runtime Design – Old vs New provides more explanation. However I couldn’t find any statement about when this change will be rolled out into production orgs.

Before this change the performance of Apex when executing CPU intensive logic in an application was surprisingly poor – see My laptop is 300 times faster. The good news is that running the same test today in a na3 Developer Edition org multiple times (to allow bytecode caching) resulted in an average time of 208ms. Hence the new conclusion that my laptop is only 10 times faster.

This is a dramatic improvement, making Force.com a more practical platform for executing CPU intensive logic as long as the logic remains within the “total number of executed code statements” governor limit.

Outputting a URL that has keys that only differ by case

There is a problem with the code in Hack to find field ids – allows a default UI “New” page to be pre-populated related to case sensitivity. The field ids are case sensitive so it is possible (and if your object has many fields likely) that field ids may only differ by case. Such fields are not correctly defaulted in the “New” page.

While specifications like RFC 3986 suggest that query string keys can be assumed to be case sensitive, the PageReference class in Force.com works hard to ensure that query string keys are not case sensitive. These tests illustrate the problem:

@isTest
static void testMap() {
    Map<String, String> m = new Map<String, String>();
    m.put('00N50000002ldUw', 'abc');
    m.put('00N50000002ldUW', '123');
    // Assertions are as expected
    System.assertEquals('abc', m.get('00N50000002ldUw'));
    System.assertEquals('123', m.get('00N50000002ldUW'));
}
@isTest
static void testParameters() {  
    PageReference p = new PageReference('/');
    Map<String, String> m = p.getParameters();
    m.put('00N50000002ldUw', 'abc');
    m.put('00N50000002ldUW', '123');
    // Assertions illustrate that keys that differ by case are not distinguished
    System.assertEquals('123', m.get('00N50000002ldUw'));
    System.assertEquals('123', m.get('00N50000002ldUW'));
}

There does not appear to be a work-around using PageReference; building the entire URL string manually and then passing it into the constructor doesn’t help, nor does replacing the alphabetic characters with their URL encoded form. The other standard practice of replacing a (case sensitive) 15 digit id with the (case insensitive) 18 character version doesn’t work in this case because the “New” page appears to be doing literal string matching of the ids and so is expecting the 15 character version.

A way of avoiding the PageReference class is to build the URL as a string and then use JavaScript to redirect to the URL. These changes to the code in Hack to find field ids – allows a default UI “New” page to be pre-populated fix the case problem and result in all fields being defaulted. The controller returns a URL string:

private String createUrl() {
    String url = '/' + DescribeSObjectResultCache.get(Abc__c.SObjectType).getKeyPrefix()
            + '/e'
            + '?retURL=' + ApexPages.currentPage().getParameters().get('retURL')
            + '&nooverride=1'
            ;
    Map m = createDefaultValues();
    for (String key : m.keySet()) {
        url += '&' + key + '=' + EncodingUtil.urlEncode(m.get(key), 'UTF-8');
    }
    return url;
}
public String url {
    get {
        if (url == null) {
            url = createUrl();
        }
        return url;
    }
    private set;
}

and the page is rendered but immediately redirects to the URL supplied by the controller:

<apex:page standardController="Abc__c" extensions="AbcNewController" showHeader="false" sidebar="false" standardStylesheets="false">
<script type="text/javascript">
window.onload = function() {
    window.location = '{! url }';
}
</script>
</apex:page>

Hack to find field ids – allows a default UI “New” page to be pre-populated

PS There is now an API – albeit one that requires a REST call – that allows the field ids to be obtained. See Querying Custom Object and Field IDs via Tooling API.

The default (layout-based) page presented when a “New Abc” button is clicked for a custom object “Abc” supports the passing of default values for the input fields via the URL. But the field id/name values required vary from org to org and there is no platform API to obtain the values. (This is the same problem described in another post on this blog nearly two years ago Lkid hack value is fragile so API support for this is unlikely to appear anytime soon.)

So where you have business logic that can default some values for the user, it is hard to create a portable solution. Here is a hack around the problem.

The approach accepts that the only place the required id values can be found is in the HTML itself and so obtains the HTML for the default edit page and uses a regular expression to pull out pairs of field labels and id values. The describe APIs can then be used to relate the labels to the underlying fields. (The method getDefaultValue is where the business logic to default the values goes.) A little formatting is needed to create the right parameter value strings:

private Map<String, String> createDefaultValues() {
    // Obtain the magic ids
    PageReference p = new PageReference('/' + Abc__c.SObjectType.getDescribe().getKeyPrefix() + '/e?nooverride=1');
    String html = p.getContent().toString();
    Map<String, String> labelToId = new Map<String, String>();
    Matcher m = Pattern.compile('<label for="(.*?)">(<span class="requiredMark">\\*</span>)?(.*?)</label>').matcher(html);
    while (m.find()) {
        String label = m.group(3);
        String id = m.group(1);
        labelToId.put(label, id);
    }
    // Relate the magic ids to the SObject fields
    Map<String, String> params = new Map<String, String>();
    for (SObjectField field : Abc__c.SObjectType.getDescribe().fields.getMap().values()) {
        Object value = getDefaultValue(field);
        if (value != null) {
            DescribeFieldResult f = field.getDescribe();
            String label = f.getLabel();
            if (labelToId.containsKey(label)) {
                // Format
                String valueAsString;
                if (f.getType() == DisplayType.Date) {
                    valueAsString = ((Date) value).format();
                } else if (f.getType() == DisplayType.Datetime) {
                    valueAsString = ((Datetime) value).format();
                } else if (f.getType() == DisplayType.Boolean) {
                    valueAsString = ((Boolean) value) ? '1' : '0';
                } else {
                    valueAsString = String.valueOf(value);
                }
                params.put(labelToId.get(label), valueAsString);
            }
        }
    }
    return params;
}
private Object getDefaultValue(SObjectField field) {
    // Defaulting business logic goes here
}
public PageReference url() {
    PageReference p = new PageReference('/' + Abc__c.SObjectType.getDescribe().getKeyPrefix() + '/e');
    Map<String, String> m = p.getParameters();
    m.putAll(createDefaultValues());
    m.put('nooverride', '1');
    m.put('retURL', ApexPages.currentPage().getParameters().get('retURL'));
    return p;
}

Then this Visualforce page is set to override the “New” “Standard Button or Link” for the “Abc” object. The page executes the above logic (but does not output any HTML) and then that logic returns the URL of the default “New” page but complete with default values for any fields that getDefaultValue returned a non-null value for:

<apex:page standardController="Abc__c" extensions="AbcNewController" action="{! url }"/>

Some words of caution:

  • It is not possible to unit test the part of this that obtains the magic id values because the testing framework does not support the getContent call. Instead the testing framework logs this message “Methods defined as TestMethod do not support getContent call, test skipped”.
  • This logic will break if salesforce change the label HTML that is emitted to something that the regular expression does not match.
  • Long (say greater than 2k) GET URLs may not work in all browsers and through all proxy servers. So consider the URL length when using large numbers of fields or fields with large values in them.

PS I hit one more gotcha related to case sensitivity in the ids; see the code changes in Outputting a URL that has keys that only differ by case that address this problem.

PPS Recently used this in a page where the logic failed. The problem is in the regular expression. While the term “(.*?)” is nominally non-greedy it was matching much more than just the field id. Changing this term to “(\\w+?)” fixes the problem because this only matches the characters A-Z, a-z, 0-9 (and underscore) which is what the field id is composed of so that the match stops immediately at the quotes surrounding the id.

When runAllTests=”false” actually means runAllTests=”true”

After several deployments into test environments, we deployed into a customer’s production environment yesterday. One of the deployment steps after installing some managed packages is to push several profiles into the target org using the Ant deploy task with a package.xml that just includes the profiles. It was an unwelcome surprise that all the unit tests in the production org ran. These are tests written by a third party that could have had dangerous side effects; in previous deployments this had not happened and runAllTests=”false” being present in the Ant script suggested it should not.

The explanation is in the Ant deploy runAllTests documentation:

This parameter is ignored when deploying to a Salesforce production organization. Every unit test in your organization namespace is executed.

Whatever the motivation for this behavior, I suggest that returning an error message (containing this text) when runAllTests=”false” is specified for a production org would be a better approach to handling the situation than just ignoring the attribute and running the tests.