Pages

Monday, March 24, 2014

An alternative to Salesforces Wsdl2Apex for calling SOAP web services in Apex

Salesforce provides a tool called Wsdl2Apex that allows you to generate Apex classes from a WSDL. These Apex classes act as a proxy for invoking the web service methods.

While functional, Wsdl2Apex has a number of limitations. Including:

  1. The generated Apex classes require code coverage, which needs to be created manually.
  2. You need to import the entire WSDL. In many cases you may only require a subset of the web methods. Reducing the number of methods cuts down the lines of Apex (a limited resource) that are generated and subsequently the number of lines requiring code coverage.
  3. Support for complex types that extend a base type. <xsd:extension base="foo:Bar">.
  4. Support for importing another WSDL. <xsd:import>
  5. Support for attributes. <xsd:attribute>
  6. The ordering of the generated methods appears to be arbitrary (maybe hash based ordering internally?). At the very least, a small change in the input WSDL can produce Apex that doesn't diff very well. This can be a pain with source control and tracking the history of changes.
  7. Conflicts between named WSDL elements and reserved keywords in Apex. E.g. long
  8. WSDLs that contain multiple wsdl:binding elements

I've been working with an intern student here at FuseIT to create a replacement tool as part of the FuseIT SFDC Explorer under the new WSDL tab in the 1.0.0.47 release. The current release is aimed at having reasonable feature parity with the existing Salesforce Wsdl2Apex implmentation.

Current functionality:

  • Import a WSDL from URL or local file.
  • User definable class names for each WSDL namespace
  • Detection of existing Apex Classes that match those being generated
  • The ability to select just the Apex methods that should be generated (including a description of the input and output parameters)
  • Publish the Apex classes directly into a Salesforce Org via the Tooling API.

Work in progress and possible future extensions:

  • Generate test Apex methods and mocks that give 100% code coverage for the generated callout code.
  • Generate HttpRequest web service calls as an alternative/backup to the WebServiceCallout.invoke calls.
  • Generate a WebServiceMock class with expanded request/response objects and doInvoke implementation.
  • Generate a wrapper Apex class that will revert to the mock implementation with running in a test case context. This could also expose the end point and timeout settings as properties.

See also:

18 comments:

  1. Nice blog..Looks like this tool will help us in the future for all SOAP implementation

    ReplyDelete
    Replies
    1. Thanks. There are still several areas that we will be expanding it. Please do let us know if you find a problematic WSDL.

      Delete
    2. Hi, I found problematic WSDL. I've tried generate SpringCmService WSDL and seems that there is the problem with inheritance. The generated apex class has errors and can not be deployed to SFDC.

      Delete
    3. Hi. Are you able to share the WSDL URL with me? I'll give it a test to see what the issue is. Also, what version of the sfdc explorer are you using?

      Delete
    4. Hi, I'm using sfdc explorer v 1.11.0.88. You can find WSDL under link: https://soapna11.springcm.com/atlas/webservices/v201305/springcmservice.asmx?wsdl

      Thanks,
      M.

      Delete
    5. Hi Magdalena,

      The missing class SCMSecurity is based on a simpleType in the WSDL with the possible values: None, See, Read, Write, Move, Create, SetAccess.

      I'll need to look at updating the tool to support this. In the short term, you could manually edit the generated Apex Class replacing "wwwSpringcmComAtlasWebservicesV2013.SCMSecurity" with "String". Then just ensure you only set a valid value.

      Cheers,
      Daniel

      Delete
    6. Thank you Daniel for quick response.

      it is not the only problem. There is also issue with inheritance.
      In WSDL: SCM Document extends SCMObject and SCMObject extends SCMBaseObject.
      SCMObject inherits properties from SCMBaseObject, but from SCMDocument I don't have an access to them. In short scmDocument object doesn't inherit the SCMBaseObject.

      Regards,
      Magda

      Delete
    7. Hi Magda,

      We've updated to tool to handle multiple levels of inheritance. It will now pull the required fields from the extended complex types as required. Please get v1.16 or later to get this update.

      There is still a potential issue with the in SCMSecurity. In your case you can just space seperate the listed restriction values and it should work fine.

      Cheers,
      Daniel

      Delete
  2. This comment has been removed by the author.

    ReplyDelete
  3. Hi Daniel.

    Very useful tool for Salesforce developers.

    I am battling to generate Apex stub classes from a RPC / encoded style WSDL. Would it be possible for you to have a quick look at the error? I have tried also after covering the WSDL to a Document / literal style WSDL, but I still cannot parse the WSDL file. I am using version 3.2.16095.1 of the FuseIT SFDC Explorer. I would really appreciate some help.

    Thanks,
    Rudolf Niehaus

    ReplyDelete
    Replies
    1. Hi Rudolf,

      Unfortunately the native WebServiceCallout.invoke Apex method doesn't support RPC style web services. It might be possible using HttpCallout and XmlDom to construct and process the raw HTTP request/response. See http://salesforce.stackexchange.com/q/94923/102

      Delete
  4. Hi Daniel.
    I Realy like the FuseIT explorer with the WSDL2Apex implementation.
    This gave me a booth start in converting wsdl 1.2 into Apex classes.
    Unfortunately most of the Attributes are set as elements instead of attributes, so a lot of manual afterwork. I cannot wait for an update that handles the attributes better. I also do not understand why all parsed nodes are stored after the value is set in de class, but I assume it is for checking afterwards. Many thanks for having the FuseIT Explorere publicly available:)
    David van 't Hooft @ mplify.nl

    ReplyDelete
    Replies
    1. Hi David,
      Are you able to share the WSDL in question so I can check it to see what the output is?

      You mention wsdl 1.2. Did you mean SOAP 1.2?

      Unfortunately SOAP 1.2 isn't support at the moment as the underlying WebServiceCallout.invoke method provided by Apex doesn't support. Instead I'd need to completely generate all the Apex to manually POST to the service and then process the response.

      Delete
    2. Hi Daniel,
      Indeed SOAP 1.2. De FuseIT explorer create's nicely the structure for the HTTP POST.
      I can share the WSDL but not then on a direct Email and not on the blog.
      I removed most of the WSDL (SOAP 1.2) unsupported items, but for the Attributes I could not. They need to be there. Single attributes are mostly converted into elements and if there are more then 1 attribute, they are being ignored. So I modified the classes manually.
      The WSDL2APEX as example generates:
      public class BusinessObjectTypeCode {
      public sapComXiApCommonGdt.BusinessObjectTypeCode_Content input;
      private String[] input_type_info = new String[]{'input','http://sap.com/xi/AP/Common/GDT','BusinessObjectTypeCode.Content','1','1','false'};
      private String[] apex_schema_type_info = new String[]{'http://sap.com/xi/AP/Common/GDT','true','false'};
      private String[] field_order_type_info = new String[]{};
      public BusinessObjectTypeCode(){
      }
      public BusinessObjectTypeCode(DOM.XmlNode responseNode){
      Set nodesParsed = new Set();
      DOM.XmlNode inputNode = responseNode.getChildElement('input', 'http://sap.com/xi/AP/Common/GDT');
      if(inputNode == null){
      this.input = null;
      } else{
      sapComXiApCommonGdt.BusinessObjectTypeCode_Content inputObj = new sapComXiApCommonGdt.BusinessObjectTypeCode_Content(inputNode);
      nodesParsed.add(inputNode);
      this.input = inputObj;
      }
      //System.debug(this.input);
      }
      public void populateXmlNode(Dom.XmlNode outerNode){

      sapComXiApCommonGdt.BusinessObjectTypeCode_Content inputObj = this.input;
      Dom.XmlNode inputNode = outerNode.addChildElement('input', 'http://sap.com/xi/AP/Common/GDT', '');
      if(inputObj != null){
      inputObj.populateXmlNode(inputNode);
      }
      }
      }

      But I need something like this (still need to do an end to end test):
      public class BusinessObjectTypeCode {
      public String input;
      public String listID;
      public String listVersionID;
      public String listAgencyID;
      private String[] input_type_info = new String[]{'input','http://sap.com/xi/AP/Common/GDT','BusinessObjectTypeCode.Content','1','1','false'};
      private String[] apex_schema_type_info = new String[]{'http://sap.com/xi/AP/Common/GDT','true','false'};
      private String[] field_order_type_info = new String[]{};
      public BusinessObjectTypeCode(){
      }
      public BusinessObjectTypeCode(DOM.XmlNode responseNode){
      Set nodesParsed = new Set();
      this.input = responseNode.getText();
      //System.debug(this.input);
      this.listID = responseNode.getAttributeValue('listID', 'http://www.w3.org/2001/XMLSchema-instance');
      //System.debug(this.listID);
      this.listVersionID = responseNode.getAttributeValue('listVersionID', 'http://www.w3.org/2001/XMLSchema-instance');
      //System.debug(this.listID);
      this.listAgencyID = responseNode.getAttributeValue('listAgencyID', 'http://www.w3.org/2001/XMLSchema-instance');
      //System.debug(this.listID);
      }
      public void populateXmlNode(Dom.XmlNode outerNode){
      if(input != null){
      outerNode.addTextNode(String.valueOf(this.input));
      }
      outerNode.setAttribute('listID',(this.listID == null) ? '' : this.listID);
      outerNode.setAttribute('listVersionID',(this.listVersionID == null) ? '' : this.listVersionID);
      outerNode.setAttribute('listAgencyID',(this.listAgencyID == null) ? '' : this.listAgencyID);
      }
      }

      David van 't Hooft @ mplify

      Delete
  5. Hi Daniel,

    I send you a E-Mail with the WSDL.

    David van 't Hooft @ mplify.nl

    ReplyDelete
    Replies
    1. I'll keep an eye out for it. On which domain did you email me? I haven't seen it yet. Or did you use the support email in the explorer?

      Delete
    2. Hi Daniel,
      I used the FishOfPrey gmail.

      Delete