Newer Version Available

This content describes an older version of this product. View Latest

Step 3: Walk Through the Java Sample Code

Once you have imported the WSDL files, you can begin building client applications that use Metadata API. The sample is a good starting point for writing your own code.

Before you run the sample, modify your project and the code to:

  1. Include the WSC JAR, its dependencies, and the JAR files you generated from the WSDLs.

    Although WSC has other dependencies, the following sample only requires Rhino (js-1.7R2.jar), which you can download from mvnrepository.com/artifact/rhino/js.

    Note

  2. Update USERNAME and PASSWORD variables in the MetadataLoginUtil.login() method with your user name and password. If your current IP address isn’t in your organization's trusted IP range, you'll need to append a security token to the password.
  3. If you are using a sandbox, be sure to change the login URL.

Login Utility

Java users can use ConnectorConfig to connect to Enterprise, Partner, and Metadata SOAP API. MetadataLoginUtil creates a ConnectorConfig object and logs in using the Enterprise WSDL login method. Then it retrieves sessionId and metadataServerUrl to create a ConnectorConfig and connects to Metadata API endpoint. ConnectorConfig is defined in WSC.

The MetadataLoginUtil class abstracts the login code from the other parts of the sample, allowing portions of this code to be reused without change across different Salesforce APIs.

1swfobject.registerObject("clippy.codeblock-0", "9");import com.sforce.soap.enterprise.EnterpriseConnection;
2import com.sforce.soap.enterprise.LoginResult;
3import com.sforce.soap.metadata.MetadataConnection;
4import com.sforce.ws.ConnectionException;
5import com.sforce.ws.ConnectorConfig;
6
7/**
8 * Login utility.
9 */
10public class MetadataLoginUtil {
11
12    public static MetadataConnection login() throws ConnectionException {
13        final String USERNAME = "user@company.com";
14        // This is only a sample. Hard coding passwords in source files is a bad practice.
15        final String PASSWORD = "password"; 
16        final String URL = "https://login.salesforce.com/services/Soap/c/30.0";
17        final LoginResult loginResult = loginToSalesforce(USERNAME, PASSWORD, URL);
18        return createMetadataConnection(loginResult);
19    }
20
21    private static MetadataConnection createMetadataConnection(
22            final LoginResult loginResult) throws ConnectionException {
23        final ConnectorConfig config = new ConnectorConfig();
24        config.setServiceEndpoint(loginResult.getMetadataServerUrl());
25        config.setSessionId(loginResult.getSessionId());
26        return new MetadataConnection(config);
27    }
28
29    private static LoginResult loginToSalesforce(
30            final String username,
31            final String password,
32            final String loginUrl) throws ConnectionException {
33        final ConnectorConfig config = new ConnectorConfig();
34        config.setAuthEndpoint(loginUrl);
35        config.setServiceEndpoint(loginUrl);
36        config.setManualLogin(true);
37        return (new EnterpriseConnection(config)).login(username, password);
38    }
39}

Java Sample Code for File-Based Development

The sample code logs in using the login utility. Then it displays a menu with retrieve, deploy, and exit.

The retrieve() and deploy() calls both operate on a .zip file named components.zip. The retrieve() call retrieves components from your organization into components.zip, and the deploy() call deploys the components in components.zip to your organization. If you save the sample to your computer and execute it, run the retrieve option first so that you have a components.zip file that you can subsequently deploy. After retrieve or deploy calls, it checks checkStatus() in a loop until the status value in AsyncResult indicates that the operation has completed.

The retrieve() call uses a manifest file to determine the components to retrieve from your organization. A sample package.xml manifest file follows. For more details on the manifest file structure, see Working with the Zip File. For this sample, the manifest file retrieves all custom objects, custom tabs, and page layouts.

1<?xml version="1.0" encoding="UTF-8"?>
2<Package xmlns="http://soap.sforce.com/2006/04/metadata">
3    <types>
4        <members>*</members>
5        <name>CustomObject</name>
6    </types>
7    <types>
8        <members>*</members>
9        <name>CustomTab</name>
10    </types>
11    <types>
12        <members>*</members>
13        <name>Layout</name>
14    </types>
15    <version>30.0</version>
16</Package>

