返回
顶部

前言

由于这段时间审计的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为:BooksPagesMenusPortlets等等

单文件 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

1607242302320

当PortalServlet检测到请求以.portal结束时,他就知道正在请求本地文件,然后就不会再去请求XML中的持久API了

PortalServlet要做的第一件事就是解析XML文件(.portal),然后根据该文件生成一个control tree,portal文件中的每一个元素代表control tree中的一个control,元素中的每一个属性代表control中的一个实例变量,同样的层次结构也能在portal文件中找到,一个control就是一个继承了UIControl类的java类,在当前的发行版本中,我们并没有向开发者明确地暴露control类,但是开发者可以通过backing filescontextskeleton 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

img

然后下面是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之间的关系:

img

可以看到从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中调用,具体来说就是`setupModeChangeEventsetupStateChangeEventsetupPageChangeEvent这三个方法,这些方法一定会在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的章节与翻译本文档的初衷无关,因此上面并未对其进行翻译,若发现翻译错误或不当的地方还望指出