快速入门:元数据 API

面向初学者开发人员的资源

如果您是初学者开发人员,并且以前没有使用过 Salesforce CLI,请了解如何设置 您的环境和示例应用程序的实践。这些 Trailhead 将引导您完成 使用 SFDX 进行设置,并向您介绍元数据 API。

使用 Salesforce CLI 和源代码管理开发应用程序 登山口

演练使用以下方法设置环境和使用 Salesforce CLI 进行开发 Dreamhouse 示例应用。向 Dreamhouse 应用程序添加功能后,将元数据部署到 使用 Salesforce CLI 的 Dev Hub 组织。

包 .xml 元数据管理

详细了解元数据和包.xml文件。生成包.xml文件以部署更改 从零开始的组织到您的 Trailhead Playground。

使用元数据 API 进行开发的快速入门

如果您在 Salesforce 开发方面有一些经验,但想开始使用元数据 API,请使用此快速入门。本快速入门将引导您完成元数据的检索 组件,这是开发过程的第一步。

  1. 先决条件 在开始使用元数据 API 进行开发之前
    ,请完成这些先决条件。
  2. 步骤 1:(可选)使用 UI
    将元数据组件添加到组织 如果您从没有自定义项的新实践组织开始,则只有无法检索的标准元数据。要使用元数据 API 检索调用,请在 Salesforce UI 上将组件添加到您的实践组织。如果您正在处理现有项目,则已经有要检索的组件,可以跳过此步骤。
  3. 步骤 2:生成包 .xml 清单
    package.xml 清单文件列出了要从组织中检索的组件。
  4. 步骤 3:使用元数据 API
    检索组件 使用 Salesforce CLI,检索包.xml 清单中指定组件的文件表示形式。

先决条件

在开始使用元数据 API 进行开发之前,请完成这些先决条件。

  • 要通过命令行访问元数据 API,请安装 Salesforce CLI。
  • 若要创建开发环境,请注册 适用于 Salesforce 开发人员版。Developer Edition 组织是一个免费开发项目 用于独立于生产数据构建和测试解决方案的环境。
  • 安装适用于 Visual Studio Code 的 Salesforce 扩展。 这些工具提供了与开发组织(临时组织、沙箱、 和 DE orgs)、Apex、Aura 组件和 Visualforce。
  • 确认您拥有“已启用 API”权限,并通过元数据 API 修改元数据 “函数”权限或“修改所有数据”权限。如果您没有这些权限 设置、修改元数据权限。
  • 在组织中启用 Dev Hub。Dev Hub 允许你 创建和管理临时组织,以便在不影响生产数据的情况下进行开发 或元数据。
  • 要允许访问受保护的资源(如生产数据和元数据),请授权您的组织。
  • 在组织中启用 Dev Hub。Dev Hub 允许你 创建和管理临时组织,以便在不影响生产数据的情况下进行开发 或元数据。

步骤 1:(可选) 使用 UI 将元数据组件添加到组织

如果您从没有自定义项的新实践组织开始,则只需 具有无法检索的标准元数据。要使用 Metadata API 检索调用,请添加 组件添加到您的实践组织。如果您正在处理现有项目,则 已有要检索的组件,可以跳过此步骤。

  1. 在“设置”中,单击“创建”。
  2. 选择“自定义对象”。
  3. “标签”(Label) 和“复数标签”(Plural Label) 输入任意名称。
  4. 保存组件。

步骤 2:生成包.xml 清单

package.xml 清单文件列出了要从 组织。

Package.xml 清单结构

package.xml 清单使用可扩展标记语言 (XML) 来标识和迁移 元数据组件。package.xml 清单的基本框架是用元素构建的。元素指定元数据类型 你想与之合作。您可以将多个添加到包.xml文件。<types><types><types>

元素内部是元素和元素。元素 选择特定类型的单个组件,<name> 元素选择 元数据组件类型。要使用特定组件,请在元素中输入该组件的 。<types><name><members><members>fullName<members>

例如,若要检索 Account 组件,请在 package.xml 的元素中添加 Account,并在元素中添加 CustomObject。当您发出检索调用时,您将检索 仅来自您组织的 Account 组件。<members><name>

