Apache 門戶項目組介紹

級別: 初級

廖 健, 首席實施顧問

2006 年 11 月 02 日

本文將快速瀏覽 Apache 門戶項目組的所有項目,並着重介紹門戶項目組中的核心項目-Jetspeed-2。

引言

JEE作爲建立在 Java平臺上的企業級應用解決方案,經過這些年不斷髮展,已經成爲企業級開發的工業標準和首選平臺。衆多廠商如IBM,BEA和Oracle等都圍繞該規範推出了相應的,功能強大的產品。JEE規範組中最受業界認同和取得最大成功的就是JEE Web層面規範,發展到今天,已經步入門戶(Portal)的時代。

門戶,簡言之就是提供包括內容聚合、單點登陸、個性化定製和安全管理等服務的基礎Web平臺。衆多JEE產品提供商基於JEE Web層技術推出了自己的Portal產品,著名的產品有IBM WebSphere Portal Server,BEA Weblogic Portal Server等。一直處於技術前沿的著名開源社區Apache,經過這幾年的技術積累也形成了自己的門戶項目組。該項目組目前已經初具規模,並且擁有了一定的用戶羣體,經受了一定的市場考驗。

本文主要面向有一定JEE編程經驗的Java開發者和試圖構建自己的門戶軟件產品的產品經理,因爲基於開源項目構建企業級的商用產品,已經在國外取得了許多成功案例。



回頁首

名詞解釋

Portal
門戶,提供包括內容聚合、單點登陸、個性化定製和安全管理等服務的基礎Web平臺。

Portlet
Portlet 是基於web的Java組件。它由Portlet容器管理,能夠處理請求,產生動態內容。Portlet被Portal用作爲可插拔的用戶接口組件,爲信息系統提供展現。由Portlet動態產生的內容也被叫做fragment。fragment是遵循某種規則的標記(例如:HTML, XHTML,WML),可與其他的fragment一起建立一個完整的文檔。一般一個Portlet產生的內容和其他的Portlet產生的內容聚集在一起形成Portal網頁。

Portlet Container
Portlet在Portlet容器中運行,Portlet容器爲Portlet提供必需的運行環境。Portlet容器包含Portlet(組件)並且管理它們的生命週期,它也爲 Portlet的參數設置提供持久化的存儲。Portlet 容器不是一個類似於 servlet 容器的獨立容器。它是在 servlet 容器上通過擴展方式實現的,並重用 servlet容器提供的功能。從Portal的角度來看,Portlet Container是Portal平臺所提供的衆多服務之一。

JSR168,JSR286
由於越來越多的公司開發了各自的Portal組件和基於其的Portal產品(如Bea, IBM, Oracle, Sun, Sybase, Novell, SAP, Jetspeed, Vignette 等.這種互不兼容的接口實現不斷帶給程序提供商各種問題和麻煩, 爲了解決這種問題, JCP發佈了JSR168 (Java Specification Request), Portlet Specification, 用以提供不同Portal和Portlets之間的互用性。JSR 286是168規範的延伸,是目前最新標準規範,目前仍處在draft狀態。

SSO Single
Sign -On,即單點登陸。當一個大系統中存在多個子系統時,用戶只需要正確登陸其中任何一個子系統,就可以在各個子系統中來回自由切換和使用授予該用戶權限的各種資源。一般可以分爲兩種類型:Web應用之間的單點登陸和門戶Web應用和它所連接的後臺系統之間的單點登陸。SSO是任何一個門戶產品必須解決的問題,必須提供的服務。

WSRP
WSRP是OASIS組織的一個規範,它定義了遠程門戶網站的Web服務。通過Web Service將遠程內容抓取到本地,最後通過本地內容聚合引擎展示出來。



回頁首

Apache門戶項目組整體架構

在引言中已經列舉了Apache門戶項目組的組成項目包括:Jetspeed-1/2,Bridges,Pluto,WSRP-4J和Graffito。由於Jetspeed-1和Jetspeed-2角色相同,下文中如果沒有特別指出,所有Jetspeed都是指Jetspeed-2。

圖一Apache門戶項目組架構圖
圖一Apache門戶項目組架構圖

上圖中粉紅色包圍部分爲Apache門戶項目,其它由土黃色包圍部分爲它們的依賴項目。通過上圖可以很清楚看到,全部項目都構建在JEE Web Tier上,理論上只要支持Servlet 2.3或以上版本規範的Web容器,都可以作爲Apache門戶項目的基礎平臺,但Jetspeed官方其實僅僅聲明Tomcat是其唯一支持的Web容器。另一塊必要的依賴,是構建在O/R mapping項目Apache OJB之上的數據倉庫,用於存放Portal系統信息和用戶個性化配置(Profile)。

Portals Bridges項目其本質就是由一組類庫構成的輕量級框架,通過該橋接器框架可以在門戶上支持衆多流行的Web框架,如上圖括號中所列舉。用戶通過它可以很容易的將已有的基於這些流行Web框架的Web應用程序,通過少量的修改和配置,作爲Portlet應用程序發佈單元發佈到Portal上。這個項目不但在Jetspeed上取得的成功,還被衆多開源的,甚至商用的門戶實現所使用,如JBoss PortalGridSphere PortalStringbeans PortalVignette Application PortalApache Cocoon PortalJetspeed Portal

Jetspeed項目是整個Apache Portal項目組的核心,它是一個功能完備的,易於擴展的企業級Portal實現,將在下面的文章中着重介紹它。

Pluto是Jetspeed默認的本地 Portlet Container實現,它是一個完全符合JSR-168規範的Portlet容器實現,其前身爲IBM捐贈的源代碼,因此我們至今還能夠在 WebSphere Portal 5.1.1中看到它的身影。這裏要注意本地的意思是指運行在該Portlet容器裏的Portlet應用程序在物理上與Portal在同一個JVM進程中。

WSRP-4j是WSRP規範的JAVA實現,目前該項目還處在孵育狀態,尚未吸引到足夠多的開發者的興趣。其實,我個人認爲這是一個很有前途的技術發展方向,它可以提供類似Html IFrame這樣速成的內容抓取能力。Jetspeed已經爲WSRP-4j預留了遠程Portlet Container的配置選項。

Graffito是用於構建內容管理應用程序的框架,從它自身的架構設計上來看應該是一個獨立平臺,但事實上該項目複用了大量Jetspeed的模塊,並且其表現層爲發佈到Jetspeed上的幾個Portlet應用程序,因此,我在上面的架構圖中,將它放在了Jetspeed之上。該項目目前也處在孵育狀態下,由於其該項目目前不太活躍,那幾個Portlet應用程序都有些小問題。



回頁首

企業級的門戶實現--Jetspeed

產品特性

標準

  • 完整兼容Java Portlet API標準1.0(JSR-168)
  • 通過JSR-168規範兼容性測試
  • 基於JAAS標準的認證和授權服務(默認支持數據庫的實現)
  • 基於LDAP的用戶認證

體系架構

  • 基於Spring Framework的組件架構
  • 靈活可配置的請求通道(通過Spring Bean XML配置)
  • Portlet應用發佈單元熱部署
  • Jetspeed AJAX XML API(基於著名的開源AJAX Framework - DOJO)
  • 擴展的Portlet頁面結構語言(支持持久化到文件或數據庫)

門戶核心特性

  • 聲明風格的安全約束
  • 基於角色的Portlet安全方面的API
  • 門戶內容管理和導航,包括頁面、菜單、文件夾和超鏈接
  • 單線程或多線程的內容聚合引擎(通過Spring Bean可以輕易切換)
  • 高度可擴展的Jetspeed 單點登陸服務框架
  • 基於權限和規則的門戶頁面和資源定位配置
  • 支持所有主流的數據庫,包括:Derby、MySQL、MS SQL、Postgres、Oracle、DB2、 Hypersonic
  • 不依賴客戶端類型的capability engine (html, xhtml, wml,vml)
  • 多語言支持(12國語言,包括簡體中文和繁體中文),而且完全可擴展
  • 完整的性能統計日誌引擎
  • 利用著名開源搜索引擎Lucene提供對所有門戶資源的全文本檢索和元數據搜索服務
  • 用戶註冊服務和忘記密碼的郵件通知服務
  • 豐富的登陸密碼配置策略

門戶管理

  • 用戶,角色,用戶組,密碼和Profile管理
  • JSR 168協議規範定義的用戶屬性編輯器
  • 門戶頁面管理
  • 單點登陸服務管理
  • Portlet應用程序管理
  • Profiler管理
  • 門戶性能統計報告

對Web框架的支持和例子Portlets

  • 通過Bridges項目支持幾乎所有的主流Web Framework與Jetspeed門戶的整合,包括:JSF(Sun的標準JSF實現和Apache MyFaces)、Apache Struts、PHP、Perl、Velocity
  • 例子Portlet包括:RSS、IFrame(通過Jetspeed SSO API還可以支持SSO效果)、日曆、書籤。
  • 支持Spring MVC

用戶個性化

  • 門戶頁面管理
  • 頁面用戶定製(包括增刪查改門戶頁面,頁面的風格,Portlet框體風格,Portlet的位置,Portlet的佈局等等)
  • 支持兩種門戶定製風格,包括傳統的基於頁面刷新的風格和基於AJAX技術的風格

門戶設計

  • 支持Portlet和Portal頁面皮膚的打包發佈
  • 基於CSS技術的可配置佈局
  • 支持Velocity模版引擎

門戶開發工具

  • 支持Maven 1.x和Maven2.0.x,部分功能支持Ant腳本
  • 支持通過Maven插件生成自定義門戶基礎框架
  • 熱部署Portlet應用發佈單元和門戶資源
  • 支持通過API調用的方式部署Portlet應用發佈單元
  • 支持Eclipse3.2.x開發環境

應用服務器

  • Apache Tomcat 5.0.x
  • Apache Tomcat 5.5.x
  • IBM WebSphere Application Server 5.1/6.0
  • JBoss
  • Geronimo 1.0(非官方支持,詳見:JS2-444)

架構體系

本節將從Jetspeed和Spring的關係,運行時架構以及Jetspeed service架構這三方面詳細介紹Jetspeed的架構體系。

Jetspeed和Spring

Jetspeed架構體系最大特點,也是其高度可訂製的根基就是,它選用著名開源POJO框架Spring作爲其底層實現。在項目之初, Jetspeed的開發者們也面臨着Spring和PicoContainer的抉擇,但事實證明當初的選擇是正確的,因爲隨着Spring不斷成長完善,Jetspeed的組件架構也跟着收益良多。從另一個角度來看,Jetspeed也可以作爲利用Spring構建自己產品架構的經典範例,值得我們考察和學習。

下圖簡單描述了目前Jetspeed對Spring的依賴關係:

圖二 Jetspeed使用到的Spring組件
圖二 Jetspeed使用到的Spring組件

Beans BeanFactory and the ApplicationContext

Jetspeed主要使用了Spring最核心的IoC引擎BeanFactory和ApplicationContext,管理所有 Jetspeed Components的生命週期和依賴關係,所有這些組件的Spring聲明全部定義在名爲assembly的文件夾中的XML文件裏。如果第三方開發者認爲默認的Jetspeed組件不足以滿足要求,只要按照自己需求編寫Jetspeed Component的Interface的實現類,然後修改Spring Bean XML定義,就可以輕易替換掉默認的實現。例如:

Jetspeed SearchEngine Component Definition

<beans default-lazy-init="false" default-dependency-check="none" default-autowire="no"> <!-- SEARCH COMPONENT --> <bean id="org.apache.jetspeed.search.SearchEngine" class="org.apache.jetspeed.search.lucene.SearchEngineImpl" abstract="false" singleton="true" lazy-init="default" autowire="default" dependency-check="default"> <constructor-arg index="0"> <value>${applicationRoot}/WEB-INF/search_index</value> </constructor-arg> <constructor-arg index="1"> <null /> </constructor-arg> <constructor-arg type="boolean"> <value>true</value> </constructor-arg> <constructor-arg> <ref bean="org.apache.jetspeed.search.HandlerFactory" /> </constructor-arg> </bean> </beans> 

Jetspeed在實現過程中遵循着面向接口編程的最佳實踐,上圖中的Bean id爲org.apache.jetspeed.search.SearchEngine,事實上這是一個定義在覈心jetspeed-api組件中的接口,org.apache.jetspeed.search.lucene.SearchEngineImpl爲該接口的實現類,這個類定義在 components/search組件中,後面的內容就是SearchEngineImpl的構造函數的輸入參數,注意最後一個參數 org.apache.jetspeed.search.HandlerFactory也是一個Java Interface的接口。Spring在實例化SearchEngine的時候,會首先分析它的構造函數參數是否已經全部滿足條件(實例化), Spring會根據搜索bean id爲org.apache.jetspeed.search.HandlerFactory的bean,如果已經實例化就直接注入到 SearchEngineImpl的構造函數調用裏;如果沒有就實例化這個bean之後,再注入。

Apache OJB O/R Mappers

由於Spring對Apache OJB提供良好的支持,因此Jetspeed中與數據庫相關的功能基本上都用過Spring的PersistenceBrokerDaoSupport實現。這些組件包括:Capablity、DatabasePageManager、PipeLine、Preferences、Profiler、 Registry、Security、SSO等。O/R Mapping的信息定義在上面這些組件jar包中的JETSPEED-INF/ojb/%component name%_repository.xml文件中,其中%component name%需要用組建名稱替代。

Declarative transaction management

在Jetspeed中,你找不到一行有關於數據庫事務的代碼,這是因爲它採用了Spring的declarative transaction機制,下面一段XML定義了SSOProvider的事物管理:

Declarative Transaction

<beans default-lazy-init="false" default-dependency-check="none" default-autowire="no"> <!-- SSO Implementation --> <bean id="PersistenceBrokerSSOProvider" class="org.apache.jetspeed.sso.impl.PersistenceBrokerSSOProvider" init-method="init" abstract="false" singleton="true" lazy-init="default" autowire="default" dependency-check="default"> <constructor-arg index="0"> <value>JETSPEED-INF/ojb/sso_repository.xml</value> </constructor-arg> </bean> <bean id="org.apache.jetspeed.sso.SSOProvider" parent="baseTransactionProxy" name="ssoProvider" abstract="false" singleton="true" lazy-init="default" autowire="default" dependency-check="default"> <property name="proxyInterfaces"> <value>org.apache.jetspeed.sso.SSOProvider</value> </property> <property name="target"> <ref bean="PersistenceBrokerSSOProvider" /> </property> <property name="transactionAttributes"> <props> <prop key="addSite*">PROPAGATION_REQUIRED</prop> <prop key="updateSite*">PROPAGATION_REQUIRED</prop> <prop key="removeSite">PROPAGATION_REQUIRED</prop> <prop key="addCredentialsForSite">PROPAGATION_REQUIRED</prop> <prop key="updateCredentialsForSite">PROPAGATION_REQUIRED</prop> <prop key="removeCredentialsForSite">PROPAGATION_REQUIRED</prop> <prop key="setRealmForSite">PROPAGATION_REQUIRED</prop> <prop key="*">PROPAGATION_SUPPORTS</prop> </props> </property> </bean> </beans> 

由上圖可知,通過Spring 的Declarative Transaction機制,Jetspeed很輕易實現了細顆粒度的事物管理,用戶可以很容易配置需要管理事務的方法,如"addSite*"、 "updateSite*"和"removeSite"等等,其中"*"爲通配符,詳細信息見此處

Spring MVC

由於Jetspeed對Spring的天生依賴,很自然Jetspeed也支持基於Spring MVC framework,詳見Jetspeed自帶的例子Portlet應用程序。

Jetspeed 組件架構啓動過程

看了前面的介紹,你一定想知道Jetspeed是如何將基於Spring的組件架構和標準JEE Web Application架構融合在一起,本節將通過描述Jetspeed Web Application的啓動過程來了解融合的細節。首先請看下圖:

圖五 Jetspeed Portal啓動流程圖
圖五 Jetspeed Portal啓動流程圖

請點擊這裏查看Jetspeed Portal啓動流程圖的大圖

由上圖可知,Jetspeed Portal從JEE角度來看其實就是一個標準的Web應用程序,只不過在Servlet架構上引入了Component Manager的概念,然後用Spring實現了ComponentManager接口。因此如果你不滿意Spring :,更換它也是有可能的。當Servlet被容器停止時,也會同時關閉SpringComponentManager。Servlet啓動完畢後,所有通過Spring Bean XML定義POJO都被實例化了,除了那些指定了lazy init屬性爲true的Bean。

Runtime架構

JetspeedServlet

Jetspeed的運行時大環境是符合Servlet 2.3或以上規範的JEE Web容器,因此大家可以通過觀察其web.xml瞭解或擴展其功能。下面是Jetspeed.war的web.xml:

<web-app> <display-name>Jetspeed-2 Enterprise Portal</display-name> <!-- Log4JConfigurator context-listener parameters --> <context-param> <param-name>log4j.config.file</param-name> <param-value>/WEB-INF/conf/Log4j.properties</param-value> </context-param> <context-param> <param-name>log4j.config.webApplicationRoot.key</param-name> <param-value>applicationRoot</param-value> </context-param> <filter> <filter-name>AJAXFilter</filter-name> <filter-class>org.apache.jetspeed.ajax.AJAXFilter </filter-class> </filter> <filter-mapping> <filter-name>AJAXFilter</filter-name> <url-pattern>*.ajax</url-pattern> </filter-mapping> <listener> <listener-class>org.apache.jetspeed.webapp.logging. Log4JConfigurator</listener-class> </listener> <listener> <listener-class>org.apache.jetspeed.engine.JetspeedServlet </listener-class> </listener> <servlet> <servlet-name>jetspeed</servlet-name> <servlet-class>org.apache.jetspeed.engine.JetspeedServlet </servlet-class> <init-param> <param-name>properties</param-name> <param-value>/WEB-INF/conf/jetspeed.properties</param-value> </init-param> <init-param> <param-name>applicationRoot</param-name> <param-value>webContext</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> - <!-- Define Velocity template compiler --> <servlet> <servlet-name>velocity</servlet-name> <servlet-class>org.apache.jetspeed.velocity. JetspeedVelocityViewServlet</servlet-class> <init-param> <param-name>org.apache.velocity.toolbox</param-name> <param-value>/WEB-INF/toolbox.xml</param-value> </init-param> <init-param> <param-name>org.apache.velocity.properties</param-name> <param-value>/WEB-INF/velocity.properties</param-value> </init-param> <init-param> <param-name>org.apache.jetspeed.cache.size</param-name> <param-value>50</param-value> </init-param> <init-param> <param-name>org.apache.jetspeed.cache.validation.interval </param-name> <param-value>10000</param-value> </init-param> <load-on-startup>10</load-on-startup> </servlet> <servlet> <servlet-name>LoginProxyServlet</servlet-name> <servlet-class>org.apache.jetspeed.login.LoginProxyServlet </servlet-class> </servlet> <servlet> <servlet-name>LoginServlet</servlet-name> <servlet-class>org.apache.jetspeed.login.LoginServlet </servlet-class> </servlet> <servlet> <servlet-name>LoginErrorServlet</servlet-name> <servlet-class>org.apache.jetspeed.login.LoginErrorServlet </servlet-class> </servlet> <servlet> <servlet-name>LoginRedirectorServlet</servlet-name> <servlet-class>org.apache.jetspeed.login.LoginRedirectorServlet </servlet-class> </servlet> <servlet> <servlet-name>LogoutServlet</servlet-name> <servlet-class>org.apache.jetspeed.login.LogoutServlet </servlet-class> </servlet> <servlet> <servlet-name>ManagerServlet</servlet-name> <servlet-class>org.apache.jetspeed.manager.ManagerServlet </servlet-class> </servlet> <servlet-mapping> <servlet-name>jetspeed</servlet-name> <url-pattern>/portal/*</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>jetspeed</servlet-name> <url-pattern>/portlet/*</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>jetspeed</servlet-name> <url-pattern>/jetspeed/*</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>jetspeed</servlet-name> <url-pattern>/fileserver/*</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>jetspeed</servlet-name> <url-pattern>/ajaxapi/*</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>jetspeed</servlet-name> <url-pattern>/desktop/*</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>jetspeed</servlet-name> <url-pattern>/action/*</url-pattern> </servlet-mapping> - <!-- Map *.vm files to Velocity --> <servlet-mapping> <servlet-name>velocity</servlet-name> <url-pattern>*.vm</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>LoginProxyServlet</servlet-name> <url-pattern>/login/proxy</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>LoginServlet</servlet-name> <url-pattern>/login/login</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>LoginErrorServlet</servlet-name> <url-pattern>/login/error</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>LoginRedirectorServlet</servlet-name> <url-pattern>/login/redirector</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>LogoutServlet</servlet-name> <url-pattern>/login/logout</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>ManagerServlet</servlet-name> <url-pattern>/manager/*</url-pattern> </servlet-mapping> <!-- The Usual Welcome File List --> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> <!-- JNDI Db resource --> <resource-ref> <description>DB Connection</description> <res-ref-name>jdbc/jetspeed</res-ref-name> <res-type>javax.sql.DataSource</res-type> <res-auth>Container</res-auth> </resource-ref> <!-- Protect LogInRedirectory.jsp. This will require a login when called --> <security-constraint> <web-resource-collection> <web-resource-name>Login</web-resource-name> <url-pattern>/login/redirector</url-pattern> </web-resource-collection> <auth-constraint> <!-- the required portal user role name defined in: --> <!-- /WEB-INF/assembly/security-atn.xml --> <role-name>portal-user</role-name> </auth-constraint> </security-constraint> <!-- securing the ManagerServlet --> <security-constraint> <web-resource-collection> <web-resource-name>Manager</web-resource-name> <url-pattern>/manager/*</url-pattern> </web-resource-collection> <auth-constraint> <role-name>admin</role-name> </auth-constraint> </security-constraint> <!-- Login configuration uses form-based authentication --> <login-config> <auth-method>FORM</auth-method> <realm-name>Jetspeed</realm-name> <form-login-config> <form-login-page>/login/login</form-login-page> <form-error-page>/login/error</form-error-page> </form-login-config> </login-config> </web-app> 

由於信息太多,這裏只能選取重要的來解釋。首先,請注意名爲 jetspeed的servlet,這就是前面一個小節裏提到的入口servlet,它同時也是Portal runtime的入口,它被映射到幾乎所有的URL Pattern。當來自客戶端的Http請求滿足這些Pattern時,jetspeed servlet將會觸發如下圖所示的處理流程:

圖六 Jetspeed runtime execution process
圖六 Jetspeed runtime execution process

請點擊這裏查看Jetspeed runtime execution process的大圖

JetspeedServlet 首先會通過RequestContextComponent爲當前Http Request創建RequestContext實例,然後在這個context下調用engine的service方法。然後就會進入Pipeline 的處理過程。

Pipeline

Jetspeed Pipeline實際上就是設計模式中常見的Chain of Responsibility模式的具體實現,其設計概念類似Servlet Filter,一個封裝了HttpServletRequest和HttpServletResponse Object的Context在Pipeline中傳遞,每個valve都根據自己的需要從HttpServletRequest 對象中獲取信息並將處理的結果寫入context或HttpServletResponse對象,以傳遞給後面的valve使用。

這些Valve的定義和排序都是通過Spring Bean來配置的,定義文件爲pipelines.xml,下面爲該文件片斷截取:

<bean id="securityValve" class="org.apache.jetspeed.security.impl.SecurityValveImpl" init-method="initialize" abstract="false" singleton="true" lazy-init="default" autowire="default" dependency-check="default"> <constructor-arg> <ref bean="org.apache.jetspeed.profiler.Profiler" /> </constructor-arg> <constructor-arg> <ref bean="org.apache.jetspeed.security.UserManager" /> </constructor-arg> <constructor-arg> <ref bean="PortalStatistics" /> </constructor-arg> </bean> 
<bean id="jetspeed-pipeline" class="org.apache.jetspeed.pipeline.JetspeedPipeline" init-method="initialize" abstract="false" singleton="true" lazy-init="default" autowire="default" dependency-check="default"> <constructor-arg> <value>JetspeedPipeline</value> </constructor-arg> <constructor-arg> <list> <ref bean="capabilityValve" /> <ref bean="portalURLValve" /> <ref bean="securityValve" /> <ref bean="localizationValve" /> <ref bean="passwordCredentialValve" /> <ref bean="loginValidationValve" /> <ref bean="profilerValve" /> <ref bean="containerValve" /> <ref bean="actionValve" /> <ref bean="DecorationValve" /> <ref bean="aggregatorValve" /> <ref bean="cleanUpValve" /> </list> </constructor-arg> </bean> 
<bean id="pipeline-map" class="java.util.HashMap" abstract="false" singleton="true" lazy-init="default" autowire="default" dependency-check="default"> <constructor-arg> <map> <entry key="/portlet"> <value>portlet-pipeline</value> </entry> <entry key="/portal"> <value>jetspeed-pipeline</value> </entry> <entry key="/ajaxapi"> <value>ajax-pipeline</value> </entry> <entry key="/login"> <value>jetspeed-pipeline</value> </entry> <entry key="/fileserver"> <value>fileserver-pipeline</value> </entry> <entry key="/desktop"> <value>desktop-pipeline</value> </entry> <entry key="/action"> <value>desktop-action-pipeline</value> </entry> </map> </constructor-arg> </bean> 

上面表格第一行定義了Security Valve,第二行定義了名爲JetspeedPipeline的一個Pipeline,第三行定義了這些Pipeline對應的URL Pattern。門戶開發者可以很容易的定義自己特有的Pipeline Valve(只需要實現org.apache.jetspeed.pipeline.valve.Valve接口,並在這個xml文件中定義它),或者改變現有Pipeline中valve執行順序,甚至創建新的Pipeline,並把它映射到某個URL Pattern上。但這裏需要注意的是新URL Pattern映射不能跟現有的重複,這是因爲映射是通過Map數據結構實現。讓我們再把注意力返回到圖六,接下來的Container處理由一個豎線分隔,這是由於在Pipeline的aggregatorValve發生了cross context dispatch。需要注意的是Valve的實現類並不是Thread Safe的,開發者必須自己管理共享變量,最好就是不要定義對象成員變量,全部使用方法內部變量。

Jetspeed的設計者之所以要自己實現這種鏈式模式而不直接使用Filter的可能原因有二:其一,Filter是標準Servlet規範定義的接口,使用上肯定受規範限制,爲了獲取更大的控制權和靈活度需要自己的Pipeline。其二, Application Server,由於每種應用服務器可能對Filter的實現和配置方法上有異,爲了實現一個Jetspeed Portal能夠在各種應用服務器上發佈並運行,因而必須自己實現類似Filter的功能。

Container

講到這裏,我們必須首先理清楚Portal和Portlet Container之間的關係。Portal並不等價於Portlet Container,一個企業級的門戶實現,可以包含或者說支持多種Portlet Container同時運行,例如IBM WebSphere Portal就既包含兼容JSR-168規範的Portlet Container又包含了支持一些IBM特有功能屬性的Portlet Container;BEA WebLogic也是採用相同策略,既有其舊有的基於Struts技術的Portlet Container,又支持JSR-168標準。隨着JSR標準規範進一步深化完善,如JSR-286規範定稿,也許這些大的Portal廠商會逐漸放棄其舊的架構,完全擁抱標準。

對於Jetspeed而言,它同樣關注的是門戶本身的實現,而不是Portlet Container的實現,但由於Jetspeed-2完全拋棄Jetspeed-1的架構,而決定徹底擁抱標準,因此Jetspeed只有一個標準的 Portlet Container實現,這就是Pluto項目。

Pluto的設計目標,其實既是一個完整的、自包含的輕量級門戶,又是一個易於內置的Portlet Container。它不需要Jetspeed也能夠單獨工作,例如Apache唯一的應用服務器項目Geronimo就內置Pluto Portlet Container,作爲它的Console實現平臺。然而,這與Jetspeed的定位起了衝突。在2005年底的Apache Con上,門戶項目組開發者齊聚一堂,就該問題作了深入的探討,也許這兩個項目在將來會更加關注於自身的技術領域。

Jetspeed所內置的Pluto版本爲穩定的1.0.1版,它所提供的內置集成方式不如目前尚未正式發佈的Pluto1.1版本豐富,因此 Jetspeed採用了Servlet 規範中的Cross Context Dispatch機制,將之集成了起來,這就是爲什麼Runtime架構圖上,兩者要用豎線分隔開來,因爲他們其實是兩個完全不同的Web應用,有着不一樣的Context設置,之間通過Cross Context Dispatch機制聯繫起來的。因此當門戶開發者需要在其它應用服務器上部署Jetspeed門戶時,必須注意開啓Cross Context Dispatch機制,也同時要注意這種使用方式所帶來的安全問題。例如Tomcat是通過設置文件%TOMCAT_ROOT% /conf/Catalina/localhost/jetspeed.xml(注意粉紅色高亮字體):

<Context path="/jetspeed" docBase="jetspeed" crossContext="true"> <Realm className="org.apache.catalina.realm.JAASRealm" appName="Jetspeed" userClassNames="org.apache.jetspeed.security.impl.UserPrincipalImpl" roleClassNames="org.apache.jetspeed.security.impl.RolePrincipalImpl" useContextClassLoader="false" debug="0" /> <Resource name="jdbc/jetspeed" auth="Container" factory="org.apache.commons.dbcp.BasicDataSourceFactory" type="javax.sql.DataSource" username="" password="" driverClassName="org.apache.derby.jdbc.EmbeddedDriver" url="jdbc:derby:/tmp/j2" maxActive="100" maxIdle="30" maxWait="10000" /> <Valve className="org.apache.catalina.authenticator.FormAuthenticator" characterEncoding="UTF-8" /> </Context> 

在Pluto和Jetspeed相互配合下,通過JetspeedContainerServlet,最終執行控制權會交給Portlet的實現類。JetspeedContainerServlet就定義在Portlet應用程序所屬的Web應用單位中,也就是說所有在Jetspeed中運行的 Portlet Web應用都必須在Web.xml中包含JetspeedContainerServlet的定義。在Tomcat中,這是通過deploy-tool組件完成的,在其它應用服務器平臺,很可能就要靠應用發佈者手動添加了,需添加的信息包含:

<servlet> <servlet-name>JetspeedContainer</servlet-name> <display-name>Jetspeed Container</display-name> <description>MVC Servlet for Jetspeed Portlet Applications</description> <servlet-class>org.apache.jetspeed.container. JetspeedContainerServlet</servlet-class> <init-param> <param-name>contextName</param-name> <param-value>rss</param-value> </init-param> <load-on-startup>0</load-on-startup> </servlet> <servlet-mapping> <servlet-name>JetspeedContainer</servlet-name> <url-pattern>/container/*</url-pattern> </servlet-mapping> <taglib> <taglib-uri>http://java.sun.com/portlet</taglib-uri> <taglib-location>/WEB-INF/tld/portlet.tld</taglib-location> </taglib> 

Jetspeed Portlet Extension Service

前面介紹了實現架構和運行時架構,接下來我們一起來看看Jetspeed爲Portlet應用提供的Jetspeed Service架構。如果瞭解JSR-168規範的開發者就會知道,這個規範是基於Servlet 2.3規範基礎上的一個簡單擴展,因此並沒有對Portlet開發提供任何特別的支持。因而每家Portal廠商都提供了自己的擴展。

Jetspeed 提供的擴展方式跟很多廠商對Servlet規範的擴展一樣,定義了一個名爲jetspeed-portlet.xml的文件,作爲標準的 portlet.xml的擴展。只要你在打包發佈portlet應用時將這個文件與portlet.xml放在一起,Jetspeed的發佈程序就會自動讀取這個文件,並根據其內容執行一系列的操作。它們的關係如同BEA Weblogic應用服務器裏面的weblogic.xml與web.xml;JBoss應用服務器裏面的jboss-web.xml與web.xml。

下面我們來看看這個文件的格式:

<portlet-app id="j2-admin" version="1.0" xmlns="http://java.sun.com/xml/ns/portlet/portlet-app_1_0.xsd" xmlns:js="http://portals.apache.org/jetspeed" xmlns:dc="http://www.purl.org/dc"> <js:services> <js:service name="ApplicationServerManager" /> <js:service name="DeploymentManager" /> <js:service name="EntityAccessor" /> <js:service name="GroupManager" /> <js:service name="PageManager" /> <js:service name="PermissionManager" /> <js:service name="PortalAdministration" /> <js:service name="PortletFactory" /> <js:service name="PortalAdministration" /> <js:service name="PortletRegistryComponent" /> <js:service name="PortalStatistics" /> <js:service name="Profiler" /> <js:service name="RoleManager" /> <js:service name="SearchComponent" /> <js:service name="SSO" /> <js:service name="UserManager" /> <js:service name="HeaderResource" /> </js:services> </portlet-app> 

跟據XML Element的名字,可以理解就是提供給j2-admin這個Portlet應用程序使用的一些Services。那麼這些Services是怎麼定義的呢?以UserManager這個服務爲例,首先回到前面提到過的assemble目錄下,找到jetspeed-services.xml和 security-managers.xml,下面分別是它們的內容節選:

jetspeed-services.xml

<beans default-lazy-init="false" default-dependency-check="none" default-autowire="no"> <!-- Portlet Services --> <bean id="PortalServices" class="org.apache.jetspeed.services.JetspeedPortletServices" abstract="false" singleton="true" lazy-init="default" autowire="default" dependency-check="default"> <constructor-arg> <map> <entry key="SearchComponent"> <ref bean="org.apache.jetspeed.search.SearchEngine" /> </entry> <entry key="UserManager"> <ref bean="org.apache.jetspeed.security.UserManager" /> </entry> <entry key="PageManager"> <ref bean="org.apache.jetspeed.page.PageManager" /> </entry> </map> </constructor-arg> </bean> </beans> 

security-managers.xml

<beans default-lazy-init="false" default-dependency-check="none" default-autowire="no"> <bean id="org.apache.jetspeed.security.UserManager" class="org.apache.jetspeed.security.impl.UserManagerImpl" abstract="false" singleton="true" lazy-init="default" autowire="default" dependency-check="default"> <constructor-arg> <ref bean="org.apache.jetspeed.security.SecurityProvider" /> </constructor-arg> </bean> </beans> 

由上面的兩個文件,我們可以清楚地看出UserManager是定義security-managers.xml文件中的,但是爲了能夠在portlet服務中引用它,還必須在jetspeed- services.xml中再次引用它的bean name,org.apache.jetspeed.services.JetspeedPortletServices封裝了一個Map數據結構, Map中存放的就是服務名稱和該服務POJO對象引用,這一切便利都是由Spring帶來的。

下面是在Portlet的代碼中使用該服務的例子:

protected UserManager userManager = null; public void init(PortletConfig config) throws PortletException { super.init(config); userManager = (UserManager) getPortletContext().getAttribute(CommonPortletServices. CPS_USER_MANAGER_COMPONENT); if (null == userManager) { throw new PortletException("Failed to find the User Manager on portlet initialization"); } } 

Jetspeed鼓勵Portlet開發者在開發過程中,將公共的Service用Spring Bean的方式封裝起來,然後添加到Jetspeed-services中,這樣就可以在Portlet代碼中輕鬆複用這些公共Service了,並且還可以利用Spring來管理這些Service的生命週期。事實上,Jetspeed自帶管理界面Portlet應用程序就大量採用了這種技術。

還有一個需要注意的問題是,用戶自定義的Service必須放到Tomcat的shared\lib下面去,以保證JAVA Classloader能夠找到它。

Jetspeed核心組件簡介

介紹了那麼多Jetspeed架構方面的信息,下面我們一起來快速瀏覽一下Jetspeed Portal的核心組件。

Jetspeed-Api

路徑:components/jetspeed-api

定義幾乎所有的jetspeed-api interfaces,一般的開發者都使用這個組件中定義的接口進行二次開發。

Component Manager

路徑:components/cm

Jetspeed組件管理器,通過接口org.apache.jetspeed.components.ComponentManager屏蔽了Spring的實現細節。可以通過實現該接口替換Spring。

Deploy-Tool

路徑:components/deploy-tool

當Web Container爲Tomcat時,通過該組件,讀取已打包好的portlet應用程序中的portlet.xml和web.xml,檢查是否包含JetspeedContainerServlet的定義,如果沒有則修改web.xml加入這部分信息。

Id-Generator

路徑:components/id-generator

用於生成全局唯一的portlet實例id。

Locator

路徑:components/locator

提供定位門戶資源的服務,資源包括:模板,Profiler等。

Page-Manager

路徑:components/page-manager

對著名的門戶結構描述文件-PSML(Portal Structure Markup Language),提供了Java對象模型映射,並且支持文本風格的PSML和數據庫風格的PSML,以及PSML管理器。

Portal

路徑:components/portal

實現絕大部分的jetspeed-api組件中定義的interface,是最核心的組件。

Preferences

路徑:components/prefs

實現了Portlet屬性偏好功能,提供將這些屬性持久化到數據庫的服務。

RDBMS

路徑:components/rdbms

Jetspeed中所有與Apache OJB O/R Mapping框架有關的組建的基礎組件。

Search

路徑:components/search

提供整個門戶資源的全文本搜索服務,具體實現依賴於Apache Lucene。

Security

路徑:components/security

提供基於標準JAAS的認證服務,支持數據庫和LDAP作爲認證信息倉庫。基於角色的授權服務,默認支持數據庫作爲權限倉庫。

Single Sign-on

路徑:components/sso

提供一個可擴展的單點登陸服務接口和一個簡單的基於JAAS Subject的實現,該組件主要提供Portal門戶與後臺應用之間的單點登陸功能。

Statistics

路徑:components/statistics

提供一個簡單的訪問請求統計服務的實現,支持將統計信息持久化到數據庫。在Jetspeed-2管理界面中,還提供了專門的Portlet瀏覽這些統計信息。



回頁首

總結

本文帶讀者瀏覽了Apache Portal項目組的所有成員,並着重介紹了Apache Jetspeed-2 Portal。希望能夠使不瞭解門戶技術的朋友對它有一個初步的認識,找到自己感興趣的方向,繼續深入研究;同時對那些試圖在項目中使用開源軟件的開發者,提供一些可以借鑑的信息。縱觀目前開源軟件中的門戶實現,還沒有哪一個社區能夠提供像Apache Portal項目組這樣完整的解決方案同時,還擁有如此友好的許可策略。只要去深入瞭解,開源軟件往往能夠給人們帶來意外的驚喜。

參考資料

關於作者

廖健,Senior Consultant,主要從事企業應用集成(EAI)領域的諮詢和實施工作。對開源軟件有着濃厚的興趣,並且有在項目中使用開源軟件的豐富經驗。目前,他正作爲首席實施顧問,在北京首都機場T3航站樓安全信息系統項目中進行EAI實施工作。可以通過[email protected]與他聯繫。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章