プログラマ38の日記

主にプログラムメモです。

Salesforce: データローダのコマンドライン操作のメモ

ETLツールがない場合など、Salesforceとのバッチ連携はCSVファイルとデータローダのコマンドラインの組み合わせで行うことになると思います。(APIで開発は意外と手間がかかります)

データローダをコマンドラインで使った際のメモを書いておこうと思います。

 

日付型の形式

yyyy-MM-dd 形式(例えば、2017年7月14日なら、2017-07-14)とし、sfdc.timezoneをUTCにする。他のやり方(JSTにして、フォーマットに時間まで含める)もありますが、こちらの方が違和感ないと思います。

日付/時間型の形式

yyyy-MM-dd'T'HH:mm:ss'.000+0900'形式(例えば、日本時間2017年7月14日8時55分13秒なら、2017-07-14T08:55:13.000+0900)

日本時間でデータを入れるケースが多いと思うのでこうしました。GMT形式で9時間マイナスする処理が不要になります。

※補足ですが、SOQLで日付/時間項目への検索条件もこの形式で可能です。

マッピングファイル(sdlファイル)の書き方のポイント

  1. Salesforceの項目名は、大文字小文字を区別する
  2. 参照関係項目に、参照先の外部IDを使用する時は「CSV項目=参照先名:参照先の外部ID項目名」の形式
  3. 固定値をセットしたい場合は、ダブルクオートで区切ってセットする「"固定値"=項目名」

2の外部IDを使用する場合ですが、複数の親オブジェクトを指定できる項目へは使えません。また、外部IDしか使えないのが辛いところです。

APIで開発する場合は、複数の親オブジェクトを指定する項目にも外部IDを指定することができます。さらに外部IDだけでなく、IdLookupが可能な項目が全て使えますので、ユーザオブジェクトの場合、ユーザオブジェクトの外部IDだけでなく、ユーザ名(Username)、メール(Email)、SAML 統合 ID(FederationIdentifier)も使うことができます。ETLツールのアダプタによっては対応してるものもあると思います。)

サンプルで用意されているprocess-conf.xmlxml宣言が省略されている

なぜかサンプルでは、xml宣言が省略されているのでutf-8で保存しないとエラーになります。マルチバイトを使わなければ問題はないですが、マルチバイトを使った時にうっかりshift_jis(CP932の方)で保存すると実行時にエラーになります。

バッチ処理の中でprocess-conf.xmlを出力したいことが多いと思いますし、その際にutf-8文字コードでの保存が面倒なので、きちんとxml宣言を追加してshift_jisで保存するようにしておけば楽だと思います。

sampleのprocess-conf.xml

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
    <bean id="accountMasterProcess"
          class="com.salesforce.dataloader.process.ProcessRunner"
          singleton="false">
        <description>AccountMaster job gets the Customer record updates from ERP (Oracle financials) and uploads them to salesforce using 'upsert'.</description>
        <property name="name" value="accountMasterProcess"/>
 ・・・・

 文字コードを指定した後のprocess-conf.xml

<?xml version="1.0" encoding="Windows-31J" standalone="no" ?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
    <bean id="accountMasterProcess"
          class="com.salesforce.dataloader.process.ProcessRunner"
          singleton="false">
        <description>AccountMaster job gets the Customer record updates from ERP (Oracle financials) and uploads them to salesforce using 'upsert'.</description>
        <property name="name" value="accountMasterProcess"/>
 ・・・・

 

ログインユーザ、パスワードではなく、インスタンスURLとセッションIDで利用する

process-conf.xmlに、"sfdc.oauth.accesstoken"の指定をすれば、"sfdc.username"と"sfdc.password"の指定は不要です。"sfdc.oauth.accesstoken"の指定をする場合、"sfdc.endpoint"は、インスタンスURLで指定することになります。

 

"sfdc.oauth.accesstoken"で指定する場合、process-conf.xmlは次のようになります。

<?xml version="1.0" encoding="Windows-31J" standalone="no" ?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
  <bean id="export" class="com.salesforce.dataloader.process.ProcessRunner" singleton="false">
    <description>export</description>
    <property name="name" value="export"/>
    <property name="configOverrideMap">
      <map>
        <entry key="process.operation"                 value="extract"/>
        <entry key="process.enableExtractStatusOutput" value="false"/>

        <entry key="sfdc.endpoint"                     value="instanceURL"/>
        <entry key="sfdc.oauth.accesstoken"            value="sessionId"/>
・・・・・・・・・

Salesforce画面からインスタンスURLとセッションIDをパラメータとして渡すことができれば、データローダをおいてある外部のサーバから、画面を操作した人としてレコードの作成・更新が可能になります。

また、いちいちログインしたくない場合などにも使えます。

 

 

