作者 Benjamin Mestrallet and Tugdual Grall译者 张龙 发布于 2008年12月5日 上午9时14分
我们很高兴地宣布,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)所提供的新特性。
相关厂商内容
使用JSF、Ajax和Seam开发Portlets(2/3)
相关赞助商
InfoQ中文站Java社区,关注企业Java社区的变化与创新,通过新闻、文章、视频访谈和演讲以及迷你书等为中国Java技术社区提供一流资讯。
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中,一个请求被分成两个阶段:
processAction()
:它处理单独的Portlet,以改变一个业务对象(类似于JavaBean)的状态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
,像TestPublicParam2
和TestProcessEvent1
。
一旦配置好后(可以应用许可),你就可以使用“+”图标将Portlets加到桌面页面左边的停靠栏上:
这里很多Portlets被加到了停靠栏上,我们显示了其中的三个:TestPublicParam1
、TestPublicParam2
、TestPublicParam3
。这些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等大多数操作系统所支持。它可以让你将远程服务器装配为本地文件系统的一部分,然后你就可以通过正常的文件系统接口来浏览远程文件夹和文档了
- DeltaV和DASL是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。
转自:http://www.infoq.com/cn/articles/exo-platform