在不同的部門連接數據存儲庫,以使協作更容易
不同的部門系統之間的數據同步化是非常重要的。例如,客戶支持與開發部門通常擁有他們自己的問 題追蹤系統,這樣數據同步化就能使不同部門之間的員工可以共享信息並且同時處理相同的問題。但是, 這實現起來是十分困難的,因為它需要手動的操作。不同部門之間的員工協作性地處理相同問題的一種方 式是,聯系兩個團隊之間的儲存庫,這樣人們相互之間可以共享並追蹤儲存庫中的對象。您可以使用項目 連接器(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.examp le.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.pro cess.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 團隊服務器上的擴展點注冊服務。為了權衡使用項目連接器,第三步是實現項目連接器的每一個構 件。連接器可以輕松支持每一個部門系統之間的數據同步化。
本文配套源碼