Summer'17(apiver40.0)のエクスポートツールはこちらです。
api39.0のものや、使い方は前回の記事を参照ください。
crmprogrammer38.hatenablog.com
修正履歴
2017/7/24 CustomFeedFilterのダウンロード時にエラーとなる事象の対応を行いました。具体的には、CustomFeedFilterはワイルドカード指定でリクエストするように変更しています。
Summer'17(apiver40.0)のエクスポートツールはこちらです。
api39.0のものや、使い方は前回の記事を参照ください。
crmprogrammer38.hatenablog.com
修正履歴
2017/7/24 CustomFeedFilterのダウンロード時にエラーとなる事象の対応を行いました。具体的には、CustomFeedFilterはワイルドカード指定でリクエストするように変更しています。
ConnectorConfig connectorConfig = new ConnectorConfig();
String soapEndpoint = "https://login.salesforce.com/services/Soap/u/39.0";
connectorConfig.setAuthEndpoint(soapEndpoint);
connectorConfig.setManualLogin(true);
connectorConfig.setCompression(true);
connectorConfig.setConnectionTimeout(120000);
//httpプロキシを指定する
{
connectorConfig.setProxy("samplehost", 9999);
ProxyUserPasswordConfig.setProxyUserPassword("sampleproxyuser", "sampleproxypasword");
}
PartnerConnection soapBinding = Connector.newConnection(connectorConfig);
LoginResult lr = soapBinding.login("sampleuser@sample.username", "samplepassword");
soapBinding.setSessionHeader(lr.getSessionId());
connectorConfig.setServiceEndpoint(lr.getServerUrl());
System.out.println(soapBinding.getServerTimestamp().getTimestamp());
httpproxyのユーザ、パスワードは「PasswordAuthentication」を使うため上記で次のクラスを使っています。(wsimportの時と同様です)
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());
}
}
wscのライブラリをclasspathに指定し次のコマンドを使います。jarまで自動で作成してくれます。jarにはソースコードも含まれます。
java -DcompileTarget=1.8 -classpath ..\lib\* com.sforce.ws.tools.wsdlc partner.wsdl partner.jar
java -DcompileTarget=1.8 -classpath ..\lib\* com.sforce.ws.tools.wsdlc metadata.wsdl metadata.jar
コード内で指定となっています。
connectorConfig.setProxy("samplehost", 9999);
ProxyUserPasswordConfig.setProxyUserPassword("sampleproxyuser", "sampleproxypasword");
ConnectorConfigで指定となります。
ConnectorConfig connectorConfig = new ConnectorConfig();
String soapEndpoint = "https://login.salesforce.com/services/Soap/u/39.0";
connectorConfig.setAuthEndpoint(soapEndpoint);
ここも、エンドポイントと同様に、ConnectorConfigで指定します。
soapBinding.setSessionHeader(lr.getSessionId());
connectorConfig.setServiceEndpoint(lr.getServerUrl());
これで上記コードのsoapBindingでSOAP APIを使用できます。
Salesforce専用ライブラリであり、他のライブラリに比べとても簡潔なコードになります。
また、スレッドセーフな設計です。(他のライブラリは1つのスタブインスタンスを複数スレッドで使うことができません)
ただし、注意事項として、insert/update/upsertで、ライブラリ側で項目にセットした値の型をチェックします。
他のライブラリは項目にセットするのは文字型であり、
なのに対して、wscは
の制限があります。
他のライブラリでは、上記型の値が不正だった場合、APIコール時に原因追求が困難なエラーがでます。が、wscはライブラリ側であえてエラー制御を実装しているということだと思います。
URL wsdlurl = Soap.class.getClassLoader().getResource("/partner.wsdl");
Soap soapBinding = new SforceService(wsdlurl).getSoap();
BindingProvider bp = (BindingProvider)soapBinding;
bp.getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY,"https://login.salesforce.com/services/Soap/u/38.0");
Client client = ClientProxy.getClient(bp);
client.getInInterceptors().add(new GZIPInInterceptor());
client.getOutInterceptors().add(new GZIPOutInterceptor());
HTTPConduit conduit = (HTTPConduit)client.getConduit();
HTTPClientPolicy policy = new HTTPClientPolicy();
policy.setAllowChunking(true);
policy.setConnectionTimeout(120000);
policy.setProxyServer("sampleproxyhost");
policy.setProxyServerType(ProxyServerType.HTTP);
policy.setProxyServerPort(9999);
ProxyAuthorizationPolicy authpolicy = new ProxyAuthorizationPolicy();
authpolicy.setUserName("sampleproxyuser");
authpolicy.setPassword("samplepassword");
conduit.setProxyAuthorization(authpolicy);
conduit.setClient(policy);
LoginResult loginResult = soapBinding.login("sampleuser@sample.username", "samplepassword", null, null);
bp.getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY,loginResult.getServerUrl());
SessionHeader sh = new SessionHeader();
String sessionId = loginResult.getSessionId();
sh.setSessionId(sessionId);
System.out.println(soapBinding.getServerTimestamp(sh, null, null).getTimestamp());
ライブラリ付属のwsdl2javaコマンドを使います。jarまで自動で作成してくれます。
set JAVA_HOME=javahomeのパス
wsdl2java.bat -d partner -b jaxb.xml -exsh true -autoNameResolution -clientjar partner.jar partner.wsdl
wsdl2java.bat -d metadata -b jaxb.xml -exsh true -autoNameResolution -clientjar metadata.jar metadata.wsdl
_jaxb.xmlというのを上記コマンドで指定していますが、_jaxb.xmlの中身は次のテキストです。 typesafeEnumMaxMembersの上限エラーになったのでサイズを増やしています。(wsimportの時と同様です)
<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>
コード内で指定となっています。
Client client = ClientProxy.getClient(bp);
HTTPConduit conduit = (HTTPConduit)client.getConduit();
HTTPClientPolicy policy = new HTTPClientPolicy();
policy.setProxyServer("sampleproxyhost");
policy.setProxyServerType(ProxyServerType.HTTP);
policy.setProxyServerPort(9999);
ProxyAuthorizationPolicy authpolicy = new ProxyAuthorizationPolicy();
authpolicy.setUserName("sampleproxyuser");
authpolicy.setPassword("samplepassword");
conduit.setProxyAuthorization(authpolicy);
conduit.setClient(policy);
BindingProviderを使うことで指定します。
URL wsdlurl = Soap.class.getClassLoader().getResource("/partner.wsdl");
Soap soapBinding = new SforceService(wsdlurl).getSoap();
BindingProvider bp = (BindingProvider)soapBinding;
bp.getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY,"https://login.salesforce.com/services/Soap/u/38.0");
ここも、エンドポイントと同様に、BindingProviderを使います。
セッションIDは、SOAPHeaderにセットしておくのではなく、APIを呼び出す際に引数で指定します。
bp.getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY,loginResult.getServerUrl());
SessionHeader sh = new SessionHeader();
String sessionId = loginResult.getSessionId();
sh.setSessionId(sessionId);
これで上記コードのsoapBindingでSOAP APIを使用できます。
cxf3.Xを使うとデータバインディングがとても遅かったので、cxf2.7がお勧めです。
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>
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");
ここも、エンドポイントと同様に、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ではかなり改善しているように思います。
String url = "https://login.salesforce.com/services/Soap/u/39.0";
SforceServiceStub soapBinding = new SforceServiceStub(url);
Options options = soapBinding._getServiceClient().getOptions();
options.setProperty(HTTPConstants.MC_ACCEPT_GZIP, Boolean.TRUE);
options.setProperty(HTTPConstants.MC_GZIP_REQUEST, Boolean.TRUE);
//httpプロキシを指定する
{
HttpTransportProperties.ProxyProperties proxyProperties = new HttpTransportProperties.ProxyProperties();
proxyProperties.setProxyName("samplehost");
proxyProperties.setProxyPort(9999);
proxyProperties.setUserName("sampleproxyuser");
proxyProperties.setPassWord("sampleproxypasword");
options.setProperty(HTTPConstants.PROXY, proxyProperties);
}
LoginDocument logininfo = LoginDocument.Factory.newInstance();
Login login = Login.Factory.newInstance();
login.setUsername("sampleuser@sample.username");
login.setPassword("samplepassword");
logininfo.setLogin(login);
LoginResult loginResult = soapBinding.login(logininfo, null,null).getLoginResponse().getResult();
options.setTo(new EndpointReference(loginResult.getServerUrl()));
SessionHeaderDocument sh = SessionHeaderDocument.Factory.newInstance();
SessionHeader sheader = SessionHeaderDocument.SessionHeader.Factory.newInstance();
sheader.setSessionId(loginResult.getSessionId());
sh.setSessionHeader(sheader);
GetServerTimestampDocument getServerTimestampDocument = GetServerTimestampDocument.Factory.newInstance();
getServerTimestampDocument.setGetServerTimestamp(GetServerTimestamp.Factory.newInstance());
System.out.println(soapBinding.getServerTimestamp(getServerTimestampDocument, sh, null).getGetServerTimestampResponse().getResult().getTimestamp());
apache2のサイト(Apache Axis2 – Apache Axis2/Java - Next Generation Web Services
)からライブラリをダウンロード
ダウンロードしたaxis2のライブラリにクラスパスを指定しコマンドを実行する。
set classpath=.;..\lib\*
java -cp %classpath% org.apache.axis2.wsdl.WSDL2Java -Euwc -Ejavaversion 1.5 -d xmlbeans -uri partner.wsdl
java -cp %classpath% org.apache.axis2.wsdl.WSDL2Java -Euwc -Ejavaversion 1.5 -d xmlbeans -uri metadata.wsdl
コマンドを実行するとJavaコードと共にresourcesのフォルダにxsbが出力されます。
resourcesフォルダのファイルは実行時に必要なので、Javaコードをコンパイルした後、コンパイルしたクラスとresourcesフォルダのファイルを含めてライブラリを作成します。
httpproxyはプログラム内部の指定となります。
HttpTransportProperties.ProxyProperties proxyProperties = new HttpTransportProperties.ProxyProperties();
proxyProperties.setProxyName("samplehost");
proxyProperties.setProxyPort(9999);
proxyProperties.setUserName("sampleproxyuser");
proxyProperties.setPassWord("sampleproxypasword");
options.setProperty(HTTPConstants.PROXY, proxyProperties);
コンストラクタで受けてくれるタイプです。
String url = "https://login.salesforce.com/services/Soap/u/39.0";
SforceServiceStub soapBinding = new SforceServiceStub(url);
optionsで、エンドポイントを指定しますが、SoapHeaderにセッションヘッダをセットはしません。セッションヘッダは実行時に毎回引数で指定することになります。
options.setTo(new EndpointReference(loginResult.getServerUrl()));
SessionHeaderDocument sh = SessionHeaderDocument.Factory.newInstance();
SessionHeader sheader = SessionHeaderDocument.SessionHeader.Factory.newInstance();
sheader.setSessionId(loginResult.getSessionId());
sh.setSessionHeader(sheader);
これで上記コードのsoapBindingはSOAP APIを使うことができます。サンプルコードの最後に、Salesforceのサーバ時刻を取得するAPIをコールしています。
サーバ時刻を取得するAPIをコールするだけで、このコード量ですから、アプリケーションで書く場合のコード量はとても多くなります。
SoapBindingStub soapBinding = (SoapBindingStub)new SforceServiceLocator().getSoap();
soapBinding._setProperty(org.apache.axis.transport.http.HTTPConstants.MC_ACCEPT_GZIP , "true");
soapBinding._setProperty(org.apache.axis.transport.http.HTTPConstants.MC_GZIP_REQUEST , "true");
soapBinding._setProperty(Stub.ENDPOINT_ADDRESS_PROPERTY, "https://login.salesforce.com/services/Soap/u/39.0");
//httpプロキシを指定する
{
Properties systemprops = System.getProperties();
systemprops.setProperty("http.useProxy", "true");
systemprops.setProperty("http.proxyHost", "samplehost");
systemprops.setProperty("http.proxyPort", "9999");
systemprops.setProperty("http.proxyUser", "sampleproxyuser");
systemprops.setProperty("http.proxyPassword", "sampleproxypasword");
}
LoginResult loginResult = soapBinding.login("sampleuser@sample.username", "samplepassword");
String serverUrl = loginResult.getServerUrl();
soapBinding._setProperty(Stub.ENDPOINT_ADDRESS_PROPERTY, serverUrl);
SessionHeader sh = new SessionHeader();
sh.setSessionId(loginResult.getSessionId());
soapBinding.setHeader((new SforceServiceLocator()).getServiceName().getNamespaceURI(), "SessionHeader", sh);
System.out.println(soapBinding.getServerTimestamp().getTimestamp());
axis1.4のライブラリとcommonshttp3.1のライブラリを手に入れます。
ダウンロードサイトが見つからない場合はこちらにアップロードしました。(いいのかなこれ、何か問題ありましたら教えてください。)
axis1.4のライブラリへクラスパスを指定し次のコマンドでコードをジェネレートします。
set classpath=..lib*
java -cp %classpath% org.apache.axis.wsdl.WSDL2Java -a partner.wsdl
出力されたファイルをコンパイルし、実行時にaxis1.4のライブラリと共にクラスパスに含めます。
通信時にhttpproxyを使う場合は、次のようにシステムプロパティをセットします。システムプロパティなので、javaコマンドの引数からも指定可能です。
Properties systemprops = System.getProperties();
systemprops.setProperty("http.useProxy", "true");
systemprops.setProperty("http.proxyHost", "samplehost");
systemprops.setProperty("http.proxyPort", "9999");
systemprops.setProperty("http.proxyUser", "sampleproxyuser");
systemprops.setProperty("http.proxyPassword", "sampleproxypasword");
本番やSandBoxのapiのエンドポイントを指定します。何も指定しないと、wsdlに記載のものを使います。
soapBinding._setProperty(Stub.ENDPOINT_ADDRESS_PROPERTY, "https://login.salesforce.com/services/Soap/u/39.0");
login後に、セッションIDとインスタンスURLが返却されます。インスタンスURLでエンドポイントを再設定し、セッションIDをSOAP Headerにセットします。
String serverUrl = loginResult.getServerUrl();
soapBinding._setProperty(Stub.ENDPOINT_ADDRESS_PROPERTY, serverUrl);
SessionHeader sh = new SessionHeader();
sh.setSessionId(loginResult.getSessionId());
soapBinding.setHeader((new SforceServiceLocator()).getServiceName().getNamespaceURI(), "SessionHeader", sh);
axis1.4の標準のtransportはaccept-Encodingでgzipに対応していないので、httpcommons3を使うように変更します。
client-config.wsddファイルに次の記載をします。
<?xml version="1.0" encoding="UTF-8"?>
<deployment name="defaultClientConfig"
xmlns="http://xml.apache.org/axis/wsdd/"
xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">
<globalConfiguration>
<parameter name="disablePrettyXML" value="true"/>
<parameter name="enableNamespacePrefixOptimization" value="false"/>
</globalConfiguration>
<transport name="http" pivot="java:org.apache.axis.transport.http.CommonsHTTPSender"/>
<transport name="https" pivot="java:org.apache.axis.transport.http.CommonsHTTPSender"/>
<transport name="local" pivot="java:org.apache.axis.transport.local.LocalSender"/>
<transport name="java" pivot="java:org.apache.axis.transport.java.JavaSender"/>
</deployment>
-Daxis.ClientConfigFile=client-config.wsdd
を指定します。
以上を行うことで、上記コードのsoapBindingは、Salesforce APIとやりとりができる状態になります。
SOAP APIのJavaクライアントには、色々なライブラリがあります。それぞれのJavaライブラリでSalesforce APIを使ってみた結果をまとめてみます。
Java ライブラリ | SOAP API | Metadata API | xsd:intにバインドされる型 | XML中の不正な制御コード | データバインディングの性能 | |
---|---|---|---|---|---|---|
Axis1.4 | ○ | × | Integer | エラー | 普通 | |
Axis2 | adb | × | × | Integer | エラー | - |
xmlbeans | ○ | ○ | int(対応するboolean変数で値がセットされたかどうかは判定可能) | エラー | やや早い | |
wsimport | ○ | ○ | Integer | エラー | 普通 | |
apache cxf | ○ | ○ | Integer | やや遅い | ||
wsc | ○ | ○ | int(対応するbooleanで値がセットされたかどうか判定可能だが、変数がprivateであり値を取得するためにリフレクションが必要) | 除去する | やや早い |
Java ライブラリ | 1回目(ms) | 2回目(ms) | 3回目(ms) | 平均(ms) |
---|---|---|---|---|
axis1.4 | 5726 | 5196 | 5165 | 5362.33 |
aixs2(xmlbeans) | 5054 | 4212 | 4571 | 4612.33 |
wsimport | 5928 | 6037 | 5819 | 5928 |
apache cxf | 7572 | 7894 | 7474 | 7646.67 |
wsc | 4181 | 4508 | 4358 | 4349 |
次回からそれぞれについて、細かくコードを紹介したいと思います。
補足
各ライブラリでのSalesforceへレコードを作成・更新する際のSobjectへの項目値の指定の仕方は次の記事に書きました。
crmprogrammer38.hatenablog.com