背景:
對於大多數的應用系統而言,保存信息無疑是最重要也是最平常的功能,目前大多數情況下這些信息 是保存在 Oracle、DB2、SqlServer等關系型數據庫中的。但是這些數據庫在處理圖像、文檔等二進制數 據方面,卻是有很多的不足。雖然我們可以用文件系統來替代,例如淘寶就開發了自己的文件系統 (Taobao File System),能夠滿足高性能的存取海量小文件以及PB級數據量和百億級數據規模的需求, 但是對於文件系統而言,他們沒有提供用於搜索信息的查詢語言,也沒有提供關系、事務等相關的功能。 而隨著應用程序的不斷擴展,允許第三方訪問這些存儲數據已經成為一個典型的需求。
同時很長一段時間以來市場上各個廠家開發的不同的CMS系統,這些系統都建立在他們各自的內容倉庫 之上,每個CMS 開發商都提供了他們自己的API來訪問內容倉庫。這對應用程序的開發者帶來了困擾,因 為要學習不同的開發商提供的API,同時代碼也與這些特定的API產生了綁定。JSR-170正是為解決這一問 題而出現的,它提供了一套標准的API來訪問任何數據倉庫。通過JSR-170,你開發代碼只需要引用 javax.jcr.* 這些類和接口。它適用於任何兼容JSR-170規范的內容倉庫。
JCR(或者JSR-170)規范:
1.JCR模型介紹:
Java內容倉庫(Java Content Repository,JCR)試圖以獨立於具體實現的方式解決這些問題。不論 底層資源(如,數據庫,本地或虛擬文件系統)是什麼,API都將相同。在數據存儲之上,JCR提供諸如訪 問粒度控制、版本控制、內容事件、全文檢索和過濾等內容服務。如圖(1-1)所示:
圖(1-1)
Java內容倉庫使用“樹結構”保存信息,提供了幾個操作數據的特性。樹由節點和屬性組成,如圖 (1-2),圓圈代表節點,方框代表屬性。1個節點有且只有1個父親,有任意數目的孩子(子節點)和任 意數目的屬性。1個屬性有且只有一個父親(它是節點),它沒有子節點,由一個名字和一個或多個值組 成。屬性值的類型可以是:布爾(Boolean)、日期(Date)、雙精(Double),長整(Long),字符串 (String)或流(Stream)。只有屬性可以被用來存儲信息,節點則被用來創建樹內部的“路徑”。在某 種程度上,這棵樹類似文件系統的結構,節點是目錄,屬性是實際的文件。
圖(1-2)
從上面的圖中不難發現,根節點下有多個子節點a、b、c,每個子節點下面又會有多個子節點或屬性。 例如:a節點下有兩個子節點d和e,而e含有兩個屬性節點j和k,屬性j包含了一幅圖片,屬性k為一個浮點 數字;同樣的屬性g包含了一段字符串而屬性h則包含了一個整型數字。
上面圖中的每個節點都可以通過他們在層次結構中的絕對路徑來唯一標識。例如:“/”可以定位到根 節點,而路徑/a/d/i則引用了值為“ture”的屬性 i。同時絕對路徑總是以“/”開始的,而相對路徑則 是以層次中的某個節點為參考物的。例如:相對於/a而言,我們可以通過d/i來定位到值為“true” 的屬 性i。
從對象關系角度上看,因為節點和屬性含有很多共性的同時又有各自的特點,因而他們在擴展了Item 接口的同時增加了自己獨特的方法。我們可以用UML圖(1-3)來表示他們之間的關系:
圖(1-3)
從UML圖中我們不難看出,Node和Property都是Item的子類,每個Property節點有且只有一個Node類型 的父節點,而每個Node節點只能有0個(根節點)或一個Node父節點,以及多個Item子節點。
2.節點類型
每個Node節點都必須要有,並且只有一個主節點類型,該類型定義了名稱、類型、屬性以及該節點必 須要有和允許有的子節點。每個節點都有一個名稱為jcr:primaryType的屬性記錄它的主節點類型。
除了主節點類型之外,每個節點可以有一個或者多個混合類型,來為特殊節點定義主類型約束之外的 特性。當一個節點被指派了一個混合類型之後,它就需要有一個名稱為:jcr:mixinTypes的多值屬性來 記錄它的混合節點類型。
滿足級別一的實現需要提供獲取節點的節點類型的接口及獲取倉庫中可訪問的節點類型定義的接口; 滿足級別二的實現需要提供為節點指定主節點類型和混合類型的接口。
規范並沒有提供定義、創建、管理主節點類型和混合類型的相關接口,但是提供了一組預定義的節點 類型。
3.同名兄弟節點
節點是否允許含有同名兄弟節點是通過父節點的節點類型定義來限定的,雖然規范提供了一組必須要 實現的節點類型,但是這些類型中沒有允許含有同名兄弟節點的定義,因而可以提供一個不支持同名兄弟 節點的內容倉庫實現。
對於支持同名兄弟節點的倉庫實現來說,可以通過Node.getNodes(String namePattern)方法來獲取當 前節點的子節點中滿足namePattern模式的所有節點的迭代器。對於同名節點組中的某個節點,可以通過 在路徑中用類似數組的語法進行定位。例如,路徑/a/b[2]/c[3]可以定位到根節點下a子節點的第二個名 字為b的子節點下的第三名字為c的子節點。注意,索引是從一開始的,而不是從零開始。這種方式來源於 xpath,但與xpath語法不同的是,內容倉庫中的路徑不需要顯示指定索引值“1”。例如:/a /b/c和/a [1]/b[1]/c[1]是等價的。
索引值取決於Node.getNodes()方法返回的迭代器中子節點的順序。例如:通過getNodes方法返回如下 順序的子節點[A, B, C, A, D],這中情況下,A[1]表示列表中的第一個節點,A[2]表示列表中的第四個 節點。
注意:屬性不能含有同名兄弟節點。
4.排序子節點
和同名兄弟節點一樣,排序子節點也是可選的實現內容,這取決於倉庫實現的節點類型集合。對於支 持排序子節點的實現來說,子節點的順序是和 Node.getNodes()方法返回的迭代器中的順序相對應的,我 們可以通過Node.orderBefore方法來改變順序。當向一個支持排序子節點的節點增加新子節點的時候,新 子節點會被增加到子節點列表的最後。對於一個即支持排序子節點又支持同名兄弟節點的實現來說,我們 可以像排序其它子節點一樣來對同名兄弟節點進行排序。例如:對於如下的子節點順序[A, B, C, A, D] ,調用orderBefore("A[2]","A[1]")方法會將A[2]子節點移動到A[1]子節點的前面,結果會是這樣:[A, A, B, C, D] 。結果中的第一個A之前在C之後,第二個A之前在列表的最前面;同時他們的索引值也發生 了改變,之前的A[1]現在是A[2],而之前的A[2]現在是 A[1]。
對於不支持排序子節點的實現來說,應用程序不應該依賴於Node.getNodes()方法返回結果中的子節點 的順序,因為這個順序是隨時可變的,除非在調用read方法或者整個session生命周期中同名兄弟子節點 保持他們之間的相對順序。
5.屬性
屬性類型有STRING、、BINARY、DATE、LONG 、DOUBLE 、BOOLEAN、NAME、PATH、REFERENCE幾種,可 以用處理java中相關類型一樣的方式處理他們。
屬性節點父節點的類型決定了屬性是否能夠支持多值屬性,這同樣是可選的。對於多值屬性來說,可 以通過Property.getValues()方法來獲得屬性值對象的數組。多值屬性中所有的值都是同類型並且是排序 好的。如果我們將多值屬性中的某個屬性值設置為null,那麼相當於從屬性值數組中刪除了這個值,同時 屬性值數組會自動壓縮。對於多值屬性調用Property.getValue方法或者對單值屬性調用 Property.getValues方法都會拋出ValueFormatException。
NAME、PATH、REFERENCE三種類型的屬性有特殊的語法。NAME屬性被用來存儲命名空間標識符;PATH屬 性代表了工作空間中的一個相對或者絕對路徑;REFERENCE屬性提供了一個到工作空間任何位置節點的引 用,該屬性的值是被引用節點的UUID值。
6.實現級別
從倉庫實現功能上來說,可以分為以下幾個級別,如圖(1-4):
a)對於所有實現,級別1是必須的,它提供對倉庫的讀訪問,即:對節點和屬性的讀訪問;對屬性值的 讀訪問;輸出到XML/SAX;支持XPATH語法的查詢服務;可訪問節點的獲取;訪問控制權限的獲取。
b)提供寫功能:增加和移除節點和屬性;對屬性值的寫操作;持久化命名空間的改變;從XML/SAX導入 數據;分配節點的節點類型。JCR的實現並不要求達到級別2或者更高層次。
c)“可選”級別包含一些高級特性,它並不是讀寫倉庫所必需的。包括:事務(它使倉庫有可能與JMS 或JDBC資源一起工作);版本標定(允許倉庫記錄節點的不同狀態,以備日後檢索);事件(允許倉庫內 發生的任何活動都會被通知給客戶端);鎖(可以凍結部分樹的功能,可以有效地返回一個只讀的子樹) ;sql查詢語法的支持。
圖(1-4)
7.JCR API
使用JCR API時,為了更容易的完成JCR更換,同時盡可能的減少代碼變動,建議使用來自javax.jcr包 的接口。
Jcr的包結構介紹如下表:
包名 作用 Javax.jcr 提供java技術下內容管理的接口和類 Javax.jcr.lock 提供內容管理鎖功能需要的接口和類 Javax.jcr.nodetype 提供對內部節點操作相關的功能 Javax.jcr.obeservation 提供事件訂閱處理相關的功能 Javax.jcr.query 提供內容查詢相關的功能 Javax.jcr.query.qom 提供內容查詢的對象模型定義 Javax.jcr.retention 提供保持管理相關的功能 Javax.jcr.security 提供訪問控制管理相關的功能 Javax.jcr.util 提供通用幫助類 Javax.jcr.version 提供版本控制相關的功能在jcr中,一個Repository對象代表了整個倉庫,客戶端可以通過Repository.login方法連接到倉庫, 連接時可以指定一個工作空間和相關憑證。Login方法返回一個Session對象,它代表客戶端和倉庫之間的 連接,該對象同時還封裝了登錄用戶的授權集合以及到可訪問工作空間的綁定。工作空間與Session之間 是一一對應的關系,我們可以把工作空間看作是當前用戶授權集合下能夠訪問到的內容實體的視圖。
下面的代碼展示了一種登錄到內容倉庫的方法:
//獲取倉庫對象
InitialContext ctx = ...
Repository repository = (Repository)ctx.lookup("myrepo");
//創建一個憑證對象
Credentials credentials = new SimpleCredentials("MyName", "MyPassword".toCharArray());
// 獲取Session對象
Session mySession =repository.login(credentials, "MyWorkspace");
在上面的示例中,我們通過JNDI獲取到倉庫對象,然後創建了一個簡單的憑證對象,進而用這個憑證 獲取到MyWorkspace的 Session。對於如何獲取Repository和Credentials對象規范中並沒有做相關約束, 本示例只是展示了其中一種可能的方式。
其它比較常用的接口如下:
Node Session.getRootNode():獲取根節點,通過該節點可以訪問該Session對象有權限訪問的所有節 點
Node Node.getNode(String relPath):通過相對路徑獲取到某節點
Node Node.addNode(String node):為Node對象添加子節點
Void Node.remove():刪除節點對象
Property Node.getProperty(String relPath):通過相對路徑獲取屬性對象
Void Node.setProperty(String name, String value):為Node對象設置屬性值
String Property.getString():獲取屬性對象的值
Value Property.getValue():獲取對屬性對象值的原類型封裝對象
String Value.getString():獲取屬性值封裝對象的實際數據值
Item Session.getItem(String abspath):通過絕對路徑直接定位到某節點對象
Void Session.save():持久化Session對象
Node Session.getNodeByUUID(String uuid):通過全局唯一定位符直接定位到有唯一定位符的節點對 象
下面的代碼展示了如何使用上述接口:
// 獲取根節點
Node root = mySession.getRootNode();
// 定位到某節點
Node myNode = root.getNode("a/e");
// 獲取到某個節點的屬性
Property myProperty = myNode.getProperty("k");
// 獲取屬性節點的值
Value myValue = myProperty.getValue();
// 將屬性值轉換成特定的格式,此處myDouble的值會是6.022 x 10^23
double myDouble = myValue.getDouble();
// 直接獲取到值為6.022 x 10^23的屬性k
Property myProperty =(Property)mySession.getItem("/a/e/k");
// 假設節點/a/e是可訪問的,並且定位符為:1111 2222 3333 4444,通過以下代碼可以//得到e 節點對象
Node myNode = mySession.getNodeByUUID("1111 2222 3333 4444");
在對節點或者屬性做變動的時候,需要注意以下幾點:
a)Node和Property發生的改變不會立即持久化到工作空間中,而是暫時存儲在Session對象中,直到這 些變動被保存或者放棄。可以通過Session或Item對象的save方法保存更改,通過refresh(false)方法來 取消更改;Session對象的save方法和 refresh方法會持久化或者放棄與當前session相關的所有改變,而 Item的相關方法則只會持久化或者放棄與特定節點相關的改動。在改變被保存之後,其它訪問該工作空間 的Session便可以發現這些變動。沒有被持久化的更改在當前Session對象中是立即生效的,但是卻不會被 其它訪問該工作空間的Session對象察覺。
b)上面所描述的情況是在沒有事務的情況下,如果save或refresh方法在一個事務域中,那麼調用save 之後,變動也不會立即對其它訪問該工作空間的Session對象可見,而是在事務提交之後才會對其它 Session可見。同時沒有調用save方法的變動不會在事務中提交。
JCR模塊實現
Spring中含有一個JCR模塊,它的主要目標是以一種類似Spring ORM包的方式,簡化使用JSR-170 API 開發應用程序。特點如下:
a)JcrTemplate:它是JCR模塊的核心類之一,它提供了與JCR會話一起工作的方便方法,將調用者從必 須處理的打開和關閉會話、事務回滾(如果底層倉庫提供)、以及處理其它特性中的異常等工作中解放出 來。它實現了JCR Session的絕大部分方法,允許執行 JcrCallback和異常處理。
b)RepositoryFactoryBean:用於配置、啟動和停止倉庫實例。支持包含預定義的用於Jackrabbit和 Jeceira的FactoryBean,以及一個很容易支持其它倉庫的抽象基類。
c)SessionFactory:用來統一Repository,Credentials和Workspace接口,允許自動注冊監聽器和自 定義名字空間。它隱藏了實現內部的認證細節,因此一旦配置完成,使用同一證書的會話可以很容易的被 檢索出來。
d)OpenSessionInView:攔截器和過濾器允許每個線程跨不同組件使用同一會話。
JSR-170項目發展:
a)Jackrabbit 是JSR-170的參考實現,Apache基金的一部分,提供級別1,2和可選功能。目前它已經 有官方公開的發布版本,最新版本是 1.6.2,該版本被認為足夠穩定,可以被用在產品環境。此外, Jackrabbit也被用來作為Day Software(JSR-170的領導者)的商業產品的基礎。除了實現JSR-170中定義 的所有特性,JackRabbit還加入了額外的功能(如 SessionListeners或CustomNode注冊),以及JCA連接 器、taglib、WebDAV接口、虛擬文件系統和JDBC後端。 JackRabbit的許可證是Apache 2.0。
b)eXo企業內容管理(ECM)是一套Portlets,提供了Web內容、文檔和記錄管理工具,它構建在eXo JCR之上。 eXo JCR是eXo platoform的一部分,包含規范要求的所有強制特性和幾個可選特性。它將內 容存儲在中央資源庫中,規定了結構、版本、鎖定及搜索內容的方式。與 eXo平台的其他組件類似,該產 品進行了大量優化和擴展,這些都包含在該產品的獨立分發包中。eXo JCR支持JDBC兼容數據庫,如MySQL 、DB2或HSQL(它是缺省的)作為後端存儲,它是雙許可的(GPL和商用)。與ECM產品一起的是一些預定 義的工作流,你可以定制它們以方便地共享和傳播公司中的ECM內容和文檔。也可以增加自己的工作流定 義,並以很多不同的方式來使用它們。eXo ECM對企業內容的捕獲、生產、管理、發布和記錄提供了強有 力的支持。所有的這一切都以高度可定制的方式進行。但是產品背後的主要驅動力卻是為了提供易於使用 的高級功能,可以模擬真實操作系統中的應用,如文件浏覽器。
c)Jeceira 與Jackrabbit和eXo JCR相比,是相對較新的項目。它實現了級別1和2的一些需求,只在 寫操作時,支持來自可選級別的觀察功能。這個項目處於未完成階段,然而它被 Magnolia(一個流行的 基於java的CMS,與作為JSR-170參考實現的Jackrabbit類似)使用。在最終版發布時,它計劃包含所有級 別的功能,發布時間目前尚不確定。Jeceira的許可證是Apache 2.0,使用HSQL數據庫作為它的存儲引擎 。
Java內容倉庫的未來:
雖然JSR-170已經於2005年5月完成,但是Java內容倉庫的工作並沒有終止。第二版JCR API已經發布了 公眾評估版(JSR-283),JSR-283作為官方的後繼者,將聚焦於功能增強,例如remoting,通過新的標准 節點類型(包括元信息和國際化)改進互操作性,客戶端/服務器協議映射和擴展內容模型的能力。同時 還存在著一些JSR之外的想法和項目:綁定/映射框架(java類和 JCR樹之間的相互映射);建構於JCR之 上的WebDAV服務器等。目前已經出現了用於不同產品的JSR-170連接器,如Alfresco、BEA Portal Server 和IBM Domino。對JCR模塊而言,路線圖包括用於幾個實現的Acegi安全集成,支持Spring 2.0名字空間模 式(它將減少XML的配置)和與其它JCR的實現集成。JCR的前景看起來一片光明。