藉助 Project Zero 在 Web 2.0 領域建立 RESTful IBM Lotus Domino 應用程式

genusBIT發表於2009-04-07
用 Project Zero 為 Lotus Domino 提供 REST 支援

大多數 Lotus Domino 開發人員都熟悉採用各種語言(LotusScript. 最為著名)編寫的 Lotus Domino 代理。基本上,RESTful 應用程式(具有之前介紹的 REST 原則)都可通過 Web 訪問。此外,可以通過命名資源對應用程式定址。使用 LotusScript. 或簡單代理等約定來例項化應用程式脫離了 REST 的基礎,即命名資源。

Lotus Domino 代理需使用類似於 http://domino.ibm.com/database.nsf/AgentName?OpenAgent 的 URL 通過 Web 呼叫,如圖 1 所示。這絕非我們希望實現的命名資源,並且任何資訊都可作為引數傳遞。這種實現或等價的 servlet 實現與基於動詞的實現同義,而不同與 RESTful 特性。舉例來說,代理或 servlet 可以接受 ?action=create&user=vstaub 這樣的引數;而另一方面,RESTful 方式只操作 POST 方法中的資料。


圖 1. Lotus Domino 代理的命名資源
Lotus Domino 代理的命名資源

Lotus Domino Security API (DSAPI) 過濾器可能是一個比較合適的實現。DSAPI 過濾器位於 Lotus Domino HTTP 棧,並且可以在滿足 URL 中的條件時呼叫。熟悉 IBM Lotus Quickr 人的都知道,所有 URL containing /quickr 都由 Lotus Quickr 伺服器的 DSAPI 過濾器處理。使用 DSAPI 過濾器,實現將有效地嵌入到 Lotus Domino 伺服器的 HTTP 棧中。遺憾的是,建立 DSAPI 對許多人來說都是一項令人畏懼的任務,因為它要求同時掌握 DSAPI 工具包和 C 程式語言。如果 RESTful 應用程式旨在促進 /database/document_unid 或 /database/view_name 命名資源約定,那麼它可以與已有 Lotus Domino Web 伺服器無縫操作,從而在預先定義的條件下呼叫 RESTful 操作。請參見圖 2。根據讀者的背景,此實現可能過於技術化或費時。


圖 2. Lotus Domino DSAPI 過濾器的命名資源
Lotus Domino DSAPI 過濾器的命名資源

最後,考慮 Java 語言。自 Lotus Domino 6 以來,我們能夠使用 Java 訪問 Lotus Domino 資源,如資料庫、檢視或支援 Java 的應用程式中的文件。在 Java 訪問中結合 servlet 便利 Web 請求,並且您可通過解決方案在網際網路上與 Lotus Domino 資源互動。但如前所述,servlet 實現並非完全是 RESTful 的。通過使用日益流行的 Project Zero 專案,應用程式的支援 Web 的 RESTful 功能得到了極大的改進。


圖 3. Project Zero 應用程式的命名資源
Project Zero 應用程式的命名資源 

識別資源

在 Lotus Domino Directory 中,我們可以識別以下資源:

  • Employee:個人記錄(等價於 Lotus Notes 文件)。
  • Employees:一組員工(等價於一個 Lotus Notes 文件或一個 Lotus Notes 檢視)。

識別資源的另一個重要方面是理解它的定址方式。作為預設端點的的一部分,員工是可訪問的:http://[hostname]/employees。

可以使用簡短名稱或電子郵件等惟一識別符號來訪問員工:http://[hostname]/employees/[uid]。

資源表示

Lotus Domino Employees 應用程式和客戶機或瀏覽器之間的資料交換是使用 JavaScript. Object Notation (JSON) 完成的。使用 JSON 的原因在於已有的 Project Zero 支援以及它在 JavaScript. 應用程式中提供的輕量級且易於使用的資料格式。基本上,JSON 用於表示資料,並且 Lotus Domino Directory 中的使用者可以作為 JSON 物件出現,如清單 1 所示。


清單 1. 員工的 JSON 表示

{
      "FirstName": "Van",
      "FullName": "CN=Van Staub\/O=IBM",
      "HTTPPassword": "(355E98E7C7B59BD810ED845AD0FD2FC4)",
      "InternetAddress": "vstaub_cnnew1@ibm.com",
      "LastName": "Staub",
      "OfficeCity": "Atlanta",
      "OfficePhoneNumber": "(123) 456-7890",
      "OfficeState": "Ga",
      "OfficeStreetAddress": "4111 Northside Pkwy",
      "ShortName": "vstaub"
}

本文將在稍後介紹,在 JavaScript. 客戶機中使用該資料的能力是即時的。JavaScript. 客戶機應用程式將 JSON 作為物件接收,因此需使用點表示(dot notation)語法引用它的值。舉例來說,要訪問使用者的姓,需要使用語法 object.LastName。此語法將降低任何解析需求,並便利對資料的直接訪問。有關 JSON 的更多資訊,請參閱 JSON 網站

對映方法

表 1 演示了用於實現各用例的 HTTP 方法。


表 1. 用例 HTTP 方法型別

用例 資源 HTTP 方法
檢視員工 員工 GET
檢視員工 員工 GET
建立員工 員工 POST
更新員工 員工 PUT
刪除員工 員工 DELETE

