eXo 平台概览

阅读数:2452 2008 年 12 月 5 日

我们很高兴地宣布,eXo 平台发布了新的 Portlet Container 2.0 和 Portal 2.1。eXo 是第一个对生产者和消费者提供全面支持的 Portal——支持新的 Java Portlet 2.0 API(JSR 286)和 Web Service Remote Portlet 2.0(WSRP)。该宣布给我们提供了一个机会来了解 eXo Portal、Portlet-Container 及企业内容管理(Enterprise Content Management,即 ECM)所提供的新特性。

eXo 平台架构概览

在详细了解 eXo 平台提供的 Portal、Portlet Container 及 ECM 组件前,我们需要了解一下该平台的架构。

从 eXo 平台的首个版本开始,它就使用了一个叫做 Pico 的控制反转容器(IoC),以降低服务间依赖的耦合。这样,组件的实例化以及将其注入到依赖它的组件中就是容器的职责了。

eXo 平台所有的组件都以插件形式开发,并且使用 IoC 容器将其包装在一起。下图从下到上展示了 eXo 平台栈的各个组件:

The Web 2.0 Portal

新版本的 eXo Portal 构建于旧版本之上,同时我们去除了一些扩展模块附加的使用复杂性,这要归功于 Web 2.0 技术,如 AJAX。

事实上,eXo 最先引入了动态布局的概念,而大多数其他的 Portal 依旧在使用静态布局。两者的主要区别在于:通过动态布局你可以管理嵌套的容器树,树中的每个叶子都是 Portlet。容器负责孩子的布局,就像操纵 Swing UI 对象一样,如下图所示。开箱即可用的容器渲染器会在行、列或者标签上显示其孩子。

这样,将容器从一个地方移到另一个地方就像数据结构中对树的操作一样。在客户端,我们使用了 JavaScript 来创建拖拽库,这样可以简化树的操作。因此,如下图所示,我们不仅可以在页面中拖拽容器和 Portlets,还可以将一类容器或者 Portlet 拖到页面中。

每个 Portlet 都有一套元数据,这使得 Portal 可以在页面中渲染它。该元数据不仅包括标题、大小、图标及边框样式(参见下图),还包括是否显示边框及图标的选项(最小化或者最大化 Portlet)。

一旦获得了所有信息,Portal 就可以渲染不同类型的页面了。用户登录时,各种类型都会集成到用户的菜单中(参见下图)。页面类型包括:

  • Portal 页面,这是每个用户都会看到的页面。一些页面甚至可被匿名用户访问,这可以作为外网或者 Internet 站点(就像我们自己)的基础
  • ;
  • 用户页面,这可看作一套面板,用户可以增加新的页面和自己的内容(只要公司政策允许就行)
  • 组页面,它会将用户所在组的页面增加到用户菜单中,比如市场或者销售组

在上面的截图中你还会看到,用户登录后左边有一个可折叠列。它包括不同页面和管理菜单之间的导航,以及用户可自行配置的一组 Widgets。正如我们所说的,Widgets 是完全的 JavaScript 组件,它们使用 REST 协议与服务器进行通信。在未来的版本中,我们将基于 Google 的 OpenSocial 标准。

eXo Portal 2.0 也是一个完全的 AJAX Portal,这意味着当你从一个页面移到另一个页面时,屏幕只会部分刷新。为了达到这个目的,每当页面改变或者需要对页面上的一个或者几个 Portlets 的内容进行修改(就如同一个 Portlet 使用 JSR 286 标准调用向另一个 portlet 发送消息)时,我们都使用 JavaScript 的 XMLHttpRequest 对象去异步调用服务器。这当然会极大地改善服务器的调用,因为除非发送了事件,否则只需调用 render() 一次就够了。这与通常情况截然相反,在通常情况下,每个 Portlet 都需要渲染其片段,而这会花费大量时间(即便采用缓存也是如此)。

每次请求时,Portal 都会返回一个 XML 文件,该文件包装了所有需要在页面中进行更新的不同 HTML 片段。该 XML 文件的结构如下:

* {PortalResponse}
* |
* |--->{PortletResponse}
* |
* |--->{PortletResponse}
* | |-->{portletId}
* | |-->{portletTitle}
* | |-->{portletMode}
* | |-->{portletState}
* | |
* | |-->{PortletResponseData}
* | |
* | |--->{BlockToUpdate}
* | | |-->{BlockToUpdateId}
* | | |-->{BlockToUpdateData}
* | |
* | |--->{BlockToUpdate}
* |
* |--->{PortalResponseData}
* | |
* | |--->{BlockToUpdate}
* | | |-->{BlockToUpdateId}
* | | |-->{BlockToUpdateData}
* | |
* | |--->{BlockToUpdate}
* |
* |--->{PortalResponseScript}

