[SalesForce] Connecting to Tooling API using Java SOAP

I have been trying to connect to the tooling API using SOAP and Java, but so far I have not been able to get it to work. I have a Java project with the tooling.jar and wsc-23.jar present, and I have a build script that generates the necessary code. The environment seems to be fine, but I am pretty sure the initialization code for the connection is wrong.

I have not been able to find a tutorial that shows how to set up this connection. However, I did download the Force.com plugin code from GitHub and used this as a guide.

Here is my code:

  private static void doStuff() throws Exception {
    Properties props = new Properties();
    try (Reader r = new FileReader("connection.properties")) {
      props.load(r);
    }

    // 1. Establish partner connection.
    ConnectorConfig partnerConfig = new ConnectorConfig();
    partnerConfig.setUsername(props.getProperty("force.user"));
    partnerConfig.setPassword(props.getProperty("force.password") + props.getProperty("force.token"));
    partnerConfig.setAuthEndpoint(props.getProperty("force.url.u"));
    partnerConfig.setServiceEndpoint(props.getProperty("force.url.u"));

    PartnerConnection partnerConnection = com.sforce.soap.partner.Connector.newConnection(partnerConfig);

    // 2. Establish tooling connection.
    ConnectorConfig toolingConfig = new ConnectorConfig();
    toolingConfig.setSessionId(partnerConnection.getSessionHeader().getSessionId());
    toolingConfig.setAuthEndpoint(partnerConfig.getAuthEndpoint());
    toolingConfig.setServiceEndpoint(props.getProperty("force.url.t"));

    SoapConnection toolingConnection = com.sforce.soap.tooling.Connector.newConnection(toolingConfig);

    // 3. Use tooling connection. THIS IS THE LINE THAT FAILS
    DescribeGlobalResult res = toolingConnection.describeGlobal();
  }

This is the referenced properties file containing configurable settings:

force.user=<nope>
force.password=<nope>
force.token=<nope>
force.url.u=https://login.salesforce.com/services/Soap/u/30.0/
force.url.t=https://login.salesforce.com/services/Soap/T/30.0/

Finally, here is the Java exception including stack trace:

[UnexpectedErrorFault [ApiFault  exceptionCode='UNKNOWN_EXCEPTION'
 exceptionMessage='Destination URL not reset. The URL returned from login must be set in the SforceService'
]
]

    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:408)
    at java.lang.Class.newInstance(Class.java:433)
    at com.sforce.ws.bind.TypeMapper.readSingle(TypeMapper.java:628)
    at com.sforce.ws.bind.TypeMapper.readObject(TypeMapper.java:505)
    at com.sforce.ws.transport.SoapConnection.parseDetail(SoapConnection.java:228)
    at com.sforce.ws.transport.SoapConnection.createException(SoapConnection.java:202)
    at com.sforce.ws.transport.SoapConnection.receive(SoapConnection.java:148)
    at com.sforce.ws.transport.SoapConnection.send(SoapConnection.java:110)
    at com.sforce.soap.tooling.SoapConnection.describeGlobal(SoapConnection.java:392)
    at Main.doStuff(Main.java:65) <--- This is the line above where I mentioned it blows up
    at Main.main(Main.java:34)

What do I need to change in my code so it will successfully login and initialize correctly so I can use the tooling API?

Best Answer

When a login is done via https://login.salesforce.com, one of the return values of the login is the instance that the username corresponds to such as https://na15.salesforce.com (as part of the overall returned URL). In terms of the Java API you are using, that means that setAuthEndpoint can use the fixed login URL but setServiceEndpoint has to be given a value returned from the login process.

Some service endpoints are directly available from the LoginResult, but the Force.com IDE obtains the tooling one by replacing the "u" with a "T" in a rather fragile looking way here.

So your code might look something like this:

ConnectorConfig partnerConfig = new ConnectorConfig();
partnerConfig.setManualLogin(true);

PartnerConnection partnerConnection = com.sforce.soap.partner.Connector.newConnection(
        partnerConfig);
LoginResult lr = partnerConnection.login(
        props.getProperty("force.user"),
        props.getProperty("force.password") + props.getProperty("force.token")
        );

ConnectorConfig toolingConfig = new ConnectorConfig();
toolingConfig.setSessionId(lr.getSessionId());
toolingConfig.setServiceEndpoint(lr.getServerUrl().replace('u', 'T'));

SoapConnection toolingConnection = com.sforce.soap.tooling.Connector.newConnection(
        toolingConfig);
Related Topic