プログラマ38の日記

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

Java: wsimportでSalesforce APIを使用する

まずはサンプルコード

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.ws.BindingProvider;
import javax.xml.ws.handler.MessageContext;

import com.sforce.soap.partner.LoginResult;
import com.sforce.soap.partner.ObjectFactory;
import com.sforce.soap.partner.SessionHeader;
import com.sforce.soap.partner.SforceService;
import com.sforce.soap.partner.Soap;
import com.sun.xml.ws.api.message.Header;
import com.sun.xml.ws.api.message.Headers;
import com.sun.xml.ws.developer.WSBindingProvider;    
    
-------------------------------------------------------------------------
    Soap soapBinding = new SforceService().getSoap();

WSBindingProvider provider = (WSBindingProvider)soapBinding;

Map<String, Object> reqContext = provider.getRequestContext();
reqContext.put(WSBindingProvider.ENDPOINT_ADDRESS_PROPERTY, "https://login.salesforce.com/services/Soap/u/39.0");
reqContext.put("javax.xml.ws.client.connectionTimeout", "180");
reqContext.put("javax.xml.ws.client.receiveTimeout", "180");

//Enable GZip compression
Map<String, List<String>> httpHeaders = new HashMap<String, List<String>>();
httpHeaders.put("Content-Encoding", Collections.singletonList("gzip"));
httpHeaders.put("Accept-Encoding", Collections.singletonList("gzip"));
reqContext.put(MessageContext.HTTP_REQUEST_HEADERS, httpHeaders);

//httpプロキシを指定する
{
System.setProperty("https.proxyHost", "samplehost");
System.setProperty("https.proxyPort", "9999");
ProxyUserPasswordConfig.setProxyUserPassword("sampleproxyuser", "sampleproxypasword");
}

LoginResult loginResult = soapBinding.login("sampleuser@sample.username", "samplepassword");

String serverUrl = loginResult.getServerUrl();

reqContext.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, serverUrl);

SessionHeader sh = new SessionHeader();
String sessionId = loginResult.getSessionId();
sh.setSessionId(sessionId);

JAXBContext jaxbContext = null;
try {
jaxbContext = JAXBContext.newInstance(ObjectFactory.class);
} catch (JAXBException e) {}

ArrayList<Header> sessionheaderlst = new ArrayList<Header>();
sessionheaderlst.add(Headers.create((JAXBContext) jaxbContext, sh));
provider.setOutboundHeaders(sessionheaderlst);

System.out.println(soapBinding.getServerTimestamp().getTimestamp());

httpproxyのユーザ、パスワードは「PasswordAuthentication」を使うため上記で次のクラスを使っています。※他のやり方もあるとは思いますがこのやり方にしました。

import java.net.Authenticator;
import java.net.PasswordAuthentication;

public class ProxyUserPasswordConfig extends Authenticator {

private String user;
private String password;

private ProxyUserPasswordConfig(String proxyuser, String proxypassword){
user = proxyuser;
password = proxypassword;
}

public static void setProxyUserPassword(String proxyuser, String proxypassword){
Authenticator.setDefault(new ProxyUserPasswordConfig(proxyuser,proxypassword));
}
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(user, password.toCharArray());
}
}

コードの生成とライブラリの作成

jdk付属のwsimportコマンドを使います。wsdlを含んだjarまで自動で作成してくれます。

mkdir partner
mkdir metadata
wsimport -B-XautoNameResolution -d partner -clientjar partner.jar -b _jaxb.xml partner.wsdl
wsimport -B-XautoNameResolution -d metadata -clientjar metadata.jar -b _jaxb.xml metadata.wsdl

_jaxb.xmlというのを上記コマンドで指定していますが、_jaxb.xmlの中身は次のテキストです。 typesafeEnumMaxMembersの上限エラーになったのでサイズを増やしています。

<jaxb:bindings
xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"
jaxb:extensionBindingPrefixes="xjc"
jaxb:version="1.0">
<jaxb:globalBindings typesafeEnumMaxMembers="2000">
<xjc:serializable/>
</jaxb:globalBindings>
</jaxb:bindings>

httpproxyの設定

proxyhostとproxyportはシステムプロパティで指定するのですが、proxyuserとproxypasswordはコード内で指定となっています。

      System.setProperty("https.proxyHost", "samplehost");
System.setProperty("https.proxyPort", "9999");
ProxyUserPasswordConfig.setProxyUserPassword("sampleproxyuser", "sampleproxypasword");

エンドポイントの設定

wsimportを標準のまま使うとエンドポイントが変更できません。そのため外部のライブラリJAXWS-RIを使います。(現在まとめてダウンロードできないのでこちらにアップしました。)このライブラリ内のWSBindingProviderを使います。

    WSBindingProvider provider = (WSBindingProvider)soapBinding;

Map<String, Object> reqContext = provider.getRequestContext();
reqContext.put(WSBindingProvider.ENDPOINT_ADDRESS_PROPERTY, "https://login.salesforce.com/services/Soap/u/39.0");

セッションIDと、インスタンスURLのセット

ここも、エンドポイントと同様に、WSBindingProviderを使います。

やりたいことは、SOAPHeaderにセッションヘッダをつけて、エンドポイントを変えたいだけなのに、結構書く必要があります。

    String serverUrl = loginResult.getServerUrl();

reqContext.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, serverUrl);

SessionHeader sh = new SessionHeader();
String sessionId = loginResult.getSessionId();
sh.setSessionId(sessionId);

JAXBContext jaxbContext = null;
try {
jaxbContext = JAXBContext.newInstance(ObjectFactory.class);
} catch (JAXBException e) {}

ArrayList<Header> sessionheaderlst = new ArrayList<Header>();
sessionheaderlst.add(Headers.create((JAXBContext) jaxbContext, sh));
provider.setOutboundHeaders(sessionheaderlst);

これで上記コードのsoapBindingでSOAP APIを使用できます。

以前(java6頃)のwsimportはデータバインディングが非常に遅かったのですが、java8ではかなり改善しているように思います。