Salesforce.com Partner SOAP API JAX-WS Tutorial Part 2

This is Part 2 in a series. See Part 1, Part 3, and Part 4 to get up to speed. By the end of this tutorial, you’ll know how to instantiate some of the classes you created in Part 1 and how to log in to Salesforce.com with the Partner API. Update: I’ve released an open source library that presents a better interface to the Partner API (as well as other Salesforce APIs).

Getting started

Follow the steps in Part 1 to generate the code to use the Partner API. We’ll assume the code was generated with the package ‘sfstub’.

You should also create a Developer Salesforce account, which you can do by following the “Free Developer Edition” link on the left of the page at http://developer.force.com/.

Preparing to log in

The first step is to get a stub that you can then use to execute API calls. This is one of the classes that was autogenerated for you by JAX-WS. You can see what class to use by looking at the bottom of the WSDL.

    <!-- Soap Service Endpoint -->
    <service name="SforceService">
        <documentation>Sforce SOAP API</documentation>
        <port binding="tns:SoapBinding" name="Soap">
            <soap:address location="https://www.salesforce.com/services/Soap/u/16.0"/>
        </port>
    </service>

JAX-WS will have generated a SforceService class and a Soap class in the sfstub package to correspond to the service and port elements in the WSDL. The service only needs to be instantiated once for the lifetime of your app. It’s also fairly expensive to create, so make sure to cache your SforceService instance for reuse.

It’s worth noting, however, that the generated code for the SforceService class contains the literal file path to the WSDL that was used to generate it. This will cause problems when you try and deploy your completed jar to somewhere other than your development machine since you very likely won’t have the WSDL file in the same place on the file system. Instead, you would presumably package the WSDL into the jar. Thus, instead of having a URL like “file:/home/yourname/eclipse/mysalesforceapp/partner.wsdl”, you want a URL like “/resources/partner.wsdl”. The no-arg constructor for SforceService defaults to using the URL for the file path to the WSDL file, so you should instead use the other constructor that allows you to specify which URL should be used. You’ll also have to specify the QName for the second argument. Simply use the same QName invocation that the default constructor for SforceService uses. You’ll end up with something like this:

        String path = "/partner-15.wsdl";
        URL url = YourClass.class.getResource(path);
        if (url == null) {
            throw new SomeException("Couldn't find sf partner wsdl for path " + path);
        }
        service = new SforceService(url,
            new QName("urn:partner.soap.sforce.com", "SforceService"));

Once you’ve got your SforceService instance, you can use it to create a port. Think of the port as the actual connection: it’s what you will use to make API calls.

        Soap port = service.getSoap();

Now you’ve got a port, but you still need to log in to get a session id so you can query(), etc. To log in through the partner API, you need a username and password as well as a partner key. The partner key is provided to you by Salesforce.com if you are a certified partner. If you’re not certified, you can leave it blank.

Once you have the three pieces of information you need, you’re ready to log in. You’ll need to specify your partner key. This is a little complicated since it involves setting an outbound header.

        CallOptions callOpts = new CallOptions();
        callOpts.setClient(YOUR_PARTNER_KEY);
 
        WSBindingProvider wsBindingProvider = (WSBindingProvider) port;
 
        JAXBContext jaxbContext;
        try {
            // use the package you created your stub classes in
            jaxbContext = JAXBContext.newInstance("sfstub");
        } catch (JAXBException e) {
            throw new SomeException(
                "Could not get the JAXB context for the stub package", e);
        }
 
        wsBindingProvider.setOutboundHeaders(
            Headers.create((JAXBRIContext) jaxbContext, callOpts));

First, create a Login object (which represents a loginRequest message in the WSDL) and set the user credentials in it.

        Login loginParam = new Login();
        loginParam.setPassword(password);
        loginParam.setUsername(username);

Making the login() call

