跳到导航
dev2dev.bea.com.cn
首页 资源中心 dev2dev学堂 在线技术论坛 专家Blog User Group CodeShare
dev2dev 首页 > 资源中心 > 技术文章
GROUPSPACE如何使用IPC创建INTERPORTLET连接

时间:2006-11-16
作者:Charles Coates
浏览次数:
本文关键字:interportlet communicationIPCgroupspacebacking filepageflowWebLogic PortalCharles Coates组空间支持文件
文章工具
推荐给朋友 推荐给朋友
打印文章 打印文章

摘要

  做为WebLogic Portal的一部分,Interportlet Communication (IPC) 是一种功能非常强大的框架。它让软件开发人员创建能对门户应用程序中被触发的各种事件做出发应的portlet。本文说明当事件在一个portlet中被触发时,如何使用IPC创建一个应用程序框架来在另一个portlet中简单产生HTML的链接。本文将特别关注作为BEA WebLogic Portal 9.2一部分的GroupSpace应用程序的特点。GroupSpace中的portlet允许用户管理不同类型的协作内容。在这些portlet中,代表这些内容的URL链接通常被显示在portlet的JSP页面中。通过IPC,这些链接基于所表示的内容类型激活相应portlet中的特定动作。这个特性增强了用户体验,并且促进了门户应用程序的整体聚合力。

  尽管本文主要介绍这一特性的GroupSpace实现,但对于在使用基于页面流portlet的其他门户应用程序中创建类似框架,本文给出的代码示例也是很好的指导。

  本文假定读者基本了解以下WebLogic Portal和Java技术:

特性描述

  GroupSpace是一个协作性应用程序,其中包含若干个让用户管理各种内容类型的portlet。这些内容类型包括一些协作性元素,例如问题(Issue)、讨论主题、GroupNote(富文本文件)和外部文档。在很多这类portlet中,经常需要显示这些内容条目的超链接。搜索portlet就是这种需求的一个例子。搜索结果包含一个由许多不同内容类型组成的列表。列表中的每一项都显示为一个HTML链接。当点击这些链接时,应用程序必须激活相应的portlet(例如,一个Issue链接应该激活Issues portlet),并且触发一个页面流动作,以显示有关特定内容条目的详细信息。

  因为这些链接将被放置在许多portlet JSP页面上,链接的URL生成被简化了。使用一个定制的JSP标记可以很好地完成这件事。

  最后,框架为添加响应新内容类型的新portlet提供了支持。对于底层的框架组件不需要额外的代码。

实现

  这种框架的最终目标是,JSP页面开发人员能够非常简单地创建一个表示GroupSpace内容条目(例如Issue、GroupNote等)的URL链接。当用户点击这样一个链接时,应用程序应该自动激活为显示该内容类型详细信息而设计的portlet。例如,一个代表GroupSpace Issue的URL链接应该激活Issue portlet,一个代表GroupNote的URL链接应该激活GroupNote portlet。问题复杂性在于创造一种方式来生成HTML锚标记的href,用来执行确定相应portlet然后显示关于特定内容条目详细信息所需要的逻辑。

  最基本的方法是从门户PostbackURL开始。我将添加一些参数到这个URL,以指示一个GroupSpace链接被点击过。我还将添加一些参数来惟一标识哪个内容条目被点击(一个内容ID),以及其内容类型是什么(比如Issue或GroupNote)。当一个请求被提交到此URL,定义在portlet 支持文件中的lifecycle方法将开始运行。您可以向支持文件添加一些逻辑,以决定应该使用哪个portlet来处理请求。接下来,可以使用IPC触发一个portlet正侦听的定制事件。确认用于处理该内容类型的portlet将响应事件,运行一个指定的页面流动作。这个动作最终将根据内容条目的惟一ID来显示该内容条目的细节。

  在这种框架实现中涉及到几个组件。接下来的章节将描述每个组件,并给出代码示例进行详细解释。代码示例来源于GroupSpace应用程序。

支持文件

  第一步是建立一个portlet支持文件,框架中所有参与的portlet都必须使用它。当用户点击一个内容链接时,这个支持文件将运行。handlePostbackData()方法将用于确定某个链接是否被点击,并且将准备正确处理portlet的事件。下面是该例子的代码:

1  public boolean handlePostbackData(HttpServletRequest req, 
2                                    HttpServletResponse res) {
3	
4    // Determine if a GroupSpace content link was clicked.
5    String linkClicked = req.getParameter("gsLink_linkClicked");
6    if (linkClicked != null && linkClicked.equals("true")) {
7
8      // Determine if the event was already prepared by a 
9      // previously run portlet backing file.
10     String eventPrepared = 
11       (String) req.getAttribute("gsLink_eventPrepared");
12     if (eventPrepared == null) {
13
14       // Find all portlets that can handle the type.
15       String type = req.getParameter("gsLink_contentType");
16       if (type != null) {
17         String[] values = a;
18         MetaData[] metaData = 
19           {new MetaData("gsLink_contentType", values)};
20
21         DesktopBackingContext dback = 
22           DesktopBackingContext.getDesktopBackingContext(req);
23         PortletBackingContext[] pback = 
24           dback.getPortletBackingContextsByMeta(metaData, 0);
25         if (pback != null && pback.length > 0) {
26
27           String instanceId = pback[0].getInstanceId();
28
29           // Determine which portlet is the best to handle the 
30           // displaying of the content details based on the
31           // current portlet and current page.
32           String curPortletId = 
33             req.getParameter("gsLink_currentPortletId");
34           String curPageId = 
35             req.getParameter("gsLink_currentPageId");
37           if (curPageId != null) {
39             for (int i = 0; i < pback.length; i++) {
40               PageBackingContext pContext = 
41                 pback[i].getPageBackingContext();
42               if(curPageId.equals(pContext.getInstanceId())) {
43                 instanceId = pback[i].getInstanceId();
44               }
45             }
46           }
47
48           if (curPortletId != null) {
49             for (int i = 0; i < pback.length; i++) {
50               if (curPortletId.equals(pback[i].getInstanceId())) {
51                 instanceId = pback[i].getInstanceId();
52               }
53             }
54           }
55
56           // Set the target portlet id (the one that will 
57           // display the details) as a req attribute.
58           req.setAttribute("gsLink_targetPortletId", 
59             instanceId);
60         }
61       }
62       req.setAttribute("gsLink_eventPrepared", "true");
63       req.setAttribute("gsLink_detailsEventHandled", "false");
64       PortletBackingContext myBacking = 
65         PortletBackingContext.getPortletBackingContext(req);
66       myBacking.fireCustomEvent("linkClickedEvent", "payload");
67     }
68   }
69
70   return false;
71 }

  第5行用于确定一个链接是否被点击。此动作可以通过检查一个已知的请求参数来验证。在本文后续内容中,您将看到当一个链接被点击时,特定的请求参数是如何被设定的。如果一个链接被点击,下一步您必须确定以前运行的支持文件已经准备了事件(代码第10行)。同样,这将通过检查一个已知的请求参数来验证。

  下一步是准备事件。代码第15行通过检查一个请求参数来确定被点击的内容类型。接下来的几行代码用来确定哪个portlet应该处理该内容类型的事件。这使用portlet元数据来完成。下面的Portlet配置一节解释了元数据的设置。

  在代码第32行到35行,通过请求参数获得两方面的更多信息。当前的portlet ID表明被点击的链接所属的portlet。当前的页ID表明该页所属的当前portlet。这两项信息在以下两种特定情况下很重要:

  • 如果当前portlet能够处理此内容类型,则此portlet将被指定去处理事件。
  • 如果能够处理这种内容类型的portlet作为当前portlet存在于同一页上,则此portlet将被指定去处理事件。

  当导航链接时,这些特殊情况帮助减少用户体验的“四处乱跳“。

  代码第37行至54行确定最适合处理这种内容类型事件的portlet的实例ID。第58行说明这个ID作为请求属性被添加。

  最后,另外两个请求属性被设置用来表明事件已经准备好,但还没有处理。接着,一个定制的事件触发了。这是两个IPC事件中的第一个。在Portlet 配置一节中,您将学会如何配置这些事件。

事件处理程序

  下一步是添加一个定制事件处理程序方法到支持文件。这个方法将被IPC框架使用。它将是针对事件的动作,事件是为参与这个框架的不同portlet而定义的。当您了解被要求作为此实现的一部分的Portlet配置时,这一点将变得更加清晰。下面是一个这样的代码示例:

