Scripting the Apex Data Loader via Ant

The Apex Data Loader can be scripted using Spring bean XML definitions. Where the configuration needs to vary for each object that approach makes sense. But I had a need to (repeatedly) export many different objects with largely the same configuration and so wanted to use Ant and keep the configuration in Ant.

The resulting Ant build.xml file is shown here, where a macro is used that accepts a file name, an object name and a SOQL string, making it simple to add additional object exports as needed:

<?xml version="1.0" encoding="UTF-8"?>
<project name="Export" default="all">
    <macrodef name="export">
        <attribute name="file"/>
        <attribute name="object"/>
        <attribute name="soql"/>
        <sequential>
            <echo message="Exporting @{object}"/>
            <mkdir dir="exports"/>
            <copy file="config/template-process-conf.xml" tofile="config/process-conf.xml" overwrite="true" failonerror="true"/>
            <replace file="config/process-conf.xml">
                <replacefilter token="_object_" value="@{object}"/>
                <replacefilter token="_soql_" value="@{soql}"/>
                <replacefilter token="_file_" value="exports/@{file}.csv"/>
            </replace>
            <java classname="com.salesforce.dataloader.process.ProcessRunner" classpath="lib/DataLoader.jar" failonerror="true">
                <sysproperty key="salesforce.config.dir" value="config"/>
                <arg line="process.name=@{object}"/>
            </java>
        </sequential>
    </macrodef>
    <target name="all">
        <export file="SampleAccounts" object="Account" soql="Select Id, Name, ... From Account"/>
        <export file="SampleContacts" object="Contact" soql="Select Id, Name, ... From Contact"/>
        ...
    </target>
</project>

The Ant macro is replacing tokens in this template-process-conf.xml file to produce a process-conf.xml file for each object export:

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
    <bean id="_object_" class="com.salesforce.dataloader.process.ProcessRunner" singleton="false">
        <description>TemplatedCsvExtract extracts to a CSV file."</description>
        <property name="name" value="TemplatedCsvExtract"/>
        <property name="configOverrideMap">
            <map>
                <entry key="sfdc.endpoint" value="..."/>
                <entry key="sfdc.username" value="..."/>
                <entry key="sfdc.password" value="..."/>
                <entry key="sfdc.debugMessages" value="false"/>
                <entry key="sfdc.timeoutSecs" value="600"/>
                <entry key="sfdc.extractionRequestSize" value="500"/>
                <entry key="process.operation" value="extract"/>
                <entry key="dataAccess.type" value="csvWrite"/>
                <entry key="sfdc.entity" value="_object_"/>
                <entry key="sfdc.extractionSOQL" value="_soql_"/>
                <entry key="dataAccess.name" value="_file_"/>
            </map>
        </property>
    </bean>
</beans>

The directories required are:

  • “config” containing the template-process-conf.xml file, an empty config.properties file and a log-conf.xml file
  • “lib” containing a copy of DataLoader.jar so that Ant can invoke its com.salesforce.dataloader.process.ProcessRunner class

The output CSV files are written to the “exports” folder.

PS I was working with this again recently on a different machine than the encrypt utility was run on. So as well as setting “sfdc.password” to be the encrypted version of the password and security token I also needed to set the “process.encryptionKeyFile” to reference a file containing the key used to encrypt the password.

Advertisements

Instanceof null is true in Apex

PS From the API version of Winter ’15 onwards, the more rational value of false will be returned.

Here is a Java test case that demonstrates some of the behavior of instanceof including that instanceof evaluates to false for null values:

public class InstanceofTest extends TestCase {
    public void testNull() {
        Date d = null;
        doTest(d, false);
        doTest(null, false);
    }
    public void testNotNull() {
        doTest(new Date(), true);
        doTest("foo", false);
    }
    private void doTest(Object o, boolean expected) {
        assertEquals(expected, o instanceof Date);
    }
}

Surprisingly in Apex instanceof evaluates to true for null values:

@isTest
private class InstanceofTest {
    @isTest
    static void testNull() {
        Date d = null;
        doTest(d, true);
        doTest(null, true);
    }
    @isTest
    static void testNotNull() {
        doTest(Date.newInstance(2010, 1, 1), true);
        doTest('foo', false);
    }
    private static void doTest(Object o, Boolean expected) {
        System.assertEquals(expected, o instanceof Date);
    }
}

So if you are using instanceof in Apex, think about adding null guards to avoid unexpected results.