響應程式碼

表 2 詳細給出了操作的 HTTP 響應以及錯誤情況下的服務響應。針對 GET、POST、PUT 和 DELETE 的任何成功請求都將獲得一個 200 OK 響應,但錯誤條件將觸發相應的 HTTP 響應。


表 2. 用例 HTTP 程式碼

用例 失敗
檢視員工 401(未授權)
404(無法找到員工)
檢視員工 401(未授權)
404(無法找到 UID)
建立員工 401(未授權)
303(指定 UID 已存在)
更新員工 401(未授權)
403(禁止)
刪除員工 401(未授權)
403(禁止)

使用 Project Zero 設計

Project Zero 允許開發人員快速建立支援 Web 的應用程式,從而可以專注於 REST。簡言之,只需結合少許 Java 程式設計和 Project Zero,您便可建立和部署 RESTful Lotus Domino 應用程式或服務。Lotus Domino Employees 應用程式使用已有的 Project Zero Employees 演示應用程式解決許多客戶端邏輯。這允許讀者實現 Project Zero 的多功能性,並且能結合新的特定於 Lotus Domino 的服務端邏輯。

Project Zero 讓您能夠專注於應用程式的業務邏輯,而不用為處理傳入 Web 請求、解析 URL 和處理各種方法大費周折。可以通過兩種方法實現 Project Zero 應用程式:PHP 和 Groovy。Groovy 實現巧妙地補充了使用 Java 訪問 Lotus Domino 資源的功能。從本質上說,通過在 Groovy 程式碼中使用 Java,或呼叫已有 Java 庫中的函式,我們可以編寫直接訪問 Lotus Domino 資源的 Groovy 應用程式。選擇在 Groovy 實現,我們可以利用一款 Eclipse 外掛來加速應用程式的建立和測試。再說一次,這允許您輕鬆地完成建立、部署和測試流程。

RESTful Lotus Domino 應用程式和大多數 Project Zero 應用程式的核心都是 RESTful 設計。Project Zero 允許您建立 Groovy 檔案(它是一個處理程式)來提供命名資源。然後,客戶機可以傳遞一個虛擬命名資源給處理程式。舉例來說,您建立了一個名稱為 employees.groovy 的處理程式指令碼。然後,使用者可通過 URL 訪問命名資源 /employees。然後,針對 /employees 的請求可以將目錄的整個內容提供給客戶機。/employees/username 就是一個類似的 URL 請求。使用者名稱成員是一個虛擬資源。employees.groovy 允許對使用者名稱的值進行訪問,而實現又恰當給出了控制程式碼的詳細資訊。請注意虛擬命名資源和 Groovy 處理程式的檔名稱之間的關係。處理程式的實現細節模擬了 REST 架???。建立、更新、檢索和刪除(CRUD)等操作通過 Groovy 處理程式中的相應函式得以簡化,如表 3 所示。


表 3. 定義 Groovy 處理程式

方法型別 URL Groovy 處理程式函式
GET /employees onList()
POST /employees onCreate()
GET /employees/[uid] onRetrieve()
PUT /employees/[uid] onUpdate()
DELETE /employees/[uid] onDelete()

雖然我們所討論的多數資訊都專注於伺服器端,但 Project Zero 也簡化了客戶端應用程式的建立。流行的 Ajax 框架 Dojo 封裝在一個增件庫中。因此,您可以建立傳入服務請求和支援 Ajax 的富 Web 應用程式。

如果決定使用 Project Zero,那麼您將體驗激動人心的一流的開發應用程式和架構,並能將 Eclipse 中的一切內容整合到更加開放的 Web 2.0 中。

此外,Project Zero 支援將物件隱式轉換為 JavaScript. 表示,即 JSON。我們可以將 Java 物件作為響應輕易提供給客戶機,然後該物件將自動轉換為 JSON 表示。我們可以輕易利用 Project Zero 平臺的便利功能。

Lotus Domino Employees 應用程式由分層的實現策略組成:一個客戶機應用程式、Project Zero 應用程式、一組 Java 類和 Lotus Domino 伺服器本身,如圖 4 所示。


圖 4. Lotus Domino 員工應用程式模型
Lotus Domino 員工應用程式模型

客戶機(支援 Web 的 JavaScript. 應用程式或自定義服務)將連線到 Project Zero 應用程式。Project Zero 負責與客戶機的任何互動,其任務涉及處理傳入請求、查詢命名資源和傳送適當響應。Project Zero 消除了處理傳入 Web 請求所需的開銷。通過使用內建的 Project Zero 函式來處理 CRUD 操作,我們不必與往常一樣在 Web 伺服器應用程式中編寫大量程式碼。

Java 類負責與 Lotus Domino 伺服器互動。從本質上說,Java 類就是 Lotus Domino Employees 應用程式的邏輯,其實就是一組幫助函式。這些幫助函式可以利用 Lotus Domino Java API 和 notes.jar 檔案與儲存在 Lotus Domino Directory 中的資料進行互動。

您可以毫不費力地編寫程式碼讓 Groovy 類與 Lotus Domino 互動,但我們推薦將應用程式邏輯作為幫助函式分離到 Java 類中。由於 Java 類與 Project Zero 是鬆散耦合的,因此可以在其他場景中重用應用程式 —— 比如說富客戶機或 servlet 中。同樣,使用 Java 編寫的訪問 Lotus Domino 伺服器的應用程式可以輕易地整合到 Project Zero 平臺中,從而提供支援 Web 的交付。

