Usability - Productivity - Business - The web - Singapore & Twins

Reporting your validation formulas

Validation formula are a convenient way to ensure your data integrity. With great powers? comes the risk of alienating users by preventing them entering data.

Why look at them?

You can easily look at all formula in the Object Manager, but it is tedious to look at every formula one by one. You might ask yourself:

  • Do all my formula exclude the integration profile?
  • Are context (e.g. the country) specific formulas set correctly?
  • Do validation rules follow the naming conventions?
  • Are messages helpful or intimidating?

Extract and report

You already use PackageBuilder to extract objects (and other stuff) as XML, so it is just a small step: slap all *.object files into one big file and run an XSLT report over it.

Not so fast! If you concatenate XML files using OS copy you end up with three problems:

  • You don't have an XML root element. Like the Highlander - there can be only one. You could sandwich the files in opening and closing tags, but then you have the next problem
  • XML files start <?xml version="1.0" encoding="UTF-8"?> and copying that file will sprinkle that statement multiple times into your result. The XSLT processor will barf
  • The result will get very big and any report will take a long time or even run out of memory

A bit of tooling

I solved, for my needs, using a small Java class and one XSLT stylesheet. Java because: I'm familiar with it and NodeJS still sucks with XML. XSLT, because: I'm familiar with it (heard that before?) and the styling of the output is independent from the processing step. I presume you know how to initiate an XSLT 2.0 transformation.

The Java class uses Google's Guava library to ensure the output path exists. You can easily comment that out, so the class uses only the JDK.

/** ========================================================================= *
 * Copyright (C)  2019 Salesforce Inc ( http://www.salesforce.com )           *
 *                            All rights reserved.                            *
 *                                                                            *
 *  @author     Stephan H. Wissel (stw) <swissel@salesforce.com>              *
 *                                       @notessensei                         *
 * @version     1.0                                                           *
 * ========================================================================== *
 *                                                                            *
 * Licensed under the  Apache License, Version 2.0  (the "License").  You may *
 * not use this file except in compliance with the License.  You may obtain a *
 * copy of the License at <http://www.apache.org/licenses/LICENSE-2.0>.       *
 *                                                                            *
 * Unless  required  by applicable  law or  agreed  to  in writing,  software *
 * distributed under the License is distributed on an  "AS IS" BASIS, WITHOUT *
 * WARRANTIES OR  CONDITIONS OF ANY KIND, either express or implied.  See the *
 * License for the  specific language  governing permissions  and limitations *
 * under the License.                                                         *
 *                                                                            *
 * ========================================================================== *
package net.wissel.sfdc;

import java.io.File;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import com.google.common.io.Files;

 * @author swissel
public class ValidationReportGenerator {

     * @param args
     * @throws Exception
    public static void main(final String[] args) throws Exception {
        if (args.length < 2) {
            System.err.println("Usage ValidationReportGenerator sourceDir outputFile.xml");

        final String sourceDirName = args[0];
        final String outputFileName = args[1];

        final ValidationReportGenerator vrg = new ValidationReportGenerator();

        vrg.extractValidations(sourceDirName, outputFileName);



    private void addElementToOutput(final Element element, final SimpleXMLDoc output)
            throws TransformerConfigurationException, SAXException {
        final NodeList children = element.getChildNodes();
        for (int i = 0; i < children.getLength(); i++) {
            Node n = children.item(i);
            if (n instanceof Element) {
                final Element childElement = (Element) n;
                output.addTag(childElement.getTagName(), childElement.getTextContent());

    public void extractValidations(final String sourceDirName, final String outputFileName) throws Exception {
        final File sourceDir = new File(sourceDirName);
        if (!sourceDir.exists() || !sourceDir.isDirectory()) {
            System.err.println("Source doesn't exist or isn't a directory");
        final File targetFile = new File(outputFileName);

        final SimpleXMLDoc output = new SimpleXMLDoc();

        final FilenameFilter filter = (dir, name) -> name.endsWith(".object");

        for (final String curFileName : sourceDir.list(filter)) {
            this.processOneObjectDefinition(sourceDir.getAbsolutePath() + "/" + curFileName, output);

        // Closure
        final OutputStream out = new FileOutputStream(targetFile);


    private NodeList getValidationList(final String curFileName)
            throws ParserConfigurationException, SAXException, IOException, XPathExpressionException {
        final File inFile = new File(curFileName);
        final DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
        final DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
        final Document doc = dBuilder.parse(inFile);
        final XPath xPath = XPathFactory.newInstance().newXPath();
        final String expression = "/CustomObject/validationRules";
        final NodeList nodeList = (NodeList) xPath.compile(expression).evaluate(doc, XPathConstants.NODESET);
        return nodeList;

    private void processOneObjectDefinition(final String curFileName, final SimpleXMLDoc output)
            throws TransformerConfigurationException, SAXException, ParserConfigurationException, IOException,
            XPathExpressionException {
        final String objectName = curFileName.substring(curFileName.lastIndexOf("/")+1, curFileName.lastIndexOf("."));

        final NodeList nodeList = this.getValidationList(curFileName);

        if ((nodeList != null) && (nodeList.getLength() > 0)) {
            final Map<String, String> attributes = new HashMap<>();
            attributes.put("objectName", objectName);
            output.openTag("Validation", attributes);

            for (int i = 0; i < nodeList.getLength(); i++) {
                final Element element = (Element) nodeList.item(i);
                this.addElementToOutput(element, output);



As usual YMMV

Posted by on 07 February 2019 | Comments (0) | categories: Salesforce


  1. No comments yet, be the first to comment