Note the error handling code that follows each API call.

1swfobject.registerObject("clippy.codeblock-2", "9");import java.io.*;
2import java.util.*;
3import javax.xml.parsers.*;
4import org.w3c.dom.*;
5import org.xml.sax.SAXException;
6import com.sforce.soap.metadata.*;
7
8/**
9 * Sample that logs in and shows a menu of retrieve and deploy metadata options.
10 */
11public class FileBasedDeployAndRetrieve {
12
13    private MetadataConnection metadataConnection;
14
15    private static final String ZIP_FILE = "components.zip";
16
17    // manifest file that controls which components get retrieved
18    private static final String MANIFEST_FILE = "package.xml";
19
20    private static final double API_VERSION = 29.0;
21
22    // one second in milliseconds
23    private static final long ONE_SECOND = 1000;
24
25    // maximum number of attempts to deploy the zip file
26    private static final int MAX_NUM_POLL_REQUESTS = 50;
27
28    private BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
29
30    public static void main(String[] args) throws Exception {
31        FileBasedDeployAndRetrieve sample = new FileBasedDeployAndRetrieve();
32        sample.run();
33    }
34
35    public FileBasedDeployAndRetrieve() {
36    }
37
38    private void run() throws Exception {
39        this.metadataConnection = MetadataLoginUtil.login();
40
41        // Show the options to retrieve or deploy until user exits
42        String choice = getUsersChoice();
43        while (choice != null && !choice.equals("99")) {
44            if (choice.equals("1")) {
45                retrieveZip();
46            } else if (choice.equals("2")) {
47                deployZip();
48            } else {
49                break;
50            }
51            // show the options again
52            choice = getUsersChoice();
53        }
54    }
55
56    /*
57     * Utility method to present options to retrieve or deploy.
58     */
59    private String getUsersChoice() throws IOException {
60        System.out.println(" 1: Retrieve");
61        System.out.println(" 2: Deploy");
62        System.out.println("99: Exit");
63        System.out.println();
64        System.out.print("Enter 1 to retrieve, 2 to deploy, or 99 to exit: ");
65        // wait for the user input.
66        String choice = reader.readLine();
67        return choice != null ? choice.trim() : "";
68    }
69
70    private void deployZip() throws Exception {
71        byte zipBytes[] = readZipFile();
72        DeployOptions deployOptions = new DeployOptions();
73        deployOptions.setPerformRetrieve(false);
74        deployOptions.setRollbackOnError(true);
75        AsyncResult asyncResult = metadataConnection.deploy(zipBytes, deployOptions);
76        DeployResult result = waitForDeployCompletion(asyncResult.getId());
77        if (!result.isSuccess()) {
78            printErrors(result, "Final list of failures:\n");
79            throw new Exception("The files were not successfully deployed");
80        }
81        System.out.println("The file " + ZIP_FILE + " was successfully deployed\n");
82    }
83
84    /*
85    * Read the zip file contents into a byte array.
86    */
87    private byte[] readZipFile() throws Exception {
88        byte[] result = null;
89        // We assume here that you have a deploy.zip file.
90        // See the retrieve sample for how to retrieve a zip file.
91        File zipFile = new File(ZIP_FILE);
92        if (!zipFile.exists() || !zipFile.isFile()) {
93            throw new Exception("Cannot find the zip file for deploy() on path:"
94                + zipFile.getAbsolutePath());
95        }
96
97        FileInputStream fileInputStream = new FileInputStream(zipFile);
98        try {
99            ByteArrayOutputStream bos = new ByteArrayOutputStream();
100            byte[] buffer = new byte[4096];
101            int bytesRead = 0;
102            while (-1 != (bytesRead = fileInputStream.read(buffer))) {
103                bos.write(buffer, 0, bytesRead);
104            }
105
106            result = bos.toByteArray();
107        } finally {
108            fileInputStream.close();
109        }
110        return result;
111    }
112
113    /*
114    * Print out any errors, if any, related to the deploy.
115    * @param result - DeployResult
116    */
117    private void printErrors(DeployResult result, String messageHeader) {
118        DeployDetails details = result.getDetails();
119        StringBuilder stringBuilder = new StringBuilder();
120        if (details != null) {
121            DeployMessage[] componentFailures = details.getComponentFailures();
122            for (DeployMessage failure : componentFailures) {
123                String loc = "(" + failure.getLineNumber() + ", " + failure.getColumnNumber();
124                if (loc.length() == 0 && !failure.getFileName().equals(failure.getFullName()))
125                {
126                    loc = "(" + failure.getFullName() + ")";
127                }
128                stringBuilder.append(failure.getFileName() + loc + ":" 
129                    + failure.getProblem()).append('\n');
130            }
131            RunTestsResult rtr = details.getRunTestResult();
132            if (rtr.getFailures() != null) {
133                for (RunTestFailure failure : rtr.getFailures()) {
134                    String n = (failure.getNamespace() == null ? "" :
135                        (failure.getNamespace() + ".")) + failure.getName();
136                    stringBuilder.append("Test failure, method: " + n + "." +
137                            failure.getMethodName() + " -- " + failure.getMessage() + 
138                            " stack " + failure.getStackTrace() + "\n\n");
139                }
140            }
141            if (rtr.getCodeCoverageWarnings() != null) {
142                for (CodeCoverageWarning ccw : rtr.getCodeCoverageWarnings()) {
143                    stringBuilder.append("Code coverage issue");
144                    if (ccw.getName() != null) {
145                        String n = (ccw.getNamespace() == null ? "" :
146                        (ccw.getNamespace() + ".")) + ccw.getName();
147                        stringBuilder.append(", class: " + n);
148                    }
149                    stringBuilder.append(" -- " + ccw.getMessage() + "\n");
150                }
151            }
152        }
153        if (stringBuilder.length() > 0) {
154            stringBuilder.insert(0, messageHeader);
155            System.out.println(stringBuilder.toString());
156        }
157    }
158
159    private void retrieveZip() throws Exception {
160        RetrieveRequest retrieveRequest = new RetrieveRequest();
161        retrieveRequest.setApiVersion(API_VERSION);
162        setUnpackaged(retrieveRequest);
163
164        AsyncResult asyncResult = metadataConnection.retrieve(retrieveRequest);
165        asyncResult = waitForRetrieveCompletion(asyncResult);
166        RetrieveResult result =
167            metadataConnection.checkRetrieveStatus(asyncResult.getId());
168
169        // Print out any warning messages
170        StringBuilder stringBuilder = new StringBuilder();
171        if (result.getMessages() != null) {
172            for (RetrieveMessage rm : result.getMessages()) {
173                stringBuilder.append(rm.getFileName() + " - " + rm.getProblem() + "\n");
174            }
175        }
176        if (stringBuilder.length() > 0) {
177            System.out.println("Retrieve warnings:\n" + stringBuilder);
178        }
179
180        System.out.println("Writing results to zip file");
181        File resultsFile = new File(ZIP_FILE);
182        FileOutputStream os = new FileOutputStream(resultsFile);
183
184        try {
185            os.write(result.getZipFile());
186        } finally {
187            os.close();
188        }
189    }
190
191    private DeployResult waitForDeployCompletion(String asyncResultId) throws Exception {
192        int poll = 0;
193        long waitTimeMilliSecs = ONE_SECOND;
194        DeployResult deployResult;
195        boolean fetchDetails;
196        do {
197            Thread.sleep(waitTimeMilliSecs);
198            // double the wait time for the next iteration
199
200            waitTimeMilliSecs *= 2;
201            if (poll++ > MAX_NUM_POLL_REQUESTS) {
202                throw new Exception(
203                    "Request timed out. If this is a large set of metadata components, " +
204                    "ensure that MAX_NUM_POLL_REQUESTS is sufficient.");
205            }
206            // Fetch in-progress details once for every 3 polls
207            fetchDetails = (poll % 3 == 0);
208
209            deployResult = metadataConnection.checkDeployStatus(asyncResultId, fetchDetails);
210            System.out.println("Status is: " + deployResult.getStatus());
211            if (!deployResult.isDone() && fetchDetails) {
212                printErrors(deployResult, "Failures for deployment in progress:\n");
213            }
214        }
215        while (!deployResult.isDone());
216
217        if (!deployResult.isSuccess() && deployResult.getErrorStatusCode() != null) {
218            throw new Exception(deployResult.getErrorStatusCode() + " msg: " +
219                    deployResult.getErrorMessage());
220        }
221        
222        if (!fetchDetails) {
223            // Get the final result with details if we didn't do it in the last attempt.
224            deployResult = metadataConnection.checkDeployStatus(asyncResultId, true);
225        }
226        
227        return deployResult;
228    }
229
230    private AsyncResult waitForRetrieveCompletion(AsyncResult asyncResult) throws Exception {
231        int poll = 0;
232        long waitTimeMilliSecs = ONE_SECOND;
233        while (!asyncResult.isDone()) {
234            Thread.sleep(waitTimeMilliSecs);
235            // double the wait time for the next iteration
236
237            waitTimeMilliSecs *= 2;
238            if (poll++ > MAX_NUM_POLL_REQUESTS) {
239                throw new Exception(
240                    "Request timed out. If this is a large set of metadata components, " +
241                    "ensure that MAX_NUM_POLL_REQUESTS is sufficient.");
242            }
243
244            asyncResult = metadataConnection.checkStatus(
245                new String[]{asyncResult.getId()})[0];
246            System.out.println("Status is: " + asyncResult.getState());
247        }
248
249        if (asyncResult.getState() != AsyncRequestState.Completed) {
250            throw new Exception(asyncResult.getStatusCode() + " msg: " +
251                asyncResult.getMessage());
252        }
253        return asyncResult;
254    }
255
256    private void setUnpackaged(RetrieveRequest request) throws Exception {
257        // Edit the path, if necessary, if your package.xml file is located elsewhere
258        File unpackedManifest = new File(MANIFEST_FILE);
259        System.out.println("Manifest file: " + unpackedManifest.getAbsolutePath());
260
261        if (!unpackedManifest.exists() || !unpackedManifest.isFile()) {
262            throw new Exception("Should provide a valid retrieve manifest " +
263                "for unpackaged content. Looking for " +
264                unpackedManifest.getAbsolutePath());
265        }
266
267        // Note that we use the fully quualified class name because
268        // of a collision with the java.lang.Package class
269        com.sforce.soap.metadata.Package p = parsePackageManifest(unpackedManifest);
270        request.setUnpackaged(p);
271    }
272
273    private com.sforce.soap.metadata.Package parsePackageManifest(File file)
274            throws ParserConfigurationException, IOException, SAXException {
275        com.sforce.soap.metadata.Package packageManifest = null;
276        List<PackageTypeMembers> listPackageTypes = new ArrayList<PackageTypeMembers>();
277        DocumentBuilder db =
278                DocumentBuilderFactory.newInstance().newDocumentBuilder();
279        InputStream inputStream = new FileInputStream(file);
280        Element d = db.parse(inputStream).getDocumentElement();
281        for (Node c = d.getFirstChild(); c != null; c = c.getNextSibling()) {
282            if (c instanceof Element) {
283                Element ce = (Element) c;
284                NodeList nodeList = ce.getElementsByTagName("name");
285                if (nodeList.getLength() == 0) {
286                    continue;
287                }
288                String name = nodeList.item(0).getTextContent();
289                NodeList m = ce.getElementsByTagName("members");
290                List<String> members = new ArrayList<String>();
291                for (int i = 0; i < m.getLength(); i++) {
292                    Node mm = m.item(i);
293                    members.add(mm.getTextContent());
294                }
295                PackageTypeMembers packageTypes = new PackageTypeMembers();
296                packageTypes.setName(name);
297                packageTypes.setMembers(members.toArray(new String[members.size()]));
298                listPackageTypes.add(packageTypes);
299            }
300        }
301        packageManifest = new com.sforce.soap.metadata.Package();
302        PackageTypeMembers[] packageTypesArray =
303                new PackageTypeMembers[listPackageTypes.size()];
304        packageManifest.setTypes(listPackageTypes.toArray(packageTypesArray));
305        packageManifest.setVersion(API_VERSION + "");
306        return packageManifest;
307    }
308}