服务器响应包含三类 XML:

  • 一类针对Portal 组件片段,包含在 <PortalResponseData> 标签中——它也包装了要更新的组件 ID
  • 一类针对每个要更新的 Portlet 片段——它也包装了 Portlet 信息,如窗口状态和更新 Portlet 的模式。 使用了 eXo Portal 2.x AJAX 特性的 Portlets 还能告诉 Portal 需要直接更新哪些 HTML 标签,以避免全部的 Portlet 刷新(对于完全兼容 JSR 286 的 Portlets 来说,这是强制的)
  • 进行动态赋值的JavaScript 代码,因此它会出现在新生成的页面上

<PortalResponseScript> 中返回的 JavaScript 是由 Portal 或者 Portlets 产生的,他们可以理解 eXo Portal 处理 JavaScript 的方式。此外,返回的 JavaScript 需要定义全局的 JavaScript 函数(使用语法function())以在eval()方法执行过程中动态添加到页面中。此外,如果 Portlet 是纯粹的 JSR 286,那就不能使用这种便捷的方式了。在这种情况下,Portal 解析 <PortletResponseData>,然后确定没有创建 blockToUpdate。因此,它将解析整个 Portlet 片段来寻找所有的脚本,以便可以将其增加到页面上。包含 JavaScript 的几个第三方 JSR 168 Portlets 已经通过了测试,比如 SpagoBI。

新 eXo Portal 的另一大改进之处就是所有的 Portal 配置文件(包括组、用户、Portal 页面以及 Widgets 和 Portlet 首选项)现在都存储在标准的资源库中。在下图中我们可以看到根用户页面存储在一个 XML 文件中,位于 JCR 地址:/exo:registry/exo:users/root/UserPortalData:

使用 JCR 不仅便于存储性能和集群能力,而且也大大方便了 JCR 提供的高级服务,如通过 REST 服务的本地接口(使得第三方应用可以操纵 Portal 页面)。

eXo Portlet Container:JSR 286 与 WSRP 2

eXo Portlet Container 2.0 实现了 Java 和 Web Services Portal 的两个主要规范:

  • Portlet API 2.0
  • WSRP 2

作为 Portal 的一部分,Portlet 容器负责处理应用在多窗口环境下的应用。

我们对该新版本的内部架构进行了彻底的改写,充分利用了以前引入的插件机制,因此对 Portlet 容器提供了一个扩展点。

Portlet API 2.0 (JSR 286)

Portlet API 2.0 于 2008 年 6 月 13 日发布,eXo 成为第一个兼容实现,同时提供了 Portlet 容器和使用这些 Portlets 的 Portal。该规范是 JSR 168 的一个扩展,同时专家组也在集中精力确保 JSR 168 Portlets 可以在 JSR 286 容器中运转。

Portlet 2.0 API 引入了很多新特性,列举如下:

  • Portlet 间通信(Inter-Portlet Communication,即 IPC),这可以将事件和参数发送到其他 Portlets(同一个 WAR 中就不必这样了)
  • Portlet 过滤器,模拟了 Servlet 过滤器的行为
  • 公共参数,在渲染阶段与所有 Portlets 共享渲染参数
  • 高级缓存行为,因为 JSR 168 中的这部分功能非常有限
  • 新方法 serverResource() ,更好地处理图像插入(不必使用 Servlet 了)
  • URL 监听器机制,当请求某个 URL 时,触发一个处理器

Portlet 间通信

我们不会浏览 JSR 286 规范中所有的新特性,但是我们要详细论述一个主要的特性:Portlet 间通信(IPC)。

在详细讲解 IPC 之前,让我们看看在 JSR 168 Portlet 请求的上下文中该过程是什么样的。在 JSR 168 中,一个请求被分成两个阶段:

  1. processAction():它处理单独的 Portlet,以改变一个业务对象(类似于 JavaBean)的状态
  2. render():由页面上的每个 Portlet 调用,负责渲染 HTML 片段

我们看到一个两阶段的请求是必需的,认清这一点非常重要,因为渲染的 HTML 片段可能依赖于一个修改过的 JavaBean 对象,因此processAction()方法应该在任何render()方法调用前被调用。

从这里我们可以看到 JSR 168 只定义了在单个 Portlet 上下文中渲染的用户界面。它没有定义关于 Portlets 之间通信的任何标准方式,而这却是构建复合应用的关键所在。

开发者经常利用这样一个事实:可以通过 HTTP Session 在多个 Portlets 之间共享 JavaBean 对象。然而这却是有限制的,因为这种基于 Session 的共享只限于部署在同一个 WAR 中的 Portlets。