所感

process-conf.xmlのテンプレートをextract、insert、upsert、deleteで用意しておいて、処理別に値を書き換えて使うようにすると使いやすいなと感じています。

また、個人的には、値の書き換えは環境変数を展開するようにしています。

 

環境変数の展開前(extractの場合)

<?xml version="1.0" encoding="Windows-31J" standalone="no" ?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
  <bean id="export" class="com.salesforce.dataloader.process.ProcessRunner" singleton="false">
    <description>export</description>
    <property name="name" value="export"/>
    <property name="configOverrideMap">
      <map>
        <entry key="process.operation"                 value="extract"/>
        <entry key="process.enableExtractStatusOutput" value="false"/>

        <entry key="sfdc.endpoint"                     value="%sfdc.endpoint%"/>
        <entry key="sfdc.username"                     value="%sfdc.username%"/>
        <entry key="sfdc.password"                     value="%sfdc.password%"/>
        <entry key="sfdc.timeoutSecs"                  value="600"/>
        <entry key="sfdc.loadBatchSize"                value="200"/>
        <entry key="sfdc.timezone"                     value="UTC"/>
        <entry key="sfdc.insertNulls"                  value="false"/>
        <entry key="sfdc.extractionRequestSize"        value="500"/>


        <entry key="sfdc.entity"                       value="%sfdc.entity%"/>
        <entry key="sfdc.extractionSOQL"               value="%sfdc.extractionSOQL%"/>

        <entry key="dataAccess.name"                   value="%daaAccess.name%"/>
        <entry key="dataAccess.writeUTF8"              value="false"/>
        <entry key="dataAccess.type"                   value="csvWrite"/>

      </map>
    </property>
  </bean>
</beans>

としておいて、環境変数に上記の変数を用意しておきます。

そして、VBScriptのWScript.Shellオブジェクトの、ExpandEnvironmentStrings関数で環境変数を展開するのが便利です。

 

次がExpandEnvironmentStrings関数を利用して環境変数を展開するプログラムサンプルです。

仕様:引数にShift_JISのファイル名を指定し、ファイルのテキストの環境変数を展開して標準出力に出力する

Option Explicit

Dim objFSO      ' FileSystemObject
Dim objFile     ' ファイル読み込み用

Dim file        'ファイル名
Dim filetxt     'ファイル文字列

file=WScript.Arguments(0)
Set objFSO = WScript.CreateObject("Scripting.FileSystemObject")
If Err.Number = 0 Then
    Set objFile = objFSO.OpenTextFile(file)
    If Err.Number = 0 Then
        filetxt = objFile.ReadAll
        objFile.Close
    Else
        WScript.Echo "ファイルオープンエラー: " & Err.Description
    End If
Else
    WScript.Echo "エラー: " & Err.Description
End If

Set objFile = Nothing
Set objFSO = Nothing

'変数
Dim objShell
Dim tmp
 
'シェルオブジェクト作成
Set objShell = CreateObject("WScript.Shell")
 
'環境変数展開
tmp = objShell.ExpandEnvironmentStrings(filetxt)
 
'環境変数表示
WScript.Echo tmp
 
'シェルオブジェクト破棄
Set objShell = Nothing

 

Salesforce: データ移行時のトリガスキップについて考えたこと

データ移行では、既存のデータを確実に移行したいのでトリガを動かしたくない時があると思います。その制御の方法について考えてみました。

No.3のユーザのカスタム項目の制御がいいかなと思っていますが、要件として、トリガをスキップしたい時スキップしたくない時がある場合は、No.4のカスタムオブジェクトの項目の制御になると思います。

 

No 制御で使う設定 スキップの制御 メリット デメリット
1 カスタム表示ラベル 制御用にカスタム表示ラベルを用意し、その値がtrueの時はトリガの先頭でreturnする。 実装がシンプル。

beforeトリガ/afterトリガも確実にスキップできる。
カスタム表示ラベルをtrueにすると他のユーザもトリガスキップされてしまうため、運用開始後に、再度移行が難しい。
2 ユーザのプロファイル 移行用のプロファイルを用意し、そのプロファイルの時は、トリガの先頭でreturnする。 実装がカスタム表示ラベルの次にシンプル。

beforeトリガ/afterトリガも確実にスキップできる。

移行のためだけにプロファイルを用意するのは意外と手間。

3 ユーザのカスタム項目 ユーザオブジェクトに、制御用のカスタム項目を用意し、その値がtrueの時はトリガの先頭でreturnする。

実装がカスタム表示ラベルの次にシンプル。

 

beforeトリガ/afterトリガも確実にスキップできる。