應用程式行為

我們使用之前介紹的策略來考慮任何 Lotus Domino 應用程式的操作流。假設某客戶機操作向 Project Zero 應用程式發起一個非同步呼叫。Project Zero 僅能接收請求和呼叫必要的 Java 程式碼來促進所需的操作。Java 類與 Lotus Domino 伺服器上的資料互動。然後,可以將 Java 物件序列化併發回請求者。

Lotus Domino Employees 應用程式由一些非常簡單的互動構成,即根據對使用者的傳入操作與 Lotus Domino 進行互動:

  1. 檢索 Person 文件中的資訊。
  2. 刪除 Person 文件。
  3. 註冊新使用者。
  4. 更新 Person 文件。

表 4 詳細說明了 Lotus Domino 伺服器的行為。


表 4. 定義 Lotus Domino 行為

方法型別 URL Groovy 處理程式函式 Lotus Domino 行為
GET /employees onList() 檢索 Person 文件中的資訊
POST /employees onCreate() 註冊新使用者
GET /employees /[uid] onRetrieve() 檢索 Person 文件中的資訊
PUT /employees/[uid] onUpdate() 更新 Person 文件
DELETE /employees/[uid] onDelete() 刪除 Person 文件

利用 Lotus Domino 安全性

為解決安全問題,我們將使用已有的 Domino 機制:多伺服器單點登入(SSO)。Web 使用者成功通過 Lotus Domino 的身份驗證之後,Lotus Domino 伺服器將以 cookie 的形式發起一個加密令牌給瀏覽器。cookie 中的值可用於身份驗證目的。在這種方式下,我們重複使用 cookie 中的加密令牌連線 Project Zero 應用程式中的 Lotus Domino。這對於保護到 Project Zero Web 應用程式的訪問具有雙重效果:

  • 它確保只有有效使用者才可以使用應用程式(身份驗證)。
  • 它確保使用者訪問 Lotus Domino 時只可獲得自己許可權範圍內的內容(授權)。

Project Zero 最近提供了對新的 Lotus Domino LTPAToken 的支援。通過結合 cookie 中的 LTPAToken 值,我們可以根據使用者的身份來建立 Lotus Notes 會話。與 Lotus Domino 的所有互動都將從 Lotus Notes 會話的建立開始。

要利用 LTPAToken,管理員可以將伺服器配置為單點登入。可以在 IBM Lotus Domino Administrator 或 Lotus Notes 客戶機中完成此操作,方法是??建一個 Web SSO Configuration 文件,如圖 5 所示。


圖 5. 建立 Lotus Domino Web SSO Configuration 文件
建立 Lotus Domino Web SSO Configuration 文件

接下來,配置 Lotus Domino 伺服器在 Server 文件中使用 Web SSO Configuration 文件,方法是使用剛才建立的 Web SSO 配置將 Session 身份驗證方法更新為 Mutliple Servers (SSO)。參見圖 6。


圖 6. 為 Lotus Domino 配置 SSO
為 Lotus Domino 配置 SSO

注意,Lotus Domino 生成的 LTPAToken 僅對於 Web SSO Configuration 文件中列出的 DNS 域有效。此外,LTPAToken 只傳遞給使用 Web SSO Configuration 文件中的域訪問的伺服器。從本質上說,這表示您希望使用與訪問 Lotus Domino 相同的 URL 來訪問 Lotus Domino Employees Web 應用程式。不要使用本地主機名或 IP 地址。

要獲取 LTPAToken,登入到 Lotus Domino 伺服器,如圖 7 所示。


圖 7. 在瀏覽器中建立 LTPAToken
在瀏覽器中建立 LTPAToken

在瀏覽器的位址列輸入以下訊息,驗證您的 LTPAToken:

javascript.:alert(document.cookie)

請參見圖 8。


圖 8. 確認您擁有 LTPAToken
確認您擁有 LTPAToken

使用 Lotus Notes 會話物件

使用 Java 類與 Lotus Domino 的所有互動都源自會話物件。通過會話物件,程式設計師可以發起各種強大的操作,如開啟資料庫、傳送控制檯命令、檢索 Notes.ini 名稱/值對和註冊新使用者。在 Lotus Domino Employees 應用程式中,會話物件使用從瀏覽器傳遞到應用程式的 LTPAToken 構建。這種流程允許應用程式只執行登入使用者許可權範圍內的操作。

獲取 Lotus Notes 會話物件的詳細資訊也是值得一提的。根據開發環境以及所實現應用程式的目的,物件的實現形式也有所不同。Lotus Domino 設計者程式設計指南,第 3 卷 Java CORBA 類 概述了獲取 Lotus Notes 會話物件之間的差異。

可以使用以下兩種方式建立 Lotus Notes 會話物件:使用遠端過程呼叫(Remote Procedure Call,RPC)或使用 Internet 物件請求代理間協議(Internet Inter-ORB Protocol,IIOP)。RPC 呼叫要求將 Lotus Notes 客戶機、IBM Lotus Domino Designer 或 Lotus Domino 伺服器安裝在執行的應用程式的本地。Lotus Domino Employees 應用程式就是這種形式的一個例子,它將在執行中的 Lotus Domino 伺服器上建立一個本地會話。這種選擇旨在最大限度地提升效能,以提供快速響應的 Web 介面。