1  public void gsLink_evalLinkHandler(HttpServletRequest req, 
2                                     HttpServletResponse res, 
3                                     Event event) {
4
5    String targetPortletId = 
6        (String) req.getAttribute("gsLink_targetPortletId");
7    PortletBackingContext myBacking = 
8        PortletBackingContext.getPortletBackingContext(req);
9    String myId = myBacking.getInstanceId();
10
11   // Only react to this event if my portlet id matches the 
12   // one set in the request.
13   if (targetPortletId != null && targetPortletId.equals(myId)) {
14
15     MetaData meta = myBacking.getMetaData("gsLink_idAttrName");
16     if (meta != null && meta.getContents() != null 
17         && meta.getContents().length > 0) {
18
19       // Determine what the content id attribute name should 
20       // be as specified in the portlet metadata.
21       // Then set it in the request.
22       String idAttrName = meta.getContents()[0];
23       req.setAttribute(idAttrName, 
24           req.getParameter("gsLink_contentId"));
25
26       // Flag the request so that the resulting portal page 
27       // knows that the details event was handled.
28       req.setAttribute("gsLink_detailsEventHandled", "true");
29
30       // Fire off the show details event.  This event is only 
31       // being listened to by itself.
32       myBacking.fireCustomEvent("showDetailsEvent", "payload");
33     }
34   }
35 }

  这个方法的第一步是确定当前portlet是否为应当处理事件的portlet(第5行至13行)。通过比较当前portlet实例ID与在事件准备期间在请求中设置的targetPortletId,可以完成这项任务(参阅支持文件一节中代码示例的第58行)。如果当前portlet是应当处理事件的portlet,您必须进一步准备请求,以使最终执行的页面流动作能够确定最初被点击的内容条目。在第22行,从portlet元数据获得ID属性的名称。此元数据将在下一节Portlet配置中做进一步的解释。从请求参数中取得内容ID,并通过输入该ID属性名称将其设置为请求属性。

  在第28行,请求属性“gsLink_detailsEventHaqndled”被设置为“true”,以指示已经有一个portlet对事件做出响应。通过这样设置请求标志,您能够在JSP页面上确定某个portlet是否已经适当地处理了点击事件。如果没有发现portlet去处理给定内容类型的事件,您可以把此信息传递给用户。

  在代码第32行,另外一个定制事件触发了。该事件是整个实现过程中两个IPC事件中的第二个。

Portlet配置

  Portlet配置文件(.portlet文件)必须与适当的IPC和元数据一起设置,以便参与此框架。本节所描述的代码必须添加到每一个可能响应不同内容链接的portlet配置文件中。例如,在GroupSpace中,与其他所有能够处理显示各种GroupSpance内容类型细节的portlet一样,这些代码存在于Issues portlet和GroupNotes portlet配置文件中。

  BackingFile属性必须添加到<netuix:portlet>元素中。在“支持文件”一节中,我们描述的支持文件完全限定类名称就在这里设置。

backingFile="fully qualified class name of the backing file"

  下面的元数据条目作为子级添加到<netuix:potlet>元素中。

<netuix:meta name="gsLink_contentType" content="ISSUE"/>
<netuix:meta name="gsLink_idAttrName" content="contentId"/>

  这些元数据提供把信息添加到portlet的机制,portlet必须能够处理相关的内容类型(见支持文件一节代码示例第18行)。它也表明希望设置内容ID的属性的名称(见事件处理程序一节代码示例第15行)。此内容ID将引用惟一标识最初被点击的内容的字符串。

  接着,添加下面与IPC相关的元素:

<netuix:handleCustomEvent eventLabel="linkClickedEvent" 
        event="gsLink_linkClickedEvent" onlyIfDisplayed="false">
    <netuix:invokeBackingFileMethod method="gsLink_evalLinkHandler"/>
</netuix:handleCustomEvent>
<netuix:handleCustomEvent eventLabel="showDetailsEvent" 
        event="gsLink_showDetailsEvent" onlyIfDisplayed="false"
        fromSelfInstanceOnly="true">
    <netuix:activatePage/>
    <netuix:invokePageFlowAction action="selectContent"/>
</netuix:handleCustomEvent>

  为portlet配置好两个事件处理程序。第一个处理程序在linkClickedEvent被触发时执行,见支持文件一节的代码示例第65行。这个事件表明一个链接被点击了。动作将调用支持文件的gsLink_evalLinkHandker()方法,见事件处理程序一节中的代码示例。

  在事件处理程序一节中,当代码示例第32行的showDetailsEvent被触发时,下一个事件处理程序执行。此事件处理程序只响应自身生成的事件。所以,这将只在确定能够显示特定内容类型的portlet中执行。这个事件处理程序执行两种动作。它首先激活包含此portlet的当前页。接下来,调用一个特定的页面流动作。在这种情况下,动作为selectContent。页面流动作一节提供了有关这个动作的更多细节。

  有关这些IPC事件处理程序的更多信息,请参考Portlet Development guide中的Interportlet Communication文档。