Now you’re ready to make the actual login() API call. I’m going to be using the method parameter and return style that you’ll see when you use a JAXB binding file. I’ll explain the other style after this example.

        LoginResponse response;
        try {
            response = port.login(loginParam);
        } catch (InvalidIdFault_Exception e) {
            throw new SomeException("Invalid Id", e, e
                    .getFaultInfo());
        } catch (LoginFault_Exception e) {
            throw new SomeException("Bad credentials for user '"
                    + username + "'", e, e.getFaultInfo());
        } catch (UnexpectedErrorFault_Exception e) {
            throw new SomeException("Unexpected error", e, e
                    .getFaultInfo());
        } catch (WebServiceException e) {
            throw new SomeException("Web Service exception", e);
        }

If you used -B-XautoNameResolution, you wouldn’t need a Login object. Instead, you would directly pass the username and password to the login() call.

        port.login(username, password);

If this applies to you, do that instead of

        port.login(loginParam);

that you see in the example.

The LoginResponse object represents the incoming loginResponse message for our outbound loginRequest.

The login() method throws three checked exceptions: InvalidIdFault_Exception, LoginFault_Exception and UnexpectedErrorFault_Exception. Those three are all declared as faults in the WSDL that can occur for the login method, so the generated code declares those in the method signature for login(). Most API calls can throw UnexpectedErrorFault_Exception. There are several other fault types declared in the WSDL as well.

In some of the calls to our hypothetical custom exception class SomeException, I’m calling getFaultInfo() to get a constructor parameter. This method returns an ApiFault object that contains a fault code and fault message. The fault code comes from this list. The fault message is generally a human-readable explanation. Keeping track of this information in whatever exception class(es) you use for faults thrown by the API will ease debugging.

The last exception being caught is WebServiceException. This is thrown by the JAX-WS stack when something goes wrong in the stack itself. Some examples of things that could throw this exception (or its subclasses) are networking failures and XML encoding errors. This is an unchecked exception, so the compiler will not warn you if you don’t catch it. Be careful to catch it whenever you make an API call on the port.

Post-Login details

Changing the endpoint

When making the initial login request, JAX-WS will automatically use the endpoint specified in the WSDL. Presumably as a load-balancing measure, Salesforce.com will then provide another endpoint to use for all further communication once you’ve logged in.

To get the new endpoint, we first need to get the LoginResultType object from the LoginResponse. This is because the loginResponse WSDL message is declared to contain a loginResult element that actually contains the data of the response. This pattern of having an object for the message that contains another object with the data is used throughout the various API calls

        LoginResultType loginResult = response.getResult();

If you chose to use my example JAXB customization for the generated class names in the previous tutorial, you should use LoginResultType like I have above. If you chose to use -B-XautoNameResolution instead, the login() call would return a LoginResult directly without an intermediate LoginResponse. (If the WSDL changes in the future to cause a name conflict for “loginResult”, you could theoretically need to use LoginResult2 if that’s the way the name conflict was resolved. As of my testing with API v16, though, there isn’t a name conflict, so regular old LoginResult works fine with -B-XautoNameResolution.) In general, if you’re using a JAXB binding file, you’ll need to wrap method arguments and unwrap method return values with their call-specific wrapper objects, whereas if you’re using -B-XautoNameResolution, you will not need to.

Now that we have the login result data, we can set the new endpoint.

        Map<string, Object> reqContext = wsBindingProvider.getRequestContext();
        reqContext.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY,
                loginResult.getServerUrl());

Enabling GZip compression

Using GZip to compress the XML SOAP data will drastically reduce the bandwidth used. We can do this by setting another request context item.

        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);

Setting the Session Id

Finally, we need to set the session id header so that future API calls will be tied to the user we just logged in as. We can do this in a way similar to how we set the partner key earlier.

        List<header> headers = new ArrayList<header>();
 
        SessionHeader sessionHeader = new SessionHeader();
        sessionHeader.setSessionId(loginResult.getSessionId());
 
        headers.add(Headers.create((JAXBRIContext) jaxbContext, sessionHeader));
        headers.add(Headers.create((JAXBRIContext) jaxbContext, callOpts));
 
        wsBindingProvider.setOutboundHeaders(headers);

This time, we want to set two headers since we need both the partner key and the session id.