<?xml version="1.0" encoding="UTF-8"?>
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
    <types>
        <members>Account</members>
        <name>CustomObject</name>
    </types>
    <version>59.0</version>
</Package>

检索自定义对象

若要检索元数据类型的所有组件,请不要指定组件的组件。请改用通配符 *(星号)。一些 组件(如标准对象)不支持 *(星号)作为说明符。fullName<members>

要从您的组织中检索所有自定义对象,请执行以下操作:

  1. (可选)如果您没有项目文件夹,请使用 Salesforce CLI 创建一个 用于组织项目的新目录。使用指定的 楼盘名称:sf project generate –name YourProjectName
  2. 在项目中创建名为 package.xml 的文件。
  3. 在文本编辑器中,打开文件并粘贴以下脚本:
<?xml version="1.0" encoding="UTF-8"?>
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
    <types>
        <members>*</members>
        <name>CustomObject</name>
    </types>
    <version>59.0</version>
</Package>

现在您有一个 package.xml 文件,我们可以使用它来检索所有自定义对象。什么时候 您可以自己开发更多组件,您可以使用以下命令从您的组织中检索更多组件 多个元素。<types>

步骤 3:使用元数据 API 检索组件

使用 Salesforce CLI,检索 包 .xml 清单。

元数据 API 检索的两个选项

您可以使用以下两个命令之一来检索元数据组件。

  1. 若要检索 package.xml 清单中指定的组件,请发出 使用 Salesforce CLI 命令检索呼叫。在命令行中,运行此调用 替换为适当的文件路径:sf project retrieve start –manifest path/to/package.xml元数据是异步的, 基于文件的命令。您可以发出多个检索或部署请求,这些请求 当资源可用时,它们会自行运行。retrieve()使用此命令,您可以 发送请求以检索 包 .xml 清单。您的请求将排队等待,直到我们的系统准备就绪 处理您的检索调用。在您的请求被取消排队后,您的检索 调用已运行。客户端检查检索的状态并通知您 通话完成后。调用返回 选择的组件。当您使用 Salesforce CLI 发出检索调用时,所有 这些过程是自动化的。该命令允许源跟踪。源 跟踪包括有关您正在处理的修订版的信息,以及 当进行最后一次更改时,这使得源命令更多 对开发人员友好。要使用源跟踪,请确保在 组织。project retrieve start
  2. 或者,在终端中运行以下命令:sf project retrieve start –manifest path/to/package.xml –target-metadata-dir path/to/retrieve/dir此命令 以 MDAPI 格式(而不是源格式)检索组件,并且不会 允许源跟踪。在实践中,管理员更频繁地使用 mdapi 命令 因为这些命令不包括源跟踪。

为元数据 API 构建客户端应用程序

使用元数据 API 检索、部署、创建、更新或删除 组织的自定义项。最常见的用途是从 沙盒或测试组织添加到您的生产环境。元数据 API 适用于 管理自定义项和构建可以管理元数据模型的工具, 而不是数据本身。

Salesforce CLI 自动执行元数据 API 的基础调用。但是,您可以使用这些 直接使用您自己的客户端应用程序进行调用。本指南为您提供了所有信息 require 开始编写直接使用元数据 API 来管理自定义项的应用程序 为您的组织。它向您展示了如何开始使用基于文件的开发。为 基于 CRUD 的开发示例,请参阅 Java 基于 CRUD 的开发示例 同步调用。

先决条件

