Salesforce: Enhanced Custom Settings

Okay book’s done, now where were we? Oh yes software development, right? Programming software engineering application development h4x0R-ing. Oh how I’ve missed getting my mitts dirty so without further ado…

Now this one goes right about... here!

Some time back Custom Settings were introduced on the Force.com Platform and we all star-jumped in the air, w00ting to anyone who would listen. Up till this point – if you’re anything like me – you were using custom objects to hold configuration data, whether this be lists of language-codes, or operational settings such at outbound web service endpoints, usernames, passwords etc. With Custom Settings you finally had a place to put this information – a home if you will – for your lonely, orphaned Control Data.

Quite quickly however I realised there was still a gaping hole that could be filled with Custom Settings but just didn’t feel right. Lists of data (such as currency codes and descriptions) fit really well into this structure but more serious Control Data that you only need to be listed once-off (such as important URLs, flags to active/deactive modules in your application, usernames and passwords) just don’t seem like they really belong with this other crowd. A quick list of reasons highlights this:

  • Control Data is typically entered once off and creating an entire Custom Setting for a single line of data feels like a waste.
  • Custom Settings are data so they can’t be deployed with code, they must be created after the fact. Control Data should be a little more important than regular data, it needs a smarter vehicle than plain-old data entry.
  • If you’re creating packages you want as much autonomy for your clients as possible. If you use custom settings there will have to be that “Create data in Custom Setting X__c” step in each and every deployment.

Whilst feeding the swans one day the solution hit me like a yellow-septic-tank-cleaning lorry. Java (and other languages) had solved this issue many years before by using plaintext files typically termed properties-files. All these files contain are markup that assigns some value(s) to a key; in their simplest form all they’ll use is:

[code language=”text”]
key=value
[/code]

Realising this, the other pieces of the puzzle become quite obvious; to implement properties files (let’s call them Enhanced Custom Settings) on the Force.com Platform we need:

  1. A plaintext file with key-value pairs in whatever markup method you prefer
  2. A static resource that holds this file (and serves as the Enhanced Custom Setting data)
  3. Apex code to parse the values out of the file

Enhanced Custom Settings File

