云计算:重新定义 IT
       在过去一年中,云计算爆炸式地增长,包括了形形色色的应用程序 — 比如 Salesforce CRM 和 Google Apps — 及 服务 — 比如托管于 Amazon Elastic Compute Cloud (Amazon EC2) 上的 IBM® DB2®、Google App Engine 和 Salesforce 的 Force.com 平台。这些服务通常被称为 Platform-as-a-Service (PaaS),因为它们提供了一个完整的平台,在这个平台之上,企业能够构建和托管其 IT 应用程序。
|
常用缩略语
- API:应用程序编程接口(Application programming interface)
- HTTPS:在安全套接字层上传输的超文本传输协议(Hypertext Transfer Protocol over Secure Sockets Layer)
- IT:信息技术(Information technology)
- SOAP:简单对象访问协议(Simple Object Access Protocol)
- UI:用户接口(User Interface)
- URL:通用资源定位符(Universal Resource Locator)
- XML:可扩展标记语言(Extensible Markup Language)
| |
       PaaS 产品被托管在多租户环境中,在这个环境中,软硬件基础设施是共享的。设置这些环境的目的是确保每个组织的数据都能以一种安全的方式与其他组织隔离 — 就像是租用一间办公室而不是购买一间办公室,不必担心它的维护和更新。使用 PaaS,组织不仅节约了大量成本,而且还具备了诸如自动升级和零维护等独特优势。这种模型还提供了另外一个独特优势,那就是减少了 IT 产品经常会遇到的超出预算和开发期限的风险。
       Salesforce 就是其中一个 PaaS 提供者。该公司最初主要是提供受托管的客户关系管理(CRM)解决方案,为常用的业务应用程序提供全程解决方案,比如销售、合作伙伴关系管理以及市场营销。 Salesforce 借助这个平台启动了 Force.com,一个完整的 PaaS,用来在浏览器中或通过使用基于 Eclipse 的 Force.com 集成开发环境(IDE)构建定制业务应用程序。应用程序也通过一个专有的类似 Java™ 的编程语言 Apex 定制。
       Salesforce 允许通过使用 SOAP 与服务器进行交互,其优势在于语言与平台相互独立。您可以使用 Web Services Description Language (WSDL) 来描述服务器上有哪种操作可用。一个 WSDL 文档先是描述一系列称为端口的网络端点,然后定义与服务器交换的消息或数据的 XML 格式。
       本文中的这个示例使用了 Java API for XML Web Services (JAX-WS)。JAX-WS 提供了很多简化 XML 和 SOAP 工作的工具,包括从 WSDL 文档中自动生成所需的对象并自动绑定 XML 和 Java 对象。
集成 Salesforce 和企业应用程序
       集成 Salesforce 和企业应用程序的方式有很多种。第一种方式就是在 Salesforce 中配置一个工作流,当数据被创建、更新或删除时随时用 Web 服务发送一个消息。这个过程使用了一个出站消息 WSDL — 类似于在数据库上配置一个触发器的过程。工作流配置 Salesforce 来在数据被创建、更新或删除时随时发送 SOAP 消息到一个预先配置好的 URL 目标。例如,在本文的 Mileage 示例中,您可以在一个新报告被添加时立即向内部系统发送里程报告。
       第二种方法提供了直接用 SOAP Web 服务与 Salesforce 进行交互的功能。这种方法使用了 Salesforce 生成的 WSDL 文件,这些文件被定制 Web 服务所使用。对于这种类型的集成,Salesforce 提供了两类 WSDL:
- Partner WSDL 是松散类型并可以在多个组织中使用;Partner WSDL 较难处理,因为 XML 必须要被转化成这个组织的正确的对象表示。
- Enterprise WSDL 是强类型并被绑定到一个单一组织;这使得 WSDL 更容易被处理,但它只能用于一个组织的模式。同样,任何对对象的改变都需要重新生成 WSDL。
       本文展示了通过扩展 Force.com Workbook 中的示例(一个里程跟踪应用程序)来与 Salesforce 进行集成。例如,我们将使用企业 Web Services WSDL。
       在处理 Salesforce Web 时,需要有一个安全令牌。用 Salesforce UI 接收或重置安全令牌。单击 Setup > My Personal Information > Reset your security token。这里,可以看到一个重置安全令牌并将其发送到您的电子邮件地址的选项,有了安全令牌后,将它与您的密码一起用于登录。例如,如果您的密码是 aaaaaa