At this point, you now have a fully logged in, ready-to-use port. In Part 3 of this series, I’ll cover how to download data from Salesforce.com using the port.

  • Digg
  • StumbleUpon
  • del.icio.us
  • Facebook
  • Twitter
  • Google Bookmarks
  • DZone
  • HackerNews
  • LinkedIn
  • Reddit
  • http://FarFromThere.org AH

    Thanks for this blog, it is helping me sooo much. I have been doing plenty of research on my own but your post help with alot of issues. Thanks again.

  • tony

    Hi Marshal,

    I am quite new to Salesforce.com, This tutorial help me a lot.

    I dont know where I can get Salesforce.com partner key?

    Thanks for any suggestion

    • Marshall Pierce

      I’m not sure exactly what the procedure is, but no doubt it requires talking to Salesforce directly.

    • http://www.genius.com Ryan Ausanka-Crues

      Based on the API docs, http://www.salesforce.com/us/developer/docs/api/Content/sforce_api_header_calloptions.htm, it looks as though the clientID (Partner Key) is arbitrary. We’ve used the same one for so long that I don’t know that anyone remembers how we got it initially. I would definitely double check with Salesforce.com to make sure there are no stipulations around the clientID but, until then, just try using one of your design.

      • tony

        Thanks Ryan.

        I will contact the salesforce.com to get one. I found I can login salesforce, just use the usename and password+token, without clientID(partner key) by soap.login(String username, String password).

  • tony

    Also, I only got stub code soap.login(String username, String password), cannot find soap.login(Login LoginParam).

    Could you help me out. Thanks in advance.

    • Marshall Pierce

      Tony, can you show me how you’re generating your stub code? Note that if you’re not using JAX-WS, the generated stub code will be different. (The way method arguments and other things are handled will vary from toolkit to toolkit.)

      • tony

        Thanks for your reply.

        I used the command wsimport.bat :
        jaxws-ri/bin/wsimport.bat -p sfstub -B-XautoNameResolution -d sfstub-build -s sfstub-src partner.wsdl
        as you mentioned in part 1

        also I tried
        wsimport.exe -p sfstub -B-XautoNameResolution -d sfstub-build -s sfstub-src partner.wsdl
        wsimport.exe from %JAVA_HOME%\bin.

        the generated stub code are similar. in the Soap class, there is only the method:

        public LoginResult login(String username, String password)

        the following is the wsdl file
        http://temp003.s3.amazonaws.com/partner.wsdl

        • Marshall Pierce

          What version of Java are you using? I’m also seeing this behavior now, even with old versions of JAX-WS, which makes me think that something may have changed in the libraries that ship with the JDK (like JAXB).

          • tony

            I am using jdk1.6.0_16.

        • Marshall Pierce

          OK, I’ve figured out what the problem is. When you generate the stub using the binding customization file instead of -B-XautoNameResolution, you get the ‘parameter’ objects for login and other calls (e.g. the Login object being passed to login()) . When you use -B-XautoNameResolution instead of the binding customization file, you get method parameters that are broken out (e.g. login(String username, String password). I’ll update the article to clarify. Thanks for catching this!

          • tony

            Thanks for your feedback.

            This is a great blog.

            Surprise, the salesforce consultant told me the Partner Key is not defined in their basic function. they don’t know partner key, either. Is it security token?

            I found I can login salesforce with any string as a parameter instead of Partner key according to your tutorial.

          • Marshall Pierce

            Hmm… it’s possible that the salesforce consultant is only familiar with the Enterprise WSDL, as that’s the most commonly used WSDL. Take a look at the comments in the sample code on http://www.salesforce.com/us/developer/docs/api/Content/sforce_api_header_calloptions.htm:

            // The real Client ID will be an API Token provided by salesforce.com
            // to partner applications following a security review.
            // For more details, see the Security Review FAQ in the online help
            string clientId = “SampleCaseSensitiveToken/100”;

            If it seems to work with any string as the ‘client id’ for the CallOptions header, just go with it then. :)

            The security token is a per-user random string. It’s used to allow access from IP ranges that haven’t been added to the list of approved networks, among other things.

          • http://www.steelbrick.com Max Rudman

            I found this article helpful so I thought I’d chime in regarding the Partner Key.
            Partner Key is NOT the same as Security Token.
            Partner Key (aka Client ID) is issued by Salesforce.com when application passes security review during publication on the AppExchange.
            This ID allows partner applications use the Web Services API with Professional Edition (PE) customers who normally don’t have API access.

  • Ismael

    Hi Marshal,
    I’m using your example, but I have the following error:

    Exception in thread “main” java.lang.ClassCastException: $Proxy42 cannot be cast to com.sun.xml.internal.ws.developer.WSBindingProvider
    at com.SForceSoapClient.main(SForceSoapClient.java:93)
    I’m trying to cast “com.sforce.soap.enterprise.Soap” to “com.sun.xml.internal.ws.developer.WSBindingProvider”

    Could you help me?

    Thanks a lot.

    Regards.

    • Marshall Pierce

      Looks like you might be using the Enterprise WSDL instead of the Partner WSDL.

      • Ismael

        I have tested both and I have the same error.
        Thanks for you replay.
        I’m using: CXF 2.2.4 (last version) and maven cxf-codegen-plugin with -autoNameResolution

        org.apache.cxf
        cxf-codegen-plugin
        ${cxf.version}

        generate-sources
        generate-sources

        ${basedir}/src

        ${basedir}/resources/wsdl/SalesForceWS.wsdl
        SforceService

        -autoNameResolution

        wsdl2java

        Regards.

        • Marshall Pierce

          Since you’re using CXF and not the JAX-WS reference implementation, the steps to set headers are probably going to be different. Check the CXF docs.

  • Ismael

    I solved it!, adding followings params to the cxf-maven-pluging
    I checked CXF documentation.
    Thanks a lot.

    extraargs

    /*Enables or disables processing of implicit SOAP headers (i.e. SOAP
    *headers defined in the wsdl:binding but not wsdl:portType section.)
    *Default is false.*/
    extraarg -exsh extraarg
    extraarg true extraarg

    /* For compatibility with CXF 2.0, this flag directs the code generator
    * to generate the older CXF proprietary WS-Addressing
    * types instead of the JAX-WS 2.1 compliant WS-Addressing types.*/
    extraarg -noAddressBinding extraarg

    /* Automatically resolve naming conflicts without
    * requiring the use of binding customizations.*/
    extraarg -autoNameResolution extraarg

    extraargs

    This is my code: I have used JAVA 6, CXF, MAVEN

    import com.sforce.soap.enterprise.SforceService;
    import com.sforce.soap.enterprise.Soap;

    public static void main(String args[]) throws Exception {

    SforceService service = new SforceService();
    Soap port = service.getPort(Soap.class);

    System.out.println(“Invoking login…”);
    String userName = “email”;
    String password = “psw + salseforce segurity token”;

    // Login to get new endpoint and session
    LoginResult loginResult = port.login(userName, password, null);

    // Setting new endpoint
    System.out.println(“ServerUrl=” +loginResult.getServerUrl());
    Map requestContext = ((BindingProvider)port).getRequestContext();
    requestContext.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, loginResult.getServerUrl());

    // Setting new session
    System.out.println(“SessionId=” + loginResult.getSessionId());
    SessionHeader sh = new SessionHeader();
    sh.setSessionId(loginResult.getSessionId());

    //Query
    querySample(port, sh);

    //logout
    port.logout(sh);
    System.out.println(“logout…”);

    System.exit(0);
    }

  • pabz

    hi all,

    I just found out this site using google while searching for a way to convert salesforce wsdl into a javascript. It seem you guys are able to convert it using apache cxf but i’m getting some error when using wsdl2js? does anyone tried it and successfully converted it? any thought or advise is gladly appreciated..thanks

  • Raina

    Hi Ryan,

    Regarding AJAX Toolkit, can I use it in Palm Pre? I’m getting an error when I try to call sforce.login. I read about it online and they said cross domain scripting won’t allow it. If this is so, what can I use to access salesforce data in my Palm Pre?

    Thanks :)

    Raina

    • http://www.genius.com Ryan Ausanka-Crues

      You are correct that the AJAX Toolkit will not allow you the API in JavaScript from your own domain. In general, I’m not sure how good of an idea it is to use the API in JavaScript. The API only allows a few thousand requests per day which can quickly be used up if calls are made directly from the client. If you manage to get msdl2js working, you’re going to have significant problems as Salesforce has restrictions on the IP addresses allowed to connect via the API. Every time you switch IP addresses on the Pre, you’re going to have problems.

      Ryan

  • Tony Rozga

    Great stuff, I don’t know how I ever would have figured this out w/out this blog. I have a question for you JAXWS gurus: I would like to ship my product w/out having to distribute JAXWS RI jars but I’m confused by the location of some the classes you are using (like JAXBRIContext). In the RI, these are found in com.sun.xml.bin.api and com.sun.xml.ws but in a JAXWS capable jre they live in rt.jar in com.sun.xml.internal.bin.api and com.sun.xml.internal.ws. Is there any problem referencing these private jre packages beside the customary “don’t ref sun internal stuff” and figuring out one needs rt.jar in the javac bootclasspath? Thanks for any insight,
    Tony

    • Marshall Pierce

      Beyond the issues you’ve already mentioned, you’ll lose control over which version of JAXWS will be used. The version bundled with the JRE often lags behind the latest stable RI release. Unless other JVM vendors have started bundling JAXWS, it won’t work on a non-sun JRE either.

      • Tony Rozga

        ah, I knew there had to be stuff I wasn’t thinking of :) tnx Marshall.

  • Sijiya

    Hi Marshall ,

    while generating the stub code from enterprise wsdl,i am getting following error.

    i used wsimport command with XautoNameResolution

    parsing WSDL…

    [WARNING] src-resolve: Cannot resolve the name ‘tns:ID’ to a(n) ‘type definition
    ‘ component.
    line 22 of file:/E:/Enterprise.wsdl#types?schema1

    [ERROR] Two declarations cause a collision in the ObjectFactory class.
    line 2910 of file:/E:/Enterprise.wsdl

    [ERROR] (Related to above error) This is the other declaration.
    line 2909 of file:/E:/Enterprise.wsdl

    when i looked in to schema,the feilds name “UnsolicitedProposal__c” and “Unsolicited_proposal__c” are making problems.i think after parsing these two fileds it becomes the same name(ignoring underscores).

    Becuase of this issue i am not able ot generate the stub

    • http://eng.genius.com Marshall Pierce

      Sijiya,
      You may be able to customize the code generation using a jaxb binding file to avoid the name conflict. I’m no JAXB expert, though, so I don’t have much guidance there. You could also use the Partner API.

  • Federico Vela

    I was getting the error:

    java.lang.ClassCastException: $Proxy42 cannot be cast to com.sun.xml.internal.ws.developer.WSBindingProvider

    I realized the imports are different in the enterprise samples, so i changed those to

    import com.sun.xml.bind.api.JAXBRIContext;
    import com.sun.xml.ws.api.message.Headers;
    import com.sun.xml.ws.developer.WSBindingProvider;

    Then imported JAX-WS 2.2.1 into my project, and all worked fine!!!

  • MinGyoon Woo

    Thanks for very helpful article.

    BTW, does anyone here who have OpenOffice UNO extension with this partner.wsdl? I’ve try that but not success…

    I can call the ‘login’ function from OpenOffice Basic, but I can not handle the ‘LoginResult’ after that.

    Because I am a newbie of Java world… so this is difficult to understand….

  • Anil

    I am connecting to salesforce with my RAD 7.5 (WAS 6.1) . Getting an exception

    Exception in thread “main” java.lang.ClassCastException: com.sun.xml.bind.v2.runtime.JAXBContextImpl incompatible with com.sun.xml.internal.bind.api.JAXBRIContext

  • Pingback: Salesforce.com Partner SOAP API JAX-WS Tutorial Part 1 | Team Lazer Beez Blog