あまりないかなと思います。
4 カスタムオブジェクトの項目 データを登録したいカスタムオブジェクトに制御用のカスタム項目を用意し、その値がtrueの時はトリガの先頭でreturnする。 移行時だけでなく、どのユーザでもトリガをスキップしたいタイミングでスキップが可能。

beforeトリガは確実にスキップできる。
実装が込み入っている。
また、複数行の更新時に、トリガをスキップしたい行とスキップしたくない行が混在すると制御がほぼ不可能なので、まとめて更新する際は、トリガのスキップの指定を混在できない。

仕様によってはafterトリガのスキップが困難な場合がある。

 

実装例は次のようになります。

No.1  カスタム表示ラベルの制御

trigger SampleiTrigger on Sample__c (after delete, after insert, after undelete, after update,
        before delete, before insert, before update) {


  if(System.Label.IKOFLAG == 'TRUE'){
    return;
  }
//処理

 No.2 ユーザのプロファイルの制御

trigger SampleiTrigger on Sample__c (after delete, after insert, after undelete, after update,
        before delete, before insert, before update) {
  
  User u = [select Profile.Name from User where Id =:Userinfo.getUserId()];
  
  if(u.Profile.Name == '移行'){
    return;
  }

 No.3 ユーザのカスタム項目の制御

trigger SampleiTrigger on Sample__c (after delete, after insert, after undelete, after update,
        before delete, before insert, before update) {
  
  User u = [select TriggerSkipFlag__c from User where Id =:Userinfo.getUserId()];
  
  if(u.TriggerSkipFlag__c){
    return;
  }

 

 No.4 カスタムオブジェクトの項目の制御

trigger SampleiTrigger on Sample__c (after delete, after insert, after undelete, after update,
        before delete, before insert, before update) {
  
  Boolean triggerskip = false;

   for(   Sample__c s : Trigger.New ){
    if( s.TriggerSkipFlag__c ){
      triggerskip = true;
//trueのままにしておくと、次に更新した際に無条件にトリガがスキップされる
//のを防ぐためフラグは常にfalseに戻す
      s.TriggerSkipFlag__c = false;
    }
  }

  if ( triggerskip ){
    //afterトリガ用にstatic変数にトリガスキップのフラグをたてる。
    Cls_StaticValue.triggerskip = true; 
  }

  if( triggerskip || Cls_StaticValue.triggerskip ){
    return;
  }

 

No.4の実装にすると、1つのトランザクション内で、何度も同じオブジェクトを登録・更新する場合、どこかでトリガをスキップすると、トリガはスキップされ続けてしまいます。Trigger.InsertやTrigger.Afterなどを使えばある程度まで細かく制御はできると思います。

 

Salesforce: EventLogFileのユニークキー

監査ログとしてEventLogFileの内容をデータベース、Database.com、AnalyticsCloudなど別の仕組みへ蓄積したい場合があると思います。

 

ユニークなキーが欲しいなと思い調べたのですが、結論として、イベントログのCSVの中には、キー項目はありませんでした。(ある程度の項目を連結すればユニークにはなると思いますが、それもちょっと違うかなと思いました)

 

一瞬、「REQUEST_ID」項目がユニークかなと期待したのですが、重複しているレコードがあり使えませんでした。

 

なので、ユニークな項目は処理の中で用意することになりますが、個人的には次がいいかなと思います。

 

「TIMESTAMP」項目の先頭8桁(yyyyMMddで取得できます)とCSVの行番号を連結

 

行番号は処理の中で作り出さないといけないのですがこれならユニークになるので、同じファイルを2回取り込んでしまっても、upsertの動きが実現できます。

 

※余談ですが、TIMESTAMP項目はGMTなので、日本時間で使う際には9時間足す必要があります。

 

Excel: VBAでセルの表をtableタグで出力するマクロを作りました

Excelで作成した表をtableタグに変換するVBAマクロを作成しました。

ダウンロードはこちらからになります。

次のように操作します。

①作成した表を選択します。(選択範囲がtableタグになります)
②tableタグ出力ボタンをクリックします。

f:id:crmprogrammer38:20170711093609p:plain

 そうすると、ポップアップされたウインドウにtableタグが出力されます。

f:id:crmprogrammer38:20170711093853p:plain

ポイント
・セルの結合はタグに反映されます。
・セルの背景色や、文字の右寄せ・左寄せはタグに反映されません。

 

 

ツールを作り終わってから思ったのが、同じようなツールは既にあるんじゃないかということでした。
案の定、同じようなツールはたくさんあって無駄なことしたなと思いました。
が、自分が使いたい機能だけに限定されていてこれも悪くないなと思うようにしました。

Java: BeanShellでSalesforceのメタデータ削除ツールを作りました

メタデータを削除したい時があります。

  • 間違えてカスタム項目をたくさん作ってしまった
  • キューを作ると自動で作成されるビューを削除する

こんな時にまとめてメタデータを削除できるツールです。

ツールはbeanshellで作成しています。

 

ダウンロードはこちらからです。

 

通信設定はスクリプトの次の箇所を変更します。

    //1.httpプロキシ情報の入力
    String proxyhost = null;
    Integer proxyport = null;
    String ntlmdomain = null;
    String proxyuser = null;
    String proxypassword = null;


    //2.Salesforceログイン情報の入力
    String sfendpoint = "https://test.salesforce.com/services/Soap/u/40.0";
    String sfusername = "sample@username";
    String sfpassword = "samplepassword";

 

そして、削除したいコンポーネントは、メタデータタイプとともに次のように指定してください。

    //3.削除するメタデータ情報の入力
    //配列に、メタデータタイプと削除するメタデータ物理名を指定します。
    ArrayList fullNames = new ArrayList();

    //以下設定例
    fullNames.add( new String[]{"CustomObject" , "SampleObject__c"} );
    fullNames.add( new String[]{"ListView" , "Opportunity.SampleViewName"} );
    fullNames.add( new String[]{"CustomLabel" , "SampleLabel"} );
    fullNames.add( new String[]{"CustomField" , "Account.SampleField__c"} );

メタデータタイプと、対象コンポーネントを指定すると削除されます。

 

設定ファイルを読み込んで、その設定ファイルに従って処理を組もうとすると、設定ファイルの仕様が発生してしまうと思います。

beanshellであればソースコードをそのまま変更できるので設定ファイルいらずで便利だなと思います。

Java: BeanShellでSalesforceのファイル(添付ファイル、ドキュメント、イベントログ)のエクスポートツールを作りました

SalesforceのバイナリファイルはSOQLで取得することができます。

例えば添付ファイルはAttachmentというオブジェクトから取得できます。


ですが、SOQLでオブジェクトからデータを取得するとbase64エンコードされた文字列となるため、バイナリにデコードしてファイルに出力する処理が必要です。
そこで、ファイルエクスポートツールを作りました。

 

ここからダウンロードしてください。ダウンロードしたzipを解凍すると次のファイルがあります。

f:id:crmprogrammer38:20170630165600p:plain

対象のファイルは添付ファイル(Attachment) 、ドキュメント(Document)、Event Log Files(EventLogFile)の3つを対象としています。
各ファイルは次のようになっています。

バッチファイル スクリプトファイル 説明
batch_Attachment.bat download_Attachment.txt 添付ファイルオブジェクト(Attachment)のファイルを出力します。
batch_Document.bat download_Document.txt ドキュメントオブジェクト(Document)のファイルを出力します。
batch_EventLogFile.bat download_Eventlogfile.txt イベントログを使うにはライセンスが必要です。
Event Log Files(Eventlogfile)のファイルを出力します。

 スクリプトファイルで、SOQLの条件は指定していないので、必要に応じて条件を指定してください。以下は添付ファイルへのSOQLを直近10日で更新されたという条件を追加した例になります。

    //3.バイナリデータを取得するSOQL
    String soql = "select Id, Name, Body from Attachment Where LastModifiedDate = LAST_N_DAYS:10";

 

Salesforceへの接続情報や通信設定は、各スクリプトの次の箇所を修正してください。

    //1.通信情報
    String proxyhost = null;
    Integer proxyport = null;
    String ntlmdomain = null;

    String proxyuser = null;
    String proxypassword = null;

    //2.Salesforce接続情報
    String sfendpoint = "https://test.salesforce.com/services/Soap/u/40.0";
    String sfusername = "sample@username";
    String sfpassword = "samplepassword";

 

 柔軟にテキストを修正できるという点でbeanshellの良さがあるなーと思いました。

 

Java: BeanShellが使いやすい

Javaスクリプトを実行する仕組みは色々ありますが、Javaコードがほぼそのまま実行できる点でbeanshellはとても使いやすいなと感じています。
(でも、ジェネリクスや、可変長引数は対応してないので、うっかり書いてしまうとエラーの解決に時間がかかります)

私は、ある程度までは、IDEでがりがり書いた後にbeanshellでの実行するようにテキストファイルに書くというスタイルをとっています。

windowsDOSバッチなどで、javaのライブラリが使えれば楽なのになーという時が、beanshellの出番です。

普通にコンパイルしたクラスで実行してもいいのですが、コンパイルが不要でテキストがそのまま実行できるので、修正・変更がとても簡単です。

 

もし、rhinoなどと迷っているなら、beanshellをお勧めします。

次以降に、beanshellを使ったSalesforceAPI操作のツールを紹介したいと思います。