Eclipse Rich Client Platform (RCP) 為富客戶機提供了一個極為靈活且功能豐富的框架。但是它的一個缺點是不能輕松地從其他源鏈接 RCP 應用程序中的業務對象。如果用戶能夠在其他應用程序中單擊某個鏈接並被帶到 RCP 應用程序中,並且已經打開了該鏈接所引用的業務對象,那將十分有幫助。例如,假定有一個用於處理費用申請的 RCP 應用程序。可能生成有關過期償付的報表並以電子表格的形式發布(或者是在內部網中,或者作為富文本文檔,或者作為電子郵件,或者其他形式等等)。該報表可能對每個過期申請使用一個引用編號。報表不會強制使用者把該引用編號復制並粘貼到 RCP 應用程序的搜索對話框中,而是包含啟動 RCP 應用程序的超級鏈接 URL,該 URL 將把用戶直接引導到對應的費用申請。本文將說明如何完成上述過程。
讀者應當擁有編寫 Eclipse 插件的經驗,以及 Windows® 注冊表和 TCP 協議的一般知識。雖然本文提供的示例針對 Windows 操作系統,但是在其他操作系統中也可以實現相同的功能。
解決方案概述
讓我們從整體上查看解決方案的工作原理,然後進行細分並討論如何實現各個部分。
遵循圖 1 中的流程:
假定用戶已經在桌面中打開 RCP 應用程序。
然後假定此用戶在他的常用郵件客戶機中收到了一封電子郵件,該郵件中包含鏈接 rcp://claim=25222 — 這是指向過期申請的引用 (1) 。
該用戶單擊鏈接,這將啟動該用戶的默認浏覽器以處理請求 (2)。
該浏覽器知道它不能在本機打開此 URL,因此它將查詢 Windows 注冊表以確定應當怎樣委托該操作 (3)。
注冊表將查找 rcp:// 協議並確定有一個與它綁定在一起的批處理文件 (4)。
此批處理文件將被執行 (5),這將觸發獨立的 Java™ 客戶機 (6),捕捉 URL 的參數,並因此向 RCP 應用程序中內嵌的 TCP 服務器發出一個本地 TCP 調用 (7)。
該 TCP 服務器將解析來自該 URL(即,claim=25222)的參數並請求 RCP 應用程序打開相應的業務對象 (8)。
然後將在 RCP 應用程序 UI 中向用戶呈現所請求的業務對象 (9)。
圖 1. 使用 URL 打開 RCP 應用程序的流程控制
此過程涉及到許多步驟,但幸運的是,每個步驟都相對簡單並且可以根據您的環境和應用程序進行調整。最後幾步將幫助解決如何使 RCP 應用程序獲得 Windows 焦點之類的細節。
解決方案詳解
向 Windows 中的 URL 協議注冊您的應用程序
Windows 使用注冊表中的條目識別有效的 URL 協議和確定應當向哪個應用程序發送帶有特定協議的請求。要使我們的 RCP 應用程序能夠響應 URL,必須在 HKEY_CLASSES_ROOT hive 下創建一個鍵。鍵名必須匹配正在創建的協議。在本文中,我們將使用 “rcp://” URL 協議,因此必須把鍵命名為 “rcp”。該鍵下的默認字符串值與新協議的顯示名稱相對應。在 “rcp” 鍵下,還必須創建帶有一個字符串值的 shell\open\command 鍵,該字符串值包含在收到 “rcp://” 請求時 Windows 應當打開的應用程序的路徑。
在本文中,我們將把 Windows 指向一個批處理文件。該鍵的字符串值應當在其末尾附加了 %1,這將告訴 Windows 把該 URL 查詢字符串傳遞給您的應用程序。這是我們把上下文信息(例如要打開的特定業務對象)傳遞給 RCP 應用程序的方法。
下面的 Windows 批處理文件代碼片段將在注冊表中創建相應的鍵,該鍵帶有將啟動記事本的 “rcp://” 協議。要自定義該腳本,請用需要使用的協議替換 rcp 的所有實例,並使用需要 Windows 執行的文件的完整路徑替換 C:\Windows\Notepad.exe。
清單 1. 把 URL 協議注冊到 Windows 注冊表的批處理文件代碼
reg add HKCR\rcp /ve /d "URL:RCP Protocol"
reg add HKCR\rcp /v "URL Protocol"
reg add HKCR\rcp\Shell\Open\Command /ve /d "C:\Windows\Notepad.exe %%1"
注冊過該 URL 協議後,您可以通過在浏覽器的地址欄中鍵入 rcp:// 並單擊 Go 進行測試。記事本(或者選擇注冊的文件)現在應當已打開。
其他操作系統(例如 Linux®)把注冊 URL 協議的工作委托給浏覽器。
批處理文件
當您測試過我們定義的 rcp:// 協議可以按預期打開記事本後,請嘗試把 rcp:// 協議重新映射到啟動 TCP 客戶機的批處理文件。為此,需要更改 HKCR\rcp\Shell\Open\Command 注冊表條目的值。參見清單 2 以查看批處理文件如何啟動 TCP 客戶機的示例。
清單 2. 啟動 TCP 客戶機的批處理文件代碼
@echo off
set JAVA_BIN=c:\<insert your java directory here>
cd <root location of your URIClient application's package.>
%JAVA_BIN%\java.exe com.company.uri.URIClient %1
EXIT
表 1. 清單 2 的注釋
行號 注釋 2 例如,set JAVA_BIN=C:\Program Files\IBM\Java50\jre\bin 3 例如,包含 TCP 客戶機的 parent 包的目錄 4 下面將介紹 URIClient
TCP 客戶機/服務器對
如概述中所述,用戶單擊 rcp:// 超級鏈接的操作最終將觸發調用 RCP 應用程序內嵌的 TCP 服務器的 TCP 請求。此解決方案包括一些網絡編程,但幸運的是,Java API 將為我們處理 TCP 通信的細節。
在我們的示例中,TCP 服務器將在 RCP 插件的啟動代碼中被啟動並且等待來自 TCP 客戶機的調用。呼叫將在客戶機和服務器已知的端口號上從客戶機發送到其本地主機所在的服務器上。客戶機將通過 Java 套接字把 URL 請求的詳細信息傳遞給服務器。TCP 服務器隨後負責執行相應的 RCP 操作以打開業務對象。這些操作專用於特定應用程序,但是我們將在 “處理業務對象” 中處理此問題。
對於我們的演示,將使用 com.company.uri 包中的四個 Java 類。
URIClient.java 由 URL 選擇觸發的獨立 TCP 客戶機URIServer.java 和 URIServerThread.java 包含嵌入到 RCP 應用程序中的 TCP 服務器URIConstants.java 客戶機和服務器之間共享的一些常量
這些 Java 文件可以下載獲得。
TCP 服務器 — URIServer 和
URIServerThread
URIServer
負責偵聽請求。在偵聽到請求時,它將生成處理與 TCP 客戶機之間的連接的 URIServerThread 線程。
URIServer 可以在插件的初始化類中啟動。每個 RCP 插件都有一個可以擴展 org.eclipse.ui.plugin.AbstractUIPlugin 的相關的啟動類。該類的 start 方法可以初始化 URIServer。
RCP 應用程序將生成 URIServer,方法是調用新的 URIServer().start(),在它自己的線程中啟動服務器並防止 RCP 應用程序由於等待 TCP 請求而被阻塞。
URIServerThread 將處理打開業務應用程序的邏輯。該過程將在 “處理業務對象” 中詳細討論。
注意,URIServer 和 URIServerThread 都擴展 java.lang.Thread。
清單 3. 樣例插件啟動方法
/**
* This method is called upon plug-in activation, starts up URIServer
*/
public void start(BundleContext context) throws Exception {
super.start(context);
new URIServer().start();
}
TCP 客戶機應用程序 — URIClient
TCP 客戶機被部署為不會被 RCP 應用程序引用的獨立 Java 文件。在由與 rcp:// 協議相關的批處理文件觸發後將執行 URIClient.main()。
構造函數將把 URL 參數傳遞給 tcpConnect 方法。此方法將通過 URIConstants 在客戶機和服務器已知的主機編號上構造一個套接字,該套接字以本地主機上的 TCP 服務器為目標。
提示:考慮一下,雖然 Java 客戶機沒有被 RCP 插件引用,但是在插件內部署 Java 客戶機將允許通過 Eclipse 更新程序框架更新代碼。如果它駐留在插件外部,那麼部署代碼更新將變得更加困難。
處理 TCP 請求
TCP 客戶機與服務器之間如何通信?假定 RCP 應用程序和 TCP 服務器已在運行。正如我們已經說明的那樣,注冊表和批處理文件將處理 TCP 客戶機的啟動。URIClient 將移除參數(即,claim=25222)並將嘗試在客戶機和服務器已知的端口號上建立與 URIServer 之間的套接字連接。
現在連接已建立,服務器將通過發送常量文件中存儲的(客戶機和服務器已知的)字符來確認客戶機並請求進一步指令。在示例中,服務器將發送字符 sendAction。當客戶機收到這條指令後,它將通過向服務器發送包含所需業務對象的信息的名稱-值對 — 在本例中為 “claim=25222” — 進行響應。此時,客戶機和服務器將終止它們的連接。當然,只要服務器已知要接收的內容,就可以自定義要發送的特定名稱-值對。
現在該由 TCP 服務器解析實參並啟動相應的操作。
在理想情況下,上述 TCP 客戶機和服務器都可以由 RCP 應用程序啟動,因此不需要使用獨立的 Java 應用程序並減少需要部署的工件數。只要使用 URL 觸發了 RCP 應用程序,就會使用應用程序的 .exe 或批處理文件啟動程序按照常規啟動應用程序。但是,早在應用程序的初始化階段,應用程序就將檢查它的實例是否已在運行。如果應用程序已在運行,則 TCP 客戶機將被初始化並把 URL 實參傳遞給在運行的 RCP 應用程序中等待收到此類通知的嵌入式 TCP 服務器。服務器將打開各自的業務對象,如上面示例所示。但是,如果 RCP 應用程序尚未運行,它將在此時繼續處於打開狀態,並且它將使用 URL 實參作為 RCP 程序實參以打開相應的業務對象。
如果您不能訪問應用程序的早期啟動代碼(例如如果您要在現有應用程序之上部署插件),則可能無法在 RCP 應用程序中嵌入 TCP 客戶機。鑒於此原因,本文介紹了更靈活的獨立 TCP 客戶機方法。
處理業務對象
如前所述,URIServerThread 將處理打開業務對象的事務。為了簡單起見,在這裡提供的清單中,代碼將啟動一個顯示 URL 中提供的業務對象 ID 的對話框。在實際應用程序中,我們將執行的某些操作要有趣得多,如調用 Action,它將查找並打開提供了標識符的業務對象。
Eclipse 有一個用於管理用戶界面的線程,而且某些 SWT API 方法只能通過該線程調用。由於 URIServerThread 正在作為它自己的線程運行,因此我們需要考慮到這個限制。為了使衍生(spawned)線程能夠回調 Eclipse 工作台,它必須使用 Display 對象中的方法才能獲得對 UI 線程的訪問。如果不這樣做,將在運行時拋出 ERROR_THREAD_INVALID_ACCESS 錯誤。
最後幾步
默認情況下,單擊 rcp:// 鏈接將打開 RCP 應用程序中的業務對象,但是 RCP 應用程序可以隱藏在其他應用程序背後,例如包含 rcp:// 鏈接的應用程序。為 RCP 應用程序獲得焦點十分重要。在 URIServerThread 清單中,注意負責使 RCP 應用程序獲得焦點的下列語句:
shell.setActive();
shell.setMaximized(true);
shell.setVisible(true);
shell.setFocus();
我們還沒有介紹錯誤處理,但是在某些情況下這個功能非常必要。首先,TCP 通信附帶有需要進行恰當處理的 try/catch 塊。此外,還需要確保能夠處理各種難看的 URL,尤其要防止受到故意攻擊。
您可能已經注意到,把 rcp:// 協議的解析委托給 Windows 默認浏覽器的一個不好的副作用是使浏覽器窗口保持打開狀態。雖然不太理想,但是這是可以接受的問題。我們期待您可以提出解決這個問題的方法,尤其是在 RCP 應用程序被廣泛分布並且無法調整 Firefox 的用戶設置的環境中。
結束語
上述過程詳細介紹了使用 URL 打開 RCP 應用程序中的一個業務對象的框架。不過,可以通過一些有趣的方式擴展這個想法。例如,可以在 URL 中包括多個名稱-值對以執行更復雜的操作。考慮一個訂單錄入應用程序,其中 URL 可用於給訂單預填充某些產品。遵循這些步驟,可以通過單擊鏈接從任意應用程序訪問 RCP 應用程序中定義的任何操作。
本文配套源碼