页面流动作

  当一个链接被点击时,提供一个将要执行的指定动作是portlet程序开发者的职责。这个动作能够访问通过请求属性惟一识别内容的ID。它被放置在请求中,如事件处理程序一节中代码示例第22行所示。请求中的ID的属性名称在portlet元数据中进行设置,见Portlet配置一节。

  在GroupSpace应用程序中,这个动作包含基于惟一ID从底层内容管理系统中获得特定内容信息所需的逻辑。然后转交给显示关于此内容详细信息的JSP页面。

JSP标记

  本框架的最后一部分内容是JSP标记。这种标记让用户能够很容易地在portlet中创建各种JSP页面上的链接。这种标记由两个文件组成:一个JSP2.0 .tag文件,以及一个作为单独的helper类的Java类文件。这个helper类在构建URL时把逻辑分离出来,从而使它可以很容易地在JSP标记外部进行访问。

  下面是.tag文件的代码:

1  <%@ tag import="com.bea.apps.groupspace.taglib.content.ContentDetailsLinkHelper"%>
2
3  <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>
4
5  <%@ attribute name="contentId" type="java.lang.String" required="true"%>
6  <%@ attribute name="contentType" type="java.lang.String" required="true"%>
7  <%@ attribute name="target" type="java.lang.String" required="false"%>
8  <%@ attribute name="replacePath" type="java.lang.String" required="false"%>
9
10 <fmt:bundle basename="com.bea.apps.groupspace.Common">
11   <fmt:message key="gsLink_baseMessage" var="gsLink_baseMessage"/>
12 </fmt:bundle>
13
14 <%
15     String contentId = (String) pageContext.getAttribute("contentId");
16     String contentType = (String) pageContext.getAttribute("contentType");
17     String target = (String) pageContext.getAttribute("target");
18     String replacePath = (String )pageContext.getAttribute("replacePath");
19
20     ContentDetailsLinkHelper helper = new ContentDetailsLinkHelper(request, 
21         response, contentId, contentType,
22         target, replacePath, null);
23 %>
24
25 <script type="text/javascript">
26     var gsLink_detailsEventHandled = 
27         <%=request.getAttribute("gsLink_detailsEventHandled")%>;
28     if (gsLink_detailsEventHandled == false) {
29         gsLink_linkNotHandledMessage = "${gsLink_baseMessage}";
30     }
31 </script>
32
33 <%
34     if (ContentDetailsLinkHelper.TARGET_OPENER.equals(target)) {
35 %>
36 <a href="#" 
37     onclick='javascript:window.opener.location.href="<%=helper.getHref()%>";
38     return false;'><jsp:doBody/></a>
39 <%
40     } else {
41 %>
42 <a href="<%=helper.getHref()%>"><jsp:doBody/></a>
43 <%
44     }
45 %>

  这个标记共有四个属性,但只有两个是必需的。只有内容ID和内容类型是必需的属性。如果要让URL在一个特定的目标窗口中打开,也可以包括一个可选的“target“属性。“replacePath”属性允许用户替换生成的URL中的路径部分。

  第10至12行设置了一条消息,当没有发现任何portlet可以处理内容类型时,就会显示这条消息。这在第25行至第31行的JavaScript中进行处理。

  第20行指派helper类来生成URL。最后,第32行至第41行用于生成链接的HTML输出。

  下面是helper类的代码:

1  public ContentDetailsLinkHelper(HttpServletRequest request, 
2                                  HttpServletResponse response,
3                                  String contentId, 
4                                  String contentType, 
5                                  String target, 
6                                  String replacePath, 
7                                  String linkText) {
8
9    try {
10     AsyncContentContext.push(request).setAsyncContentDisabled(true);
11             
12     this.contentId = contentId;
13     this.contentType = contentType;
14     this.target = target;
15     this.replacePath = replacePath;
16     this.linkText = linkText;
17
18     PagePresentationContext context = 
19         PagePresentationContext.getPagePresentationContext(request);
20     PortletPresentationContext portletContext = 
21         PortletPresentationContext.getPortletPresentationContext(request);
22            
23     PostbackURL contentDetailsLink_url = 
24         PostbackURL.createPostbackURL(request, response);
25     contentDetailsLink_url.addParameter("gsLink_linkClicked", 
26         "true");
27     contentDetailsLink_url.addParameter("gsLink_contentType", 
28         contentType);
29     contentDetailsLink_url.addParameter("gsLink_contentId", 
30         contentId);
31
32     if (context != null && context.getInstanceId() != null) {
33       contentDetailsLink_url.addParameter("gsLink_currentPageId", 
34           context.getInstanceId());
35     }
36            
37     if (portletContext != null && portletContext.getInstanceId() != null) {
38       contentDetailsLink_url.addParameter("gsLink_currentPortletId", 
39           portletContext.getInstanceId());
40     }
41             
42     if (replacePath != null) {
43       contentDetailsLink_url.setPath(replacePath);
44     }
45
46     href = contentDetailsLink_url.toString();
47
48   } finally {
49     AsyncContentContext.pop(request);
50   }
51 }
52
53 public String getHref() {
54   return href;
55 }

  这个类的构造方法用于处理创建URL的逻辑。如果请求是从使用该配置的portlet生成的,则第10行禁用async内容。这一点十分重要,因为链接框架依赖于IPC。这些URL必须作为完整门户请求而产生,这样IPC才能够执行。有关更多信息,请参阅Asynchronous Content Rendering and IPC

  POstbackURL 类用于生成基本URL。第23行至第40行添加所有必要的参数到请求中。如果提供了“replacePath”参数,则所生成的URL的路径在第43行代码将被替换。

  最后,HTML锚标记的href被创建,并通过getHref属性公开。

结束语

  最后总结一下所有这些组件如何协同工作,以创建GroupSpace内容链接框架:

  • 一个URL链接在一个GroupSpace portlet中(也就是GroupSpace Search portlet)显示。这个URL代表着一块内容。此内容可以属于GroupSpace应用程序管理的众多内容类型之一。例如,链接可能代表一个问题。它被解释成问题的“题目”。
  • 这个URL使用JSP标记生成,具体实现参见JSP标记一节。本质上,由门户postback URL和一些附加参数提供了关于所代表问题的信息。
  • 当此链接被点击时,一个请求被发送回执行各种portlet生命周期的门户。这导致在支持文件一节中描述的支持文件中的handlePostback()方法被执行。
  • 支持文件中的handlePostBack()方法逻辑,基于参与此框架的portlet的内容类型和portlet元数据,决定处理内容显示信息的最合适的portlet。
  • 当确定了一个合适的portlet(针对ISSUE类型的内容条目的问题portlet),一个IPC事件被触发。
  • 响应IPC事件所执行的动作将是执行支持文件方法。这个方法在事件处理程序一节中描述。
  • 事件处理程序把一些信息添加到请求,然后触发第二个正在被指定portlet侦听的IPC事件。
  • 第二个IPC事件所执行的动作将是激活包含portlet的门户页面,并且执行指定的页面流动作。
  • 页面流动作使用内容惟一标识符从底层内容管理系统获得数据,然后将数据转交给合适的显示内容细节的JSP。

  使用定制的JSP标记,使用如下所示的简单JSP代码,内容链接在GroupSpace中生成了。

<wlpGroupSpaceLinksFW:contentDetailsLink contentId="${content.id}" 
    contentType="${content.type}">
    ${content.title}
</wlpGroupSpaceLinksFW:contentDetailsLink>

  这部分代码生成如上所述的触发事件和处理程序的URL。最终,合适的portlet被激活,执行定义好的页面流事件。

  在GroupSpace中添加一个参与此链接框架的portlet应该非常容易。Portlet程序开发者应该添加需要的元数据和IPC元素到portlet配置文件中,然后添加定制的页面流动作。

  IPC是一个非常强大的门户框架工具。这只是一个有关如何使用IPC增强门户应用程序用户体验的例子。

参考资料

 作者简介
Charles Coates 是一名已经在BEA工作2年的软件工程师。他目前是一名UI components of GroupSpace(这是一款基于社区的、构建于WebLogic Portal 9.2之上的新应用程序)开发人员。
dot dot dot

dot
  作者其它文章
您对本文的评价
您对这篇文章的看法如何?
太棒了!5分 不错啊 4分 一般般 3分 有待提高 2分 不好 1分

   
相关产品
相关技术