為 Project Zero 配置 RPC 會話

要執行任何 Lotus Notes Java 程式設計,必須在專案的類路徑中包含合適的 Lotus Notes Java 庫。在使用 RPC 呼叫時,需在應用程式的 Java 編譯路徑中包括 notes.jar 檔案。第一次執行 Project Zero 應用程式時,可能會出現如圖 9 所示的錯誤訊息。


圖 9. 缺少 Lotus Notes Java 庫
缺少 Lotus Notes Java 庫

從本質上說,此錯誤表示無法找到 nlsxbe 庫的本機呼叫,您必須將它新增到 Project Zero 應用程式的 java.library.path 變數中。notes.jar 庫向 Lotus Domino 程式目錄中的函式發起本機呼叫。反過來,本機函式又呼叫其他本機函式。如果您在 Eclipse 中有過 Lotus Notes Java 程式設計經驗,那麼可能知道最簡單的解決方案就是將 Lotus Domino 或 Lotus Notes 程式目錄新增到系統的 PATH 環境變數中。在 Project Zero 中,需求則稍有不同。要解決之前的錯誤,將 Lotus Domino 或 Lotus Notes 程式目錄的路徑新增到 Project Zero 應用程式的執行時配置中,如圖 10 和 11 所示。

在 Run 對話方塊中選擇 Arguments 選項卡,如圖 10 所示。然後,在 VM 引數窗格中,將 Lotus Domino 或 Lotus Notes 安裝的路徑附加到 java.library.path 引數中:

-Djava.library.path="${ZERO.NATIVES};C:/Lotus/Domino"

記住使用分號將任何已有引數與最新新增的 Lotus Domino 或 Lotus Notes 安裝路徑分隔開。


圖 10. 配置 Lotus Domino VM 引數
配置 Lotus Domino VM 引數

在 Run 對話方塊中選擇 Environment 選項卡,如圖 11 所示。編輯列表中的 PATH 變數,然後在出現的 Edit Environment Variable 對話方塊中附加 Lotus Domino 或 Lotus Notes 安裝的路徑。

再說一次,記住使用分號將任何已有引數與最新新增的 Lotus Domino 或 Lotus Notes 安裝路徑分隔開。


圖 11. 配置 Lotus Domino PATH 變數值
配置 Lotus Domino PATH 變數值

建立 RPC 會話

假設使用者已經通過了 Lotus Domino 伺服器的身份驗證,Groovy 檔案會將 LTPAToken 傳遞給 Java SessionHelper 類。

Session session= SessionHelper.getInstance()
.getSession(zget("/request/cookies/in").get("LtpaToken"));

而 SessionHelper 類將建立 Lotus Notes 會話物件,如清單 2 所示。


清單 2. Lotus Notes 會話物件的建立

// verify that the Zero HTTP thread has been properly
// initialized for Notes RPC communication
Long threadId= new Long(NotesThread.currentThread().getId());
		
System.out.println("Zero thread : " + threadId + " using LtpaToken : " + ltpaToken);
		
…
// already created the NotesThread?
if(sessions.containsKey(threadId)){
	// not really an error
	System.err.println("NotesThread already initialized");
} else {
	System.out.println("Initializing NotesThread");
		
	// initialize the NotesThread
	NotesThread.sinitThread();
}
			
// create a Notes Session
// use null to indicate an RPC connection to the
// server on which the Project Zero application runs
Session session= NotesFactory.createSession(null, ltpaToken);
		
System.out.println("Storing NotesSession for Zero thread : " + threadId);
		
// cache the session for future use
sessions.put(threadId, session);
		
System.out.println("NotesSession created for user " + session.getUserName());
		
return session;

…

在發起 RPC 呼叫時,必須在初始化 NotesThread 物件之後才能利用該會話。此步驟並非 IIOP 實現所必需的。您還必須在處理結束時呼叫 stermThread 函式,以避免異常終結對執行中的 Lotus Domino 伺服器造成影響。獲得會話物件之後,可以使用由 LTPAToken 定義的授權給使用者的訪問控制級別在它與 Lotus Domino 伺服器之間實現互動。

大多數時間,我們所投資的??目都涉及到如何處理 Lotus NotesThread 物件。Project Zero 應用程式預設使用三個單獨的執行緒以輪叫的形式處理傳入 HTTP 請求。在與 Lotus Domino 互動之前,NotesThread 類必須正確完成各個請求的初始化任務。初始化失敗通常將導致清單 3 所示的錯誤。


清單 3. 未正確初始化 NotesThread 的錯誤

