Salesforceの添付ファイルを移行用にダウンロードする

Salesforce上のあるオブジェクトについている、メモ&添付ファイル(Attachment)をまとめて別の組織に移行したいというときのTipsです。

このようなことを実現するためには、

①Salesforceから添付ファイルをダウンロードする。この際に、移行先のオブジェクトに紐付けられるよう、移行元オブジェクトのIDや名称も同時に取得しておく。

②添付するオブジェクトを、移行先の組織に作成する。

③それらのオブジェクトに添付ファイルをアップロードする。

という手順を踏むことになると思います。

どの添付ファイルがどのオブジェクトに添付されていたかの情報と、実際の添付ファイルがあれば、アップロード自体はDataloaderやプログラムでできますので、今回は「添付されている親のオブジェクトのIdと名称のディレクトリを作成し、そこに添付ファイルをダウンロードする」プログラムをJavaで作成しようと思います。

Eclipseのプロジェクトは、「Salesforceに接続して、オブジェクト加工等を行うEclipseプロジェクトのテンプレート 」を利用すると簡単です。

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
 
import com.sforce.soap.partner.Connector;
import com.sforce.soap.partner.PartnerConnection;
import com.sforce.soap.partner.QueryResult;
import com.sforce.soap.partner.sobject.SObject;
import com.sforce.ws.ConnectionException;
import com.sforce.ws.ConnectorConfig;
import com.sforce.ws.bind.XmlObject;
import com.sforce.ws.util.Base64;
 
 
public class Main {
   
  static final String USERNAME = "YOUR-USERNAME";
  static final String PASSWORD = "YOUR-PASSWORD&SECURITY-TOKEN";
  static final String OUTPUT_DIR = "C:\OUTPUT\";
  //対象のオブジェクトのAPI参照名をセット
  static final String OBJ_TYPE = "Account";
  static PartnerConnection connection;
 
  public static void main(String[] args) {
 
    ConnectorConfig config = new ConnectorConfig();
    config.setUsername(USERNAME);
    config.setPassword(PASSWORD);
    //config.setTraceMessage(true);
    //config.setProxy("xxxxxxxxx.co.jp", 8888);
 
     
    try {
       
      connection = Connector.newConnection(config);
       
      // display some current settings
      System.out.println("Auth EndPoint: "+config.getAuthEndpoint());
      System.out.println("Service EndPoint: "+config.getServiceEndpoint());
      System.out.println("Username: "+config.getUsername());
      System.out.println("SessionId: "+config.getSessionId());
       
 
      queryAttachments();
       
       
    } catch (ConnectionException e1) {
        e1.printStackTrace();
    }  
 
  }
   
 
  private static void queryAttachments() {
        
    try {
         
      QueryResult queryResults = connection.query("SELECT Id, Name "
            + "FROM Attachment "
            + "WHERE Parent.Type IN ('"+ OBJ_TYPE + "')");
       
      System.out.println(queryResults.getSize());
       
      if (queryResults.getSize() > 0) {
          for (int i=0;i<queryResults.getRecords().length;i++) {
               
              SObject s = (SObject)queryResults.getRecords()[i];
               
              QueryResult queryAttachResults = connection.query("SELECT Id, Name, Body, "
                        + "BodyLength, ContentType, "
                        + "IsDeleted, Description, OwnerId, IsPrivate, "
                        + "ParentId, Parent.Id, Parent.Name "
                        + "FROM Attachment "
                        + "WHERE Id = '" + s.getId() + "'");
               
              SObject sAttach = (SObject)queryAttachResults.getRecords()[0];
               
              System.out.println("Id: " + sAttach.getId() + " " + sAttach.getField("Id") + " " + 
                      sAttach.getField("Name") +  sAttach.getField("ContentType"));
               
              XmlObject obj = (XmlObject)sAttach.getField("Parent");
              System.out.println(obj.getField("Id"));
              System.out.println(obj.getField("Name"));
 
              writeAttachments(sAttach, obj);
            
          }
        }
       
    } catch (Exception e) {
      e.printStackTrace();
    }    
     
  }
   
   
  private static void writeAttachments(SObject sObject, XmlObject xmlObj) throws IOException{
       
        FileOutputStream writer = null;
      
        try {
            String subdir = OUTPUT_DIR + xmlObj.getField("Id") + "_"+ xmlObj.getField("Name");
            File suvdir = new File(subdir);
 
            suvdir.mkdir();
 
            writer = new FileOutputStream(subdir + "\" + (String)sObject.getField("Name"));
 
            String s1= (String)sObject.getField("Body");
 
            writer.write(Base64.decode(s1.getBytes()));
  
            if (writer != null) {
                writer.close();
            }
 
        }catch(Exception e){
            e.printStackTrace();
             
        } 
        finally {
            if (writer != null) {
                writer.close();     }
      
        }
    }
   
  
}

上記のプログラムは、「Account」オブジェクトに添付されているファイルを、「C:OUTPUT」フォルダ直下に「親オブジェクトのID_親オブジェクトの名称」というフォルダを作成して、そこにダウンロードします。「C:OUTPUT」ディレクトリは前もって作成しておいてください。(上記のプログラムでディレクトリを変更した場合は、そのディレクトリに読み替えてくださいね。)

また、

If your query returns the Body field, your client application must ensure that only one row with one Attachment is returned; otherwise, an error occurs.

https://developer.salesforce.com/docs/atlas.en-us.object_reference.meta/object_reference/sforce_api_objects_attachment.htm

の通り、SOQLにBodyを含めると、戻りの件数は1件になってしまうため、一度添付ファイルのIDを取得してから、Bodyを一件ずつ取得するようになっています。