在开始使用元数据 API 之前,请确保完成这些先决条件。

  • 创建开发环境。我们强烈建议您使用沙盒,它是 生产组织。企业版、无限制版和性能版随附 免费的开发者沙盒。有关详细信息,请参阅 http://www.salesforce.com/platform/cloud-infrastructure/sandbox.jsp。或者,您可以使用 Developer Edition (DE) 组织。DE 组织提供对 Enterprise Edition 提供的所有功能,但受用户数量限制 以及存储空间的大小。DE 组织不是生产组织的副本/它提供了一个 您可以在其中构建和测试解决方案而不会影响 组织的数据。Developer Edition 帐户可在 https://developer.salesforce.com/signup 免费获得。
  • 标识具有“已启用 API”权限和“修改元数据”的用户 元数据 API 函数权限或修改所有数据权限。这些权限是 需要访问元数据 API 调用。注意如果用户需要访问元数据,但不需要 data,请启用“通过元数据 API 函数修改元数据”权限。否则 启用“修改所有数据”权限。
  • 安装 SOAP 客户机。元数据 API 适用于当前的 SOAP 开发环境, 包括但不限于 Visual Studio® .NET 和 Web 服务连接器 (WSC)。在本文档中,我们提供了基于 WSC 和 JDK 6(Java 平台)的 Java 示例 标准版开发套件 6)。若要运行示例,请先下载最新的 force-wsc JAR 文件及其依赖项来自 mvnrepository.com/artifact/com.force.api/force-wsc/。列出了依赖项 在选择版本时的页面上。注意开发平台在以下方面各不相同 SOAP 实现。某些开发平台的实现差异可以 阻止访问元数据 API 中的部分或全部功能。

步骤 1:生成或获取 Web 服务 组织的 WSDL

若要访问元数据 API 调用,需要 Web 服务描述语言 (WSDL) 文件。The WSDL file 定义可供您使用的 Web 服务。您的开发平台使用 此 WSDL 生成存根代码以访问它定义的 Web 服务。您可以获取 WSDL 文件(如果有) 访问 Salesforce 用户界面中的 WSDL 下载页面,您可以生成它 你自己。有关 WSDL 的更多信息,请参见 http://www.w3.org/TR/wsdl

在访问元数据 API 调用之前, 您必须通过身份验证才能使用调用来使用 Web 服务,该调用在企业中定义 WSDL 和合作伙伴 WSDL。因此,您还必须获得以下一项 这些 WSDL。login()具有“通过元数据 API 函数修改元数据”或“修改所有数据”权限的任何用户 可以下载 WSDL 文件以集成和扩展 Salesforce 平台。

注意

如果用户需要访问元数据,但不需要 data,请启用“通过元数据 API 函数修改元数据”权限。否则 启用“修改所有数据”权限。

步骤 3:演练 Java 示例代码中的示例代码使用企业 WSDL,但合作伙伴 WSDL 工作 同样好。

要为您的组织生成元数据和企业 WSDL 文件,请执行以下操作:

  1. 登录到您的 Salesforce 帐户。 您必须以管理员或具有“修改 所有数据“权限。
  2. 在“设置”中,输入“快速查找”框,然后选择“API”。API
  3. 单击“生成元数据 WSDL”,并将 XML WSDL 文件保存到您的文件中 系统。
  4. 单击“生成企业 WSDL”,并将 XML WSDL 文件保存到您的文件中 系统。

第 2 步:将 WSDL 文件导入到 开发平台

获得 WSDL 文件后,将它们导入到开发中 平台,以便您的开发环境可以生成必要的 用于生成客户端 Web 服务应用程序的对象。这 部分提供了 WSC 的示例说明。有关以下内容的说明 其他开发平台,请参阅平台的产品文档。

注意

导入 WSDL 文件的过程与元数据相同 和企业 WSDL 文件。

Java 环境说明 (WSC)

Java 环境通过 Java 对象访问 API,这些对象 充当服务器端对应物的代理。在使用 API 之前,您必须 首先从组织的 WSDL 文件生成这些对象。

每个 SOAP 客户端都有自己的工具用于此过程。对于 WSC,请使用 实用程序。wsdlc

注意

在运行 之前,必须在系统上安装并引用 WSC JAR 文件 在您的类路径中。您可以下载最新的 force-wsc JAR 文件 及其依赖项(依赖项在页面上列出时 从 mvnrepository.com/artifact/com.force.api/force-wsc/ 中选择一个版本。wsdlc的基本语法是:

wsdlc

java -classpath pathToWsc;pathToWscDependencies com.sforce.ws.tools.wsdlc pathToWsdl/WsdlFilename pathToOutputJar/OutputJarFilename

例如,在 Windows 上:

java –classpath force-wsc-30.0.0.jar;ST4-4.0.7.jar;antlr-runtime-3.5.jar com.sforce.ws.tools.wsdlc metadata.wsdl metadata.jar

在 Mac OS X 和 Unix 上,使用冒号而不是分号 类路径中的项:

java –classpath force-wsc-30.0.0.jar:ST4-4.0.7.jar:antlr-runtime-3.5.jar com.sforce.ws.tools.wsdlc metadata.wsdl metadata.jar

wsdlc生成 JAR 文件 以及用于创建客户端的 Java 源代码和字节码文件 应用。对企业 WSDL 重复此过程以创建 一个企业。JAR 文件。

步骤 3:演练 Java 示例代码

导入 WSDL 文件后,可以构建使用 元数据 API。此示例是编写自己的代码的良好起点。

在运行示例之前,请修改项目和代码,以便:

  1. 包括 WSC JAR、其依赖项以及您从 WSDL。注意尽管 WSC 具有其他依赖项,但仅以下示例 需要 Rhino (js-1.7R2.jar),您可以从 mvnrepository.com/artifact/rhino/js 下载。
  2. 使用您的用户名和密码更新方法中的 USERNAME 和 PASSWORD 变量。如果 您当前的 IP 地址不在组织的受信任 IP 范围内,您将 需要将安全令牌附加到密码中。MetadataLoginUtil.login()
  3. 如果您使用的是沙盒,请务必更改登录 URL。

登录实用程序

Java 用户可用于连接到 企业、合作伙伴和元数据 SOAP API。 创建对象并使用企业 WSDL 登录名登录 方法。然后,它检索并创建一个并连接到元数据 API 端点。 定义于 WSC。ConnectorConfigMetadataLoginUtilConnectorConfigsessionIdmetadataServerUrlConnectorConfigConnectorConfig

该类抽象化登录名 示例其他部分的代码,允许重用此代码的某些部分 无需更改不同的 Salesforce API。MetadataLoginUtil

import com.sforce.soap.enterprise.EnterpriseConnection;
import com.sforce.soap.enterprise.LoginResult;
import com.sforce.soap.metadata.MetadataConnection;
import com.sforce.ws.ConnectionException;
import com.sforce.ws.ConnectorConfig;

/**
 * Login utility.
 */
public class MetadataLoginUtil {

    public static MetadataConnection login() throws ConnectionException {
        final String USERNAME = "user@company.com";
        // This is only a sample. Hard coding passwords in source files is a bad practice.
        final String PASSWORD = "password"; 
        final String URL = "https://login.salesforce.com/services/Soap/c/59.0";
        final LoginResult loginResult = loginToSalesforce(USERNAME, PASSWORD, URL);
        return createMetadataConnection(loginResult);
    }

    private static MetadataConnection createMetadataConnection(
            final LoginResult loginResult) throws ConnectionException {
        final ConnectorConfig config = new ConnectorConfig();
        config.setServiceEndpoint(loginResult.getMetadataServerUrl());
        config.setSessionId(loginResult.getSessionId());
        return new MetadataConnection(config);
    }

    private static LoginResult loginToSalesforce(
            final String username,
            final String password,
            final String loginUrl) throws ConnectionException {
        final ConnectorConfig config = new ConnectorConfig();
        config.setAuthEndpoint(loginUrl);
        config.setServiceEndpoint(loginUrl);
        config.setManualLogin(true);
        return (new EnterpriseConnection(config)).login(username, password);
    }
}

注意

此示例使用用户和密码身份验证来获取会话 ID,该 ID 然后用于调用元数据 API。或者,您可以使用 OAuth 认证。使用 OAuth 向 Salesforce 发送请求后,将返回的 访问令牌,而不是会话 ID。例如,将访问令牌传递给 上的调用。了解如何使用 OAuth 在 Salesforce 中进行身份验证,请参阅使用以下方式对应用程序进行身份验证 Salesforce 帮助中的 OAuth。setSessionId()ConnectorConfig

基于文件的 Java 示例代码 发展

示例代码使用登录实用程序登录。然后它显示一个菜单 检索、部署和退出。

和调用都对名为 components.zip 的 .zip 文件进行操作。该调用将组织中的组件检索到 components.zip 中,并且该调用将 components.zip 中的组件部署到组织。如果保存示例 到您的计算机并执行它,首先运行 retrieve 选项,以便您有一个可以随后部署的组件.zip文件。后 检索调用,示例在循环中调用,直到操作完成。 同样,在部署调用之后,示例会在循环中进行检查,直到操作完成。retrieve()deploy()retrieve()deploy()checkRetrieveStatus()checkDeployStatus()

该调用使用清单文件执行以下操作: 确定要从组织中检索的组件。下面是一个示例包 .xml 清单文件。有关 清单文件结构,请参阅使用 Zip 文件部署和检索元数据。在此示例中,清单文件检索所有自定义对象。 自定义选项卡和页面布局。retrieve()

<?xml version="1.0" encoding="UTF-8"?>
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
    <types>
        <members>*</members>
        <name>CustomObject</name>
    </types>
    <types>
        <members>*</members>
        <name>CustomTab</name>
    </types>
    <types>
        <members>*</members>
        <name>Layout</name>
    </types>
    <version>59.0</version>
</Package>

请注意每个 API 调用后面的错误处理代码。

注意

此示例需要 API 版本 34.0 或 后。

import java.io.*;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.rmi.RemoteException;
import java.util.*;

import javax.xml.parsers.*;

import org.w3c.dom.*;
import org.xml.sax.SAXException;

import com.sforce.soap.metadata.*;

/**
 * Sample that logs in and shows a menu of retrieve and deploy metadata options.
 */
public class FileBasedDeployAndRetrieve {

    private MetadataConnection metadataConnection;

    private static final String ZIP_FILE = "components.zip";

    // manifest file that controls which components get retrieved
    private static final String MANIFEST_FILE = "package.xml";

    private static final double API_VERSION = 29.0;

    // one second in milliseconds
    private static final long ONE_SECOND = 1000;

    // maximum number of attempts to deploy the zip file
    private static final int MAX_NUM_POLL_REQUESTS = 50;

    private BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));

    public static void main(String[] args) throws Exception {
        FileBasedDeployAndRetrieve sample = new FileBasedDeployAndRetrieve();
        sample.run();
    }

    public FileBasedDeployAndRetrieve() {
    }

    private void run() throws Exception {
        this.metadataConnection = MetadataLoginUtil.login();

        // Show the options to retrieve or deploy until user exits
        String choice = getUsersChoice();
        while (choice != null && !choice.equals("99")) {
            if (choice.equals("1")) {
                retrieveZip();
            } else if (choice.equals("2")) {
                deployZip();
            } else {
                break;
            }
            // show the options again
            choice = getUsersChoice();
        }
    }

    /*
     * Utility method to present options to retrieve or deploy.
     */
    private String getUsersChoice() throws IOException {
        System.out.println(" 1: Retrieve");
        System.out.println(" 2: Deploy");
        System.out.println("99: Exit");
        System.out.println();
        System.out.print("Enter 1 to retrieve, 2 to deploy, or 99 to exit: ");
        // wait for the user input.
        String choice = reader.readLine();
        return choice != null ? choice.trim() : "";
    }

    private void deployZip() throws Exception {
        byte zipBytes[] = readZipFile();
        DeployOptions deployOptions = new DeployOptions();
        deployOptions.setPerformRetrieve(false);
        deployOptions.setRollbackOnError(true);
        AsyncResult asyncResult = metadataConnection.deploy(zipBytes, deployOptions);
        DeployResult result = waitForDeployCompletion(asyncResult.getId());
        if (!result.isSuccess()) {
            printErrors(result, "Final list of failures:\n");
            throw new Exception("The files were not successfully deployed");
        }
        System.out.println("The file " + ZIP_FILE + " was successfully deployed\n");
    }

    /*
    * Read the zip file contents into a byte array.
    */
    private byte[] readZipFile() throws Exception {
        byte[] result = null;
        // We assume here that you have a deploy.zip file.
        // See the retrieve sample for how to retrieve a zip file.
        File zipFile = new File(ZIP_FILE);
        if (!zipFile.exists() || !zipFile.isFile()) {
            throw new Exception("Cannot find the zip file for deploy() on path:"
                + zipFile.getAbsolutePath());
        }

        FileInputStream fileInputStream = new FileInputStream(zipFile);
        try {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            byte[] buffer = new byte[4096];
            int bytesRead = 0;
            while (-1 != (bytesRead = fileInputStream.read(buffer))) {
                bos.write(buffer, 0, bytesRead);
            }

            result = bos.toByteArray();
        } finally {
            fileInputStream.close();
        }
        return result;
    }

    /*
    * Print out any errors, if any, related to the deploy.
    * @param result - DeployResult
    */
    private void printErrors(DeployResult result, String messageHeader) {
        DeployDetails details = result.getDetails();
        StringBuilder stringBuilder = new StringBuilder();
        if (details != null) {
            DeployMessage[] componentFailures = details.getComponentFailures();
            for (DeployMessage failure : componentFailures) {
                String loc = "(" + failure.getLineNumber() + ", " + failure.getColumnNumber();
                if (loc.length() == 0 && !failure.getFileName().equals(failure.getFullName()))
                {
                    loc = "(" + failure.getFullName() + ")";
                }
                stringBuilder.append(failure.getFileName() + loc + ":" 
                    + failure.getProblem()).append('\n');
            }
            RunTestsResult rtr = details.getRunTestResult();
            if (rtr.getFailures() != null) {
                for (RunTestFailure failure : rtr.getFailures()) {
                    String n = (failure.getNamespace() == null ? "" :
                        (failure.getNamespace() + ".")) + failure.getName();
                    stringBuilder.append("Test failure, method: " + n + "." +
                            failure.getMethodName() + " -- " + failure.getMessage() + 
                            " stack " + failure.getStackTrace() + "\n\n");
                }
            }
            if (rtr.getCodeCoverageWarnings() != null) {
                for (CodeCoverageWarning ccw : rtr.getCodeCoverageWarnings()) {
                    stringBuilder.append("Code coverage issue");
                    if (ccw.getName() != null) {
                        String n = (ccw.getNamespace() == null ? "" :
                        (ccw.getNamespace() + ".")) + ccw.getName();
                        stringBuilder.append(", class: " + n);
                    }
                    stringBuilder.append(" -- " + ccw.getMessage() + "\n");
                }
            }
        }
        if (stringBuilder.length() > 0) {
            stringBuilder.insert(0, messageHeader);
            System.out.println(stringBuilder.toString());
        }
    }
    

    private void retrieveZip() throws Exception {
        RetrieveRequest retrieveRequest = new RetrieveRequest();
        // The version in package.xml overrides the version in RetrieveRequest
        retrieveRequest.setApiVersion(API_VERSION);
        setUnpackaged(retrieveRequest);

        AsyncResult asyncResult = metadataConnection.retrieve(retrieveRequest);
        RetrieveResult result = waitForRetrieveCompletion(asyncResult);

        if (result.getStatus() == RetrieveStatus.Failed) {
            throw new Exception(result.getErrorStatusCode() + " msg: " +
                    result.getErrorMessage());
        } else if (result.getStatus() == RetrieveStatus.Succeeded) {  
	        // Print out any warning messages
	        StringBuilder stringBuilder = new StringBuilder();
	        if (result.getMessages() != null) {
	            for (RetrieveMessage rm : result.getMessages()) {
	                stringBuilder.append(rm.getFileName() + " - " + rm.getProblem() + "\n");
	            }
	        }
	        if (stringBuilder.length() > 0) {
	            System.out.println("Retrieve warnings:\n" + stringBuilder);
	        }
	
	        System.out.println("Writing results to zip file");
	        File resultsFile = new File(ZIP_FILE);
	        FileOutputStream os = new FileOutputStream(resultsFile);
	
	        try {
	            os.write(result.getZipFile());
	        } finally {
	            os.close();
	        }
        }
    }

    private DeployResult waitForDeployCompletion(String asyncResultId) throws Exception {
        int poll = 0;
        long waitTimeMilliSecs = ONE_SECOND;
        DeployResult deployResult;
        boolean fetchDetails;
        do {
            Thread.sleep(waitTimeMilliSecs);
            // double the wait time for the next iteration

            waitTimeMilliSecs *= 2;
            if (poll++ > MAX_NUM_POLL_REQUESTS) {
                throw new Exception(
                    "Request timed out. If this is a large set of metadata components, " +
                    "ensure that MAX_NUM_POLL_REQUESTS is sufficient.");
            }
            // Fetch in-progress details once for every 3 polls
            fetchDetails = (poll % 3 == 0);

            deployResult = metadataConnection.checkDeployStatus(asyncResultId, fetchDetails);
            System.out.println("Status is: " + deployResult.getStatus());
            if (!deployResult.isDone() && fetchDetails) {
                printErrors(deployResult, "Failures for deployment in progress:\n");
            }
        }
        while (!deployResult.isDone());

        if (!deployResult.isSuccess() && deployResult.getErrorStatusCode() != null) {
            throw new Exception(deployResult.getErrorStatusCode() + " msg: " +
                    deployResult.getErrorMessage());
        }
        
        if (!fetchDetails) {
            // Get the final result with details if we didn't do it in the last attempt.
            deployResult = metadataConnection.checkDeployStatus(asyncResultId, true);
        }
        
        return deployResult;
    }

    private RetrieveResult waitForRetrieveCompletion(AsyncResult asyncResult) throws Exception {
    	// Wait for the retrieve to complete
        int poll = 0;
        long waitTimeMilliSecs = ONE_SECOND;
        String asyncResultId = asyncResult.getId();
        RetrieveResult result = null;
        do {
            Thread.sleep(waitTimeMilliSecs);
            // Double the wait time for the next iteration
            waitTimeMilliSecs *= 2;
            if (poll++ > MAX_NUM_POLL_REQUESTS) {
                throw new Exception("Request timed out.  If this is a large set " +
                "of metadata components, check that the time allowed " +
                "by MAX_NUM_POLL_REQUESTS is sufficient.");
            }
            result = metadataConnection.checkRetrieveStatus(
                    asyncResultId, true);
            System.out.println("Retrieve Status: " + result.getStatus());
        } while (!result.isDone());         

        return result;
    }

    private void setUnpackaged(RetrieveRequest request) throws Exception {
        // Edit the path, if necessary, if your package.xml file is located elsewhere
        File unpackedManifest = new File(MANIFEST_FILE);
        System.out.println("Manifest file: " + unpackedManifest.getAbsolutePath());

        if (!unpackedManifest.exists() || !unpackedManifest.isFile()) {
            throw new Exception("Should provide a valid retrieve manifest " +
                "for unpackaged content. Looking for " +
                unpackedManifest.getAbsolutePath());
        }

        // Note that we use the fully quualified class name because
        // of a collision with the java.lang.Package class
        com.sforce.soap.metadata.Package p = parsePackageManifest(unpackedManifest);
        request.setUnpackaged(p);
    }

    private com.sforce.soap.metadata.Package parsePackageManifest(File file)
            throws ParserConfigurationException, IOException, SAXException {
        com.sforce.soap.metadata.Package packageManifest = null;
        List<PackageTypeMembers> listPackageTypes = new ArrayList<PackageTypeMembers>();
        DocumentBuilder db =
                DocumentBuilderFactory.newInstance().newDocumentBuilder();
        InputStream inputStream = new FileInputStream(file);
        Element d = db.parse(inputStream).getDocumentElement();
        for (Node c = d.getFirstChild(); c != null; c = c.getNextSibling()) {
            if (c instanceof Element) {
                Element ce = (Element) c;
                NodeList nodeList = ce.getElementsByTagName("name");
                if (nodeList.getLength() == 0) {
                    continue;
                }
                String name = nodeList.item(0).getTextContent();
                NodeList m = ce.getElementsByTagName("members");
                List<String> members = new ArrayList<String>();
                for (int i = 0; i < m.getLength(); i++) {
                    Node mm = m.item(i);
                    members.add(mm.getTextContent());
                }
                PackageTypeMembers packageTypes = new PackageTypeMembers();
                packageTypes.setName(name);
                packageTypes.setMembers(members.toArray(new String[members.size()]));
                listPackageTypes.add(packageTypes);
            }
        }
        packageManifest = new com.sforce.soap.metadata.Package();
        PackageTypeMembers[] packageTypesArray =
                new PackageTypeMembers[listPackageTypes.size()];
        packageManifest.setTypes(listPackageTypes.toArray(packageTypesArray));
        packageManifest.setVersion(API_VERSION + "");
        return packageManifest;
    }
}