This is quite simple. In my example I’ve used “key=value” markup with some setting per line. My intention is that empty lines and comment lines (those starting with ‘#’) will be ignored.

[code language=”text”]
# Throw in a few key-value pairs
# You can even give them context using dot-notation, hooray!

package.url=http://th3silverlining.com
package.username=starMonkey
package.password=h4X0r

webservice1.url=http://salesforcehandbook.wordpress.com
webservice1.active=0

testMode=0
[/code]

Utility Parser Class

Now this guy’s responsibility will be to read the static resource and then chunk it up into a neat dictionary (using a Map). Note how I use String.split() instead of for-loops to do the parsing. Can you guess why?

[code language=”java”]
public class EnhancedCustomSettings {
private final String RESOURCE_NAME = ‘EnhancedCustomSettings’;

// The lines of the properties/settings files broken on the newline char
private List<String> bodyLines = new List<String>();

// The map of our enhanced custom settings
public Map<String,String> settings{get;set;} {settings = new Map<String,String>();}

public StaticResource theStaticResource{get;set;}

public EnhancedCustomSettings(){
try{
theStaticResource = [SELECT id, Body FROM StaticResource WHERE name = :RESOURCE_NAME];

parseCustomSettings(theStaticResource.body.toString());
}catch(System.QueryException e){
System.debug(e);
// You should always let the admins/devs know if
// something unexpected happened.
// ExceptionUtils.mail(Administrator);
}
}

/* You’ll notice I make prolific use of String.split(). This method
* is incredibly useful at creating iterators over large amounts of data.
* I guarantee you’ll use at most 50% of the scripted lines by replacing
* for-loops with .split() in most situations.
*/
public void parseCustomSettings(String bodyText){
System.debug(‘Body: ‘+bodyText);
// Break the body into it’s constituent lines
List<String> bodyLines = bodyText.split(‘\n’);

for(String line: bodyLines){
// Ignore comment and empty lines
if(line.startsWith(‘#’) || line.trim().length() == 0){
continue;
}

// Split the key-value pair
List<String> pair = line.trim().split(‘=’);
// Assign them a position in the Map
settings.put(pair[0], pair[1]);
}
}
}
[/code]

Example Usage

Let’s look at an example of where this might be useful, and how it could be applied.

[code language=”java”]
public with sharing class EnhancedCustomSettingsController {
public EnhancedCustomSettings ecs{get;set;} {ecs = new EnhancedCustomSettings();}
public String message{get;set;}

public EnhancedCustomSettingsController(){
String endPoint = ecs.settings.get(‘webservice1.url’);
// Some turnary love – this statement translates the string value to boolean
Boolean active = ecs.settings.get(‘webservice1.active’) == ‘1’ ? true : false;

// Only make the call if the service has been set as
// active in the enhanced custom setting.
if(active){
HttpRequest req = new HttpRequest();
req.setEndpoint(endPoint);
req.setMethod(‘GET’);

Http http = new Http();
HTTPResponse res = http.send(req);

message = res.getBody();
}else{
message = ‘Service is inactive’;
}
}
}
[/code]

What’s in it for Me?

And that’s how easy it is. So what do we get for this extra effort?

  • Your Enhanced Custom Settings are independent of user data.
  • You can DEPLOY these Enhanced Custom Settings in the form of Static Resources – either in your typical deployments or in your packages (how do you like them apples?!)
  • For application components such as Control Variables, this approach makes a better fit than traditional Custom Settings.

Some things to look out for though:

  • You cannot update Static Resource bodies from Apex. From the Metadata API, yip. From the Web Services API, most certainly. But not from within your Apex code.
  • Static Resources are not query-able as Guest Users i.e. you cannot issue a [SELECT id FROM StaticResource] if your pages are going to be used in a Force.com Sites page. Everywhere else you’re good to go.

25 thoughts on “Salesforce: Enhanced Custom Settings”

  1. This post reminded me of all those java properties files, I miss all that action in Apex for sure. I liked the idea of custom setting’s data being available as static resource, no more dependency on admin being creating a managed value for custom setting, after installing packages.

    Great post Wes !

    Reply
  2. This post reminded me of all those java properties files, I miss all that action in Apex for sure. I liked the idea of custom setting’s data being available as static resource, no more dependency on admin being creating a managed value for custom setting, after installing packages.

    Great post Wes !

    Reply
  3. This post reminded me of all those java properties files, I miss all that action in Apex for sure. I liked the idea of custom setting’s data being available as static resource, no more dependency on admin being creating a managed value for custom setting, after installing packages.

    Great post Wes !

    Reply
  4. Very, very clever. Only downside might be somewhat less visibility for the values (harder to explain to a non-techie how to change a value in a Static Resource vs. clicking through the Custom Settings pages). But worth it for the ability to deploy the values.

    Reply
  5. Another nice thing about this if you are using this in an AppExchange app is you can deploy new settings in a push upgrade since you are just editing text. As an object, you’d have to put out a new release and ask your customers to upgrade.

    Reply
  6. Reading StaticResource is easy enough… but how about creating one programmatically? The API documentation says that it supports create() and update(), but it certainly isn’t working for me.

    Any tips/tricks/pointers for creating a StaticResource from inside my code and saving it?

    Jerry H.

    Reply
  7. If you don’t care about supporting multiple languages you can just use Custom Labels, then you can refer to them as Label.My_Cool_Setting, configure them via the UI, and deploy them.

    Reply
  8. If you don’t care about supporting multiple languages you can just use Custom Labels, then you can refer to them as Label.My_Cool_Setting, configure them via the UI, and deploy them.

    Reply
  9. The problem with using static resources is that once you ship it with a managed package, the subscriber admin cannot modify it. As with all properties file, what you are looking for is configurability with soime defaults. Perhaps this could be shiipped as a document object in a managed package – they can be replaced. The admin would download the existing document and replace it with the config they want.

    I was experimenting with hierarchical custom settings and perhaps that another option. You can defile a custom setting where each field has a default value. You can just use the org wide default portion of it and ignore profile/user level overrides.. But it doesn’t seem like salesforce instantiates the org wide instance automatically – you still have to click the “New” button once.

    Reply
  10. The problem with using static resources is that once you ship it with a managed package, the subscriber admin cannot modify it. As with all properties file, what you are looking for is configurability with soime defaults. Perhaps this could be shiipped as a document object in a managed package – they can be replaced. The admin would download the existing document and replace it with the config they want.

    I was experimenting with hierarchical custom settings and perhaps that another option. You can defile a custom setting where each field has a default value. You can just use the org wide default portion of it and ignore profile/user level overrides.. But it doesn’t seem like salesforce instantiates the org wide instance automatically – you still have to click the “New” button once.

    Reply

Leave a Comment