Caused by: java.lang.UnsatisfiedLinkError: NCreateSessionWithTokenOrName
	at lotus.domino.local.Session.NCreateSessionWithTokenOrName(Native Method)
	at lotus.domino.local.Session.createSessionWithTokenOrName(Unknown Source)
	at lotus.domino.NotesFactory.createSessionC(Unknown Source)
	at lotus.domino.NotesFactory.createSession(Unknown Source)
	at com.ibm.devworks.SessionHelper.createSession(SessionHelper.java:24)
	at com.ibm.devworks.SessionHelper.getSession(SessionHelper.java:60)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
	at java.lang.reflect.Method.invoke(Unknown Source)
	at org.codehaus.groovy.runtime.metaclass.ReflectionMetaMethod.invoke
	(ReflectionMetaMethod.java:56)
	at org.codehaus.groovy.runtime.MetaClassHelper.doMethodInvoke
	(MetaClassHelper.java:599)
	at groovy.lang.MetaClassImpl.invokeStaticMethod(MetaClassImpl.java:1030)
	at org.codehaus.groovy.runtime.Invoker.invokeMethod(Invoker.java:69)
	at org.codehaus.groovy.runtime.InvokerHelper.invokeMethod(InvokerHelper.java:66)
	at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.invokeMethodN
	(ScriptBytecodeAdapter.java:165)
	at employees.onList(employees.groovy:23)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
	at java.lang.reflect.Method.invoke(Unknown Source)
	at org.codehaus.groovy.runtime.metaclass.ReflectionMetaMethod.invoke
	(ReflectionMetaMethod

UnsatisfiedLinkError 表示 NotesThread 未正確初始化。

完成初始化之後,HTTP 執行緒必須得到正確處理。NotesThread.stermThread() 函式為執行緒的處理任務提供了方便。我們的意圖是在 Zero 中接收到傳入請求時初始化 Zero HTTP 執行緒,隨後處理 NotesThread 並呈現輸出。請參見清單 4。


清單 4. 處理會話物件和 NotesThread

def onList() {

    try {
        
        // create a Session from the SessionHelper
        // all Domino operations require a Session
        Session session= SessionHelper.getInstance()
        .getSession(zget("/request/cookies/in").get("LtpaToken"));
        
        …

    } catch (NotesException e) {

        request.status = HttpURLConnection.HTTP_INTERNAL_ERROR
          request.error.message = e.getMessage()
        request.view = "error"

    }
    
    // properly dispose of any Domino objects by using the
    // SessionHelper
    SessionHelper.getInstance().shutdown();
    
    // finally render the content to the client
    render()

}

/**
* Properly dispose of any remaining Sessions or NotesThreads.
*
*/
public void shutdown() {
    
    System.out.println("Closing NotesSession ...");
    
    // get the Zero thread requesting Notes shutdown
    Long threadId= new Long(NotesThread.currentThread().getId());
    
    try {
        Session session= (Session)sessions.get(threadId);
    
        System.out.println("Closing NotesSession for user " + session.getUserName());
        
        session.recycle();
        
        System.out.println("NotesSession closed");
        
    } catch (NotesException e) {
        e.printStackTrace();
    } finally {
        
        // terminate the NotesThread properly
        NotesThread.stermThread();
        
        // remove a the so a NotesThread can be initialized
        // on the Zero thread
        sessions.remove(threadId);
        
        System.out.println("Removed NoteSession for Zero thread : " + threadId);

    }
}

此模型已被證實極具挑戰性。有時,當其他請求造成 JVM 崩潰時,NotesThreads 會被正常終止。清單 5 所示的系統崩潰將出現在程式設計過程中。


清單 5. JVM 崩潰錯誤內容

#
# An unexpected error has been detected by HotSpot Virtual Machine:
#
#  EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x60001457, pid=2660, tid=6448
#
# Java VM: Java HotSpot(TM) Client VM (1.5.0_12-b04 mixed mode)
# Problematic frame.
# C  [nnotes.dll+0x1457]
#

出現此類錯誤的原因是對已初始化的 Zero HTTP 執行緒進行 NotesThread 初始化操作,或嘗試終止已初始化 Zero HTTP 執行緒的 NotesThread 任務。在正確初始化和處理 NotesThreads 以及離開初始化執行緒之間,我們陷入了兩難的境地,但可能會造成 Lotus Domino 伺服器的不穩定。基於本文的演示目的,我們選擇了後者。可以採取一些相應的措施來解決這種設計問題。您可以擴充套件 NotesThread 類並將自己的邏輯封裝在其中。完成 run() 或 runNotes() 方法後,NotesThread 將被適當處理,但您可能需要實現一些執行緒來完成非常具體的操作。我們選擇的設計方式相當有趣,您可以在 Project Zero 論壇 上發表問題尋求 Project Zero 小組和其他社群使用者的幫助。

此處的目的並非阻止您探索 Project Zero 和 Lotus Domino,而是將我們的經驗告訴您。本文中所描述的問題可能都有解決方法或已知的技術限制。

還需注意,應用程式將建立一個本地 Notes 會話,但它在訪問本地伺服器或 Lotus Notes 客戶機上的資料時不受任何限制。通過在 getDatabase() 函式中指定 Lotus Domino 伺服器名稱,您可以更新程式碼訪問遠端 Lotus Domino 伺服器上的資料庫。

有關更多詳細資訊,請參閱本文稍後的 “使用 Lotus Domino Employees 應用程式” 一節。

建立 IIOP 會話

雖然並未在 Lotus Domino Employees 應用程式中實現,但 IIOP 是 RPC 格式的替代之選。當 Project Zero 應用程式並未執行在擁有本地 Lotus Domino 伺服器或 Lotus Notes 客戶機安裝的伺服器中時,IIOP 的作用就會突顯出來。放棄本地 Lotus Domino 或 Lotus Notes 安裝需求的代價是需要在 Lotus Domino 伺服器上執行 DIIOP 任務。DIIOP 任務將促進遠端連線訪問資料庫、檢視和文件等內容。

如清單 6 所示,RPC 程式碼在更新後可便利 IIOP 與 Lotus Domino 的連線。


清單 6. IIOP 連線程式碼的差異

private static Session createSession(String server, String token) {
System.out.println("Creating Session");

	try {
		// server should be a DNS address or IP		
		Session s = NotesFactory.createSession(server, token);

		System.out.println("Creation successful");

		return s;

	} catch (NotesException e) {
		System.err.println("Creation failed");

		e.printStackTrace();

		return null;
	}
}

注意,惟一的實際變化在於移除了 NotesThread 處理。當您使用 IIOP 時,NotesThread 會話將不再需要。

IIOP 在伺服器上引入了新配置考慮:安全性、訪問 Lotus Domino 伺服器上的 diiop_ior.txt 檔案,以及各種埠配置。您還應在確保在 Lotus Domino Server 文件的 Internet Protocols - HTTP 選項卡上選中 “Allow HTTP clients to browse databases” 選項。


圖 12. 配置 DIIOP
配置 DIIOP

此設定允許會話獲得伺服器上的資料庫列表??有關 DIIOP 的更多資訊,請參閱 Lotus Domino Designer Programming Guide 的 NotesFactory 一節。

訪問 Lotus Domino 資料

現在,我們已經使用 Notes 會話建立了 Lotus Domino Employees 應用程式的行為和幫助函式,並開始查詢或修改 Lotus Domino 目錄。由於 Lotus Domino Employees 應用程式的專注點與使用者相關,因此幫助函式將操作 Lotus Domino Person 文件。

舉例來說,對 resource /employees 的請求將檢索目錄中的所有聯絡方式。此操作等價於獲取使用 names.nsf 資料庫中的 Person 表單建立的所有文件。清單 7 中的程式碼演示了此行為。


清單 7. 使用 Lotus Notes 文件集合獲取所有員工

public Employees getAllPeople() {
		// conduct a search for people created with the Form. People
		// this is true for all users
		DocumentCollection dc= searchNab("Form=\"Person\"");
		
		// convert the collection into Employees
		Employees contacts= getPeople(dc);
		
		// recycle the document collection
		try {
			dc.recycle();
		} catch (NotesException e) {
			e.printStackTrace();
		}
		
		return contacts;
	}


注意,getAllPeople 函式將傳送一個搜尋公式給 NAB,以獲取所有的 Person 文件,這等同於搜尋可以操作 Lotus Domino 資料庫的客戶機使用者。您可以在目錄中開啟 People 檢視並在其中返回 DocumentCollection,而不用開啟目錄並執行搜尋。這種方法允許您更好地利用已有的 Lotus Domino 結構來減少程式碼和效能開銷。它還降低了全文索引目錄的需求。

Lotus Domino 資料表示

在之前獲取所有員工的檢索操作時,所返回的結構是 Notes 物件 DocumentCollection。DocumentCollection 恰如其名;它由一系列 Document 物件組成。Document 物件對應於目錄中的實際 Person 文件。為了使資料表示流程清晰可見,我們首先將文件中需要的值複製到新的 Employee 物件中,如清單 8 所示。


清單 8. Document 到 Employee 物件的轉換

public Employee(Document doc){
data= new HashMap();
		
	try {
		// verify that the document is a person doc
		if(doc != null &&
			doc.hasItem("Form") &&
			doc.getItemValueString("Form").
				equalsIgnoreCase("Person")){
					
			// pull data as Strings from the person doc
			for(int i= 0; i < Employee.FIELDS.length; i++){
				data.put(Employee.FIELDS[i], doc.getItemValueString(
							Employee.FIELDS[i]));
			}

			notesUrl= doc.getNotesURL();
			
}
	} catch (NotesException e) {
		e.printStackTrace();
		
		notesUrl= "";
	}
}

程式碼將迭代在 Employee 物件中指定的預先定義的欄位名稱(如清單 9 所示),並將 Person 文件中相應欄位的值複製到 Employee 物件的 HashMap 中。


清單 9. 迭代欄位名稱

public static final String NAME= "FullName";
public static final String UID= "ShortName";
public static final String FIRSTNAME= "FirstName";
public static final String LASTNAME= "LastName";
public static final String EMAIL= "InternetAddress";
public static final String PHONE= "OfficePhoneNumber";
public static final String PASSWORD= "HTTPPassword";
public static final String ADDRESS= "OfficeStreetAddress";
public static final String CITY= "OfficeCity";
public static final String STATE= "OfficeState";
public static final String ZIP= "OfficeState";

從本質上說,我們在 Employee 物件中模擬了 Document 物件中的名稱/值對。在大型系統中,複製實際資料將阻止 Lotus Domino 物件在 JSON 轉換流程開始之前被回收或被刪除,這有時會造成錯誤。

將 Lotus Domino 資料轉換為 JSON

當 Employee 物件中填充了使用者資料,並且需要將它轉換為 JSON 並將該 JSON 傳送給客戶機時,請考慮應用程式的狀態。該流程發生在使用請求 employees/[uid] 檢索使用者時。再一次,Project Zero 極大地簡化了這一操作,如清單 10 所示。


清單 10. Project Zero 提供對 JSON 轉換的內在支援

// retrieve the Employees Collection by creating a
// NabHelper and requesting all the people
Employees employees= new NabHelper(session).getAllPeople();
				
if(employees != null){
	
// automatically serialize the object to JSON using
	// Project Zero's custom converter
	request.view='JSON'
	request.json.output= employees; 
}

render()

以上程式碼的關鍵步驟在於將實際物件作為 JSON 輸出提供給客戶機。對於簡單物件,Project Zero 會使用公有 get 函式自動將物件轉換為 JSON。對於比較複雜的物件,您應該使用自定義轉換程式定義轉換流程。

使用自定義轉換程式轉換 Lotus Domino 資料

為了使用 request.json.output = object 語法簡單地完成轉換,我們建立了一個自定義轉換程式。對於給定物件,Project Zero 將例項化適當的自定義轉換程式將物件轉換為 JSON。為實現此目的,我們建立了一個類來實現 IConverter(Milestone 1)或 Converter(Milestone 2)介面。清單 11 給出了這個類的程式碼。


清單 11. 員工使用者轉換程式

public class EmployeeConverter implements Converter {

	public Object toJson(Object o) {
		Employee person = (Employee) o;
		JSONObject json = new JSONObject();
		
		// the person document's fieldname value pairs have already been
		// stored as a hashmap when the document was originally read
		// simply convert the contents into JSON
		
		Iterator it= person.getData().
			keySet().iterator();

		while(it.hasNext()){
			String param= (String)it.next();
			json.put(param,
					(String)person.getData().get(param));
		}

		return json;
	}

…

request.json.output 賦值操作中的傳入物件將作為 toJson 函式中的引數提供。然後,我們將建立一個 JSONObject 來儲存已儲存在 Employee 物件中的名稱/值對。聯絡人的 JSONObject 表示如清單 12 所示。


清單 12. Person 文件中的資料的 JSON 表示

{
      "FirstName": "Van",
      "FullName": "CN=Van Staub\/O=IBM",
      "HTTPPassword": "(355E98E7C7B59BD810ED845AD0FD2FC4)",
      "InternetAddress": "vstaub@ibm.com",
      "LastName": "Staub",
      "OfficeCity": "Atlanta",
      "OfficePhoneNumber": "(123) 456-7890",
      "OfficeState": "Ga",
      "OfficeStreetAddress": "4111 Northside Pkwy",
      "ShortName": "vstaub"
}

JSONObject 並不侷限於名稱/值對。您可以使用字串、數字、值、陣列和其他物件建立複雜物件。舉例來說,清單 13 是 Lotus Domino 伺服器上的資料庫的一個表示。JSONObject 含有一個 JSONObjects 陣列(JSONArray),其中含有資料庫資訊。各個物件還包含一個錯誤狀態和錯誤訊息。


清單 13. Lotus Domino 資料庫的一個複雜表示

{
   "databases": [      
      {
         "filename": "names.nsf",
         "filepath": "names.nsf",
         "httpurl": "http:\/\/domino.ibm.com\/__85257350001864E6.nsf?OpenDatabase",
         "message": "",
         "notesurl": "notes:\/\/Domino@IBM\/__85257350001864E6.nsf?OpenDatabase",
         "replicaid": "85257350001864E6",
         "status": 0,
         "template": "StdR4PublicAddressBook",
         "title": "IBM's Directory"
      }, 
      {
         "filename": "webadmin.nsf",
         "filepath": "webadmin.nsf",
         "httpurl": "http:\/\/domino.ibm.com\/__85257350001883E1.nsf?OpenDatabase",
         "message": "",
         "notesurl": "notes:\/\/Domino@IBM\/__85257350001883E1.nsf?OpenDatabase",
         "replicaid": "85257350001883E1",
         "status": 0,
         "template": "",
         "title": "Domino Web Administrator (7)"
      }
   ],
   "message": "",
   "status": 0
}

配置自定義轉換程式

實現了自定義轉換程式之後,您需要連線物件和轉換程式。為此,可以更新 Project Zero 的配置檔案 zero.config。要將轉換程式連線到物件,可以使用以下語法:

[/config/json/converters/object=convert_class

舉例來說,要連線 Employee 物件與員工轉換程式,可以使用以下配置:

/config/json/converters/com.ibm.devworks.Contact=com.ibm.devworks.json. EmployeeConverter

如果您決定為 Lotus Notes 類本身實現轉換程式,務必要注意物件的返回型別。如前所述,您可以建立各種型別的 Sessions 來訪問 Lotus Domino 資料。根據會話的連線,Lotus Domino 資料的返回型別可能會有所不同。要確保行為的一致性,可以建立自定義轉換程式並在其中包含相同 Lotus Domino 資源的各種型別。請參見清單 14。


清單 14. 不同型別的 Lotus Domino 物件需要一個轉換程式o

config/json/converters/lotus.domino.Database=
com.lotus.rest.json.converters.DatabaseConverter
config/json/converters/lotus.domino.local.Database=
com.lotus.rest.json.converters.DatabaseConverter
config/json/converters/lotus.domino.cso.Database=
com.lotus.rest.json.converters.DatabaseConverter

使用 Lotus Domino Employees 應用程式

要使用 Lotus Domino Employees 應用程式,您需要在 Lotus Domino 伺服器上安裝 Eclipse 和 Project Zero 外掛。安裝好這些外掛之後,遵循以下步驟開始使用應用程式:

  1. 在 Eclipse 中,選擇 File - Import - General - Existing Projects into Workspace,然後選擇 Select Archive File 選項。
  2. 單擊 Browse 按鈕找到 employee.domino.demo.-all-1.0.zip 檔案,然後單擊 OK 按鈕。
  3. 單擊 Finish 按鈕匯入所選專案。
  4. 選擇 Run - Run As - Project Zero Application,建立一個新的執行時配置。啟動專案後,使用控制檯檢視上的 Stop 圖示停止應用程式。
  5. 選擇 Run - Run - Project Zero Application - employee.domino.demo,編輯執行時配置。分別編輯 Arguments 和 Environment 選項卡上的 java.library.path 和 PATH 變數,並參考本文的 “配置 Project Zero 的 RPC 會話” 一節。單擊 Apply 按鈕,然後關閉程式。
  6. 開啟 config/zero.config 檔案。
  7. 更新 config/domino/rest 部分中的設定,應用到您的 Lotus Domino 伺服器。請參見清單 15 中的程式碼。


    清單 15. 配置 Lotus Domino Employees 應用程式
    						
    # mail directory of the Domino server relative
    # to the data directory
    # during contact creation a mail file is also created
    
    /config/domino/rest/mailDirectory = "mail"
    
    
    # field that uniquely identifies people
    # this should conform. to the value passed using REST
    # for example, /employees/[uid] is /employees/vstaub
    
    /config/domino/rest/employeeSearchField = "ShortName"
    
    
    # certifier ID password
    
    /config/domino/rest/certIdPassword = "password"
    

  8. 再次,選擇 Run - Run As - Project Zero Application。執行應用程式時將出現一個類似於 “C:/Eclipse_ProjectZero/Workspace/employee.domino.demo running on port 8080” 的訊息。
  9. 請求受 Lotus Domino 保護的資源,如 names.nsf。
  10. 登入 Lotus Domino 伺服器。
  11. 請求 http://:8080。
  12. 單擊 List Employees 按鈕開始。

注意,names.nsf 必須為全文索引。您可以使用 Lotus Notes 客戶機通過 names.nsf 資料庫的 Properties 框中實現此目的。Firefox 客戶機已經為 Dojo 部件生成了最穩定的客戶端應用。如果一切順利,您將在瀏覽器中看到如圖 13 所示的 Lotus Domino Employee 應用程式。


圖 13. Lotus Domino Employees 應用程式的成功部署

日誌記錄已經作為 System.out 和 System.err 語句實現。如果出現問題,請檢視控制檯日誌中的資訊。

一般技巧

使用 Eclipse 除錯。在除錯執行中應用程式的有用資訊時,檢查 Global Context 引數變數。使用除錯方法確認了我們的猜測,一些執行緒用於促進 HTTP 請求,並且各執行緒都要求 Lotus Notes 初始化。

參閱文件。從 Milestone 1 變更為 Milestone 3 需要移植原始程式碼。瞭解已安裝的版本和所參閱的任何示例的版本。

從一個示例開始。嘗試新特性或整合,方法是從 Project Zero 小組提供的一個工作示例開始。

在單獨的應用程式中測試程式碼。Bug 可能會成為 JVM 或 Project Zero 之間產生細微差別的一個因素,而不一定是由程式碼產生的。這種方法可幫助您理解該問題並適應使用 Project Zero 的工作方式。

未來計劃

本文演示的概念有一個具體的用例;但是,其底層模型是使用 Project Zero 對 Lotus Domino 進行資源建模的一個例子。因此,您可以使用 JSON 表示所有的 Lotus Domino 資料並極大地增強已有應用程式的可用性。一些通用思想可用於編寫 Ajax 友好的 IBM Lotus Domino Web Access 頁面,用於自動檢索使用者的郵件檔案並將它動態地顯示在頁面中。另一個想法是在 IBM Websphere Portal 環境中建立響應更快的 Lotus Domino 檢視和文件的清單。Project Zero 可以輕鬆地將新的 Web 2.0 理念結合到已有的 Lotus Domino 概念中,這是令人驚歎不已的。

結束語

藉助 Project Zero,我們將一個簡單的 Lotus Domino 資料庫轉換成為了一個功能性的 RESTful 應用程式。我們在之前已經看到,在 Project Zero 框架中結合使用 Groovy 等指令碼語言可以極大地改進開發流程。最後,我們檢視了基於 REST 的 Domino 應用程式的結果。由於 REST 和 Web 2.0 概念的的普及,任何 Lotus Domino 開發人員都可利用已有概念使用 Project Zero 開發和實現 RESTful Domino 應用程式。在此過程中,您可以通過各種創新方式擴充套件 Lotus Domino 應用程式的使用。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/14751907/viewspace-586489/,如需轉載,請註明出處,否則將追究法律責任。

相關文章