Newer Version Available

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

Step 3: Walk Through the Java Sample Code

When you have imported the WSDL files, you can build 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.

1import 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/37.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}

This example uses user and password authentication to obtain a session ID, which is then used for making calls to Metadata API. Alternatively, you can use OAuth authentication. After you athenticate with OAuth to Salesforce, pass the returned access token instead of the session ID. For example, pass the access token to the setSessionId() call on ConnectorConfig. To learn how to use OAuth authentication in Salesforce, see Authenticating Apps with OAuth in the Salesforce Help.

Note

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 a retrieve call, the sample calls checkRetrieveStatus() in a loop until the operation is completed. Similarly, after a deploy call, the sample checks checkDeployStatus() in a loop until the operation is 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>37.0</version>
16</Package>

Note the error handling code that follows each API call.

This sample requires API version 34.0 or later.

Note

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