Join us as we review the capabilities of the existing WSDL-to-Apex code generation feature, and explain how we built a tool to provide expanded features using the Tooling API. The resulting tool has greater support for more WSDL features, generates test cases and the associated mocks to maximize code coverage, and optionally includes HttpRequest versions of the callouts.
3. Have you ever seen…
Feature Limits
Apex and
Deployment
No file chosen
Choose File
Error: Failed to parse wsdl: Unknown element: import
No file chosen
Choose File
Error: Failed to parse wsdl: Found more than one wsdl:binding. WSDL with multiple binding not supported
Apex Generation Failed
Unsupported WSDL. Found more than one part for message XAVRequestMessage
Apex Generation Failed
Unsupported schema type: {http://www.w3.org/2001/XMLSchema}anyType
Apex Generation Failed
The total size of apex code in this application after removing comments exceeds the maximum character size of
3000000
Apex Generation Failed
Class name 'toolingSoapSforceCom' already in use. Please delete this class or specify a new class name
Supported WSDL Features
http://www.salesforce.com/us/developer/docs/apexcode/Content/apex_callouts_wsdl2apex.htm#supported_wsdls_topic-title
4. In This Session
•The FuseIT SFDC Explorer tool
• Existing Salesforce WSDL functionality
•Demo custom WSDL to Apex tooling
•How it was made
5. The FuseIT SFDC Explorer Tool – FusesFriend
• Features
• Windows only for the full tool
• Basic web version of WSDL tooling
– Session management
– Schema Explorer + Tooling API
– SOQL queries
– Running Test Cases
– Debug Log Monitoring
– Data Export Retrieval
– Anonymous Apex
– Code Coverage
– WSDL import to Apex
http://www.fuseit.com/explorer
6. Unsupported
feature
Existing Wsdl2Apex Experience
WSDL
> 1 MB
<xsd:import>
<xsd:include>
Existing Class
Name
Manually
Edit WSDL
Superfluous
bindings
Download
single WSDL
Generate
from WSDL
Upload
single WSDL
Namespaces
to Classes
Generate
Apex Code
Call
Generated
Code
7. Shaving the [WSDL] Yak
“Yak shaving is what you are doing when you're doing
some stupid, fiddly little task that bears no obvious
relationship to what you're supposed to be working
on, but yet a chain of twelve causal relations links
what you're doing to the original meta-task.”
Scott Hanselman
Photo by Dennis Jarvis CC BY-SA 3.0
10. Code Coverage Testing for Generated Code
•Need to implement WebServiceMock Interface
• Invoke each web service method
•Handle Request and Response types
•Supporting Types
12. The Metadata API – Handling larger WSDLs
Class name 'Metadata' already in use.
Please edit WSDL to remove repeated
names
• CustomObject et al. extend Metadata and
need elements from it to work correctly.
<xs:extension base="Metadata">
• 330 KB WSDL
Apex Character Limit
Metadata
590,915
Remaining
• 590,915 characters / 6740 lines
without comments
• Approximately 20% of 3,000,000
character Apex limit
13. Method filtering functionality
•Reduces amount of generated code
•Reduces testing requirements for dead code
• Potentially skips unsupported features
• Optionally remove supporting Apex classes
15. How it works
Deploy to
Salesforce
• Tooling API to
deploy all
associated Apex
classes at once
Transform
to Apex
• T4 (Text Template
Transformation
Toolkit)Templates to
convert the object
model into Apex
Build Apex
Model
• C# Object Model of
Apex Classes,
Members and
Methods
Parse
WSDL
• Import all WSDL
data and extract
required elements
16. .NET Representation of Apex Class model
ApexClass
public class ListMetadataQuery {
public String folder;
public String type_x;
private String[] folder_type_info =
new String[]{'folder','http://soap.sforce.com/2006/04/metadata','string','0','1','false'};
private String[] type_x_type_info =
1 InnerApexClasses * Members * Methods
new String[]{'type','http://soap.sforce.com/2006/04/metadata','string','1','1','false'};
private String[] apex_schema_type_info =
ApexClassCollection ApexMember
new String[]{'http://soap.sforce.com/2006/04/metadata','true','false'};
private String[] field_order_type_info = new String[]{'folder','type_x'};
StringArrayApexMember
ApexMethod
ApexMethodParameter
ApexMethodHttp
ReturnType
ReturnVoid ReturnPrimitive ReturnClassType
Type Infos
Inner Classes
Namespace Collection
Parameters
*
ReturnType
1
ApexClasses
0..*
Outer class for each Namespace
Created for the Port/Service
Each Request/Response
public class soapSforceCom200604Metadata {
public class CallOptions_element {
// ...
}
// ...
public class MetadataPort {
// ...
public fp[] listMetadata(lmq[] q,Double asOfVersion) {
// ...
}
}
public class SessionHeader_element {
// ...
}
}
}
17. Tooling API deployment
Existing ID
• Create a MetadataContainer with a
unique name
• Create a collection of
ApexClassMember referencing the
MetadataContainerId
• Create a ContainerAsyncRequest
var cr = new ContainerAsyncRequest();
cr.MetadataContainerId =
New – no existing ID
• Call create and collect the SaveResults
• Or create an empty stub class to get an
MIeDta adantda Cuosntea ithneer McoenttaadianetarC=onnetwaiMneetradataContainer();
List<ApexClass> apexClassesToInsert = new List<ApexClass>();
// Add ApexClass records with T4 generated Body
ApexClass classToInsert = new ApexClass() { Name = "HelloDF" };
classToInsert.Body = "public class " + classToInsert.Name + " { public class Add{ }}";
apexClasses.Add(classToInsert);
– Option to Validate Only (IsCheckOnly)
• Keep retrieving until the State is no
longer Queued.
• Delete MetadataContainer
toUpdate = new List<ApexClassMember>();
foreach(ApexClass ac in classesToUpsert) {
var acm = new ApexClassMember();
acm.ContentEntityId = ac.Id; // 01p…
acm.Body = ac.Body; // T4 Template output
acm.FullName = ac.Name; // class name
acm.MetadataContainerId =
metadataContainerId.CaseSafeID;
// toUpdate.max length Add(acm);
32 characters!
container.Name = "UAC " + DateTime.Now.Ticks;
SaveResult[] containerResults = toolingService.create(new sObject[] { container });
if (!containerResults[0].success) {
SaveResult[] saveResults = toolingService.create(apexClassesToInsert.ToArray());
if (saveResults != null) {
throw new ToolingCreateException(container, containerResults[0]);
}
foreach (SaveResult sr in saveResults) {
if (!sr.success) {
throw new ToolingCreateException(apexClassesToInsert.ToArray(), saveResults);
Id metadataContainerId = new Id(containerResults[0].id);
}
sObject[] toCreate = toUpdate.ToArray();
SaveResult[] cResult =
toolingService.create(apexClassMembersToCreate);
foreach (SaveResult sr in cResult) {
if (!sr.success) {
throw new ToolingCreateException(toCreate,
cResult);
}
}
metadataContainerId.CaseSafeID;
cr.IsCheckOnly = false;
SaveResult[] cars = this.create(new sObject[]{cr});
if (! cars[0].success) {
throw new ToolingCreateException(cr,cars[0]);
}
ContainerAsyncRequest retrieve =
WaitForClassToUpdate(containerAsyncResults);
if (retrieve.State == "Failed") {
throw ApexUpdateException.FromDeployDetails
(retrieve.DeployDetails);
}
DeleteResult[] dr = this.delete(new string[]
{ metadataContainerId.CaseSafeID });
}
}
}
19. Summary
•Manually dealing with WSDL import errors is time consuming
and error prone
• Extended SOAP support for several common WSDL issues:
– Increased support – <xsd:import> <xsd:include> <xsd:extension>
– Generate basic code coverage
– Method filtering to reduce Apex
20. Q&A
+ What Next
• Download: the free FuseIT SFDC Explorer Tool (or get the web version)
http://www.fuseit.com/explorer
• Idea: Run anonymous apex as if it were a test case
http://goo.gl/Aci1ys
• Salesforce: The now open source WSDL2Apex generator
http://goo.gl/tUcnVj
• Session: Building Callouts Without WSDL2Apex and Apex Parsers
Tuesday, 4:00 PM - 4:30 PM | Moscone Center West | Mobile Theater
www.fuseit.com
@FishOfPrey
www.fishofprey.com
@GirishaArora
Girisha Arora
21.
22. Show Flow
Dreamforce 2104 Breakout Session Show Flow Template
Notes
Session Title
1:30pm session
Presentation Device: Laptop with HDMI out
Customer Speaker: Daniel Ballinger
Salesforce Speakers: n/a
Demo Device
Demo Driver:
Deck Owner/Filename:
1:15 PM Doors open
1:30 PM Start
:01 Welcome and Intros
:03
Overview of topic - Common WSDL2Apex errors, things we'll
cover
:05 Introduce the FuseIT SFDC Explorer Tool
:09 Existing Wsdl2Apex flow Run through current process - devs goal isn't to be editing WSDLs
:13 Demo - Importing a WSDL to Apex basic example
:15 The need manually do code coverage for generated code
:19 Demo - Generating WebServiceMock and Code Coverage
:21 Dealing with big WSDLS - Metadata API
:25 Demo - Selective Metadata methods. Anon call sample
:28 How it works - High level steps + Apex Class model
:30 How it works - Tooling API Deployment
:32 Demo - Tooling API code sample
:34 Summary – recap what was covered
:39 Q&A
:39 Wrap-up, Thank yous and Close
:40 Walking off Stage
24. Handling Extensions
• <xsd:extension base=“”/>
• Copy fields from base type to the
sub class.
• Use extends in the future
• Metadata API
• AccountOwnerSharingRule extends
• OwnerSharingRule extends
• BaseSharingRule extends
• Metadata
25. HTTP Requests
• When WebServiceCallout.invoke() fails
• A template for supporting more complex callouts
• Access full fault messages
SOAPFault Information for Apex
https://success.salesforce.com/ideaView?id=08730000000BqG9AAK
Using the Tooling API to Generate Apex SOAP Web Service Clients
Extending the capabilities of WSDL2apex
Nelson
Since 2007
Active on Salesforce StackExchange
Integration with SOAP based web services lead to this talk.
Tools needed for repeatedly handling SOAP based web services.
FuseIT – ISV Partner
Sitecore CMS
HP TRIM
Some of the errors you might encounter when generating Apex code for from a WSDL
http://www.fuseit.com/Solutions/SFDC-Explorer/WSDL-Parsing-Errors.aspx
Error: Failed to parse wsdl: Found more than one wsdl:binding. WSDL with multiple binding not supported
Error: Failed to parse wsdl: Found more than one wsdl:portType. WSDL with multiple portType not supported
Failed to parse wsdl: Failed to parse WSDL: Unable to find binding
Error: Failed to parse wsdl: Found schema import. External schema import not supported
Apex Generation Failed Unable to find schema for element
Error: Failed to parse wsdl: type not specified for attribute
Apex Generation Failed: No type specified for element
Error: need to modify WSDL with import tags or include tags
Enterprise wsdl error: Unsupported schema type: {http://www.w3.org/2001/XMLSchema}anyType
Overview of what the FuseIT SFDC Explorer tool is.
First step is a quick review of the existing functionality and highlighting some of the limitations therein. Provides a grounding for comparison.
A walkthrough of the free tool we have built.
To make working with SOAP based Web Services easier.
Avoid the need to manipulate the WSDL before importing into Salesforce
Improve support for WSDL features
Reduce the amount of generated code that isn't otherwise used.
Provide basic code coverage for the generated code so you don't have to.
Use the existing Salesforce WebServiceCallout.invoke where possible
Optionally support direct HTTP requests
Built out of necessity when dealing with an evolving webservice.
Fuses Friend
Note that this is not a full IDE replacement tool. Intended to be supplementary to existing tools such as Mavens Mate and the Developer Console.
Built both as a test bed for G4S functionality and as a tool for day to day development needs.
Free as in free beer.
Run through the steps using the existing built in tool:
Support for importing another WSDL <wsdl:import> (different namespace) or schema <xsd:include> (same namespace). Requires a single complete WSDL
Pain of trying to manually stitch the WSDL into one file.
Removing extra SOAP 1.2 bindings that won’t be used.
Note the 1MB file limit. Time consuming to manually prune the file size down. Don't want to be handling a WSDL with frequent changes. Reading a 1MB xml file isn’t much fun.
a megabyte of data can roughly be - a typical English book volume in plain text format (500 pages × 2000 characters per page)
Time spent manipulating the WSDL so you can call the web service from Apex
Installing tooling to manipulate complex XML documents,
Creating XSDs
Researching the differences between SOAP 1.1 and 1.2
http://xkcd.com/974/
Callout the actual error. Quick sample call against Salesforce with the same WSDL
Import the unmodified WSDL using SFDC Explorer. Show that the UPSSecurity_hns is correctly generated.
Don't generate the test classes at this stage.
http://www.channel4.com/programmes/the-it-crowd
Generated hundreds or thousands lines of code in seconds
Now before we can deploy or package that code we need to write test cases to at the minimum ensure code coverage.
Avoid writing basic code to test code that was generated.
Generate Test classes
Run test classes directly after deployment.
Partner API – 3000+ lines of apex
Anonymous Apex Code for Partner API:
partnerSoapSforceCom.Soap partnerAPI = new partnerSoapSforceCom.Soap();
partnerAPI.SessionHeader = new partnerSoapSforceCom.SessionHeader_element();
partnerAPI.SessionHeader.sessionId = UserInfo.getSessionId();
partnerAPI.endpoint_x = URL.getSalesforceBaseUrl().toExternalForm() + '/services/Soap/u/31.0';
partnerSoapSforceCom.QueryResult qr = partnerAPI.query('select Id from Account limit 1');
System.debug(qr);
171 cases of <xsd:extension base”…”>
(excluding comments and @isTest annotated classes)
Size Without Comments: 590,915 characters – 6740 lines
Org limited to 3,000,000 characters v31.0 Summer 14
One fifth of available Apex character used before it is called or tested!
Note the character count exceeds the quoted 100,000 limit from the docs.
Acknowledge existence of Andrew Fawcett's Apex Wrapper Salesforce Metadata API. It's more about the exercise than the end result.
Option to
CSV of generated methods. Listed in the generated classes for future regens.
Option to exclude or comment out unreferenced code
Use csv to define the methods to generate for.
Actively filter the list of methods to generate.
Choose if you also want to generate additional http requests.
View the return types.
soapSforceCom200604Metadata.MetadataPort service = new soapSforceCom200604Metadata.MetadataPort();
service.SessionHeader = new soapSforceCom200604Metadata.SessionHeader_element();
service.SessionHeader.sessionId = UserInfo.getSessionId();
service.endpoint_x = URL.getSalesforceBaseUrl().toExternalForm() + '/services/Soap/m/31.0';
System.debug(service.endpoint_x);
List<soapSforceCom200604Metadata.ListMetadataQuery> queries = new List<soapSforceCom200604Metadata.ListMetadataQuery>();
soapSforceCom200604Metadata.ListMetadataQuery queryWorkflow = new soapSforceCom200604Metadata.ListMetadataQuery();
queryWorkflow.type_x = 'Workflow';
queries.add(queryWorkflow);
soapSforceCom200604Metadata.ListMetadataQuery queryValidationRule = new soapSforceCom200604Metadata.ListMetadataQuery();
queryValidationRule.type_x = 'ValidationRule';
queries.add(queryValidationRule);
double asOfVersion = 31.0;
soapSforceCom200604Metadata.FileProperties[] fileProperties = service.listMetadata(queries, asOfVersion);
for(soapSforceCom200604Metadata.FileProperties fileProperty : fileProperties) {
System.debug(fileProperty.fullName);
}
Parse WSDLimports and includes.
Apex Model in C# classes
T4 Transform Model into Apex Classes
Deploy using the Tooling API
Compiled code that generates Apex code.
Outer classes represent the namespace. Inner classes the service proxy and the request/response types.
String [] members for the _type_info arguments for Web Service Invoke.
Param_type_info - XML Element Name, Namespace, XML Base Type, min, max, nillable
Apex_schema_type_info – Namespace, 'true' if elementFormDefault="qualified" , 'true' if attributeFormDefault="qualified"
TODO: Improve visibility on projector. Fine lines and Text.
Don’t really have the concept of an upsert for an Apex Class.
Outline the advantage of putting all the apex classes in one go for dependencies. Deploy all at once.
Brief Description of how the Tooling API is used to deploy multiple Apex classes in one go to allow for dependencies.
Validate only for existing Apex classes
Each new WSDL seems to have a unique way of applying the spec.
Share your WSDLs with us to improve support
Not a silver bullet solution. Can’t fix invalid XML or handle missing elements.
There are features that WebServiceCallout.invoke can’t handle and will require HTTP callouts instead.
<xsd:extension base=“”/> Copy fields from base type to the sub class.
Increase WSDL support – import/include/extension
Generate basic Code Coverage
Method filtering to reduce Apex characters
<xsd:import> - namespace
<xsd:include> - schema
<xsd:extension> - inheritance
Unsupported PortTypes/Bindings
Generated Code Coverage
Reserved Keywords
Source WSDL > 1 MB
CalloutException fault response
G4S as a freely available tool for working against developer edition orgs.
http://www.fuseit.com/en/Solutions/G4S.aspx
Meta Data API.
AccountOwnerSharingRule
<xsd:extension base="tns:OwnerSharingRule">
<xsd:extension base="tns:BaseSharingRule">
<xsd:extension base="tns:Metadata">
soapSforceComSchemasClassExamplefau.ExampleFaultWebServices service = new soapSforceComSchemasClassExamplefau.ExampleFaultWebServices();
service.SessionHeader = new soapSforceComSchemasClassExamplefau.SessionHeader_element();
service.SessionHeader.sessionId = UserInfo.getSessionId();
string result = service.basicStringMethod('hello');
soapSforceComSchemasClassExamplefau.ExampleFaultWebServices service = new soapSforceComSchemasClassExamplefau.ExampleFaultWebServices();
service.SessionHeader = new soapSforceComSchemasClassExamplefau.SessionHeader_element();
service.SessionHeader.sessionId = UserInfo.getSessionId();
//string result = service.basicStringMethod('hello');
string result = service.basicStringMethod_Http('hello');
Small changes in the WSDL can cause generated classes to change ordering. Makes finding the actual changes harder.
Handled by Sorting generated Apex Classes by name
Wsdl sorting for source control. The native Wsdl2Apex generator can generate with vastly different ordering from WSDL changes.
V30 to v31. Spring to Summer 2014