在真实场景中,Portlets 的来源可能不同,并且绑定在不同的 WAR 中。但是我们仍然想为 Portlets 增加一些交互,使得 Portal 用户可以创建强大的复合应用。比如说,你可能构建了一个地址簿 Portlet,上面显示了包括用户地址在内的详细信息,这时你想更新一个使用了 Google Maps Web Services 的第三方 Portlet 以在地图上显示该地址。

这就是 JSR 286(参见下图,摘自其规范)引入processEvent()阶段的原因,它会在processAction()阶段结束时触发事件。

事件来源于processAction()处理方法,通过将其绑定到ActionResponse对象(继承了 PortletResponse)实现的。然后 Portlet 容器将事件传播到 Portal 层,由该层负责识别该事件对应的 Portlet 实例。

事件分发已经超出了规范的范畴,但 Portal 层的一个解决方案是找出相同页面中的以及能处理该事件的每个 Portlet 实例。注意,processEvent()方法也可以触发一个新事件。避免调用的死循环是 Portal 的职责。

IPC 配置

Portlet 的 IPC 配置在portlet.xml文件中完成的。

<portlet>
<description xml:lang="EN">TestProcessEvent</description>
<portlet-name>TestProcessEvent</portlet-name>
<display-name xml:lang="EN">Test ProcessEvent</display-name>
<portlet-class>
org.exoplatform.services.portletcontainer.test.portlet2.TestProcessEvent
</portlet-class>
<expiration-cache>0</expiration-cache>
<cache-scope>PRIVATE</cache-scope>
<supports>
<mime-type>text/html</mime-type>
<portlet-mode>view</portlet-mode>
<portlet-mode>edit</portlet-mode>
<portlet-mode>help</portlet-mode>
</supports>
<supported-locale>EN</supported-locale>
<portlet-info>
<title>TestProcessEvent</title>
<short-title>TestProcessEvent</short-title>
</portlet-info>
<supported-processing-event><name>MyEventPub</name></supported-processing-event>
<supported-publishing-event><name>MyEventPub</name></supported-publishing-event>
</portlet>

[...]

<event-definition>
<name>MyEventPub</name>
<value-type>
org.exoplatform.services.portletcontainer.test.portlet2.MyEventPub
</value-type>
</event-definition> 

在这个例子中,同一个 Portlet 发布并处理事件。事实上,在真实情况下这应该是两个不同的 Portlet。注意,在这里使用了事件别名,而且在 XML 文件中关联的事件类只定义了一次。

使用 IPC

Portlet 通常继承GenericPortlet类,该类实现了定义有processEvent()方法的EventPortlet接口。

下面的代码示例展示了如何在processAction()方法中注册事件对象,以及如何在processEvent()方法中获取它:

public void processAction(ActionRequest actionRequest, ActionResponse actionResponse)
throws PortletException, IOException {
MyEventPub sampleAddress = new MyEventPub();
sampleAddress.setStreet("myStreet");
sampleAddress.setCity("myCity");
actionResponse.setEvent(new QName("MyEventPub"), sampleAddress);
}

public void processEvent(EventRequest req, EventResponse resp)
throws PortletException, IOException {
System.out.println("In processEvent method of EventDemo... !!!!!!!!!!!!!");
Event event = req.getEvent();
System.out.println(" -- name: " + event.getName());
System.out.println(" -- value: " + event.getValue());
MyEventPub sampleAddress = new MyEventPub();
sampleAddress = (MyEventPub) event.getValue();
resp.setPortletMode(PortletMode.EDIT);
}

请注意,WSRP 2 规范也支持 processEvent() 阶段,所以事件可以从一个消费者传播到另一个服务器上的生产者,开发者只需确保事件可用 JAXB 序列化即可。

独立式 Portlet 容器

eXo Portlet Container 2.0 可以以独立应用的形式下载,这样可以降低其在第三方应用中的嵌入性。与之相伴的是一个轻量级的管理 Portal,该 Portal 展示了你的 JSR 286 Portlets 的兼容性,如下图所示。你可以选择部署在 WAR 中的一个或者几个 Portlets 并查看他们。

在下图中,我们选择了ResourceDemo Portlet,该图片使用了serverResource()方法,同时还显示了 AJAX 调用后返回的一些标记。

嵌入式 Portlet 容器

由于portlets2.war是标准的 Portlets,你可以在 eXo 企业版 Portal 或者 WebOS 中部署它们来测试并了解新规范。

你需要打开 Application Registry。该管理 Portlet 可以让你分类浏览 Portlets。有一个按钮可以快速导入所有已部署的 Portlet 应用 WARs:

该截图显示了“portlets2”类别,在你部署并导入了portlets2.war后,你就会看到它。在右边,你会看到我们的示例JSR 286 Portlets,像TestPublicParam2TestProcessEvent1

一旦配置好后(可以应用许可),你就可以使用“+”图标将 Portlets 加到桌面页面左边的停靠栏上:

