Introduction to the Force.com Web Services Connector   Leave a comment

Abstract

The Force.com Web Services Connector (WSC) is a code-generation tool and runtime library for use with Force.com Web services. WSC uses a high-performing Web services client stack implemented with a streaming parser and is the preferred tool for working with Salesforce.com APIs. You can write Java applications with WSC that utilize the Force.com Web services API, Bulk API and Metadata API. There are even runtime libraries that allow you to access the Force.com Web services API from applications running on Google App Engine.

Using WSC, you can perform operations with a few lines of code that would take many more lines of code with other Web services clients.

This article provides an introduction to WSC. The WSC can be used to invoke any doc-literal wrapped web service, but in this article we’ll focus on the Web services API with both the enterprise and partner WSDLs, and the Metadata API. Along the way you’ll learn how to get started with WSC, and see an example of a console application that demonstrates WSC functionality.

Force.com APIs

There are several available Web services APIs. Choose the API that supports what you are trying to accomplish.

The SOAP-based Web services API, commonly referred to as “the API”, is the most widely used API and allows you to create, retrieve, update, or delete records, along with 20+ different calls, including query, search, merge, and convertLead. The Web services API has two WSDL options:

  • Enterprise WSDL – This WSDL is used by developers to build client applications for a single Salesforce.com organization. It is more straightforward to use as it is strongly typed and closely represents the object model in your organization. The major drawback of the enterprise WSDL is that whenever you make a change to an object or field you must download and re-consume the WSDL to generate new client code.
  • Partner WSDL – This WSDL is used to create dynamic, metadata-driven client applications that work with multiple Salesforce.com organizations. The partner WSDL is more flexible and generic in nature, representing a loosely-typed data model consisting of name-value pairs instead of specific data types. The Partner WSDL is typically, but not always, used by Salesforce.com partners or ISVs to build packaged applications.

The Metadata API is used to retrieve, deploy, create, update, or delete metadata information for an organization, such as custom object definitions, tabs, profiles, and page layouts. The API is designed to manage the underlying metadata of an organization and not the data itself. It is typically used to build tools to retrieve customizations for an organization or deploy changes from a sandbox to a production organization.

There are two more APIs – the Bulk API and REST API (currently in developer preview). In this article, we’ll focus on the SOAP-based Web services API and Metadata API.

Getting Started

We’ll assume that you have some experience building Java applications with Force.com APIs, so we’ll skip over some of the granular details involved in the process.

To get started, you’ll need to download the appropriate WSDLs and WSC JAR (see below), run WSC over the WSDL (which generates client-side Java code), and then compile the client code.

To get your WSDL, log into your organization and go to Your Name -> Setup -> App Setup -> Develop -> API. Generate the enterprise WSDL (unique to your organization) and the Metadata WSDL, and save them to your desktop. You don’t need to download the partner WSDL as it is the same for every organization and the WSC project provides a pre-compiled client library for your convenience.

WSC is an open-source project hosted at Google Code. Download the latest version of the WSC (currently wsc-20.jar) and the pre-compiled Partner library (partner-18.jar) from the project’s download page to your desktop.

Now make sure you have Java 6 installed: Open a Terminal or a Command Prompt, depending on your machine, and change your working directory to the desktop. To generate the stub client code, run wsdlc on the downloaded WSDLs.

To generate the enterprise client jar, run the following command with the enterprise WSDL you downloaded from your organization.

java -classpath wsc-19.jar com.sforce.ws.tools.wsdlc enterprise.wsdl enterprise.jar

To generate the metadata client jar, run a similar command with the Metadata WSDL. There is a small issue with the Metadata WSDL, so see this blog post for more information.

java -classpath wsc-19.jar com.sforce.ws.tools.wsdlc metadata.wsdl metadata.jar

Enterprise WSDL Application