,安全令牌是 XXXXXXXXXX
,那么您必须在密码一栏中键入 aaaaaaXXXXXXXXXX
。
通过 Web 服务集成 Salesforce
       为了能提供一个如何使用 XML 集成 Salesforce 的示例,我开发了一个能登录到 Salesforce、查询数据并能创建新记录的示例 Java 应用程序。所有与 Salesforce 的通信都要通过安全 HTTPS 通道。请按以下步骤建立 Java Web 服务来集成 Salesforce:
- 在 Salesforce 中生成 Enterprise WSDL 文档。
登录到您公司的 Salesforce 帐号并单击 Setup > Develop > API。然后右键单击您想生成的 WSDL 文档。
- 从 WSDL 生成 Java 类和域对象。
对于本例,我使用了 JAX-WS 工具箱内已包含的 wsimport 工具。为了简化运行 wsimport 的流程,这个示例使用了构建模板,它也是 Salesforce 示例的一部分。这个工具生成的 Java 域对象允许 XML 文档自动绑定到 Java 对象以简化与服务器的通信。
- 通过 Web 服务登录到 Salesforce。
您必须首先进行登录来获取一个服务器 URL 和会话 ID。清单1中这个代码显示了如何登录。
登录的一个重要部分就是获取服务器 URL。Salesforce 在多个实例上运行,例如 na1.salesforce.com 或 na2.salesforce.com,目的是为了提高可靠性和性能。一旦登录到服务器,您就需要为所有后续的 Web 服务调用使用相同的 URL 以维持会话信息。
清单 1. 登录到 Salesforce
public void doLogin(String userName, String password) {
if (userName.length() == 0 || password.length() == 0) {
throw new RuntimeException("user name length and/or password length
cannot be 0 length. \n",
new IllegalArgumentException("Invalid password or user name\n"))
} else {
try {
URL wsdlLocation =
this.getClass().getClassLoader().
getResource("etc/enterprise.wsdl")
if (wsdlLocation == null) {
WebServiceException e =
new WebServiceException("enterprise.wsdl not found!")
//exceptionLogger(e.getMessage(), e)
throw e
}
port = new SforceService(wsdlLocation,
new QName("urn:enterprise.soap.sforce.com",
"SforceService")).getSoap()
} catch (WebServiceException wse) {
//exceptionLogger("Error creating salesface port ", wse)
throw wse
}
try {
loginResponse = port.login(userName, password)
} catch (Exception e) {
System.out.println("Error logging in to Salesforce.com " + e)
return
}
System.out.println("Login was successful.")
System.out.print("The returned session id is: ")
System.out.println(loginResponse.getSessionId())
System.out.print("Your logged in user id is: ")
System.out.println(loginResponse.getUserId() + " \n\n")
System.out.print("The server url is: ")
System.out.println(loginResponse.getServerUrl() + " \n\n")
// on a successful login, you should always set up your session id
// and the url for subsequent calls
....
|
       建立了客户会话后,就可以通过 Web 服务调用来调用服务器并与其进行交互了。本文介绍了其中的两个操作:通过查询从服务器读取数据以及向服务器中添加新记录。
通过 Web 服务查询 Salesforce
       要查询 Salesforce 数据,必须使用一种称为 Salesforce Object Query Language (SOQL) 的特殊语言。可以使用 SOQL 查询来搜索特定对象或是一个对象的特定字段,这与用 SELECT
查询表中的特定字段非常类似。您还可以使用 SOQL 查询来统计满足查询条件的记录数量并可以对结果进行特定的排序。
       熟悉 SOQL 最好的办法就是使用 Force.com IDE 中的 Schema Explorer(见 图 1)。要打开一个项目中的 Schema Explorer,可以双击项目根部的 salesforce.schema。在 Schema Explorer 中,可以浏览您公司内所有的对象;通过钻入各种对象,可以选择特定对象或字段来自动生成一个查询。您还可以通过钻入到一个对象的子关系列表来将相关对象包括进一个查询。
图 1. Schema Explorer
       当完善了想要执行的 SOQL 查询后,可以将它嵌入到 Web 服务调用。这个查询生成的结果是数据的 XML 表示。这个 XML 文档被自动地绑定到您前面用 JAX-WS 生成的 Java 域对象。
       对于这个示例应用程序,只需获取所有里程记录。这个查询的结果之后会被绑定到在设置时生成的域对象,如 清单 2中所示。
清单 2. 里程搜索的查询结果
public void getMileageReports(ForceLogin login) throws UnexpectedErrorFault,
InvalidSObjectFault, InvalidIdFault, InvalidQueryLocatorFault,
MalformedQueryFault, InvalidFieldFault {
QueryResult queryResult = login.port
.query("Select Contact__c, Date__c, Miles__c from Mileage__c")
if (queryResult.getSize() > 0) {
List<SObject> records = queryResult.getRecords()
for (SObject record : records) {
MileageC mileageC = (MileageC) record
System.out.println(mileageC.getMilesC().getValue()
+ " " + mileageC.getContactC().getValue())
}
}
}
|
       在编写这些查询时,有一点需要加以注意,即要将定制对象和字段与标准对象和字段区分开。定制对象是专用于某个组织的定制 Salesforce 数据库表。为了区分定制对象和字段与具有相同名称的标准 Salesforce 对象,要在每个对象名称后面追加一个 __c
。
       清单 2中的查询从 Mileage
对象中获取所有记录。请注意,__c
约定将定制字段名称与此里程对象名称区分开来。之后,可以遍历查询结果,并将它们绑定到 MileageC
对象,这是一个从企业 WSDL 生成的域对象。MileageC
对象将自动地被实际的里程记录数据所填充。
       前面的查询返回了所有里程记录,这些记录往往会超过您所需要的数据。幸运的是,您可以指定一个过滤条件 — 例如,一个日期或联系人 — 这样您就可以从所有记录中只提取那些所需要的记录子集。假设您希望找到所有里程数超过 300 的里程报告,就可以把这个查询改为:
Select Name, Miles__c From Mileage__c where Miles__c > 300
|
       过滤数据的另一个方法是只获取那些在一段时间内更改过的数据。要做到这一点,可以使用 getUpdated
方法,而不是运行一个查询,这个方法获得要进行更新的对象以及起始和结束时间。getUpdated
将返回那些在指定时间段内修改过的所有对象。清单 3中的代码返回了在过去一个月中变更过的所有的里程记录。
清单 3. 获取更新过的里程记录
GregorianCalendar cal =
port.getServerTimestamp().getTimestamp().
toGregorianCalendar()
GregorianCalendar calEnd = (GregorianCalendar) cal.clone()
cal.add(GregorianCalendar.MONTH, -1)
DatatypeFactory datatypeFactory = DatatypeFactory.newInstance()
GetUpdatedResult updatedRecords = port.getUpdated("Mileage",
datatypeFactory.newXMLGregorianCalendar(cal),
datatypeFactory.newXMLGregorianCalendar(calEnd))
|
       getDeleted
操作与 getUpdated
类似。这个方法有着与 getUpdated
同样的方法签名并会返回在某个时间段内被删除的所有记录 。
       另一个查询 Salesforce 的方法是使用 Salesforce Object Search Language (SOSL)。SOSL 与基于文本的搜索相似,它可以实现更自由的搜索。有了 SOSL,就可实现对多个、不相关的对象进行搜索,甚至在您不知道哪个(些)对象包含所需数据时,也可以用它来进行搜索。用 SOSL 所进行的查询也可以返回多个不相关的对象。
       清单 4— 一个来自于 Salesforce JAX-WS 样例的 SOSL 查询示例 — 是这种方法的一个很好的示例。清单 4 中的代码查询了一个电话号码的联系人、领导及帐号,展示了如何跨多个不相关对象进行搜索。
清单 4. 跨多个对象进行电话号码搜索
SearchResult searchResult = port
.search("find {4159017000} in phone fields returning
contact(id, phone, firstname, lastname),
lead(id, phone, firstname, lastname),
account(id, phone, name)")
List<SearchRecord> records = searchResult.getSearchRecords()
List<Contact> contacts = new ArrayList<Contact>()
List<Lead> leads = new ArrayList<Lead>()
List<Account> accounts = new ArrayList<Account>()
if (records != null && !records.isEmpty()) {
for (SearchRecord recordType : records) {
SObject record = recordType.getRecord()
if (record instanceof Contact) {
contacts.add((Contact) record)
} else if (record instanceof Lead) {
leads.add((Lead) record)
} else if (record instanceof Account) {
accounts.add((Account) record)
}
}
}
|
       在本示例中,search
方法被用来替代 query
来执行一个 SOSL 查询。其结果被映射到一个 SearchResults
对象,并且搜索结果中的单个记录随后被检查以确定它们类型。
通过 Web 向 Salesforce 添加数据
       要向 Salesforce 添加数据,可以使用从企业 WSDL 生成的相同的域对象。这些对象像标准对象一样被填充,然后以 XML 形式被串行化到服务器,进行持久化。这里有一个难点,就是创建不同对象间的关系。假设,您需要为某个特定的联系人添加一个新的里程记录。首先,需要查找想要使用的联系人 ID。在本示例中,您需要搜索名为 Edna 的联系人,如 清单 5所示。
清单 5. 在数据库中查找特定的联系人
QueryResult qr = login.port
.query("Select Id, FirstName, LastName, AccountId " +
"from Contact where FirstName = Edna")
String contactID = null
if (qr.getSize() > 0) {
Contact contact = (Contact) qr.getRecords().get(0)
contactID = contact.getId().getValue()
}
|
       这个查询提供了列表中的第一个联系人的 ID。(在一个真正的应用程序中,您可能希望做更广泛的错误检查以确保记录的正确性。)当有了这个用户的 ID 后,可以将它保存在新的里程记录里,参见 清单 6。
清单 6. 创建一个新的里程记录
MileageC mileageC = new MileageC()
GregorianCalendar cal = new GregorianCalendar()
cal.setTime(new Date())
XMLGregorianCalendar activityDate =
DatatypeFactory.newInstance().newXMLGregorianCalendar(cal)
mileageC.setDateC(soFactory.createMileageCDateC(activityDate))
mileageC.setMilesC(soFactory.createMileageCMilesC(new Double(300)))
mileageC.setContactC(soFactory.createMileageCContactC(contactID))
|
      添加了新的里程记录后,接下来需要调用 Web 服务上的 create
方法来将记录保存到 Salesforce。请注意,create
接受对象的列表,允许您添加一批里程记录。清单 7显示了将数据保存到 Salesforce 的代码。
清单 7. 将一个新里程记录保存到数据库
// call the create method passing the array of tasks as Sobjects
ArrayList list = new ArrayList()
list.add(mileageC)
List<SaveResult> saveResults = login.port.create(list)
for (SaveResult saveResult : saveResults) {
if (saveResult.isSuccess()) {
System.out.println("saveResult success, id= " + saveResult.getId())
} else {
System.out.println("saveResult error")
// there were errors during the create call, go through the
// errors and write them to the screen
List<Error> errorList = saveResult.getErrors()
for (Error error : errorList) {
System.out.println("Error code is: "
+ error.getStatusCode().toString())
System.out
.println("Error message: " + error.getMessage() + "\n\n")
}
}
}
|
       上述代码用来将记录保存到 Salesforce 并返回一个保存结果列表,它显示了这个保存是否成功。这个从 Salesforce 返回的错误消息通常很容易理解,这样就可以很容易弄清楚出现的问题。然而,这个简单的方法不能确保数据被正确地保存到 Salesforce。对于关键性的数据,可以考虑自动进行再次保存直到保存成功。
       用同样的方法,可以在 Salesforce 中更新数据。最简单的方法是先查询您想要改变的记录,然后再将这些记录导入到一个对象中。可以更新这个对象,然后调用 update
方法。
       更新数据的另一个方法是合并备份记录。用一个合并操作,可以指定一个主记录以及要合并的记录。用这种方法,就可以用所有合并记录中的数据更新主记录。一次可以同时合并三个记录。
结束语
       通过利用 XML 的强大功能,可以很容易地将云数据集成到一个现有的企业应用程序中。XML 提供了一个可以在不同服务和语言间转换的公共数据格式。本文中的这个示例应用程序演示了如何通过 XML Web 服务,特别是 SOAP,实现与 Salesforce 的交互。
       这个使用 Web 服务来将本地(on-site)应用程序与云数据集成的技术还可以被广泛地应用到其他应用程序,从 Google Apps 到 Basecamp 都可以使用这个技术。通过与云服务的集成,企业和组织就可以使用现有的软件投入快速地在云中建立各种各样的新应用程序。