这里很多 Portlets 被加到了停靠栏上,我们显示了其中的三个:TestPublicParam1TestPublicParam2TestPublicParam3。这些 Portlets 显示了新的 JSR 286 公共参数。

该特性可以让你在processAction()阶段设置一些渲染参数。接着就可以在所有 Portlets 的每个render()方法中获取这些参数。而 JSR 168 中,渲染参数只能被关联的 Portlet 访问。

在上面的截图中,如果我们在 Portlet 1 或 2 上选择了“set public param”,我们将看到公共参数会出现在 3 个 Portlets 中。如果我们在第 3 个 Portlet 中点击该链接,那么 Portlet 1 和 2 的公共参数就会被第 3 个 Portlet 的覆盖。

WSRP 2

除了 Java Portlet API,Web Services Remote Portlet 2.0(WSRP)也进行了更新,以支持我们上面讨论的新特性。

现在是时候提供一个插件实现了,该实现会将外部组件暴露给 Portal 层,就好象它们是真的 Portlets 一样。正如下图所示,真实的插件示例——WSRP 2 消费者插件会向 Portal 暴露远程 Portlets,其方式与通过 JSR 286 插件来暴露本地 Portlets 一样。

这样做的好处在于 Portal UI 会对本地和远程 Portlets 进行同样的处理:不再需要 JSR 168 或者 286 代理 Portlets 了。它们非常麻烦,因为用户不得不配置很多东西,如在 Portlet 首选项中配置 WSDL 文件的远程地址。现在配置消费者插件已经变成管理员的职责了。

WSRP 生产者架构基本上没有变化,但是代码被完全重写了。在 2008 年,只要有其它生产者和消费者可供测试,我们就会继续检查生产者 / 消费者与市场上其它产品的交互性。

WebOS: 一种 Portal 布局

从技术上说,WebOS 就是一个针对 Portal 2.0 的简单客户化布局。这意味着 Portlets 无需在容器中。因此,Portlets 和 Widgets 以可拖拽的窗口方式呈现,彼此可以交叠,就像你桌面上标准的操作系统一样,但不同的是,它们都在你的浏览器范围内。当一个页面以多窗口的方式呈现时,通常的 Portal 就可以拥有几种桌面(Portal、组或者用户的)。例如,一个桌面可以包含邮件、日历或内容(如下图所示)这些协作的应用,而另一个桌面可以包含访问 ERP 后端的 Portlet。

WebOS 的目标就是在浏览器中重现最佳的工程学模式。这就是我们在单一环境中混合几个现有操作系统的特性和皮肤的原因。最值得注意的有:

  • 包含 WebOS 页面上 Portlets 的停靠栏。在图标上右击可以从停靠栏上移除该 Portlet。下面两个截图显示了以不同皮肤呈现的停靠栏。“+”按钮可以让你向停靠栏和用户可用的页面上添加 Portlets。
  • 左列的“开始菜单”使用户可以访问发布和管理特性,如创建页面的向导。它还可以改变使用的本地信息以及选择的皮肤。就像前面提到的,它还可以让用户定义一套对所有页面都适用的 Widgets,而那个添加到 WebOS 页面中的 Widgets 只能应用在那里。

eXo Application Registry 中任何 Portlets 和 Widgets 都可以在 WebOS 布局中使用。

eXo Java 内容资源库和 ECM

除了新的 Portal 外,eXo 平台还发布了其企业内容管理(ECM)解决方案的新版本,它跟 Portal 一样以 JCR 为基础。

Java 内容资源库:文件系统

eXo Java 内容资源库(eXo JCR)是对 JSR 170 的实现。它将内容存储在中央资源库中,规定了结构、版本、锁定及搜索内容的方式。与 eXo 平台的其他组件类似,该产品也进行了大量优化和扩展(参见下面的模式)。这些都包含在该产品的独立分发包中,可以从 OW2 forge 上下载。

JCR 模式可分为三个领域:

  • 核心,这部分实现了规范并对 API 进行了扩展,同时解决了 eXo 如何管理存储部分的问题
  • 协议连接器,如 FTP、CIFS、WebDAV、DeltaV、DASL
  • 应用和插件,比如 Microsoft Office 和 Open Office 插件

我们将详细论述这三部分。

JCR 核心

eXo JCR 实现是对 JSR 170 的多资源库与多工作空间的实现。JCR 后端的入口是一个资源库对象,但是规范并没有强制要求提供几个资源库,然而在某些情况下这却非常有用。

一个资源库可以被一个或者几个工作空间访问;再一次强调,规范并没有强制要求支持多个工作空间,但我们实现了它,因为这对我们很有帮助。