Most developers build client applications with the enterprise WSDL, so we’ll start with that one first. In Eclipse, create a new Java project named “WSC – Enterprise” and copy the wsc-xx.jar and enterprise.jar you just generated to the project. Add these two jars to your project’s build path by going to Project -> Properties -> Java Build Path -> Libraries. Create a new class (e.g., Main.java) and paste in the code from the Appendix. Make sure you add your user credentials (user name, and password with security token) for the appropriate static members. When you successfully run the application, it queries and displays the five most recent contacts and their account names, creates five new accounts, updates them, and then finally deletes them.

Here are some snippets from the code:

A key to starting is the ConnectorConfig class. Simply create a new ConnectorConfig object, set your credentials, create a new EnterpriseConnection object, connection, and then pass in the configuration to connect to Force.com.

1 ConnectorConfig config = new ConnectorConfig();
2 config.setUsername(USERNAME);
3 config.setPassword(PASSWORD);
4 //config.setTraceMessage(true);

A handy configuration switch to use is the setTraceMessage attribute. When setting this value to true, WSC displays the request and response XML packets in the Terminal window so you can easily see what is going across the wire.

Once you have the ConnectorConfig instance, create your connection:

1 connection = Connector.newConnection(config);

With this in hand, you can query, update, delete, or insert records very easily. Here’s a simple query:

01 QueryResult queryResults = connection.query("SELECT Id, FirstName, LastName, Account.Name " +
02 "FROM Contact WHERE AccountId != NULL ORDER BY CreatedDate DESC LIMIT 5");
03 if (queryResults.getSize() > 0) {
04 for (int i=0;i<queryResults.getRecords().length;i++) {
05 // cast the SObject to a strongly typed Contact
06 Contact c = (Contact)queryResults.getRecords()[i];
07 System.out.println("Id: " + c.getId() + " - Name: "+c.getFirstName()+" "+
08 c.getLastName()+" - Account: "+c.getAccount().getName());
09 }
10 }

So simple! Note how we can cast the SObject to a Contact object.

Creating is just as simple. Because you have Java classes representing the SObjects, you can simply go ahead and create them like so:

1 Account a = new Account();
2 a.setName("Test Account”);
3 Account[] records = new Account[] {a};
4 SaveResult[] saveResults = connection.create(records);

If you’ve retrieved records and updated them, you can write them back by using the update method – for example:

1 SaveResult[] saveResults = connection.update(records);

To delete records, call the delete() method with an array of IDs. For example:

1 String[] ids = new String[5];
2 // set the ids
3 DeleteResult[] deleteResults = connection.delete(ids);

Most of the methods also let you handle errors that can occur when invoking the web service. Here, for example, is how we can check for success on the delete operation:

01 for (int i=0; i< deleteResults.length; i++) {
02 if (deleteResults[i].isSuccess()) {
03 System.out.println(i+". Successfully deleted record - Id: " + deleteResults[i].getId());
04 } else {
05 Error[] errors = deleteResults[i].getErrors();
06 for (int j=0; j< errors.length; j++) {
07 System.out.println("ERROR deleting record: " + errors[j].getMessage());
08 }
09 }
10 }

If you compare this WSC code with SOAP-based API Quick Start code, you’ll see there is significantly less code required to connect to Force.com with the WSC.

Partner WSDL Application

Applications written with the partner WSDL look and function similarly to their enterprise WSDL cousins. Your application will differ in only a few places so we’ll just highlight those. In Eclipse, create a new Java project named “WSC – Partner” and copy the wsc-xx.jar and partner-yy.jar from your desktop to the project. Add these jars to your build path, create a new class (e.g., Main.java), and paste in the same code from the previous section.

Change the static connection object to use the PartnerConnection instead of the EnterpriseConnection. Now you need to replace portions of the Java code. Because the enterprise WSDL is typed, you get Java objects that represent the database object on the other end of the web service. This makes it easy to program of course – just use the Java object as you would any other, as shown in the previous section.

Notice the partner WSDL is untyped. There is no strong correlation, meaning it’s also more flexible – an app built against the partner WSDL can probably run against multiple different environments. Because it’s untyped, there’s no Java class, such as “Account.java,” to represent the Account type on the database. Instead, you need a more generic approach.

As an example, replace the following code snippet in the queryContacts method. The results from Force.com are returned as SObjects and since we are using the partner WSDL, these fields are accessible as name-value pairs instead of strongly-typed objects. You can use the getField() method to return a simple string value for a field, while related objects can be accessed using the getChild() method in conjunction with getField().

1 if (queryResults.getSize() > 0) {
2 for (SObject s : queryResults.getRecords()) {
3 System.out.println("Id: " + s.getId() + " " + s.getField("FirstName") + " " +
4 s.getField("LastName") + " - " + s.getChild("Account").getField("Name"));
5 }
6 }

Creating new objects is also slightly different when using the partner WSDL. Replace the following snippet in the createAccounts method. To create records in Force.com, create a new SObject object, set the type of SObject, and then populate its fields with data by using the setField() method.

01 SObject[] records = new SObject[5];
02
03 try {
04
05 // create 5 test accounts
06 for (int i=0;i<5;i++) {
07 SObject so = new SObject();
08 so.setType("Account");
09 so.setField("Name", "Test Account "+i);
10 records[i] = so;
11 }

Updating records with the partner WSDL is slightly different as well. Replace the following snippet in the updateAccounts method. Create new SObjects that only contain the fields that you want to change and pass these SObjects to Force.com.

01 if (queryResults.getSize() > 0) {
02 for (int i=0;i<queryResults.getRecords().length;i++) {
03 SObject so = (SObject)queryResults.getRecords()[i];
04 System.out.println("Updating Id: " + so.getId() + " - Name: "+so.getField("Name"));
05 // create an sobject and only send fields to update
06 SObject soUpdate = new SObject();
07 soUpdate.setType("Account");
08 soUpdate.setId(so.getId());
09 soUpdate.setField("Name", so.getField("Name")+" -- UPDATED");
10 records[i] = soUpdate;
11 }
12 }

Deleting records with the partner WSDL is similar to using the enterprise WSDL. Simply pass an array of IDs to be deleted. Replace the following snippet in the deleteAccounts method.

1 if (queryResults.getSize() > 0) {
2 for (int i=0;i<queryResults.getRecords().length;i++) {
3 SObject so = (SObject)queryResults.getRecords()[i];
4 ids[i] = so.getId();
5 System.out.println("Deleting Id: " + so.getId() + " - Name: "+so.getField("Name"));
6 }
7 }

When you are done, you’ll need to fix the import statements to use the partner client code. Type Ctrl-Shift-O to fix all imports using Eclipse.

Metadata Application

The Metadata API does not affect data in your org. It is used for managing customizations (typically sandbox to production deployments) and for building tools that can manage or inspect the metadata model. The Metadata API does not have a login function so you need to use the login() call in the Web services API to establish a session with Force.com.

In our application, we’ll connect with the partner WSDL and create a new Custom Object using the Metadata API. In Eclipse, create a new Java project named “WSC – Metadata” and copy the wsc-xx.jar, metadata.jar and partner-yy.jar from your desktop to the project. Add these jars to your build path, create a new class (e.g., Main.java), and paste in the code below.

01 package wsc;
02
03 import com.sforce.soap.metadata.*;
04 import com.sforce.soap.partner.PartnerConnection;
05 import com.sforce.ws.ConnectionException;
06 import com.sforce.ws.ConnectorConfig;
07
08 public class Main {
09
10 static final String USERNAME = "YOUR-USERNAME";
11 static final String PASSWORD = "YOUR-PASSWORD&SECURITY-TOKEN";
12 static PartnerConnection connection;
13
14 public static void main(String[] args) throws ConnectionException {
15
16 ConnectorConfig partnerConfig = new ConnectorConfig();
17 ConnectorConfig metadataConfig = new ConnectorConfig();
18
19 partnerConfig.setUsername(USERNAME);
20 partnerConfig.setPassword(PASSWORD);
21 //partnerConfig.setTraceMessage(true);
22
23 @SuppressWarnings("unused")
24 PartnerConnection partnerConnection = com.sforce.soap.partner.Connector.newConnection(partnerConfig);
25
26 // shove the partner's session id into the metadata configuration then connect
27 metadataConfig.setSessionId(partnerConnection.getSessionHeader().getSessionId());
28 MetadataConnection metadataConnection = com.sforce.soap.metadata.Connector.newConnection(metadataConfig);
29
30 // create a new custom object
31 String objectName = "WSCCustomObject";
32 String displayName = "WSC Custom Object";
33
34 CustomObject co = new CustomObject();
35 co.setFullName(objectName+"__c");
36 co.setDeploymentStatus(DeploymentStatus.Deployed);
37 co.setDescription("Created by the WSC using the Metadata API");
38 co.setLabel(displayName);
39 co.setPluralLabel(displayName+"s");
40 co.setSharingModel(SharingModel.ReadWrite);
41 co.setEnableActivities(true);
42
43 // create the text id field
44 CustomField field = new CustomField();
45 field.setType(FieldType.Text);
46 field.setDescription("The custom object identifier field");
47 field.setLabel(displayName);
48 field.setFullName(objectName+"__c");
49 // add the field to the custom object
50 co.setNameField(field);
51
52 try {
53 // submit the custom object to salesforce
54 AsyncResult[] ars = metadataConnection.create(new CustomObject[] { co });
55 if (ars == null) {
56 System.out.println("The object was not created successfully");
57 return;
58 }
59
60 String createdObjectId = ars[0].getId();
61 String[] ids = new String[] {createdObjectId};
62 boolean done = false;
63 long waitTimeMilliSecs = 1000;
64 AsyncResult[] arsStatus = null;
65
66 /**
67 * After the create() call completes, we must poll the results
68 * of the checkStatus() call until it indicates that the create
69 * operation is completed.
70 */
71 while (!done) {
72 arsStatus = metadataConnection.checkStatus(ids);
73 if (arsStatus == null) {
74 System.out.println("The object status cannot be retrieved");
75 return;
76 }
77 done = arsStatus[0].isDone();
78 if (arsStatus[0].getStatusCode() != null )  {
79 System.out.println("Error status code: "+arsStatus[0].getStatusCode());
80 System.out.println("Error message: "+arsStatus[0].getMessage());
81 }
82 Thread.sleep(waitTimeMilliSecs);
83 // double the wait time for the next iteration
84 waitTimeMilliSecs *= 2;
85 System.out.println("The object state is "+arsStatus[0].getState());
86 }
87
88 System.out.println("The ID for the created object is "+arsStatus[0].getId());
89 }
90 catch (Exception ex) {
91 System.out.println("\nFailed to create object, error message was: \n" +ex.getMessage());
92 }
93
94 }
95
96 }

The PartnerConnection object is used to log into Force.com and the resulting session ID is set to the metadataConfig object. When newConnection() for the MetadataConnection is called, the existing session ID is used. For this application, the partnerConnection is no longer needed after the session ID has been obtained.

The remaining code creates a new Custom Object named “WSCCustomObject” and submits it to Force.com asynchronously. The metadataConnection then polls Force.com using the checkStatus method. Once the Custom Object is created successfully, you can view it by going to Your Name -> Setup -> App Setup -> Create -> Objects.

The Metadata API is asynchronous. After you submit a create request, you must use the checkStatus call to poll to see if the operation has completed. See the Metadata API Developer’s Guide for more information.

Summary

The Force.com Web Services Connector is a great way to write code that uses the Force.com APIs. It can generate client libraries for the SOAP-based Web service API and the Metadata API. You can use these libraries to quickly build client applications. As this article demonstrates, WSC greatly simplifies your API code. The client libraries it generates are also very fast.

References

  • Force.com Web Service Connector (WSC) source code
  • See the Documentation page for the Web Services API Developer’s Guide and Metadata API Developer’s Guide
  • Integration Resource Page on Developer Force

About the Author

Jeff Douglas is a Senior Technical Consultant at Appirio where he creates cutting-edge applications on the Force.com platform for some of the best companies in the world. He is a foster and adoptive parent, and is trying to save the world one Apex line of code at a time. He actively blogs about cloud computing (especially Force.com) at http://blog.jeffdouglas.com.

Appendix

Enterprise WSDL Example

view sourceprint?
001 package wsc;
002
003 import com.sforce.soap.enterprise.Connector;
004 import com.sforce.soap.enterprise.DeleteResult;
005 import com.sforce.soap.enterprise.EnterpriseConnection;
006 import com.sforce.soap.enterprise.Error;
007 import com.sforce.soap.enterprise.QueryResult;
008 import com.sforce.soap.enterprise.SaveResult;
009 import com.sforce.soap.enterprise.sobject.Account;
010 import com.sforce.soap.enterprise.sobject.Contact;
011 import com.sforce.ws.ConnectionException;
012 import com.sforce.ws.ConnectorConfig;
013
014 public class Main {
015
016 static final String USERNAME = "YOUR-USERNAME";
017 static final String PASSWORD = "YOUR-PASSWORD&SECURITY-TOKEN";
018 static EnterpriseConnection connection;
019
020 public static void main(String[] args) {
021
022 ConnectorConfig config = new ConnectorConfig();
023 config.setUsername(USERNAME);
024 config.setPassword(PASSWORD);
025 //config.setTraceMessage(true);
026
027 try {
028
029 connection = Connector.newConnection(config);
030
031 // display some current settings
032 System.out.println("Auth EndPoint: "+config.getAuthEndpoint());
033 System.out.println("Service EndPoint: "+config.getServiceEndpoint());
034 System.out.println("Username: "+config.getUsername());
035 System.out.println("SessionId: "+config.getSessionId());
036
037 // run the different examples
038 queryContacts();
039 createAccounts();
040 updateAccounts();
041 deleteAccounts();
042
043
044 } catch (ConnectionException e1) {
045 e1.printStackTrace();
046 }
047
048 }
049
050 // queries and displays the 5 newest contacts
051 private static void queryContacts() {
052
053 System.out.println("Querying for the 5 newest Contacts...");
054
055 try {
056
057 // query for the 5 newest contacts
058 QueryResult queryResults = connection.query("SELECT Id, FirstName, LastName, Account.Name " +
059 "FROM Contact WHERE AccountId != NULL ORDER BY CreatedDate DESC LIMIT 5");
060 if (queryResults.getSize() > 0) {
061 for (int i=0;i<queryResults.getRecords().length;i++) {
062 // cast the SObject to a strongly-typed Contact
063 Contact c = (Contact)queryResults.getRecords()[i];
064 System.out.println("Id: " + c.getId() + " - Name: "+c.getFirstName()+" "+
065 c.getLastName()+" - Account: "+c.getAccount().getName());
066 }
067 }
068
069 } catch (Exception e) {
070 e.printStackTrace();
071 }
072
073 }
074
075 // create 5 test Accounts
076 private static void createAccounts() {
077
078 System.out.println("Creating 5 new test Accounts...");
079 Account[] records = new Account[5];
080
081 try {
082
083 // create 5 test accounts
084 for (int i=0;i<5;i++) {
085 Account a = new Account();
086 a.setName("Test Account "+i);
087 records[i] = a;
088 }
089
090 // create the records in Salesforce.com
091 SaveResult[] saveResults = connection.create(records);
092
093 // check the returned results for any errors
094 for (int i=0; i< saveResults.length; i++) {
095 if (saveResults[i].isSuccess()) {
096 System.out.println(i+". Successfully created record - Id: " + saveResults[i].getId());
097 } else {
098 Error[] errors = saveResults[i].getErrors();
099 for (int j=0; j< errors.length; j++) {
100 System.out.println("ERROR creating record: " + errors[j].getMessage());
101 }
102 }
103 }
104
105 } catch (Exception e) {
106 e.printStackTrace();
107 }
108
109 }
110
111 // updates the 5 newly created Accounts
112 private static void updateAccounts() {
113
114 System.out.println("Update the 5 new test Accounts...");
115 Account[] records = new Account[5];
116
117 try {
118
119 QueryResult queryResults = connection.query("SELECT Id, Name FROM Account ORDER BY " +
120 "CreatedDate DESC LIMIT 5");
121 if (queryResults.getSize() > 0) {
122 for (int i=0;i<queryResults.getRecords().length;i++) {
123 // cast the SObject to a strongly-typed Account
124 Account a = (Account)queryResults.getRecords()[i];
125 System.out.println("Updating Id: " + a.getId() + " - Name: "+a.getName());
126 // modify the name of the Account
127 a.setName(a.getName()+" -- UPDATED");
128 records[i] = a;
129 }
130 }
131
132 // update the records in Salesforce.com
133 SaveResult[] saveResults = connection.update(records);
134
135 // check the returned results for any errors
136 for (int i=0; i< saveResults.length; i++) {
137 if (saveResults[i].isSuccess()) {
138 System.out.println(i+". Successfully updated record - Id: " + saveResults[i].getId());
139 } else {
140 Error[] errors = saveResults[i].getErrors();
141 for (int j=0; j< errors.length; j++) {
142 System.out.println("ERROR updating record: " + errors[j].getMessage());
143 }
144 }
145 }
146
147 } catch (Exception e) {
148 e.printStackTrace();
149 }
150
151 }
152
153 // delete the 5 newly created Account
154 private static void deleteAccounts() {
155
156 System.out.println("Deleting the 5 new test Accounts...");
157 String[] ids = new String[5];
158
159 try {
160
161 QueryResult queryResults = connection.query("SELECT Id, Name FROM Account ORDER BY " +
162 "CreatedDate DESC LIMIT 5");
163 if (queryResults.getSize() > 0) {
164 for (int i=0;i<queryResults.getRecords().length;i++) {
165 // cast the SObject to a strongly-typed Account
166 Account a = (Account)queryResults.getRecords()[i];
167 // add the Account Id to the array to be deleted
168 ids[i] = a.getId();
169 System.out.println("Deleting Id: " + a.getId() + " - Name: "+a.getName());
170 }
171 }
172
173 // delete the records in Salesforce.com by passing an array of Ids
174 DeleteResult[] deleteResults = connection.delete(ids);
175
176 // check the results for any errors
177 for (int i=0; i< deleteResults.length; i++) {
178 if (deleteResults[i].isSuccess()) {
179 System.out.println(i+". Successfully deleted record - Id: " + deleteResults[i].getId());
180 } else {
181 Error[] errors = deleteResults[i].getErrors();
182 for (int j=0; j< errors.length; j++) {
183 System.out.println("ERROR deleting record: " + errors[j].getMessage());
184 }
185 }
186 }
187
188 } catch (Exception e) {
189 e.printStackTrace();
190 }
191
192 }
193
194 }

Posted 2010年11月28日 by gw8310 in 未分类

发表评论

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / 更改 )

Twitter picture

You are commenting using your Twitter account. Log Out / 更改 )

Facebook photo

You are commenting using your Facebook account. Log Out / 更改 )

Google+ photo

You are commenting using your Google+ account. Log Out / 更改 )

Connecting to %s

%d 博主赞过: