Liferay Dockbar点击Add->More后弹出的Portlet以及分类文本分析

 因为我们以前项目中经常遇到配置了Portlet但是在对话框中找不到的情况,所以我们就来细致分析这里显示的文本是从哪里得来的。

对于整个dialog,它的代码是/html/portlet/layout_configuration/view_category.jsp.我们只想关注2个,一个是分类从哪里来,一个是Portlet 从哪里来:

 

分类文本:

对于黑色字的分类(比如我们这里的WalmartPlatformPortalDemo),其对应的代码在view_category.jsp中如下:

 
  
  1. <h2> 
  2.             <span><%= Validator.isNotNull(externalPortletCategory) ? externalPortletCategory : LanguageUtil.get(pageContext, portletCategory.getName()) %></span> 
  3.  </h2> 

所以它最后会去调用LanguageUtil的get方法,而它最终内容会根据你的locale去读取resource bundle资源包,而传入的portletCategory.getName()则是当资源包中找不到匹配项时候的defaultName,因为大多数名字(我们为分类起的名字)都是项目特有的,不太可能和资源包中的key重名,所以大多数情况下最终显示的内容就是portletCategory.getName()返回的字符串值。

 

所以我们现在焦点在于portletCategory.getName()值是如何取来的。

view_category.jsp的上方我们找到了:

 
  
  1. <%@ include file="/html/portlet/layout_configuration/init.jsp" %> 
  2.  
  3. <
  4. PortletCategory portletCategory = (PortletCategory)request.getAttribute(WebKeys.PORTLET_CATEGORY); 
  5. .. 

所以,我们一定想到,这个request域上的属性值设置肯定是在包含这个jsp的文件中给出的。因为view_category.jsp中是被循环引入到layout_configuration/view.jsp,果然在view.jsp中我们找到了对应的代码

 
  
  1.     PortletCategory portletCategory = (PortletCategory)WebAppPool.get(company.getCompanyId(), WebKeys.PORTLET_CATEGORY); 
  2.  
  3.                 portletCategory = _getRelevantPortletCategory(permissionChecker, portletCategory, panelSelectedPortlets, layoutTypePortlet, layout, user); 
  4.  
  5.                 List categories = ListUtil.fromCollection(portletCategory.getCategories()); 
  6.  
  7.                 categories = ListUtil.sort(categories, new PortletCategoryComparator(locale)); 
  8.  
  9.                 int portletCategoryIndex = 0
  10.  
  11.                 Iterator itr = categories.iterator(); 
  12.  
  13.                 while (itr.hasNext()) { 
  14.                     PortletCategory curPortletCategory = (PortletCategory)itr.next(); 
  15.  
  16.                     if (curPortletCategory.isHidden()) { 
  17.                         continue; 
  18.                     } 
  19.  
  20.                     request.setAttribute(WebKeys.PORTLET_CATEGORY, curPortletCategory); 
  21. ... 

 

所以解开我们的钥匙就在WebAppPool的get方法,它的值肯定是设置进去的(否则怎么取呢),所以我们找到WebAppPool的put方法:

 
  
  1. public static void put(Long webAppId, String key, Object obj) { 
  2.         _instance._put(webAppId, key, obj); 
  3.     } 

 

而这个方法是被PortalInstances的_initCompany方法所调用的:

 
  
  1. private long _initCompany(ServletContext servletContext, String webId) { 
  2.  
  3.         ... 
  4.         try { 
  5.             String xml = HttpUtil.URLtoString(servletContext.getResource( 
  6.                 "/WEB-INF/liferay-display.xml")); 
  7.  
  8.             PortletCategory portletCategory = (PortletCategory)WebAppPool.get( 
  9.                 companyId, WebKeys.PORTLET_CATEGORY); 
  10.  
  11.             if (portletCategory == null) { 
  12.                 portletCategory = new PortletCategory(); 
  13.             } 
  14.  
  15.             PortletCategory newPortletCategory = 
  16.                 PortletLocalServiceUtil.getEARDisplay(xml); 
  17.  
  18.             portletCategory.merge(newPortletCategory); 
  19.  
  20.             for (int i = 0; i < _companyIds.length; i++) { 
  21.                 long currentCompanyId = _companyIds[i]; 
  22.  
  23.                 PortletCategory currentPortletCategory = 
  24.                     (PortletCategory)WebAppPool.get( 
  25.                         currentCompanyId, WebKeys.PORTLET_CATEGORY); 
  26.  
  27.                 if (currentPortletCategory != null) { 
  28.                     portletCategory.merge(currentPortletCategory); 
  29.                 } 
  30.             } 
  31.  
  32.             WebAppPool.put( 
  33.                 companyId, WebKeys.PORTLET_CATEGORY, portletCategory); 
  34.         } 
  35.  
  36.  
  37. .. 

从这段代码可以看出来,它08-09行会先去判断这个值是否已经在WebAppPool中有,对于第一次访问,池中显然是没有的,所以第15行构造了一个新的PortletCategory对象,并且让其读取WEB-INF/liferay-display.xml中的设定,最后和空的PortletCategory对象merge起来。

 

我们追述到PortletLocalServiceUtilgetEARDisplay方法中:

 
  
  1. public static com.liferay.portal.model.PortletCategory getEARDisplay( 
  2.         java.lang.String xml) 
  3.         throws com.liferay.portal.kernel.exception.SystemException { 
  4.         return getService().getEARDisplay(xml); 
  5.     } 

 

它最终会去调用PortletLocalServiceImplgetEARDisplay方法:

 
  
  1. public PortletCategory getEARDisplay(String xml) throws SystemException { 
  2.         try { 
  3.             return _readLiferayDisplayXML(xml); 
  4.         } 
  5.         .. 
  6.     } 

进入调用_readLiferayDisplayXML方法,然后调用重载的_readLiferayDisplayXML(servletContextName,xml)方法:

 
  
  1. private PortletCategory _readLiferayDisplayXML( 
  2.             String servletContextName, String xml) 
  3.         throws Exception { 
  4.  
  5.         PortletCategory portletCategory = new PortletCategory(); 
  6.  
  7.         if (xml == null) { 
  8.             xml = ContentUtil.get( 
  9.                 "com/liferay/portal/deploy/dependencies/liferay-display.xml"); 
  10.         } 
  11.  
  12.         Document document = SAXReaderUtil.read(xml, true); 
  13.  
  14.         Element rootElement = document.getRootElement(); 
  15.  
  16.         Set<String> portletIds = new HashSet<String>(); 
  17.  
  18.         _readLiferayDisplay( 
  19.             servletContextName, rootElement, portletCategory, portletIds); 
  20.  
  21.         // Portlets that do not belong to any categories should default to the 
  22.         // Undefined category 
  23.  
  24.         Set<String> undefinedPortletIds = new HashSet<String>(); 
  25.  
  26.         for (Portlet portlet : _getPortletsPool().values()) { 
  27.             String portletId = portlet.getPortletId(); 
  28.  
  29.             PortletApp portletApp = portlet.getPortletApp(); 
  30.  
  31.             if ((servletContextName != null) && (portletApp.isWARFile()) && 
  32.                 (portletId.endsWith( 
  33.                     PortletConstants.WAR_SEPARATOR + 
  34.                         PortalUtil.getJsSafePortletId(servletContextName)) && 
  35.                  (!portletIds.contains(portletId)))) { 
  36.  
  37.                 undefinedPortletIds.add(portletId); 
  38.             } 
  39.             else if ((servletContextName == null) && 
  40.                      (!portletApp.isWARFile()) && 
  41.                      (portletId.indexOf( 
  42.                         PortletConstants.WAR_SEPARATOR) == -1) && 
  43.                      (!portletIds.contains(portletId))) { 
  44.  
  45.                 undefinedPortletIds.add(portletId); 
  46.             } 
  47.         } 
  48.  
  49.         if (!undefinedPortletIds.isEmpty()) { 
  50.             PortletCategory undefinedCategory = new PortletCategory( 
  51.                 "category.undefined"); 
  52.  
  53.             portletCategory.addCategory(undefinedCategory); 
  54.  
  55.             undefinedCategory.getPortletIds().addAll(undefinedPortletIds); 
  56.         } 
  57.  
  58.         return portletCategory; 
  59.     } 

 

从这段代码我们可以清晰的对于category的处理分为两种情况:

(1)对于liferay-display.xml中有<category>元素的分类,我们跟进第18行:

 
  
  1. private void _readLiferayDisplay( 
  2.         String servletContextName, Element element, 
  3.         PortletCategory portletCategory, Set<String> portletIds) { 
  4.  
  5.         for (Element categoryElement : element.elements("category")) { 
  6.             String name = categoryElement.attributeValue("name"); 
  7.  
  8.             PortletCategory curPortletCategory = new PortletCategory(name); 
  9.  
  10.             portletCategory.addCategory(curPortletCategory); 
  11.  
  12.             Set<String> curPortletIds = curPortletCategory.getPortletIds(); 
  13.  
  14.             for (Element portletElement : categoryElement.elements("portlet")) { 
  15.                 String portletId = portletElement.attributeValue("id"); 
  16.  
  17.                 if (Validator.isNotNull(servletContextName)) { 
  18.                     portletId = 
  19.                         portletId + PortletConstants.WAR_SEPARATOR + 
  20.                             servletContextName; 
  21.                 } 
  22.  
  23.                 portletId = PortalUtil.getJsSafePortletId(portletId); 
  24.  
  25.                 portletIds.add(portletId); 
  26.                 curPortletIds.add(portletId); 
  27.             } 
  28.  
  29.             _readLiferayDisplay( 
  30.                 servletContextName, categoryElement, curPortletCategory, 
  31.                 portletIds); 
  32.         } 
  33.     } 

可以看出,它会循环的遍历liferay-display.xml中所有<category>元素,然后依次读取它们的<name>属性,并且把这些name属性都分别封装在PortletCategory对象中,然后把这些对象放入参数的PortletCategory中,然后递归继续读下去(PS:这是GOF中典型的Composite 设计模式)

(2) 对于liferay-display.xml中没有分类的Portlet,会归结到category.undefined分类中

  1. PortletCategory undefinedCategory = new PortletCategory( 
  2.                 "category.undefined"); 

 

 

Portlet名字文本:

对于有紫色正方形标示的Portlet名字文本,比如我们这里的(ClusterNodeInfoPortlet),对照页面:

因为没有 portletItemId属性,所以我们断定走的是以下代码:

 
  
  1. <div 
  2.                             class="lfr-portlet-item <c:if test="<%= portletLocked %>">lfr-portlet-used</c:if> <c:if test="<%= portletInstanceable %>">lfr-instanceable</c:if>
  3.                             id="<portlet:namespace />portletItem<%= portlet.getPortletId() %>" 
  4.                             instanceable="<%= portletInstanceable %>" 
  5.                             plid="<%= plid %>" 
  6.                             portletId="<%= portlet.getPortletId() %>" 
  7.                             title="<%= PortalUtil.getPortletTitle(portlet, application, locale) %>" 
  8.                         > 
  9.                             <p><%= PortalUtil.getPortletTitle(portlet, application, locale) %> <a href="javascript:;"><liferay-ui:message key="add" /></a></p> 
  10.                         </div> 

 

所以,显示文本就在 PortalUtil.getPortletTitle方法中,而它最终会调用PortalImplgetPortletTitle方法中:

 
  
  1. public String getPortletTitle( 
  2.         Portlet portlet, ServletContext servletContext, Locale locale) { 
  3.  
  4.         PortletConfig portletConfig = PortletConfigFactoryUtil.create( 
  5.             portlet, servletContext); 
  6.  
  7.         ResourceBundle resourceBundle = portletConfig.getResourceBundle(locale); 
  8.  
  9.         return resourceBundle.getString(JavaConstants.JAVAX_PORTLET_TITLE); 
  10.     } 

 

所以,文本实际在09行的getString方法中获取的。我们跟进到ResourceBundle的getString(key)方法,它会调用ResourceBundle的getObject(key)方法:

 
  
  1. public final Object getObject(String key) { 
  2.        Object obj = handleGetObject(key); 
  3.        if (obj == null) { 
  4.            if (parent != null) { 
  5.                obj = parent.getObject(key); 
  6.            } 
  7.        ... 
  8.    } 

 

进而会调用PortletResourceBundle的handleGetObject(key)方法:

 
  
  1. protected Object handleGetObject(String key) { 
  2.         .. 
  3.         if ((value == null) || (value == ResourceBundleUtil.NULL_VALUE)) { 
  4.             value = _getJavaxPortletString(key); 
  5.         } 
  6.         .. 
  7.         return value; 
  8.     } 

 

从这里看出因为满足03行的条件,所以会调用PortletResourceBundle_getJavaxPortletString(key)方法:

 
  
  1. private String _getJavaxPortletString(String key) { 
  2.         if (key.equals(JavaConstants.JAVAX_PORTLET_TITLE)) { 
  3.             return _portletInfo.getTitle(); 
  4.         } 
  5.         else if (key.equals(JavaConstants.JAVAX_PORTLET_SHORT_TITLE)) { 
  6.             return _portletInfo.getShortTitle(); 
  7.         } 
  8.         else if (key.equals(JavaConstants.JAVAX_PORTLET_KEYWORDS)) { 
  9.             return _portletInfo.getKeywords(); 
  10.         } 
  11.         else if (key.equals(JavaConstants.JAVAX_PORTLET_DESCRIPTION)) { 
  12.             return _portletInfo.getDescription(); 
  13.         } 
  14.  
  15.         return null
  16.     } 

 

因为我们传入的参数为 JavaConstants.JAVAX_PORTLET_TITLE,所以返回的结果是_portletInfo.getTitle()的字符串值。这个值是在PortletInfo构造函数中被赋值的:

 
  
  1. public PortletInfo( 
  2.         String title, String shortTitle, String keywords, String description) { 
  3.  
  4.         _title = title; 
  5.         _shortTitle = shortTitle; 
  6.         _keywords = keywords; 
  7.         _description = description; 
  8.     } 

 

而这个构造函数的最终也是在PortletLocalServiceImpl_readPortletXML方法中被调用的:

 
  
  1. private void _readPortletXML( 
  2.         String servletContextName, Map<String, Portlet> portletsPool, 
  3.         PluginPackage pluginPackage, PortletApp portletApp, 
  4.         Set<String> portletIds, long timestamp, Element portletElement) {
  5.  
  6.  
  7.  
  8.   Element portletInfoElement = portletElement.element("portlet-info");
  1. .. 
  2.  
  3.         if (portletInfoElement != null) { 
  4.             portletInfoTitle = portletInfoElement.elementText("title"); 
  5.             portletInfoShortTitle = portletInfoElement.elementText( 
  6.                 "short-title"); 
  7.             portletInfoKeyWords = portletInfoElement.elementText("keywords"); 
  8.         } 
  9.  
  10.         PortletInfo portletInfo = new PortletInfo( 
  11.             portletInfoTitle, portletInfoShortTitle, portletInfoKeyWords, 
  12.             portletInfoDescription); 
  13. .. 

从这里可以看出,这个title是在08行从中获取<title>元素,而这个title元素是从portletElement中获取<portlet-info>元素得来的。

 

那么portletInfoElement如何得来的呢?参见PortletLocalServiceImpl_readLiferayDisplay方法:

 
  
  1.  
  2.  
  3.     for (Element portletElement : categoryElement.elements("portlet")) { 
  4.         String portletId = portletElement.attributeValue("id"); 
  5. ...
     

所以,从这里可以看出它是从配置文件中<portlet>资源数中获得的。

而这个配置文件是哪个文件呢?

从我highlight的一行点入可以看到,它传入的是下标为0的xml文件,这个调用位于PortletLocalServiceImpl类中:

 
  
  1. Set<String> portletIds = _readPortletXML( 
  2.                 servletContext, xmls[0], portletsPool, servletURLPatterns, 
  3.                 pluginPackage); 

而下标为0的配置文件的文件名传入是在MainServletinitPortlets()方法声明的:

 
  
  1. protected List<Portlet> initPortlets(PluginPackage pluginPackage) 
  2.         throws Exception { 
  3.  
  4.         ServletContext servletContext = getServletContext(); 
  5.  
  6.         String[] xmls = new String[] { 
  7.             HttpUtil.URLtoString( 
  8.                 servletContext.getResource( 
  9.                     "/WEB-INF/" + Portal.PORTLET_XML_FILE_NAME_CUSTOM)), 
  10.             HttpUtil.URLtoString( 
  11.                 servletContext.getResource("/WEB-INF/portlet-ext.xml")), 
  12.             HttpUtil.URLtoString( 
  13.                 servletContext.getResource("/WEB-INF/liferay-portlet.xml")), 
  14.             HttpUtil.URLtoString( 
  15.                 servletContext.getResource("/WEB-INF/liferay-portlet-ext.xml")), 
  16.             HttpUtil.URLtoString( 
  17.                 servletContext.getResource("/WEB-INF/web.xml")) 
  18.         }; 
  19.  
  20.         PortletLocalServiceUtil.initEAR(servletContext, xmls, pluginPackage); 
  21. ... 

现在一切真相大白了,原来,显示页面上的portlet的display-nameportlet.xml(被portlet-ext.xml覆盖后,如果有)的<portlet>下面的<portlet-info>下面的<title>元素的值。

 

总结:

经过3个多小时的探索,我们得到以下结论:

在Dockbar->Add->More显示的Portlet名称以及所属分类是按照 以下规则得到的:

对于分类:如果定义了Portlet的分类,则在liferay-display.xml中的<category>元素的值,否则,这个Portlet被加在 category.undefined 分类中。

对于Portlet名字,它是定义在portlet.xml的<portlet>元素下面的<portlet-info>元素下面的<title>元素的值。

 





本文转自 charles_wang888 51CTO博客,原文链接:http://blog.51cto.com/supercharles888/911399,如需转载请自行联系原作者

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值