在工作空间中你可以浏览 Nodes 树,该 Nodes 可看作是标准文件系统文件夹的增强版。树的叶子是 Property 对象,该对象可分为几种类型,如日期或者二进制。如果我们使用文件系统来做类比,你可以将二进制属性看作文件。下图描绘了这一切:

在 eXo 实现中,每个工作空间都可以指向不同的数据库,在数据库中存储了所有的元数据信息,比如文件路径以及一些属性(像 Dublin 核心元数据)。出于性能上的考虑,文件本身可以存储在关联的文件系统中(NAS、 SAN 等)。每个工作空间还可以配置一些高级缓存、缓冲、替换、索引或者安全策略。有可伸缩性需求时,还可以对资源库进行集群。

下面的 XML 片段显示了用来建立 JCR 环境的客户化配置文件。我们可以在这里定义所有的资源库配置,但是我们只详细说明一个工作空间配置(请阅读 XML 片段中的注释):

<repository-service default-repository="repository">
<repositories>

<!-- ############################################################ -->
<!-- Every repository needs a system workspace to store the JCR -->
<!-- configuration info as well as the version history. The -->
<!-- default workspace is the one that would be returned by the -->
<!-- login method when no workspace is specified -->
<!-- ############################################################ -->
<repository name="repository" system-workspace="system" 
default-workspace="collaboration">

<!-- ########################################################################## -->
<!-- The first step is to define the workspace security such as the JAAS domain -->
<!-- it applies to. It is also possible to plug a custom policy manager there -->
<!-- ########################################################################## -->
<security-domain>exo-domain</security-domain>
<access-control>optional</access-control>
<authentication-policy>org.[..].PortalAuthenticationPolicy</authentication-policy>

<workspaces>
<!-- ######################################################################### -->
<!-- Each repository can contains several workspaces, each one with a Root -->
<!-- Node that can have its own structure and permission configuration -->
<!-- ######################################################################### -->
<workspace name="system" auto-init-root-nodetype="nt:unstructured" 
auto-init-permissions="*:/platform/administrators read;
*:/platform/administrators add_node;
*:/platform/administrators set_property;
*:/platform/administrators remove" >

<!-- ####################################################################### -->
<!-- Each workspace can have its own way to store content but the most usual -->
<!-- one is to use a database for the metadata information and a File System -->
<!-- for the binary documents. Here we use a simple file system but with a -->
<!-- storage algorithm that store the content in a tree to split the number -->
<!-- of files per folder. The Swap directory is used when we upload files -->
<!-- ####################################################################### -->
<container class="org.exoplatform.services.jcr.[..].JDBCWorkspaceDataContainer">
<properties>
<property name="sourceName" value="jdbcexo"/>
<property name="dialect" value="hsqldb"/>
<property name="multi-db" value="false"/>
<property name="update-storage" value="true"/>
<property name="max-buffer-size" value="204800"/>
<property name="swap-directory" value="../temp/swap/system"/>
</properties>
<value-storages>
<value-storage id="system" class="org.[..].TreeFileValueStorage">
<properties>
<property name="path" value="../temp/values/system"/>
</properties>
<filters>
<filter property-type="Binary"/>
</filters>
</value-storage>
</value-storages>
</container>

<!-- ###################################### -->
<!-- Caching, Indexing and locking are then -->
<!-- configured -->
<!-- ###################################### -->
<cache enabled="true">
<properties>
<property name="maxSize" value="20000"/>
<property name="liveTime" value="30000"/>
</properties>
</cache>
<query-handler class="org.exoplatform.services.jcr.[..].SearchIndex">
<properties>
<property name="indexDir" value="../temp/jcrlucenedb/index"/>
</properties>
</query-handler>
<lock-manager>
<time-out>900000</time-out><!-- 15min -->
<persister class="org.exoplatform.services.jcr.[..].FileSystemLockPersister">
<properties>
<property name="path" value="../temp/lock"/>
</properties>
</persister>
</lock-manager>
</workspace>

规范的重要一点就是定义内容结构的能力,我们称其为NodeType。例如在一个公司里,每篇要发布在 Web 上的文章都包括标题、作者、摘要、图片和文章正文。我们使用 NodeType 来定义每个属性、类型(Date、String 等)和其他信息,比如是否必须要有标题。创建的每篇文章都要满足这些 NodeType 标准。

eXo JCR 一个有用的特性就是能通过 Java 接口 API 动态增加新的 NodeType。该 API 可用来开发构建于 eXo JCR 之上的任何类型应用;这就是 eXo ECM 实现的方式。

规范中还增加了其他几个特性,以便第三方应用可以在 JCR 之上构建新服务:

  • 对 Dublin 核心的本地支持,支持每个增加到 JCR 中的 office 文档,因为它会抽取任何 Word、PDF 或者 Open Office 文档的属性,然后附加到 JCR Node 上
  • 审计功能,JCR Nodes 中增加了审计功能,对内容进行的任何行为都会被记录下来
  • 安全,增强了安全性,用户可以使用自己的安全管理器——这对于分析文档的访问是非常方便的
  • 附加的事件通知,除了标准的观测特性外,将改变持久化到工作空间时还会进行事件的分发。eXo JCR 也提供了一个扩展,每当发生瞬时的 Session 级别变化时,都会触发事件。比如说,Dublin 核心的本地支持就采用了这种方式。

最后,我们也开发了一个客户化存储和认证机制,这为我们提供了一个 SaaS 版的 JCR 资源库。为了达到该目的,我们使用了 Amazon Web Services EC2 和 S3 服务。EC2 可以让你动态实例化一个虚拟机,而 S3 是一个 Internet 上的存储系统。因此,如果你有 EC2 和 S3 帐号,你就可以加载一个配置有 eXo JCR 客户化版本的 Amazon Machine Instance(AMI)。它使用 S3 来存储文件,使用 EC2 存放数据库和 JCR 服务器。我们发布的首个 AMI 还带有一个可插拔的认证模块(Pluggable Authentication Module,即 PAM),这使得你可以使用 Linux OS 上定义的用户。

JCR 协议

平台开发一开始,我们就确定了 JCR 在我们存储策略的中心地位,这将在标准的关系数据库上使用一些高级服务。因此,我们开发的每个应用或者产品线上的部分应用都会将其内容存储到 JCR 中。对于大多数企业数据来说这提供了非常棒的集中化。

因此,从第三方应用访问那些信息一定很直接。为了简化交互过程,我们让 JCR 资源库支持很多协议:

  • WebDAV可能是应用最广的一个了,因为它天生就被 Windows、Mac OS 等大多数操作系统所支持。它可以让你将远程服务器装配为本地文件系统的一部分,然后你就可以通过正常的文件系统接口来浏览远程文件夹和文档了
  • DeltaVDASL是 WebDAV 协议的两个 RFC 扩展。他们分别往默认协议中增加了版本控制和搜索功能。eXo 插件如 Microsoft Office 插件广泛地使用了他们,我们在后面将会对其进行详细介绍
  • CIFS是一个在网络上共享驱动的 Microsoft 协议。eXo 已经实现了一个抽象层以使任何 JCR 资源库可用作 CIFS/Samba 服务器
  • RMI是一个 Java 协议,该协议使你可以将 Java 对象暴露给运行在不同 JVMs 上的远程 Java 应用。我们已经实现了所有的 RMI stubs,这样你就可以将整个 JCR API 暴露给远程
  • FTP是一个针对大文件交换进行优化的协议。eXo 在 JCR 之上实现了一个 FTP 服务器。这样我们就可以用简单的客户端来传送 6G 大小的文件(参见下图)

WebDAV 的更多细节

WebDAV 协议就是 HTTP 协议(使用 PUT、GET 及 DELETE 方法)的一个扩展。正如我们所见,JCR 规范允许你处理结构化内容,然而对于一个标准的文件系统来说却不是这样,因此通过该协议我们只能暴露 JCR 语义的一个子集。通常这在处理文档管理系统(Document Management Systems,即 DMS)时就够用了。注意,我们的 WebDAV 服务器完全是通过我们的 REST 框架构建起来的。

下图显示了名为 collaboration 的 JCR 工作空间,它被配置为 Mac OS 文件系统上的一个文件夹:

最后,为了减轻第三方开发者的工作量,我们已经使用 Java 和 C# 创建了一些客户端 APIs,以使用面向对象的方式创建 WebDAV HTTP 请求。下面是用 Java 编写的一个简单的 WebDAV 拷贝方法:

DavContext context = new DavContext("localhost", 8080, "/jcr-webdav/repository");
CopyCommand copy = new CopyCommand(context);
copy.setResourcePath(srcName);
copy.setDestinationPath(destName);
int status = copy.execute();

通过不同协议暴露存储在 JCR 中的内容使得第三方厂商或集成商可以开发与平台交互的客户化应用。

Word 或扫描这样的应用非常常见,因此我们决定直接为现有的产品编写插件。

对 Microsoft Office 2003 套件来说就是这种情况,我们已经为其编写了一个 C# 插件。我们也开发了一个基于 Java 的 OpenOffice 插件。在这两种情况下,我们都使用了 WebDAV、DeltaV 和 DASL 协议。这两个插件的特性几乎一样,并且创建了一个叫做“Remote Documents” 的新菜单。这样的话你可以:

  • 直接在远程 eXo JCR 服务器上保存当前编辑的文档,并且可以保存为几种不同的格式——例如,支持 Word 模板文档(扩展名为.dot 的文件):
  • 支持文档版本,这意味着文档的每次修改都会创建一个新版本:
  • 我们还可以使用 Microsoft Word 内建的比较工具来比较存储在 JCR 服务器上的文档的两个版本:
  • 使用全文检索来查找保存在服务器上的文档。该查找会搜索文档内容和相关的元数据。下图显示了 OpenOffice 中的界面:

由于可以通过很多协议来访问 JCR 存储,所以扩展已有的应用、将其连接到 eXo 内容资源库是非常容易的。另一个有趣的插件(也是基于 WebDAV 协议,使用 C# 编写)是由 Kofax 为 Ascent Capture 应用编写的,这是一个扫描和光学字符识别工具(OCR)。该插件允许你在 JCR 中动态存储输入的打印字母。我们还将提取的信息注入到 JCR 中的文档元数据中。

企业内容管理

eXo 企业内容管理(ECM)是一套 Portlets,提供了 Web 内容、文档和记录管理工具。它构建在 eXo JCR 之上,而且使用了我们上面介绍的功能和连接器。

与 ECM 产品一起的是一些预定义的工作流,你可以定制它们以方便地共享和传播公司中的 ECM 内容和文档。你也可以增加自己的工作流定义,并以很多不同的方式来使用它们。工作流引擎实现本身是可插拔的,我们提供了两个实现(jBPM 和 Bonita),但你也可以编写自己的工作流引擎实现。

eXo Portal 集成是通过某个将要用到的展示层 Portlets 实现的,它提供了很多为 Intranet 或者 Internet/Extranet 呈现 Portal 内容的方式。

这样,eXo ECM 对企业内容的捕获、生产、管理、发布和记录提供了强有力的支持。所有的这一切都以高度可定制的方式进行。但是产品背后的主要驱动力却是为了提供易于使用的高级功能,可以模拟真实操作系统中的应用,如文件浏览器。

ECM 文件浏览器

eXo ECM 的核心组件之一就是文件浏览器。该 Portlet 提供了一个到 ECM 的用户友好的后端入口。

驱动器:

文件浏览器的主目录显示了到受管资源库(我们称其为“驱动器”)的快捷方式。有三类不同的驱动器:

  • 个人驱动器供每个用户使用。里面的文档可以是私有,也可以与其他人共享
  • 组驱动器定义了只能被某些用户组使用的区域
  • 一般驱动器是资源库中其他区域的快捷方式

可以在驱动器上设置权限,这样用户就只能看到与其权限相关的驱动器了。驱动器还可以配置很多其它的内容,如工作空间中的路径或者查看浏览器的视图(缩略图、列表等)。下图显示了 ECM 管理 Portlet 和几个预先配置好的驱动器:

文件浏览

ECM 文件浏览的用户界面简单得就像管理本地计算机上的文件一样。左边是我们熟悉的树形视图,显示文件夹。上边的地址栏显示了当前文件夹的路径。右边的工作区显示了文件夹中的文件。工具栏可以让用户操纵内容。工具栏的行为可以按主题分组(比如一般、协作、查找),同时 UI 提供了一种我们所熟悉的感官,就像 Windows 的文件浏览器一样:

根据用户的权限,用户可对文档进行不同的操作。一些 Web 2.0 特性,如标记、对文档的投票或者评论(参见下图),可被所有的记录管理文档所禁用,但是对于发布在 Web 上的内容来说它们又是可用的。

还有其他几个动作,涵盖管理文档版本这样的通用行为和检查文档结构(比如与其关联的是何种元数据)这样的复杂行为。

该 Portlet 是任何文档管理、记录管理及 Web 内容管理的基础。

文件浏览器能让你轻松地向 File Plan 中添加一个文档,然后这就会成为一条记录,在切断二者之间的关系前会一直保存在系统中。eXo ECM 实现了 DOD 5015.2 记录管理规范。当上传一个 PDF 时,PDF 的属性就被抽取出来,然后绑定到 JCR 中的文档上。接下来,我们可以就该元数据执行高级搜索,例如寻找所有在“subject”属性上具有 Dublin 核心“Article”值的文档。在下图中,我们上传了一个 PDF 文档并执行一个高级搜索。表单允许我们选择想要搜索的 Dublin 核心属性。接下来会出现一个弹出窗口,在这里可以选择属性具备的所有值。在该截图中,属性值列表只有唯一的一个值 “Article”。我们还可以组合属性约束以执行更高级的搜索:

eXo ECM 中,我们可以通过 JCR NodeType 特性来指定文档的结构。这是依靠管理 Portlet(在“Type of content”部分内)实现的。一旦创建了内容结构,下一步就是创建表单以使你可以创建内容实例。在 eXo ECM 中该表单叫做会话(dialog),它作为一个 Groovy 模板存储在 eXo JCR(因此可以版本化)中。下面的截图显示了一个拥有几个字段(名字、标题、概要等)的表单。该表单是渲染过的会话模板,并且每个字段都具备几种类型(如富文本编辑器、日历、上传等)。所见即所得的编辑器使你可以从 JCR 资源库中导入图片、上传图片到资源库中。一旦创建好后,就可以渲染结构化内容了,这要归功于另一个叫做视图模板(view template)的 Groovy 模板,它使用一些 HTML 代码简化了内容实例字段的修饰。现在工作流进程就准备验证内容并将其发布到 Web 上了。

行为(Actions)

eXo ECM 的一个主要特性就是可以将行为附加到任何节点(包括文件夹)上。行为可以在节点的生命周期(如增加、更新、删除)中得到回调,这样它就可以开始一个新的工作流或者运行一段客户化 Groovy 脚本。

下图显示了默认的文档验证工作流,文档只要被拷贝到“Validation Request”文件夹中,该验证工作就会被激活。文档验证一旦通过,就会被拷贝到“Pending”文件夹中等待发布。当发布日期来临时,文档会被移动到“Live”文件夹中直到发布日期结束:

当文档处于 Live 文件夹中时,第三方应用或者内容发布 Portlets 只需指向该文件夹,进行一次查询以抽取出最后一个发布的文档,并使用一个漂亮的 HTML 模板来呈现它。

行为也可以加载 Groovy 脚本,那么让我们用一个简单的脚本来解释一下这个功能,该脚本使用 DocumentReaderService 在控制台中打印文档元数据属性,如作者和创建日期。事实上,行为脚本也使用了 IoC,这会简化对注册的 eXo 服务的调用:

public class DisplayDocumentProperties implements CmsScript {

private DocumentReaderService readerService;
private RepositoryService repositoryService;

// Constructor injection of needed eXo services
public DisplayProperties(RepositoryService repositoryService, 
DocumentReaderService readerService) { 
this.repositoryService = repositoryService ;
this.readerService = readerService;
}

public void execute(Object context) {

// load node from JCR
String workspace = context["srcWorkspace"];
String path = context["srcPath"];
Node document = (Node) repositoryService.getRepository()
.getSystemSession(workspace).getItem(path);

// extract document properties
String mime = document.getProperty("jcr:mimeType").getString();
InputStream data = document.getNode("jcr:content")
.getProperty("jcr:data").getStream();
Set properties = readerService.getDocumentReader(mime)
.getProperties(data).entrySet();

// display properties
propreties.each() { print " ${it.key} ${it.value}" };
} 

我们可以利用管理用户界面增加脚本,然后将其绑定到一个行为上,当然也就绑定到了一个 JCR Node 上。比如说,在发布过程中,我们也可以基于最近发布的新闻动态生成 RSS 种子(该行为存在于 eXo ECM 本地)。

业务模型

eXo 平台的 SAS 业务模型是一个传统的开源模型,与 Red Hat 及 MySQL 非常相似。eXo 为最终用户和公司提供了两个发布包:

可以从 OW2 forge 上免费下载社区版。该社区版每隔 2、3 周就会发布新的版本,其中包含所有最新的代码和提交物。在其之上应用了一个基本的 QA 过程。我们在三个开源的应用服务器上对其进行了测试:Tomcat、JOnAS 和 JBoss。其源代码放置在公共的 SVN 服务器上,具体位于上面所述的每个模块的 trunk 部分上。

企业版是社区版的一个稳定化版本。它在 SVN 服务器上有自己的分支,而且只对购买了年度许可的人开放。每个产品的发布周期约为 6 到 9 个月,发布时会对 SVN 服务器上的源代码打标记。该分发包的 QA 过程更加宽泛,有 1500 多个集成测试用例,还有压力测试。该分发包在几个收费的应用服务器上都通过了测试,比如 BEA WebLogic、Oracle Application Server 和 IBM Websphere,还有收费的数据库。同时该分发包还含有管理和用户手册。最后,该分发包也应用了扩展的授权和保护。

eXo 平台 SAS 还面向另外两个市场:

  • OEM 市场,独立软件供应商(ISV)会将 eXo 产品与其应用绑定。根据客户需求会使用专门的协议和特权模型(royalty models)。
  • SaaS(Software as a Service,软件即服务)可以让你租借 eXo 应用一段时间。从技术上说,该模型使用了 Amazon EC2(分配了一些虚拟机)和 S3(使你可以在云中存储数据)。

查看英文原文:An Overview of the eXo Platform


志愿参与 InfoQ 中文站内容建设,请邮件至editors@cn.infoq.com。也欢迎大家到InfoQ 中文站用户讨论组参与我们的线上讨论。

收藏

评论

微博

发表评论

注册/登录 InfoQ 发表评论