使用 Rational Team Concert Item Connector 同步数据存储库
2010-08-04 00:00:00 来源:WEB开发网不同的部门系统之间的数据同步化是非常重要的。例如,客户支持与开发部门通常拥有他们自己的问题追踪系统,这样数据同步化就能使不同部门之间的员工可以共享信息并且同时处理相同的问题。但是,这实现起来是十分困难的,因为它需要手动的操作。不同部门之间的员工协作性地处理相同问题的一种方式是,联系两个团队之间的储存库,这样人们相互之间可以共享并追踪储存库中的对象。您可以使用项目连接器(Item Connector)来完成该项操作,其中的项目连接器是 IBM® Rational Team Concert™ 中一个可以扩展的框架,它将外部储存库中的对象与基于 IBM® Jazz™ 技术储存库中的永久性对象联系了起来。本文向您解释了怎样设计并创建新的连接器,而且笔者提供了一些范例以便学习。
项目连接器工作的方式
当您得到来自用户的访问时,您该怎样回应呢?这完全取决于用户。如果它是一个外部性的用户,那么支持人们接到一个访问然后创建一个凭证以记录这次访问。这叫做一个 访问追踪系统 。用户可能是一个内部性的用户,它没有接受到来自运行系统的警告。在这种情况下,内部支持员工会得到访问,而问题会得到 问题追踪系统 的追踪。这些都是 追踪系统 的实例。
最近的追踪系统很依赖于软件,这样报告的问题会最终带到软件开发项目中。但是,怎样将这些问题与开发活动联系起来呢?这是一个主要的陷阱。在很多情况下,这些问题可以得到手动的解决。例如,信息可以通过电子邮件发送到软件产品中,或者通过电话来进行讨论。重要的信息很容易丢失。因为在开发和前端问题追踪系统之间没有联系,时间会浪费在处理错误上(图 1)。
图 1. 信息通常是无法追踪的
查看原图(大图)
IBM® Rational Team Concert™ 提供了一种协作性的开发环境以处理这些情况。它是一个开放性的平台,可扩展的设计。在 Jazz.net 上已经可以得到各种不同的连接器。但是,您的公司可能会使用一个自己的系统,该系统没有与任何软件开发系统相联系。使用 Rational Team Concert 软件处理存储库没有障碍。Item Connector 是处理这样代码最佳的方法。
Item Connector 是 Rational Team Concert 内一种可扩展的框架,它将外部性的存储库与 Jazz 存储库中的 Jazz 项目(“项目”是 Item Connector 中的一个术语用于代表基于 Jazz 存储库的永久性对象)联系了起来。连接是通过首先将外部性对象复制到 Jazz 存储库中,然后将外部性对象与 Jazz 项目的状态进行同步化来实现的。这就是所谓的同步化过程。一个公司可以使用它来自动将系统中的信息,例如问题追踪系统,与软件开发活动联系起来。系统相互之间联系起来并提供了追踪性(图 2)。
图 2. 使用 Item Connector 来追踪信息
查看原图(大图)
本文向您解释了怎样开发新的连接器。它涉及到了四个步骤:
设计总体的结构。
准备环境以及范例源代码。
设计 Jazz 存储库与服务。
扩展 Item Connector 的构件。
Jazz 团队服务器
Jazz™ 团队服务器基于 Eclipse Equinox 框架,它是 OSGi 的实施。通过载入插件可以轻松添加一个新的服务。Jazz 团队服务器提供了扩展点,这样开发员就可以创建并注册他们自己的插件了。
项目连接器构件
重点提示
本文基于 Rational Team Concert 1.0.1 版本。关于 2.0 版本或者后续版本,请参考 Resources 部分中的每一个构件页面。
项目连接器是 Rational Team Concert 的框架。它提供了扩展点以将外部性的储存库与 Jazz 储存库中的 Jazz 项目联系起来(提示:项目 意思是 Jazz 储存库中的一个永久性对象)。项目连接器一个最显著的特性是代理机制。外部性的对象会首先复制到代理对象中,然后代理对象才会复制到背景中的 Jazz 项目。为了实施这种机制,项目连接器提供了三种特定的 Jazz 项目:ExternalProxy、ExternalState 与 SyncRule。
ExternalProxy 含有对外部性对象与 Jazz 对象的连接。
ExternalState 是一个外部性对象属性的容器。
SyncRule 含有同步性规则,它将外部性属性名映射到 Jazz 属性名。
Item Connector 由这些构件组成,在接下来的章节中将分别进行介绍:
项目连接器客户端
项目管理器
外部存储库管理器
值转换器
Item Connector 客户端
Item Connector 客户端创建了一个外部性对象与一个 ExternalProxy 项目之间的联系,并使用以下七个步骤来向 Jazz 存储库发送对象的数据:
获取或者创建对象的 ExternalProxy 项目。
获取或者创建对象的 ExternalState 项目。
获取 SyncRule 项目。
使用 SyncRule 来决定应该向 Jazz 存储库发送什么属性。
将属性复制到 ExternalState 项目中。
将 SyncRule 项目附属到 ExternalProxy 项目中。
保存 ExternalState 项目与 ExternalProxy 项目。
项目管理器
项目管理器是一种服务器端的插件,可以通过 com.ibm.team.interop.service.itemManager 扩展点获得(图 3)。项目管理器会创建 Jazz 项目或者更新已经被创建的 Jazz 项目的属性。
图 3. 项目管理器的扩展点
外部存储库管理器
外部存储库管理器也是一种服务器端的插件,可以通过 com.ibm.team.interop.service.externalManager 扩展点获得(图 3)。外部存储库管理器可以创建、获取和更新外部性对象。
值转换器
您可以从 com.ibm.team.interop.service.valueTransformer 扩展点处获得值转换器。该构件的具体内容超出了本文的讨论范围。如果您想要得到更多信息,那么您可以查看文献中的“值转换器实施。
设计总体架构
在您可以设计项目与包结构之前,您需要选择一个开发模式。
开发模式
该程序同样在“创建一个新的项目连接器”章节中有所描述。其中提供了四个场景,及其在外部性服务器中的前提条件。但是,决定场景对于新项目直接可用是很困难的,因为有四个描述去决定总体的结构。这里,我们只关注项目连接器客户端的开发。对于开发,您必须决定目标 Java™ Virtual Machine (VN),项目连接器就是在它上面运行的。可能有三种 Jazz VNs:一个外部性的服务器,一个 Jazz 服务器,或者一个独立的服务器。这就是所谓的“开发模式”。
集成到一个外部性的服务器中
与“保存”操作一起运行的端效果
位于一个 Jazz 服务器中
等待并响应来自外部性服务器的更新通知
阶段性地查询更新
独立(与外部性 Jazz 服务器或者 Jazz 服务器相隔离)
在请求时激活过程(客户端)
阶段性地查询更新(服务器)
模式 1.1、2.1、2.2 与 3.1 分别对应于四种场景,但是对于 3.2 没有场景。重要的因素是选择一个开发模式来决定总体的结构。表 1 总结了项目连接器客户端的开发,以及开发模式中外部性服务器的前提条件。
表 1. 开发模式中的部署与前提条件
开发模式 | Java VM 的开发 | 外部性服务器的前提条件 |
1.1 | 使用外部存储库 | 可定制的保存操作 |
2.1 | 使用 Jazz 存储库 | 事件通知机理 |
2.2 | 使用 Jazz 存储库 | 得到及设定数据的服务 |
3.1 | 独立 | 得到并设置数据的服务 |
3.2 | 独立 | 得到并设置数据的服务 |
图 4 是一个开发模式 3.1 的结构性概述。对于这种开发,项目连接器客户端会隔离于 Jazz 或者外部性服务器而运行。在这种结构中,我们假设外部性的服务器提供了控制外部存储库的服务。为了提供服务,项目连接器客户端会访问外部性服务器中的 getAllObjects 服务,因为该构件获得了所有的外部性对象,然后项目连接器就可以执行操作以创建或者更新相应的 Jazz 项目了。反过来,外部存储库管理器会一个接一个地选择 Jazz 项目,并执行操作以一个接一个地创建或者更新相应的外部性对象,所以该评论使用外部性服务器中的 getObject,postObject,以及putObject 服务。
图 4. 模式 3.1 的结构性概述
查看原图(大图)
在本文的稍后部分中,我们将会向您解释模式 3.1 的执行过程。
包设计
在选择一个开发模式之后,您就可以为将会实现模式的所有内容而设计项目以及包结构了。表 2 显示了对于执行模式 3.1 或者 3.2 来说典型的一些结构。有一个 Java 项目以及四个插件项目。这些项目起的名字用以匹配相应的包名。我们使用“类栈”作为使用项目和包所创建的范例的名字。
表 2. 新连接器的项目名与内容
包(Java project) | 内容 |
ClassicTalkConnectorClient | 项目连接器客户端 |
包 (插件项目) | 内容 |
com.example.classictalk.common | Jazz 存储库与服务(界面) |
com.example.classictalk.interop.managers | 项目管理器,外部存储库管理器 |
com.example.classictalk.launches | 启动文件 |
com.example.classictalk.service | 服务(执行) |
准备环境以及范例源代码
注意:
在开始学习下一个章节之前,我们会参考文献中的相应的章节,来为开发一个 Jazz 存储库与服务创建一个环境。您所需要的所有代码都可以从这里的下载资源( 下载)中获得。您可以使用本文来学习代码,或者只是从下载资源中所包含的范例处开始。
创建环境
为 C:\classictalk 目录的 Jazz 构件开发创建一个环境。该位置在以下的子步骤中将会引用为 [InstallDir]。
运行 [InstallDir]\jazz\client\eclipse\TeamConcert.exe。
选择 Preferences > Plug-in Development > Target Platform,并点击 Add。
选择 File System,然后点击 Next。
点击 Add,并选择 [InstallDir]\jazz\server\license-update-site\plugins。
点击 Finish。
点击 Window > Open Perspective > Java。
导入 classictalk.zip 文件,您可以从本文的 Downloads 部分中得到该文。
选择 File > Import > Existing Projects into Workspace,然后点击 Next。
点击 Select archive file,然后选择 classictalk.zip。
点击 Finish。
向 ClassicTalkConnectorClient 项目添加 Java 客户端库:
下载 Jazz Plain Java Client Libraries,它位于 Web 上 Rational Team Concert 1.0.1 下载页面 Incubators 之下。
右击项目,并选择 Build Path > Configure Build Path,然后选择 Libraries 项。
按照“使项目连接器就绪”中的“RTC 1.0”用例来添加 JAR 文件。
重点:
不要忘了添加 org.eclipse.core.runtime_3.3.100.v20070530.jar 文件,它没有在 Rational Team Concert (RTC) 1.0 用例中列出。
设计 Jazz 储存库与服务
在这个部分中,您将会关注以下三种插件项目:
com.example.classictalk.common
com.example.classictalk.services
com.example.classictalk.launches
这些项目与 Jazz 储存库,以及 图 4 中的 RESTful 服务相关。
存储模型
为将您自己的对象复制到 Jazz 储存库中,您必须为 Jazz 服务器上存储对象创建您自己的储存库。存储模型允许外部性对象,以一种外部性对象可以映射到 Jazz 储存库数据库的方式,来作为特定构件的数据存储到 Jazz 储存库中,同时填充到对象中,然后与其他的数据交换格式一起使用。在实践中,存储模型只限定于核心模型,这意味着对于使用存储模型有一些特定的习惯与规则。存储外部性对象的数据结构可以使用 EMF 核心模型来定义。在存储模型中,准备好了三种类型的:Auditable、SimpleItem 与 Helper。
Auditable 与 SimpleItem 都是 Jazz 项目。Auditable 保持了以前状态的线性历史(没有分支),而 SimpleItem 则不能。
Helper 并不是一个 Jazz 项目,它属于 Auditable 或者 SimpleItem。Helper 必须与它们中的一个一起保存,而当相应的 Auditable 或者 SimpleItem 终止时 Helper 也会终止。
重点:
Item Connector 只能使用可审计的 ESuperType 来管理 Jazz 项目。这是因为 Item Connector 通过将 Jazz 项目的新状态与过去的状态相比较,来决定 Jazz 项目是否被改动过。这样的 Jazz 项目应该包含有一个“标识符”属性,通过它来识别相应的外部性对象。图 5 是 classictalk 核心模型的一个屏幕截图。
图 5. com.example.classictalk.common 中的 classictalk.ecore 项
Jazz 服务器的服务
Item Manager 需要一种服务来保存 Jazz 项目(图 4 中的“保存服务”)。通常来说,服务的界面是在普通包中定义的(例如,com.example.classictalk.common)。 com.ibm.team.repository.common.components 扩展点用于配置界面。图 6 显示了 MANIFEST.MF 文件中的 classictalk 界面,该文件通过 Plug-in Manifest Editor 来打开。
图 6. com.example.classictalk.common 中的 MANIFEST.MF 文件
服务本身是在服务包(com.example.classictalk.services project)中定义的。com.ibm.team.repository.service.serviceProvider 扩展点用于配置服务。图 7 显示了在 Plug-in Manifest 编辑器中打开 MANIFEST.MF 文件中 classictalk 服务的定义。
图 7. com.example.classictalk.service 中的 MANIFEST.MF 文件
查看原图(大图)
外部性服务器上的服务
外部性服务器通常为 External Repository Manager 提供了至少四种服务:
获取所有的对象
获取一种对象
创建一个对象
更新一个对象
在一个典型的实施过程中,这些服务是很方便的,它们的名字分别是 getAllObjects、 getObject、 postObject 以及 putObject。外部性服务器必须为处理外部性对象和 Jazz 服务器或者客户端提供这些可用的服务,以让客户端为访问这些服务做好准备。
导出一般的包
一般包被 Item Connector 客户端所引用,也被 Jazz 服务器所引用。您可以使用导出向导来将包作为 Java Archive (JAR)文件导出,而导出的 JAR 文件在一般包被导出之后,必须添加到 ClassicTalkConnectorClient 项目的 Referenced Libraries。图 8 显示了导出向导,它将一般包作为 JAR 文件导出。
图 8. Export 向导
构建储存库数据库
构建 Jazz 储存库的一种方式是使用 AllTestsCreateDB JUnit 测试,它包含在 com.ibm.team.repository.service.tests 插件中。物理上,该储存库是在 -Dcom.ibm.team.repository.db.jdbc.location 选项中指定的文件夹中创建的。图 9 显示了 JUnit 插件测试中所做配置的一个范例。
下面是构建 Jazz 存储库的步骤:
右击 com.example.classictalk.launches > launches > create Jazz repository.launch 来选择 runAs。
点击 Create Jazz repository,如图 9 所示。
图 9. 创建 Jazz repository.launch
查看原图(大图)
扩展项目连接器的构件
在这个部分中,我们将会把注意力放在两个项目上:ClassicTalkConnectorClient 与 com.example.classictalk.interop.managers 项目。这些项目对应于 图 4 中的项目连接器客户端、Item Manager、Synchronization Rule 与 External Repository Manager。
项目连接器客户端
基本上,所有外部性的对象都由外部性服务的 getAllObjects 服务获取,而每一个对象都根据前面所列出的七步来进行处理。ExternalProxy 项目有对外部性对象的链接,而该链接使用一个 URL 进行描述。在 Classic Talk Connector Client 项目中,我们将外部存储库的 URL 与作为外部性对象 ID 属性名字及值混合体的 标识符组合而成。
按照以下的步骤来创建一个 ClassicTalkConnectorClient 项目:
创建一个名为 ClassicTalkConnectorClient 的 Java 项目。
右击项目并选择 Build Path > Configure Build Path,然后点击 Libraries 项。
然后添加“导出普通包”部分中导出的 com.example.classictalk.common_1.0.0.jar 文件。
向 [InstallDir]\jazz\client\eclipse\plugins 文件夹添加 com.example.classictalk.common_1.0.0.jar 文件。这就是同步化状态编辑器中链接项目的信息 (见于 图 17)。
通过参考“项目连接器构件”部分中“项目连接器客户端”章节的七个步骤来实施项目连接器客户端。
项目管理器
项目管理器用于创建并更新 Jazz 项目。为了实现这些功能,该构件应该实施诸如 createItem、saveState、 getState 之类的方法,扩展 com.ibm.team.interop.service.AbstractInteropItemManager 类并从 com.ibm.team.interop.service.IInteropItemManager 中实施界面来实现以上的功能。这些方法的解释与范例项目中的源代码一起出现,您可以在本文的 Downloads 部分中找到它们。接下来描述 斜体 显示的词就是源代码中实现变量的名字(见于下面列出的代码清单 1,2,3)。
createItem: 方法通过访问 com.ibm.team.repository.common.IItemType#createItem 方法来负责创建一个 Jazz 项目。
saveState: 方法将 newState 的属性映射到 workingItem 项目,然后使用保存服务来保存项目(图 4)。
getState: 方法根据 propertyNames 定义的密钥列表来从 item 获取密钥/值对,并将它们作为一个映射对象来返回。
清单 1. 创建项目方法
public IItem createItem(Map<String, ?> itemState, IProcessArea processArea,
Map<String, ?> preferences) {
return ChatThread.ITEM_TYPE.createItem();
}
清单 2. saveState 方法
public IItem saveState(IItem workingItem, Map<String, ?> newState,
IProcessArea processArea, Map<String, ?> preferences)
throws TeamRepositoryException {
ChatThread workingChatThread = (ChatThread)workingItem;
for(Map.Entry<String, ?> entry : newState.entrySet()) {
setProperty(entry.getKey(), entry.getValue(), workingChatThread);
}
return getClassicTalkService().saveChatThread(workingChatThread);
}
清单 3. 获得状态方法
public Map<String, ?> getState(IItem item, List<String> propertyNames,
Map<String, ?> preferences) throws TeamRepositoryException {
ChatThread chatThread = (ChatThread) item;
Map<String, Object> state = new HashMap<String, Object>();
Iterator<String> iter = propertyNames.iterator();
while(iter.hasNext()){
String propName = iter.next();
state.put(propName, getProperty(propName, chatThread));
}
return state;
}
另一个重要的方法是 getSupportedTypeInfo,它定义了同步化规则的 项目类型 与 项目属性(图 11)。项目类型 就是与该同步化规则相同步化的 Jazz 项目,并且该名字通过访问 ITypeInfoDTO 对象的 setName 方法来设置。Item Property 是与该同步化规则同步化的 Jazz 项目的属性,而且这些属性通过访问 IPropertyInfoDTO 对象的 add 方法来设置(见于代码清单 4)。
清单 4. 得到支持的 TypeInfo 方法
private static ITypeInfoDTO[] fgTypeInfo;
//Item Property
public static final String ID_PROPERTY = "id";
public static final String TEXT_PROPERTY = "text";
private static final String[] PROPERTIES = {
ID_PROPERTY,
TEXT_PROPERTY,
};
public ITypeInfoDTO[] getSupportedTypeInfo(IProcessArea processArea) {
if(fgTypeInfo == null){
fgTypeInfo = new ITypeInfoDTO[1];
fgTypeInfo[0] = InteropFactory.INSTANCE.createTypeInfoDTO();
//Item type
fgTypeInfo[0].setName(ChatThread.ITEM_TYPE.getNamespaceURI() +
"." + ChatThread.ITEM_TYPE.getName());
List<IPropertyInfoDTO> propertyInfo = fgTypeInfo[0].getProperties();
for(String name : PROPERTIES){
IPropertyInfoDTO info = InteropFactory.INSTANCE.createPropertyInfoDTO();
info.setName(name);
info.setIdentifier(name.equals(ID_PROPERTY));
propertyInfo.add(info);
}
}
return fgTypeInfo;
}
按照以下的步骤来创建一个 com.example.classictalk.interop.managers 项目:
创建一个名为 com.example.classictalk.interop.managers 的插件项目(图 10)。
点击 MANIFEST.MF 文件中的 Dependencies 项,并添加这些需要的插件:
com.ibm.team.interop.service
com.ibm.team.interop.common
com.ibm.team.process.common
com.ibm.team.repository.common
com.ibm.team.repository.service
com.example.classictalk.common
点击 MANIFEST.MF 文件中的 Extensions 项,并添加以下的扩展项(见于图 10):
扩展点: com.ibm.team.interop.service.itemManager
项目管理器:
ID: com.example.classictalk.interop.managers.ClassicTalkItemManager
名字:ClassicTalkItemManager
扩展服务:
构件 Id: com.example.classictalk.interop
实施类: com.example.classictalk.interop.managers.ClassicTalkItemManager
前提条件:
界面: com.example.classictalk.common.IClassicTalkService
实施 createItem、 saveState、 getState 与 getSupportedTypeInfo 方法。
图 10. Item Manager 与 External Repository Manager 的扩展
查看原图(大图)
External Repository Manager
External Repository Manager 就是您用来创建、获取并更新外部性对象的工具。为了提供这些功能,该构件实施了一些方法:createObject、getState、updateState 以及 findObject。它们都是通过扩展 com.ibm.team.interop.service.AbstractInteropExternalManager 类及实施界面 com.ibm.team.interop.service.IInteropExternalManager 来得以实施的。这些方法的解释应该基于范例项目中 External Repository Manager 的源代码(见于 Downloads)。以下描述的斜体词就是源代码实例化变量的名字(也见于代码清单 5、6、7 和 8)。
createObject:这种方法根据密钥列表 propertyNames 从 state 映射对象中获得密钥/值对,并使用 postObject 服务在外部性储存库中创建一个外部性的对象(图 4)。像用户 ID、密码、储存库 URI 这样的连接信息,会为 externalConnection 定义。postObject 服务会返回新创建对象的状态数据,当由于外部性储存库保存操作可能存在的边效应而访问服务时,它可能与使用的 状态 数据不一样。返回的数据应该得到剖析并存储在 returnedState 映射对象中。最终,该方法会使用外部对象 ID 属性的名字与值的混合项来创建一个 java.net.URI 对象,然后使用 URL 对象来访问 com.ibm.team.interop.service.internal.IInteropExternalManager#CreationInfo 方法,并返回它的结果。
getState: 该方法会找到一个外部性对象,该对象的标示符会定义为一个 uri URI 对象,并得到对象的值,其相应的密钥在 propertyNames 中指定。这是通过访问外部性储存库中的 getObject 服务来实现的。联系信息在 externalConnection 中得到了定义。最终,密钥与值会放到一个映射对象中并返回。
updateState: 该方法找到了一个标示符定义为 uri URI 对象的外部性对象,并使用外部性储存库中的 putObject 服务来更新密钥为 propertyNames 的值。更新的值可以使用 propertyNames 的密钥列表来从 newState 处获得更新的值。然后该方法会分析来自 propertyNames 服务的返回数据,然后将密钥/值属性置于 returnedState 所定义的映射对象中,因为新创建对象中的返回数据由于外部性储存库中保存操作的边界效应,可能不同于 newState。如果 newState 与 returnedState 的值返回了相似或者其他值的话,该方法会返回 true。
findObject: 这种方法使用 getObject 服务,来使用 idPropName 密钥及其来自 state 映射的值来找到外部性的对象,如果没有找到外部性的对象的话,就使用 propertyNames 密钥剖析返回的数据以设置密钥/值对。最后,该方法会创建并返回标示符的密钥/值的 URL 对象。联系的信息存储在 externalConnection 中。
清单 5. createObject 方法
public CreationInfo createObject(String typeName, Map<String, ?> state,
Map<String, Object> returnedState, List<String> propertyNames,
IExternalRepositoryConnection externalConnection,
IProcessArea processArea) {
String repositoryUri = externalConnection.getName();
String userId = externalConnection.getUserId();
String password = externalConnection.getPassword();
Map<String, String> castState = new HashMap<String, String>();
for(int index = 0; index < propertyNames.size(); index++){
String propertyName = propertyNames.get(index);
castState.put(propertyName, (String)state.get(propertyName));
}
Document doc = RestClient.doMethod(repositoryUri, path, clazz,
userId, password, castState, RestClient.METHOD_POST);
List<Map<String, String>> extProps = RestClient.parseDocument(doc,
xpath, propertyNames);
CreationInfo creationInfo = null;
if(extProps.size() == 1){
Map<String, String> extProp = extProps.get(0);
String extIdValue = extProp.get(EXT_ID_PROPERTY);
String urlStr = repositoryUri + "/" + EXT_ID_PROPERTY + "=" + extIdValue;
try {
URI uri = new URL(urlStr).toURI();
creationInfo = new CreationInfo(uri);
for(int index = 0; index < propertyNames.size(); index++){
String propertyName = propertyNames.get(index);
returnedState.put(propertyName, extProp.get(propertyName));
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (URISyntaxException e) {
e.printStackTrace();
}
}
return creationInfo;
}
清单 6. getState 方法
public Map<String, ?> getState(URI uri, List<String> propertyNames,
IExternalRepositoryConnection externalConnection) {
String repositoryUri = externalConnection.getName();
String userId = externalConnection.getUserId();
String password = externalConnection.getPassword();
Map<String, String> params = extractIdMapFromExtURI(uri);
for(int index = 0; index < propertyNames.size(); index++){
String propertyName = propertyNames.get(index);
if(params.containsKey(propertyName))continue;
params.put(propertyName, "");
}
Document doc = RestClient.doMethod(repositoryUri, path, clazz,
userId, password, params, RestClient.METHOD_GET);
List<Map<String, String>> extProps = RestClient.parseDocument(doc,
xpath, propertyNames);
if(extProps.size() == 1){
return extProps.get(0);
}else{
return null;
}
}
清单 7. updateState 方法
public boolean updateState(String typeName, URI uri,
Map<String, ?> newState, Map<String, ?> lastState,
Map<String, Object> returnedState, List<String> propertyNames,
IExternalRepositoryConnection externalConnection,
IProcessArea processArea) {
String repositoryUri = externalConnection.getName();
String userId = externalConnection.getUserId();
String password = externalConnection.getPassword();
Map<String, String> params = extractIdMapFromExtURI(uri);
for(int index = 0; index < propertyNames.size(); index++){
String propertyName = propertyNames.get(index);
if(params.containsKey(propertyName))continue;
if(newState.containsKey(propertyName)){
params.put(propertyName, (String)newState.get(propertyName));
}
}
Document doc = RestClient.doMethod(repositoryUri, path, clazz,
userId, password, params, RestClient.METHOD_PUT);
List<Map<String, String>> extProps = RestClient.parseDocument(doc,
xpath, propertyNames);
boolean returnedFlag = false;
if(extProps.size() == 1){
Map<String, String> extProp = extProps.get(0);
if(params.get(EXT_ID_PROPERTY).equals(extProp.get(EXT_ID_PROPERTY)) &&
params.get(EXT_TEXT_PROPERTY).equals(extProp.get(EXT_TEXT_PROPERTY))){
returnedFlag = true;
}
returnedState.put(EXT_ID_PROPERTY, extProp.get(EXT_ID_PROPERTY));
returnedState.put(EXT_TEXT_PROPERTY, extProp.get(EXT_TEXT_PROPERTY));
}
return returnedFlag;
}
清单 8. findObject 方法
public URI findObject(String typeName, String idPropName,
Map<String, ?> state, Map<String, Object> returnedState,
List<String> propertyNames,
IExternalRepositoryConnection externalConnection) {
String repositoryUri = externalConnection.getName();
String userId = externalConnection.getUserId();
String password = externalConnection.getPassword();
Map<String, String> params = new HashMap<String, String>();
params.put(idPropName, (String)state.get(idPropName));
Document doc = RestClient.doMethod(repositoryUri, path, clazz,
userId, password, params, RestClient.METHOD_GET);
List<Map<String, String>> extProps = RestClient.parseDocument(doc,
xpath, propertyNames);
URI uri = null;
if(extProps.size() == 1){
Map<String, String> extProp = extProps.get(0);
String extIdValue = extProp.get(EXT_ID_PROPERTY);
String urlStr = repositoryUri + "/" + EXT_ID_PROPERTY + "=" + extIdValue;
try {
uri = new URL(urlStr).toURI();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (URISyntaxException e) {
e.printStackTrace();
}
returnedState.put(EXT_ID_PROPERTY, extProp.get(EXT_ID_PROPERTY));
returnedState.put(EXT_TEXT_PROPERTY, extProp.get(EXT_TEXT_PROPERTY));
}
return uri;
}
另一种重要的方法是 getSupportedTypeInfo 方法,它为同步化规则定义了 External Type 与 External Property (图 11)。External Type 是该同步化规则中要同步化的外部性对象的类型名,而且其名字是由 ITypeInfoDTO 对象的 setName 方法来设置的。External Property 是与该同步化规则中的外部性对象一起同步化,而且这些属性可以使用 IPropertyInfoDTO 对象的 add 方法来进行设置(见于代码清单 9)。
清单 9. getSupportedTypeInfo 方法
private static ITypeInfoDTO[] fgTypeInfo;
//External Property
private static final String EXT_ID_PROPERTY = "id";
private static final String EXT_TEXT_PROPERTY = "text";
private static final String[] PROPERTIES = {
EXT_ID_PROPERTY,
EXT_TEXT_PROPERTY,
};
public ITypeInfoDTO[] getSupportedTypeInfo(
IExternalRepositoryConnection externalConnection) {
if (fgTypeInfo == null) {
fgTypeInfo = new ITypeInfoDTO[1];
fgTypeInfo[0] = InteropFactory.INSTANCE.createTypeInfoDTO();
//External type
fgTypeInfo[0].setName(ChatThread.ITEM_TYPE.getNamespaceURI() +
"." + ChatThread.ITEM_TYPE.getName());
List<IPropertyInfoDTO> propertyInfo = fgTypeInfo[0].getProperties();
for(String name : PROPERTIES) {
IPropertyInfoDTO info = InteropFactory.INSTANCE.createPropertyInfoDTO();
info.setName(name);
info.setIdentifier(name.equals(EXT_ID_PROPERTY));
propertyInfo.add(info);
}
}
return fgTypeInfo;
}
下面是创建 com.example.classictalk.interop.managers 项目的步骤:
选择一个已存在的项目。例如,选择 com.example.classictalk.interop.managers。
点击 MANIFEST.MF 文件中的 Extensions 项,并添加这些扩展(图 10):
扩展点:com.ibm.team.interop.service.externalManager
外部性管理器:
ID:com.example.classictalk.interop.managers.ClassicTalkExternalManager
名字:ClassicTalkExternalManager
扩展服务:
构件 Id: com.example.classictalk.interop
实施类: com.example.classictalk.interop.managers.ClassicTalkExternalManager
实施 createObject, getState, updateState 与 getSupportedTypeInfo 方法。
同步化规则
同步化规则将外部性数据映射到 Jazz 储存库属性名。为了提供这种功能,同步化规则应该选择 Item Manager 和 External Repository Manager,并引用 Item Manager 和 External Repository Manager 中的 getSupportedTypeInfo 方法定义的属性,来将外部性属性名映射到 Jazz 属性名。您可以使用同步化规则编辑器来编辑这些规则。它们都存储在项目区域中(图 11)。这需要服务器(例如,Jetty)已经处于运行状态,而且项目区域已经创建完毕。
图 11. 编辑同步化规则
查看原图(大图)
运行用例
为连接器列出了四种用例,以将一个外部性储存库和 Jazz 储存库进行同步化。在这些用例中,服务器中数据的创建与更新操作可以通过权衡 postObject 与 putObject 服务的实现来完成。通过在浏览器上使用 getAllObjects 服务,您可以轻松检查对象是否成功导入了。在“classictalk”范例中(通过 下载 部分获得),外部性服务器或者 Jazz 服务器分别在同一台机器上的端口 7443 或者 9443 处运行,同时 Jazz 客户端也是在相同的机器上运行的。这是一种用于简化的范例环境。
设置配置
警告
您在一个真实的商业环境中运行一个系统之前,您必须配置 Java 2 平台、Enterprise Edition 认证。这个值决定了 J2EE 认证机制是否处于激活状态。该值设置为真,用于节省开发环境中 J2EE 认证配置的时间。激活 J2EE 认证的操作超出了本文的讨论范围。
向 [InstallDir]\jazz\server 文件夹中的 teamserver.properties 添加以下的参数 (重点: 查看工具栏):
com.ibm.team.repository.ws.allow.identity.assertion = true
激活外出性同步化并设置时间跨度。
向 [InstallDir]\jazz\server 文件夹中的 teamserver.properties 属性添加以下的参数:
com.ibm.team.interop.OutgoingSyncTask.fixedDelay=30
同步化时间跨度(秒)
com.ibm.team.interop.service.outgoingSyncEnabled=true
外出性同步化是否处于激活状态
在 Jazz 服务器运行之后,您可以使用 ADMIN 作为用户 ID 与密码,来访问 https://localhost:9443/jazz/admin,并选择 Server > Advanced Properties > Item Interoperation 以编辑以下的参数(见于图 12):
Outgoing Synchronization Task Fixed Delay:(按照您的喜欢任意设置)
Outgoing Synchronization Enabled: true
向 [InstallDir]\jazz\server 文件夹中的 log4j.properties 为 Item Connector 的日志添加以下的参数:
log4j.logger.com.ibm.team.interop=DEBUG
log4j.logger.com.ibm.team.interop.service.outgoingSyncJob=
DEBUG
图 12. 使用一个 Web 浏览器来编辑参数
查看原图(大图)
创建项目区域
右击 com.example.classictalk.launches > launches > run Jazz server.launch 来选择 runAs,然后点击 run Jazz server。
点击 Window > Open Perspective > Work item。
点击 Create a Repository Connection 然后添加以下的条目:
URI: https://localhost:9443/jazz
用户 ID: ADMIN
密码: ADMIN
右击存储库连接并选择 New > user,然后创建一个用户。
点击 No 以回到“导入用户?”对话框。
输入以下的用户信息:
名字: jazzuser
用户 ID: jazzuser
邮件地址: (您所喜欢的)
客户访问许可证: Rational Team Concert - Developer
点击 Save。
删除以前的存储库连接,然后创建新的连接:
URI: https://localhost:9443/jazz
用户 ID: jazzuser
密码: password
右击新存储库连接并点击 New > Project Area 。
输入项目名和总结 :
名: JazzProject
总结 (根据您的喜好自由设定)
点击 Deploy Templates。
选择 Agile Process,然后点击 Finish。
编辑外部存储库连接与同步化规则
如果 Jazz 服务器尚未运行的话就运行它。
右击 com.example.classictalk.launches > launches > run Jazz server.launch 来选择 runAs。
选择 Window > Show view > other > Team > Synchronization Rules 来打开同步化视图,然后右击带有以下三个参数的 External Repository Connections 来选择 New > External Repository Connections (图 13):
名字: http://localhost:7080/jazz
连接信息:(根据您的喜好自由设定)
用户 ID: extuser
密码: password
项目区域: JazzProject
注意:
这些参数可以通过 External Repository Manager 中的 IExternalRepositoryConnection 对象来得到引用。
取消 Disable automatic outgoing synchronization 的选择。
在同步化视图中,右击 JazzProject,并选择 New > Synchronization Rule 来创建同步化规则(查看 图 11)
选择 Item Manager 与 External Repository Manager:
名字: My ClassicTalk Manager Rule (根据您的喜好自由设定)
项目类型:ChatThread – com.example.classictalk
项目管理器:ClassicTalkItemManager
外部存储库:http://localhost:7080/jazz
外部性管理器:ClassicTalkExternalManager
外部性类型: ChatThread – com.example.classictalk
点击 Property Mappings 中的 initialize,然后点击 Save。
分别根据 Item 或者 External Repository Manager 来设置 Item 或者 External 属性。
关闭 Jazz 服务器。
图 13. 编辑外部存储库连接
创建外部性服务器
右击 com.example.classictalk.launches > launches > create External repository.launch 来选择 runAs。
右击 com.example.classictalk.launches > launches > run External server.launch 文件来选择 runAs。
点击 Window > Open Perspective > Work item。
右击 Repository Connections,选择 New > Jazz Repository Connection,然后添加以下的条目:
URI: https://localhost:7443/jazz
用户 ID: ADMIN
密码: ADMIN
注意:
当您在访问 https://localhost:9443/jazz 时会出现一个出错信息,您只管忽略它。这是因为“创建 Jazz 服务器”处的存储库连接会试着访问已经关闭的 Jazz 服务器。
右击存储库连接来选择 New > user,然后创建一个用户:
点击 No 以回应“导入用户?”对话框。
输入用户信息:
名字: extuser
用户 ID: extuser
邮件地址 (根据您的喜好自由设定)
客户访问许可证: Rational Team Concert - Developer
点击 Save。
删除以前的存储库连接,然后再一次创建新的连接:
URI: https://localhost:7443/jazz
用户 ID: extuser
密码: password
右击新存储库连接来点击 New > Project Area。
输入项目名和总结 :
名字: ExternalProject
总结:(根据您的喜好自由设定)
点击 Deploy Templates。
选择 Agile Process,然后点击 Finish。
用例
对于项目连接器有四种典型的用例。为了运行这些用例,您需要让外部性服务器与 Jazz 服务器已经处于运行状态。让我们选择 com.example.classictalk.launches > launches > use case 1.1.launch,然后 运行 Item Connector Client.launch,来运行用例 1.1。图 14 显示了 Jazz 服务器上的 Jazz 项目,外部性对象会导入到 Jazz 服务器中,您可以使用一个 Web 浏览器并使用以下的 URL 来查看这种操作:http://localhost:9080/jazz/service /com.example.classictalk.common.IClassicTalkService/allChatThreads。
创建一个新的对象:
外部存储库中新创建的对象导入到 Jazz 存储库中。
Jazz 存储库中新创建的数据导出到外部存储库中。
更新一个已经存在的对象:
更新 Jazz 存储库中外部存储库更新数据中的数据。
更新外部存储库中 Jazz 存储库更新数据中的数据。
图 14. 带有导入外部性对象的 Jazz 服务器的 Jazz 项目
查看原图(大图)
进入-外出冲突
当您在更新一个已存在的对象时,会发生两种类型的冲突。第一种是数据得到同步化之后,当外部存储库中一个对象更新的属性发送到 Jazz 存储库中更新的相应 Jazz 项目时,会发生进入性冲突(图 15)。为了避免进入性冲突,那么外部性对象的更新属性应该在更新 Jazz 项目之前使用项目连接器客户端来进行同步化。
图 15. 一个进入性冲突外部性对象与 Jazz 项目的时间限制
查看原图(大图)
第二种冲突是外出性冲突,当数据得到同步化后,在 Jazz 存储库中某个 Jazz 存储库中的更新属性发送到外部存储库中相应的更新对象时,就会发生这种冲突(图 16)。为了避免外出性冲突,那么 Jazz 项目的更新属性应该在更新外部性对象之前使用外部存储库管理器来进行同步化。
图 16. 外出性冲突中外部性对象与 Jazz 项目的时间限制
查看原图(大图)
图 17 显示了一个进入性冲突,如 Synchronization Status 编辑器中所示。底部的两个按钮允许您去决定什么数据是有效的(图 17 中的红色圆圈)。如果您点击 Accept External State,那么 Jazz 项目的属性就会被外部性对象的属性所覆盖。如果您点击的是 Accept Item State,那么外部性对象的属性会被 Jazz 项目的属性所覆盖(图 18)。外部性冲突的解决方式与之类似(图 19)。
图 17. Synchronization Status 编辑器视图中的一个进入性冲突
查看原图(大图)
图 18. 通过选择“Accept Item State”来解决冲突
查看原图(大图)
图 19. 同步化状态编辑器的一个输出性的冲突
查看原图(大图)
总结
我们解释了怎样使用 Rational Team Concert 软件中的项目连接器来设计和创建新的连接器。外部性与 Jazz 存储库可以轻松地通过三步来完成。为了决定连接器的总体结构,第一步是选择一种开发模式,在这个开发模式下项目连接器客户端运行。为了开发 Jazz 存储库与服务,第二步是使用一个核心模型,并在 Jazz 团队服务器上的扩展点注册服务。为了权衡使用项目连接器,第三步是实现项目连接器的每一个构件。连接器可以轻松支持每一个部门系统之间的数据同步化。
更多精彩
赞助商链接