内容管理系统的基本功能和 IBM ECM 的数据模型
内容管理系统的基本功能
随着 IT 应用的深入普及,各种企业都积累了大量的信息资源 , 在一些诸如政府、电信、金融、媒体等以内容为其主要核心业务的行业中,内容数据已成为关键性资产。科学管理和合理开发这些内容信息资源已经成为企业正确决策、增强竞争力的关键。企业内容管理系统是一种管理、集成和访问各种格式的内容数据的应用软件,它的主要目标是使用一系列的策略、方法和工具来组织和存储涉及企业流程的内容文档,处理的对象范围包括所有结构化的数据和非结构化的文档。企业内容管理主要包含以下五个组成部分,即内容的获取、管理、存储、保留和交付。
- 获取 : 内容获取涉及了将纸质内容转换为电子格式,已经将电子格式的内容转换为管理所需的一致性结构。
- 管理 : 内容管理包含了文档管理、内容协作、网页文档管理、记录管理和业务流程管理五个传统应用领域。
- 存储 : 内容存储包含三个组成部分,存贮具体数据的资料库,管理资料库数据的库服务以及底层的数据存储。
- 保留 : 内容保留主要针对不可变数据的长期安全存储和备份。主要是为内容管理系统的记录管理功能提供支持。
- 交付 : 内容交付为企业内容管理系统的管理,存储和保留组件中的信息提供展现。
IBM ECM 产品的数据模型
IBM ECM 企业内容管理产品提供了一套功能强大的企业内容管理体系架构,使得企业用户能非常容易地访问电子文档的内容。这种体系架构能够通过具有强劲、灵活的数据模型的单一存储库来支持不同的、异构的内容管理技术。使用这一数据模型还能够非常容易地在不同系统之间实现内容共享,从而提高了业务处理流程的效率。
IBM ECM 企业内容管理产品的数据模型,能够非常容易地实现任何复杂和多变的元数据标准。简单来说,任何用 XML 格式描述的元数据信息,都可以在 IBM ECM 中得到快速实现,并且灵活地进行改变,从而充分和容易地通过客户化来实现各种元数据标准,而不需要任何系统设计和编程工作,且实现系统性能的最优化。
图 1. IBM ECM 数据模型结构图
图 1 所示是 IBM ECM 元数据模型的结构图。通过扩展使用该数据模型,可以实现 Document,Resource,Item,Folder 等非结构化数据存储系统所需的各类基本数据类型。该模型有以下几个特点:
- Root Component 和 Child Component 共享一套相同的“Component”特性 ( 例如 Component 可以包含属性 )。Root Component 可以包含 Child Component 的继承结构。
- Root Component 和 Root Component 之间可以实现链接功能。
- 任何 Component(Root 或 Child) 都可以引用其他的 Root Component。
- 一个 Child Component 中可以包含它自己的多个子 Child Component。
- Root Component 中可以包含多个 Resource Component,Resource Component 中不能包含子 Resource Component 或 Child Component。
- Resource object 代表一个实际的二进制资源文件。
回页首
JCR 和 Jackrabbit 的历史及基本概念
由于业界对企业内容管理系统有着非常强烈的需求,越来越多的软件企业投身于这类应用软件的开发中,进而产生了对于公共的、标准的内容仓库 API 的需求。Content Repository for Java Technology(JCR)规范提供了这一接口。它的目标是满足行业对这些内容仓库 API 的需求。它在 javax.jcr 命名空间中提供了统一的 API,允许以厂商中立的方式访问任何符合规范的仓库实现。JCR 规范在 Java Community Process 中作为 JSR-170(JCR1.0) 和 JSR-283(JCR2.0) 开发。
- JSR-170: 提供了标准的 JCR API 接口包 javax.jcr 包。API 的核心类是 Session,它代表客户端和仓库之间的连接。这个包还包含了组成仓库的接口的定义 (Workspace,Credentials,Node,Property,Item 和 Value);javax.jcr.query 包负责处理查询 ; javax.jcr.nodetype 包负责定义节点类型 ;javax.jcr.version、javax.jcr.observation、javax.jcr.lock 负责其他可选级别的功能。
- JSR-283: 对 JCR 1.0 进行了改进和增强。加强了访问控制和节点类型的管理 ; 通过新的标准节点类型改进互操作性 ; 扩展内容建模能力 ; 支持联邦、交叉仓库和交叉工作区功能 ; 增强查询语言、版本标定功能。
JCR 的基本结构
JCR 仓库使用如图 2 所示的树型结构来保存信息。树由节点和属性组成,每个节点可以有任意数量的子节点和属性,但有且只有一个父节点。属性由属性名以及属性值组成,每个属性只有一个父节点,没有子节点或子属性。属性值可以是单值也可以是多值的,其类型可以是:String,boolean,double,long,javax.jcr.Binary,java.util.Calendar,java.math.BigDecimal。在 JCR 内容仓库中,主要使用属性来存储信息,节点则被用来创建树内部的路径。
图 2. JCR 的数据存储结构
JCR 存贮树中的每一个节点都可以通过他们在层次结构中的绝对路径来唯一标识。以图 2 所示的树结构为例,“/”表示根节点,路径 /a/d/i 则表示值为“ture”的属性 i。绝对路径总是以“/”开始,相对路径则是以层次中的某个节点为参照物。例如:相对于 /a 而言,我们可以通过 e/j 来定位到值为一副图片的属性 j。
由于 JCR 的节点和属性具有很多相同的特性,因此他们都实现了 Item 接口中定义的通用方法,此外他们还添加了各自特有的功能方法。下图 3 是 JCR 树状数据存储模型的 UML 结构图,由该图可知,Node 和 Property 都是 Item 的子类,每个 Property 节点有且只有一个 Node 类型的父节点,而每个 Node 节点只能有一个 Node 父节点 ( 跟节点除外,它没有父节点 ),以及多个 Item 子节点。
图 3. JCR 数据模型结构图
回页首
JCR 的数据模型与 ECM 数据模型的对应关系
对比图 1 中的 IBM ECM 数据模型和图 3 所示的 JCR 数据模型,可以发现 ECM 和 JCR 具有非常相似的数据对象对应关系。在 ECM 数据模型中,Root Component 代表了一种特定数据类型的最顶级对象,在一个 ECM 的存储库中可以有多个 Root Component( 代表多个不同的数据类型 ),每一个 Root Component 可以含有多个表示二进制数据资源的 Resource Component( 或 Resource Item),每个 Resource Component 中包含有一个 Resource Object,但不能包含其他子 Resource Component 或 Child Component。每个 Root Component 还可以含有多个表示非二进制数据的 Child Component,Child Component 可以嵌套包含其他的 Child Component,但是不能含有 Root Component 和 Resource Component 二进制数据。ECM 的这种数据结构实质上也是一个树状结构。如图 4 所示,我们可以使用 JCR 中的数据节点来表示 ECM 模型中的各种数据对象及其对应关系。
图 4. JCR 与 ECM 数据模型的映射关系
我们可以使用 JCR 树状结构的根节点 ( 图 4 中蓝色节点 ) 来表示一个 ECM 存储库。Root Component 对象可以使用 JCR 的一个第 1 代子节点 ( 图 4 中红色节点 ) 来表示。这个 1 代子节点可以含有多个二进制类型的 JCR 子节点 ( 图 4 中浅绿色节点 ) 来表示 ECM 的 Resource Component 二进制内容资源,还可以包含若干层级的 JCR 子节点 ( 图 4 中黄色节点 ) 来表示 ECM 的 Child Component 对象,这些节点中只能包含有除 javax.jcr.Binary 之外的其他类型的属性 ( 图 4 中浅黄色属性 )。在下一节中将演示如何使用 JAVA 和 JCR API 来编码实现这一使用 JCR 技术的 ECM 存储模型。
回页首
使用 JAVA 和 JCR API 实现 ECM 存储模型
我们可以通过以下三个步骤来使用 JAVA 以及 JCR API 创建一个 ECM 存储模型。
- 使用 Java Interface 来定义 ECM 存储模型的 Java 数据对象结构。
- 创建 Java 工厂方法来获取基于 JCR 技术的 ECM 数据存储空间的 Java 实现。
- 使用 JCR 的 Java API 来实现步骤 1 中定义的各个 Java Interface。
定义 ECM 存储模型的 Java 数据对象结构
对应于 ECM 存储模型,我们需要创建相应的 Java Interface 来为最终用户提供抽象的、不暴露后端 JCR 实现的 Java 接口对象,并在这些接口对象中提供相应的数据对象访问方法。为此我们需要创建 Java packageecm.contentManagement, 并在这个包中创建以下 Java 接口:RootComponent,ChildComponent,ResourceComponent,CommonProperty,ResourceObject和ContentSpace。其中 ContentSpace 用来表示整个 ECM 存储模型的存取容器。创建好接口后我们还需要为各个接口定义相应的数据对象操作访问方法。以下代码清单 1 中所示的是 RootComponent 接口中的方法定义,其他接口的方法定义请参照本文下载中的 eclipse 示例工程( 注:在附件的工程中并未包含类库 jackrabbit-standalone-2.1.1.jar,请在使用前自行下载并包含这一类库 )。
清单 1. RootComponent Java 接口
package ecm.contentManagement;
import java.util.List;
public interface RootComponent extends Component {
public List<ResourceComponent> getResourceComponents();
public List<ChildComponent> getChildComponents();
public ResourceComponent getResourceComponentByName(String name);
public ChildComponent getChildComponentByName(String name);
public ResourceComponent addResourceComponent(String name);
public ChildComponent addChildComponent(String name);
public boolean removeResourceComponentByName(String name);
public boolean removeChildComponentByName(String name);
public CommonProperty addCommonProperty(String name,int type,Object value);
public boolean removeCommonPropertyByName(String name);
public List<CommonProperty> getCommonPropertys();
public CommonProperty getCommonPropertyByName(String name);
}
创建 Java 工厂方法来获取 ECM 数据存储空间
JCR 中使用 javax.jcr.Repository 来代表一个存储数据的仓库对象,在实际使用中需要通过 login Repository 来获取 Session 和 Node。而在 ECM 数据模型中并不存在相应的 Repository,Session 以及 Node 的概念。为此我们需要创建工厂方法来包装对 JCR 中的 Repository,Session 和 Node 的操作,并把这些对象以 ContentSpace 的形式包装起来,供各个 JCR 实现类来使用。为实现这一目的我们需要创建 package ecm.contentManagement.util,并在这个包中创建工具类 ContentManagementObjetFactory。在这个类中提供获取 ContentSpace 的工厂方法。代码清单 2 中所示的是 ContentManagementObjetFactory 中的获取 ContentSpace 的代码片段,该类的详细实现请参照本文下载中的 eclipse 示例工程。
清单 2. ContentManagementObjetFactory 中的获取 ContentSpace 的方法
......
private static Repository repository;
private static Repository get_JCR_Repository() {
if (repository == null) {
repository = new TransientRepository();
}
return repository;
}
public static ContentSpace connectContentSpace(){
Repository contentRepository = get_JCR_Repository();
Session session=null;
SimpleCredentials loginCredentials=new SimpleCredentials(
ECM_JCR_Constant.ECM_JCR_USER_NAME,
ECM_JCR_Constant.ECM_JCR_USER_PWD.toCharArray());
try {
session =
contentRepository.login(loginCredentials,ECM_JCR_Constant.ECM_JCR_WORKSPACE);
Workspace jcrWorkspace=session.getWorkspace();
ContentSpace contentSpace=new ContentSpaceImpl(session,jcrWorkspace);
return contentSpace;
} catch (LoginException e) {
e.printStackTrace();
} catch (NoSuchWorkspaceException e) {
return createContentSpace();
} catch (RepositoryException e) {
e.printStackTrace();
}
return null;
}
......
|
使用 JCR API 实现 Java 数据对象结构
当各个 ECM 存储模型对象的 Java 接口创建完成后,我们需要使用 JCR Java API 来创建它们的基于 JCR 的实现类,从而为最终用户提供真正的业务功能。为此我们需要创建 Java packageecm.contentManagement.impl,并在其中为各个 Java 接口创建实现类。JCR API 操作的核心是代表客户端和仓库间连接的 Session 类以及代表当前节点的 Node 类。为此我们需要在创建各个实现类时为其注入 Session 和 Node 对象,通过使用这两个对象在实现类的内部操作和获取后端 JCR 仓库中的数据。由于最终用户在使用中始终操作的是 Java 接口对象,基于 JCR 技术的实现对于他们来说是透明的。代码清单 3 中所示的是最终用户操作 ECM 数据模型所使用的代码片段。代码清单 4 所示的是相应 JCR 实现类的内部代码结构片段。完整的 ECM 数据模型接口的 JCR 实现请参照本文下载中的 eclipse 示例工程。
清单 3. 最终用户操作 ECM 数据模型所使用的代码片段
// 连接到 ECM 存储库
ContentSpace contentSpace=ContentManagementObjetFactory.connectContentSpace();
// 创建 RootComponent
contentSpace.addRootComponent("TestingRootComponent_1");
// 获取 RootComponent
RootComponent rootComponent=
contentSpace.getRootComponentByName("TestingRootComponent_1");
// 为 RootComponent 添加属性
rootComponent.addCommonProperty("root_propert1_String",
ECM_JCR_Constant.ECM_JCR_COMMON_PROPERTY_STRING, "root property1_String Value");
// 断开与 ECM 存储库的连接
|
清单 4. 相应 JCR 实现类的内部代码结构片段
//ContentSpaceImpl 构造函数
public ContentSpaceImpl(Session session,Workspace jcrWorkspace){
this.setJcrWorkspace(jcrWorkspace);
this.setJcrSession(session);
}
//ContentSpaceImpl 中 addRootComponent 方法 JCR 实现
public void addRootComponent(String rootComponent) {
try {
Node _ECM_JCR_ROOT_NODE= jcrSession.getRootNode()
.getNode(ECM_JCR_Constant.ECM_JCR_ROOT_NODE);
_ECM_JCR_ROOT_NODE.addNode(rootComponent);
jcrSession.save();
} catch (PathNotFoundException e) {
e.printStackTrace();
} catch (RepositoryException e) {
e.printStackTrace();
}
}
//RootComponentImpl 构造函数
public RootComponentImpl(Session session,Node node){
this.jcrSession=session;
this.jcrNode=node;
}
//RootComponentImpl 中 addCommonProperty 方法 JCR 实现
public CommonProperty addCommonProperty(String name, int type, Object value) {
try {
switch(type){
case ECM_JCR_Constant.ECM_JCR_COMMON_PROPERTY_BOOLEAN:
boolean propValue_boolean=((Boolean)value).booleanValue();
this.jcrNode.setProperty(name, propValue_boolean);
break;
case ECM_JCR_Constant.ECM_JCR_COMMON_PROPERTY_DOUBLE:
double propValue_double=((Double)value).doubleValue();
this.jcrNode.setProperty(name, propValue_double);
break;
case ECM_JCR_Constant.ECM_JCR_COMMON_PROPERTY_LONG:
long propValue_long=((Long)value).longValue();
this.jcrNode.setProperty(name, propValue_long);
break;
case ECM_JCR_Constant.ECM_JCR_COMMON_PROPERTY_DECIMAL:
BigDecimal propValue_bigDecimal=(BigDecimal)value;
this.jcrNode.setProperty(name, propValue_bigDecimal);
break;
case ECM_JCR_Constant.ECM_JCR_COMMON_PROPERTY_DATE:
Calendar propValue_calendar=(Calendar)value;
this.jcrNode.setProperty(name, propValue_calendar);
break;
case ECM_JCR_Constant.ECM_JCR_COMMON_PROPERTY_STRING:
this.jcrNode.setProperty(name, value.toString());
break;
}
this.jcrSession.save();
CommonProperty cp=new CommonPropertyImpl(name,type,value);
return cp;
} Exception e) {
e.printStackTrace();
}
return null;
}
|
代码清单 5 中所示的是最终用户使用完成的基于 JCR 的 ECM 数据模型的调用代码。代码清单 6 是标准的输出结果,同时在相应的文件结构中可以得到新产生的二进制文件 newFile.jpg。
清单 5. ECM 数据模型调用代码
public static void testContentSpace(){
// 连接到 ECM 存储库
ContentSpace contentSpace=ContentManagementObjetFactory.connectContentSpace();
// 创建 RootComponent
contentSpace.addRootComponent("TestingRootComponent_1");
// 获取 RootComponent
RootComponent rootComponent=contentSpace.
getRootComponentByName("TestingRootComponent_1");
// 为 RootComponent 添加属性
rootComponent.addCommonProperty("root_propert1_String",
ECM_JCR_Constant.ECM_JCR_COMMON_PROPERTY_STRING,
"root property1_String Value");
rootComponent.addCommonProperty("root_propert2_Boolean",
ECM_JCR_Constant.ECM_JCR_COMMON_PROPERTY_BOOLEAN,
new Boolean(false));
// 创建 ChildComponent
ChildComponent childComponent_level_1=
rootComponent.addChildComponent("child_L1");
// 为 ChildComponent 添加属性
childComponent_level_1.addCommonProperty("child_L1_propert1_String",
ECM_JCR_Constant.ECM_JCR_COMMON_PROPERTY_STRING,
"child level1 property1_String Value");
childComponent_level_1.addCommonProperty("child_L1_propert2_Long",
ECM_JCR_Constant.ECM_JCR_COMMON_PROPERTY_LONG, new Long(1234567890));
// 为 ChildComponent 添加子 ChildComponent
ChildComponent childComponent_level_2=childComponent_level_1.
addChildComponent("child_L2");
// 为子代 ChildComponent 添加属性
childComponent_level_2.addCommonProperty("child_L2_propert1_Long",
ECM_JCR_Constant.ECM_JCR_COMMON_PROPERTY_DOUBLE, new Double(123.45678));
// 创建 ResourceComponent
ResourceComponent resourceComponent=
rootComponent.addResourceComponent("resource_1");
// 添加 文件 testPic.jpg 到 ResourceObject 中
resourceComponent.addResourceObject(new File("testPic.jpg"));
// 断开与 ECM 存储库的连接
contentSpace.closeContentSpace();
// 再次连接 ECM 存储库
ContentSpace contentSpace2=ContentManagementObjetFactory.connectContentSpace();
// 获取 RootComponent
RootComponent rootComponent2=contentSpace2.
getRootComponentByName("TestingRootComponent_1");
// 获取 RootComponent 属性
CommonProperty rootCommonProperty_1=rootComponent2.
getCommonPropertyByName("root_propert1_String");
System.out.println(rootCommonProperty_1.getName());
System.out.println(rootCommonProperty_1.getValue());
CommonProperty rootCommonProperty_2=rootComponent2.
getCommonPropertyByName("root_propert2_Boolean");
System.out.println(rootCommonProperty_2.getName());
System.out.println(rootCommonProperty_2.getValue());
// 获取 ChildComponent
ChildComponent childComponent_L1=rootComponent2.
getChildComponentByName("child_L1");
// 获取 ChildComponent 属性
CommonProperty _L1commonProperty_1=childComponent_L1.
getCommonPropertyByName("child_L1_propert1_String");
System.out.println(_L1commonProperty_1.getName());
System.out.println(_L1commonProperty_1.getValue());
CommonProperty _L1commonProperty_2=childComponent_L1.
getCommonPropertyByName("child_L1_propert2_Long");
System.out.println(_L1commonProperty_2.getName());
System.out.println(_L1commonProperty_2.getValue());
// 获取子代 ChildComponent
ChildComponent childComponent_L2=childComponent_L1.
getChildComponentByName("child_L2");
// 获取 子代 ChildComponent 属性
CommonProperty _L2commonProperty_1=childComponent_L2.
getCommonPropertyByName("child_L2_propert1_Long");
System.out.println(_L2commonProperty_1.getName());
System.out.println(_L2commonProperty_1.getValue());
// 获取 ResourceComponent
ResourceComponent resourceComponent2=rootComponent2.
getResourceComponentByName("resource_1");
// 获取 ResourceObject
ResourceObject robj=resourceComponent2.getResourceObject();
System.out.println(robj.getName());
System.out.println(robj.getMimeType());
System.out.println(robj.getLastModified());
System.out.println(robj.getContent());
try{
File f=new File("newFile.jpg");
InputStream is=robj.getContent();
OutputStream out=new FileOutputStream(f);
byte buf[]=new byte[1024];
int len;
while((len=is.read(buf))>0){
out.write(buf,0,len);
}
out.close();
is.close();
}catch (IOException e){
e.printStackTrace();
}
contentSpace2.closeContentSpace();
}
|
清单 6. 标准的输出结果
root_propert1_String
root property1_String Value
root_propert2_Boolean
false
child_L1_propert1_String
child level1 property1_String Value
child_L1_propert2_Long
1234567890
child_L2_propert1_Long
123.45678
testPic.jpg
application/octet-stream
java.util.GregorianCalendar....
org.apache.jackrabbit.core.data.LazyFileInputStream@86558
|
回页首
结束语
本文介绍了如何使用 JCR 技术来开发一个简易的 ECM 企业级内容管理系统数据模型的 JAVA 实现。通过使用类似的方法,读者可以使用 JCR 技术方便快捷的开发出类似的功能强大的内容管理系统的基础结构和模型,进而显著的提高此类应用系统的开发效率。