前言
由于这段时间审计的weblogic漏洞涉及到了这一部分,就顺便把官方文档翻译了一下,也方便大家查阅
参考链接:
正文内容
摘要
本文档旨在对portal framework
的技术进行一个简要的介绍,本文档的目标读者为具有J2EE
技术背景且已经熟悉Weblogic Portal的开发者
本文档是在线文档的补充,并且我们假设你已经阅读过相关的在线文档,然后想要查找技术性更强的资料
本文档只会介绍portal framework,不直接讨论与Weblogic Workshop的交互相关的问题,比如Weblogci后台管理portal以及任何Weblogic Portal的附加功能都不会在本文档中介绍,
术语、缩略约定
事先声明这些术语和缩略词很重要,因为这次词汇贯穿整个文档,请稍微花点时间熟悉一下
Netui
别名Page Flows,用于构建model 2类型应用程序的编程模型,基于Struts Framework进行构建
Netuix
用于渲染应用程序的XML框架,netuix开始只是netui的扩展,但是如今的netuix已经和netui没有任何关系了,他俩是完全不同的两种技术,只是名字相似罢了,同时netuix可以完美托管基于netui的应用程序
客制化(自定义)
通过API对portal进行修改的术语,这个API通常由我们的Weblogic后台管理portal和Visitor Tools页面进行调用,同时也可以被想要进行自定义设计的开发者使用
API提供了所有在客制化过程中可能用到的CURD操作。客制化不同于个性化,对于客制化而言,我们自主决定对应用的更改,而个性化是需要基于预设的规则和固定的行为的。比如今天是星期五,游客住在丹佛,所以放一个赛马相关的广告
Portal Framework
weblogic portal中负责渲染和客制化portal的一部分,这也是本文档主要介绍的内容
轻量级Portal(基于文件的Portal)
所谓轻量级portal就是一个精简版的weblogic portal,该portal不部署任何的EJB或者数据库,轻量级portal支持portal framework除了客制化以外的所有功能
轻量级Portal只能渲染portal文件,不能访问数据库来进行客制化操作,轻量级portal渲染会在Weblogic Workshop部署时进行,也有可能被用在产品系统中
UIControl
用户界面控制,不要和Weblogic Workshop的business control混淆,XML文档中的每一个元素都代表了一个UIControl的实例,典型的Control为:Books
、Pages
、Menus
、Portlets
等等
单文件 vs 流式渲染
你在Weblogic Workshop创建的.portal
文件是一个完全可以正常运行的portal,不过它也可以被用来作为创建desktop的模板,在这个模板中你可以创建books、pages以及对其他portlets的引用并定义他们的默认行为
当你通过浏览器去访问你所创建的.portal
文件时,是通过单文件模式进行渲染的,这意味着你是从文件系统中访问portal而不是从数据库中
portal文件中的XML内容会被解析,然后向浏览器返回一个渲染过的portal,portal文件的创建和使用主要用于开发和静态portal(没有被用户或者管理员客制化过的portal),没有了数据库的加入,客制化的功能就没了
流式渲染就是多了个数据库,这里不再赘述
库
这里的库指的就是与desktop无关的公共control的集合,换言之就是Books、Pages、Portlets这些control可以在desktop范围外进行创建,之后再加入到desktop中
对库中对象的改变会影响到desktop和客制化
Netuix
Controls
前面已经提到过,netuix是一个用于渲染应用程序的XML框架,不管这些应用看起来像不像portal,很多我们的客户使用我们的框架所创建出来的应用完全不像portal,特别是当人们认为portal应该是My Yahoo!
那样的web应用时,实际上很多使用netuix构建的应用像My Yahoo!
,同时也有一部分和My Yahoo!
相差甚远
一个netuix应用通常呈现为一个XML文档,最常见的就是.portal
后缀文件,这个portal文件可能会包含其他的portal包含文件,这些文件通常以.pinc
作为后缀,意为portal include
,这一点和JSP文件很相似,他们都具有通过包含来分散文件内容的特性
.pinc
文件和portal文件有所不同,protal文件会包含根元素或者contorl,但是.pinc
文件没有这些东西,相关细节我们会在后面进行讨论,此外,.pinc
文件必须以Book
或者Page
元素作为根元素
对于portal文件,你可以认为每一个元素代表一个UIControl的实例,这些control在继承树中看起来有些奇怪,换言之,每一个control都有一个父control,有0或着多个子control,这些control可以在运行时找到彼此,而且可以通过添加或者移除子control来改变继承树的结构,所有的control都通过一个生命周期运行,所谓生命周期就是按照特定顺序在control中被调用的方法的集合,所有的方法通过深度优先进行调用
为了更好地解释这件事,我们先来捋一遍在单文件模式下通过浏览器访问portal时发生的事件,在此之前,我们需要先介绍一下portal framework的一些框架相关的知识,所有对protal或者desktop的请求都通过PortalServlet发送过来,PortalServlet在web.xml中进行注册,其注册在url-pattern:*.portal
下
当PortalServlet检测到请求以.portal
结束时,他就知道正在请求本地文件,然后就不会再去请求XML中的持久API了
PortalServlet要做的第一件事就是解析XML文件(.portal
),然后根据该文件生成一个control tree
,portal文件中的每一个元素代表control tree中的一个control,元素中的每一个属性代表control中的一个实例变量,同样的层次结构也能在portal文件中找到,一个control就是一个继承了UIControl类的java类,在当前的发行版本中,我们并没有向开发者明确地暴露control类,但是开发者可以通过backing files
,context
,skeleton jsp
和control进行交互,这个将会在后面进行讨论
注意:PortalServlet不会在每次请求过程中都解析XML文件,这中间有许多缓存和little tricks,这一切都是为了能在企业级应用中表现出更佳的性能
一旦control tree建立起来并且所有control的实例变量也都被设置,control tree就会通过生命周期运转起来,生命周期方法如下:
init()
loadState()
handlePostbackData()
raiseChangeEvents()
preRender()
saveState()
render()
dispose()
每个方法通过深度优先的顺序进行调用,先是init()
方法被调用,然后是loadState()
方法,以此类推
比如我们现在有一个下图中的control tree,那么init()
方法被调用的顺序为: C1, C2, C5, C3, C6, C7, C4
然后下面是loadState()
方法按照同样的顺序(深度优先的顺序)被调用
上图中control tree的生命周期方法最后被调用的就是C4 control的dispose()
方法
Portal Controls
本节我们将要描述组成portal framework的所属有netuix control,这些control的关系在XML约束中定义:controls-netuix-1_0_0.xsd
,下面的,描述对约束定义进行了总结
Desktop
Desktop control是所有netuix control的父control,每一个portal都必须包含一个Desktop control,实际上Desktop control除了权限校验以及提供查找其他子control的方式之外没有任何功能
从开发者的角度来说,Desktop control最重要的用处就是它拥有一个可以遍历获取所有子control引用的PresentationContext
,比如books、pages和portlets,在8.1 sp3版本中又新增了DesktopBackingContext
,相比PresentationContext
`拥有更丰富的用于定位子control的方法
Windows
Windows control提供了和微软操作系统的windows相似的概念,Windows支持States和Modes,States会影响Windows的渲染,比如最小化,最大化,浮动和删除,Modes会影响内容,比如Edit和Help(客制化modes同时也被支持),Windows同时也可以作为其他Windows的容器,例如一个book可以包含一个page
所有的Window control都必须包含一个Content control,Content control负责托管window中的实际内容,Windows control其实就是一个抽象类,它是所有的portal中都必须被使用的三个类之一,这三个类分别为:Books、Pages、Portlets,下图展示了Windows、Books、Pages和Portlets之间的关系:
可以看到从Body往下,就是Windows control的范围了
Book
Book集成多个navigable,一个navigable是一个Book或者Page,一个Book可能包含一个可选的menu control用于在众多的navigable中进行导航,站在代码编写的角度,Navigable就是一个由Book和Page实现的接口
Page
Page用于显示Placeable的集合,Placeable就是一个Portlet或者Book,Page拥有一个具有一个或者多个占位符的布局,这些占位符可以用来托管一个或者多个Placeable
Portlet
Portlet被用作托管不同类型的应用的窗口,在编写此文档时,只有下面这些应用可以被托管:
- HTML页面
- JSP文件
.pinc
文件- Page Flows
- Struts
- WebFlows
- JSR 168 Portlets
- WSRP proxy portlets
Menus
Menus是一个可选组件,它松耦合于Book和Page,Menu负责显示某些导航组件,不管它是选项卡的集合还是链接的集合,又或者是一些树结构,menu触发PageChangeEvents
事件,该事件由Page进行监听并根据该事件做出相应的动作
在编写此文档时,Weblogic Portal提供两种类型的menu:singlelevel和multilevel,未来的发行版本和服务包可能会包含更多的类型,你可以使用JSP和<render:pageUrl>
来创建自己的menu,或者通过backing file在Book、Page或者Portlet backing context中在preRender
方法调用之前调用setupPageChangeEvent
方法来创建自己的menu
SingleLevelMenu
为book的直属page和子book提供一个选项
MultiLevelMenu
为所有的book和包含在book中的page递归创建一个具有层次结构的menu,这种类型的menu不会停留在第一层子节点,它会贯穿整个树结构,如果父book使用了一个multilevelmenu
,那么子book就不应该再使用multilevelmenu
,因为父book的menu会覆盖它们
Layouts
Layouts和Placeholders(不要将其与个性化的placeholders混淆)被用来定义portlet和book显示在page中的方式,Layout占位符会被渲染成HTML表格单元
Weblogic Portal提供了一些预定义的layout以及创建自定义layout的方法,更多的layout会在将来的服务包和发行版本中提供,如果提供的layout不符合你的需求,那么你就必须自己创建自定义的layout,下面一节我们会讲解创建自定义layout的详细过程
与UI Control进行交互
control并未直接暴露给开发者,因此开发者需要一种直接与control进行交互并控制其行为的方式,为了达到这个目的,Weblogic Portal暴露了context、backing files、skeletons和events,开发者在与portal framework进行交互或者更改其行为时会用到这些组件
Context
context就是底层control的委派,此委派只会暴露control中支持的方法
context被分为两种类型:backing context以及presentation context,backing context可以通过backing files进行操作,presentation context可以通过JSP文件进行操作
两种类型的context都会被用到,因为在生命周期的特定阶段,特定的方法会被调用,比如在presentation context中调用setTitle()
方法就没有意义,因为portal已经开始渲染,因此调用此方法没有任何作用,但是通过backing file调用这个方法才是正确的
Backing Context
Backing Context通过backing files进行操作,可以通过两种方式获得对backing context的引用
- 第一种方法就是使用context类的静态方法
getXXXBackingContext
,这个方法会返回一个active backing context,类型为所使用的backing file的属主portlet,具体点来说,如果我从portlet A的backing file中调用这个方法,那么我将会获得portlet A的backing context,而不会是portlet B
相似地,如果我从portlet A中调用getPageBackingContext(request)
方法,我将会获得portlet A所在page的page backing context
- 第二种方法就是通过另外一个context获得一个backing context,当你需要的并不是active context时这个方法是很有用的,举个例子来说,我想从portlet A获取portlet B的backing context
如果portlet A和portlet B位于同一个page中,那么有如下用法:
PortletBackingContext portletB = PageBackingContext.getPageBackingContext(request).PortletBackingContext getPortletBackingContextRecursive("Portlet Bs instance label");
如果A不知道B所在的位置,那么你可以委派给DesktopBackingContext
PortletBackingContext portletB = DesktopBackingContext.getPageBackingContext(request).PortletBackingContext getPortletBackingContextRecursive("Portlet Bs instance label");
参考javadoc以及其他的backing context以获取更多信息
com.bea.netuix.servlets.controls.page.PageBackingContext
com.bea.netuix.servlets.controls.application.backing.DesktopBackingContext
Presentation Context
Presentation Context可以通过JSP文件进行操作,presentation context的引用也可以通过两种方式获取到
- 第一种方法是使用context类的静态方法
getXXXPresentationContext
来获取对应类型的active presentation context,也就是说如果我从portlet A的content JSP中调用这个方法,那么返回的presentation context类型就是portlet A而不会是portlet B,相似地,如果我从portlet A中调用getPagePresentationContext(request)
,我将会获得portlet A所在page的page presentation context - 第二种方法就是通过另一个context来获取presentation context,当你需要的并不是active context时,这个方法会很有用,比如我想从portlet A中获取portlet B的presentation context
Backing Files
Backing file就是一个实现了com.bea.netuix.servlets.controls.content.backing.JspBacking
接口或者是继承了com.bea.netuix.servlets.controls.content.backing.AbstractJspBacking
抽象类的Java类(按理来说他不应该叫做Backing File而应该叫做backing class),接口上的方法模仿了control生命周期方法并且调用的时间点和control的生命周期方法是一致的
编写此文档时支持backing file的control一共只有下面这几个:
- Books
- Pages
- Portlets
- JspContent controls
注意:在service pack 3中Desktop同样也支持backing files
每一次请求都会新建一个backing file的实例,因此你不必担心线程安全问题,新的Java VM针对短期对象进行了优化,并且已经不存在过去的性能问题,JspContent control也同样支持一个特殊类型的backing file,该backing file允许你指定该backing file是否是线程安全的,如果值被设为true,那么所有的请求将会共用一个backing file实例
Skeletons
Skeletons指的是在渲染阶段被使用的JSP,渲染阶段被分为两部分:开始渲染和结束渲染,父control的开始渲染先被调用,然后是子control的开始渲染被调用,然后是孙子control的开始渲染,依此类推,在最后一个开始渲染被调用之后,开始调用结束渲染,然后调用该control的父control的结束渲染,依此类推,这种方式允许父control创建一个容器,比如HTML表格,由子control提供表格内容
每一个Skeleton都会被调用两次,在Skeleton中有特殊的标签的值只会在特定的渲染阶才为true
Events
一共有四种类型的event:
- Window Mode
- Windw State
- Page Change
- Generic Portlet Event
上面四个事件都不会直接暴露给开发者,但是可以通过配置以使用特殊的方法在Window backing file中调用,具体来说就是`setupModeChangeEvent
、setupStateChangeEvent
和 setupPageChangeEvent
这三个方法,这些方法一定会在preRender
方法被调用前调用,因为事件会在handlePostbackData
方法之后被触发,并且它们只会在handlePostbackData
方法返回值为treu时才会被调用(参考相关javadoc)
注意:当调用任何一个setupxxevent
之前,handlePostbackData
都必须在backing file所绑定的backing context中完成,不然事件不会被触发
Portlet事件(不要和page flow事件混淆)允许不同portlet之间进行通信,一个portlet可以创建一个事件,然后另一个portlet对该事件进行监听,并且这些事件可以传递参数
下面是一个例子,一个portlet从backing file中触发事件,另一个portlet监听该事件
/**
* This is the implementation on the backing file of the portlet that wants to fire the event.
*/
public boolean handlePostbackData(HttpServletRequest request, HttpServletResponse response)
{
// Create a new portlet event with the results in the paylod
PortletEvent portletEvent = new PortletEvent(new MyPayload("Hello From portlet A"));
// Get a hold of the portlet event manager and fire the event.
PortletBackingContext portletBackingContext =
PortletBackingContext.getPortletBackingContext(request);
PortletEvent.Manager portletEventManager =
PortletEvent.getEventManager(this, portletBackingContext);
portletEventManager.fireEvent(portletEvent);
// Needed for the event to fire.
return true;
}
/**
* This is the implementation of the portlet that wants to receive the event.
*/
public class ResultBacking extends AbstractJspBacking implements PortletEventListener
{
MyPayload result;
public void init(HttpServletRequest request, HttpServletResponse response)
{
result = null;
// Register for Portlet Events
PortletBackingContext portletBackingContext =
PortletBackingContext.getPortletBackingContext(request);
PortletEvent.addGlobalListener(portletBackingContext, this);
CustomPortletEvent.Manager portletEventManager =
CustomPortletEvent.getEventManager(this, portletBackingContext);
}
public void handleEvent(Object source, AbstractEvent event)
{
// Can check the source of the event
if (source instanceof PortletA)
{
result = (MyPayload)((PortletEvent)event).getPayload();
}
}
后记
源文档中间有一部分关于创建自定义Layout的章节与翻译本文档的初衷无关,因此上面并未对其进行翻译,若发现翻译错误或不当的地方还望指出