背景:
对于大多数的应用系统而言,保存信息无疑是最重要也是最平常的功能,目前大多数情况下这些信息是保存在 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对象
ession 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");